ESP32 Tutorial: Keypad Interface and Web Server
Abstract
This tutorial demonstrates how to interface a standard 4×3 Matrix Keypad with the ESP32 and display the pressed digits on a web page hosted by the ESP32’s built-in web server. This requires reading the keypad matrix, accumulating the digits pressed, and using the web server to serve an HTML page that dynamically updates the displayed number via a simple refresh mechanism. The special keys (∗ and #) are used to clear the displayed number.
1. Introduction: Keypad Interface and Web Server
This tutorial guides you through an essential Intermediate ESP32 project that bridges physical input with a modern web interface. The goal is to interface a standard 4×3 Matrix Keypad with the ESP32 and display the entered digits in real-time on a web page hosted directly by the microcontroller. This setup combines two core skills: managing external hardware communication (keypad matrix scanning) and utilizing the ESP32’s capability to act as a standalone Web Server using the Arduino framework.
You’ll learn how to configure the Keypad library with flexible ESP32 GPIO pins to read matrix inputs reliably. The pressed digits will be collected in a global string variable. This string is then dynamically injected into the HTML content, allowing any connected web browser to display the input instantly. This project is a practical foundation for building access control systems, connected interfaces, or remote configuration tools.
2. Prerequisites and Wiring
2.1 Hardware and Library
- Hardware: ESP32 Dev Board, 4×3 Matrix Keypad.
- Library: You must install the Keypad Library via the Arduino Library Manager.
2.2 Keypad-to-ESP32 Wiring
A 4×3 keypad has 7 wires (4 rows, 3 columns). These must be connected to 7 available ESP32 GPIO pins.
Keypad Pin | Function | ESP32 GPIO Pin | Notes |
R1 | Row 1 | GPIO 13 | Output pins (rows) should be able to drive current. |
R2 | Row 2 | GPIO 12 | |
R3 | Row 3 | GPIO 14 | |
R4 | Row 4 | GPIO 27 | |
C1 | Column 1 | GPIO 26 | Input pins (columns) will use internal pull-downs. |
C2 | Column 2 | GPIO 25 | |
C3 | Column 3 | GPIO 33 |
3. Web Server Code and Logic
The core logic uses the ESP32 to track the keyed digits in a string variable and serve the HTML page, passing the string value.
3.1 Global Variables and Keypad Setup
#include
#include
#include
// --- Network Credentials ---
const char* ssid = "SSID";
const char* password = "PASSWORD";
WebServer server(80);
// Global variable to hold the digits pressed
String currentNumber = "";
// --- Keypad Setup (4x3 Configuration) ---
const byte ROWS = 4; // four rows
const byte COLS = 3; // three columns
char keys[ROWS][COLS] = {
{'1', '2', '3'},
{'4', '5', '6'},
{'7', '8', '9'},
{'*', '0', '#'}
};
// Row pins (R1, R2, R3, R4)
byte rowPins[ROWS] = {13, 12, 14, 27};
// Column pins (C1, C2, C3)
byte colPins[COLS] = {26, 25, 33};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
3.2 Keypad and Web Handler Functions
The handleKeypad() function is a non-blocking check for key presses, updating the global currentNumber variable.
// Non-blocking function to check the keypad for presses
void handleKeypad() {
char key = keypad.getKey(); // Read the key
// Print if key pressed
if (key) {
Serial.print("Key Pressed : ");
Serial.println(key);
}
if (key) {
// If a digit (0-9) is pressed, append it to the number string
if (key >= '0' && key <= '9') {
currentNumber += key;
}
// If a special key is pressed, reset the number
else if (key == '*' || key == '#') {
currentNumber = ""; // Reset the number string
}
// Send the updated page instantly on any key press
server.send(200, "text/html", getHtmlPage());
}
}
// Handler for the root web page (/)
void handleRoot() {
server.send(200, "text/html", getHtmlPage());
}
3.3 The Setup and Loop
// --- Setup and Loop ---
void setup() {
Serial.begin(115200);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected.");
Serial.print("Web Server IP: ");
Serial.println(WiFi.localIP());
// Setup Web Server Routes
server.on("/", handleRoot);
server.begin();
}
void loop() {
// Must continuously handle incoming web requests
server.handleClient();
// Must continuously check the keypad for presses
handleKeypad();
}
4. HTML Generation
The HTML code includes minimal CSS for a clean look and uses a placeholder KEY_VALUE_PLACEHOLDER that is dynamically replaced by the current number string before being sent to the client.
// --- HTML Generation Function with Custom Styles ---
String getHtmlPage() {
// Brand Colors Used:
// #121212 (Dark Background)
// #00f5a0 (Primary Accent/Green)
// #d1d5db (Light Gray Text)
String html = R"rawliteral(
ESP32 Keypad Input
Keypad Digit Input
The number is dynamically updated when a key is pressed.
KEY_VALUE_PLACEHOLDER
Press * or # on the keypad to clear the display.
)rawliteral";
// Replace the placeholder with the actual number string
html.replace("KEY_VALUE_PLACEHOLDER", currentNumber.isEmpty() ? "---" : currentNumber);
return html;
}
4.1 Source Code
The entire code can be copied from here:
#include
#include
#include
// --- Network Credentials ---
const char* ssid = "SSID";
const char* password = "PASSWORD";
WebServer server(80);
// Global variable to hold the digits pressed
String currentNumber = "";
// --- Keypad Setup (4x3 Configuration) ---
const byte ROWS = 4; // four rows
const byte COLS = 3; // three columns
char keys[ROWS][COLS] = {
{'1', '2', '3'},
{'4', '5', '6'},
{'7', '8', '9'},
{'*', '0', '#'}
};
// Row pins (R1, R2, R3, R4)
byte rowPins[ROWS] = {13, 12, 14, 27};
// Column pins (C1, C2, C3)
byte colPins[COLS] = {26, 25, 33};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// --- HTML Generation Function with Custom Styles ---
String getHtmlPage() {
// Brand Colors Used:
// #121212 (Dark Background)
// #00f5a0 (Primary Accent/Green)
// #d1d5db (Light Gray Text)
String html = R"rawliteral(
ESP32 Keypad Input
Keypad Digit Input
The number is dynamically updated when a key is pressed.
KEY_VALUE_PLACEHOLDER
Press * or # on the keypad to clear the display.
)rawliteral";
// Replace the placeholder with the actual number string
html.replace("KEY_VALUE_PLACEHOLDER", currentNumber.isEmpty() ? "---" : currentNumber);
return html;
}
// --- Web Server Handler Functions ---
// Non-blocking function to check the keypad for presses
void handleKeypad() {
char key = keypad.getKey(); // Read the key
// Print if key pressed
if (key) {
Serial.print("Key Pressed : ");
Serial.println(key);
}
if (key) {
// If a digit (0-9) is pressed, append it to the number string
if (key >= '0' && key <= '9') {
currentNumber += key;
}
// If a special key is pressed, reset the number
else if (key == '*' || key == '#') {
currentNumber = ""; // Reset the number string
}
// Send the updated page instantly on any key press
server.send(200, "text/html", getHtmlPage());
}
}
// Handler for the root web page (/)
void handleRoot() {
server.send(200, "text/html", getHtmlPage());
}
// --- Setup and Loop ---
void setup() {
Serial.begin(115200);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected.");
Serial.print("Web Server IP: ");
Serial.println(WiFi.localIP());
// Setup Web Server Routes
server.on("/", handleRoot);
server.begin();
}
void loop() {
// Must continuously handle incoming web requests
server.handleClient();
// Must continuously check the keypad for presses
handleKeypad();
}
4.2 Code Explanation: Keypad Web Server
This code establishes an ESP32 web server that simultaneously monitors a 4×3 matrix keypad and displays the entered sequence on a web page. The core mechanism relies on non-blocking code execution to handle both tasks continuously.
4.3 Key Setup and Initialization
Libraries: The sketch uses three main libraries:
WiFi.h: For connecting the ESP32 to a local network.WebServer.h: For creating and managing the HTTP server on port 80.Keypad.h: For handling the complex row/column scanning logic of the 4×3 matrix keypad.
Keypad Configuration: The
keys2D array maps the physical layout of the keypad, whilerowPinsandcolPinsdefine the 7 specific ESP32 GPIO pins used for the interface. TheKeypadobject is initialized with this map.Global Variable:
String currentNumber = "";stores the accumulated digits. This variable is the bridge between the physical input and the web output.Networking: In
setup(), the ESP32 connects to the specified Wi-Fi network. It then sets up the web server route (server.on("/", handleRoot)) and starts the server (server.begin()).
4.4 Non-Blocking Keypad Logic (handleKeypad)
The handleKeypad() function is called repeatedly in the loop() to check the physical input without pausing the entire program:
Key Read:
char key = keypad.getKey();non-blockingly checks if a key has been pressed since the last check.Digit Accumulation: If a key is detected (
if (key)):Digits (
0through9) are appended to the globalcurrentNumberstring.Special characters (
*or#) cause thecurrentNumberstring to be reset ("").
Instant Update: Crucially, if any key is pressed, the code calls
server.send(200, "text/html", getHtmlPage());. This immediately sends the updated HTML page back to the browser, providing a near real-time update of the displayed number.
4.5 Web Server and HTML Generation
HTML Structure (
getHtmlPage): This function generates the complete HTML page, including styling. The content for the keypad display uses a placeholder (KEY_VALUE_PLACEHOLDER).Dynamic Content Injection: Before returning the HTML string, the code performs the replacement:
html.replace("KEY_VALUE_PLACEHOLDER", currentNumber.isEmpty() ? "---" : currentNumber);. This substitutes the placeholder with the actual, current value of thecurrentNumberstring.Loop Execution: The
loop()function ensures that both the network and the keypad are continuously monitored:server.handleClient(): Processes incoming web requests (like initial page loads or refresh requests).handleKeypad(): Checks for physical keypad presses.
This decoupled and non-blocking architecture allows the ESP32 to efficiently manage both external hardware interaction and network communication simultaneously.
5. Hands On
This is the expected HTML page, showcasing the IP:
Whenever the keypad is pressed, the Serial Monitor will also show which key was pressed:
With the current setup using the HTML meta refresh tag (<meta http-equiv=”refresh” content=”1″>), the system exhibits decoupled asynchronous behavior. When you press a key on the keypad, the handleKeypad() function immediately updates the global variable currentNumber and logs the event to the Serial Monitor, but it does not send any data back to the browser. The web page, which you see in your first image, ignores the keypad presses until the 1-second refresh timer in the browser expires. At that moment, the browser automatically requests the entire page again, the ESP32 rebuilds the HTML with the current state of the currentNumber variable, and only then do you see the accumulated digits appear on the screen, resulting in a slight, but consistent, delay between pressing a key and seeing the update.
6. Lab Recap
This project successfully integrates physical input with a web interface:
- The Keypad Library is used for non-blocking reading of the 4×3 matrix.
- Pressed digits (0-9) are accumulated in the currentNumber
- The special keys (∗ and #) trigger a reset of the string.
- The ESP32 web server serves an HTML page with clean CSS, where the keypad value is injected into a KEY_VALUE_PLACEHOLDER before being sent to the browser.
- Calling send() inside handleKeypad() ensures the web page updates immediately upon any key press, providing a real-time user experience.


