ESP32 UNO - LCD Keypad Shield

Need a screen and a few buttons on the ESP32 Uno with zero jumper wires? Plug an LCD keypad shield onto the ESP32 Uno form board, and you get a 16x2 character display plus five keys you can read from Arduino code. Press a key, and its name pops up on the LCD. One cheap resistor makes this keypad LCD shield safe for the 3.3V board, and a small upload trick keeps the A0 pin from blocking new sketches.

What you will do:

  1. Find out which Uno pins the LCD shield takes and what each GPIO number is
  2. Fit a 3.3 kΩ resistor so the 5V key ladder stays under 3.3V
  3. Print text on the 16x2 display with the LiquidCrystal library
  4. Read all five keys from a single analog input with analogRead()
  5. Run a menu demo that controls a servo, three LEDs and a DHT11 sensor
ESP32 Uno LCD keypad shield

Components You Need

1×ESP32 Uno-form Board
1×USB Cable Type-A to Type-C (for USB-A PC)
1×USB Cable Type-C to Type-C (for USB-C PC)
1×LCD Keypad Shield
1×3.3 kΩ Resistor (voltage divider for the A0 key pin)

Or you can buy the following kits:

1×DIYables Sensor Kit (18 sensors/displays)
Disclosure: Some of the links provided in this section are Amazon affiliate links. We may receive a commission for any purchases made through these links at no additional cost to you.
Additionally, some of these links are for products from our own brand, DIYables .

Inside the LCD Keypad Shield

By the end of this part, you know every part on the shield and the job it does.

Part What It Is Can Your Code Use It?
16x2 LCD HD44780 character display, 2 rows of 16 characters, 4-bit mode Yes, with the LiquidCrystal library
Five keys Up, Down, Left, Right and Select, all on one resistor ladder Yes, through one analogRead() on A0
Reset key Wired to the board's RST (EN) pin No, it restarts the board
Contrast trimmer Small blue potentiometer No, you turn it by hand

Each of the five keys connects a different resistor value to the A0 line. So every key gives its own voltage, and the sketch tells the keys apart by the ADC number it reads.

Pinout

LCD Keypad Shield Pinout

The shield sits on the normal Uno header spots, so each shield signal lands on a fixed ESP32 Uno GPIO:

Shield Pin Role ESP32 Uno Pin
DB4 LCD data bit 4 D4 (GPIO17)
DB5 LCD data bit 5 D5 (GPIO16)
DB6 LCD data bit 6 D6 (GPIO27)
DB7 LCD data bit 7 D7 (GPIO14)
RS Register select D8 (GPIO12)
E Enable D9 (GPIO13)
Key ladder Analog key input A0 (GPIO2)

Good to know: the LCD pins only take signals from the board, and a 3.3V HIGH is enough for the HD44780. The A0 key line is the opposite: the shield pushes a voltage up to 5V into the board, and that needs the fix in the next sections.

※ NOTE THAT:

Many LCD keypad shields switch the backlight with a transistor on D10 (GPIO5). The sketches here never touch D10. Do not make D10 an OUTPUT and set it HIGH, because on some shields this pulls too much current from the pin.

ESP32 Uno Pinout

Your sketch talks to GPIO numbers, not to the D4 or A0 labels on the headers. Find the shield pins (D4 to D9 and A0) on the picture below to see which GPIO each one is.

ESP32 Uno pinout diagram

How to Connect

When this step is done, the shield is stacked and the A0 line can never go above 3.3V.

The wiring diagram between ESP32 Uno LCD keypad shield

This image is created using Fritzing. Click to enlarge image

Step 1 — Add the resistor to the shield

Solder a 3.3 kΩ resistor from the A0 header pin to a GND header pin, on the underside of the shield. You can also push the resistor legs into the A0 and GND holes of the board before you stack the shield on top.

Step 2 — Stack the shield

Line up the shield pins with the ESP32 Uno headers and press it down evenly. No jumper wires are needed.

Shield Pin ESP32 Uno Pin
5V 5V
GND GND
DB4 D4 (GPIO17)
DB5 D5 (GPIO16)
DB6 D6 (GPIO27)
DB7 D7 (GPIO14)
RS D8 (GPIO12)
E D9 (GPIO13)
Keys A0 (GPIO2), plus 3.3 kΩ from A0 to GND

WARNING

The key ladder on the shield has a 2 kΩ pull-up resistor to 5V. With no key pressed, A0 sees the full 5V, and the Select key gives about 3.6V. No ESP32 Uno pin is 5V tolerant. The 3.3 kΩ resistor from A0 to GND forms a voltage divider with the 2 kΩ pull-up, so the top value drops to about 3.1V and every key stays in the 0 to 3.3V window. Never power the shield without this resistor.

WARNING

A0 (GPIO2) is a boot strapping pin. The chip checks it when it enters upload mode, and it must be LOW or floating then. With the shield on, A0 sits near 3.1V, so an upload can stop with "Failed to connect" or "Wrong boot mode detected". Fix: hold the RIGHT key (it pulls A0 to 0V) while the IDE shows "Connecting...", or take the shield off for the upload. Normal start-up is not affected.

WARNING

The LCD RS line uses D8 (GPIO12), another boot strapping pin that must be LOW at power-up. The LCD RS input does not drive the line, so the board's internal pull-down keeps it LOW and the board starts normally. If the board ever fails to boot with the shield on, remove the shield, power up, and check your shield for extra parts on D8.

Good to know: A0 (GPIO2) is also an ADC2 pin. It reads fine in these sketches, but analogRead() on it stops working as soon as WiFi is on.

ESP32 Uno Code

This sketch shows "Hello!" for 3 seconds, then checks the keys 5 times per second and writes the name of the pressed key on the LCD.

/* * This ESP32 Uno code was developed by newbiely.com * * This ESP32 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-uno/esp32-uno-lcd-keypad-shield */ #include <LiquidCrystal.h> // shield LCD pins on the ESP32 Uno headers: // RS = D8 (GPIO12), E = D9 (GPIO13), DB4 = D4 (GPIO17), DB5 = D5 (GPIO16), DB6 = D6 (GPIO27), DB7 = D7 (GPIO14) LiquidCrystal lcd(12, 13, 17, 16, 27, 14); #define KEYPAD_PIN 2 // A0 (GPIO2) reads the button ladder - add the 3.3 kOhm resistor from A0 to GND first void setup() { Serial.begin(9600); // set the ADC attenuation to 11 dB (up to ~3.3V input) analogSetAttenuation(ADC_11db); Serial.println("ESP32 Uno - LCD Keypad Shield"); // print once to check the upload lcd.begin(16, 2); lcd.print("Hello!"); delay(3000); } void loop() { int key = analogRead(KEYPAD_PIN); // ESP32 Uno ADC value, 0 to 4095 //Serial.println(key); // remove the slashes to see the raw value lcd.clear(); if (key < 400) lcd.print("RIGHT"); else if (key < 1250) lcd.print("UP"); else if (key < 2000) lcd.print("DOWN"); else if (key < 2750) lcd.print("LEFT"); else if (key < 3450) lcd.print("SELECT"); else if (key < 3750) lcd.print("RST"); // put a range here if your RST key gives a value else lcd.print("Press key!"); delay(200); }
Code Part What It Does
LiquidCrystal lcd(12, 13, 17, 16, 27, 14) Tells the library the GPIOs of RS, E and DB4 to DB7
analogSetAttenuation(ADC_11db) Opens the ADC range to about 0 to 3.3V
analogRead(KEYPAD_PIN) Reads the key ladder on A0 (GPIO2) as 0 to 4095
if / else if chain Compares the value with five limits and picks a key name
lcd.clear() and lcd.print() Wipes the screen and writes the key name

The result: each key press shows RIGHT, UP, DOWN, LEFT or SELECT, and "Press key!" stays on the screen while no key is down.

Good to know: without analogSetAttenuation(ADC_11db), the ESP32 Uno ADC only covers a small voltage range. The 11 dB setting lets A0 read from 0V up to about 3.3V. Leave it out, and the readings reach 4095 too early, so LEFT, SELECT and "no key" all look the same.

※ NOTE THAT:

Shield makers use slightly different ladder resistors, and the ESP32 Uno ADC is not perfectly linear. If a key shows the wrong name, delete the // before Serial.println(key);, upload, and write down the value of each key in the Serial Monitor. Then set each limit halfway between two neighboring key values.

Upload and Test

  1. First time with this board? Set it up with ESP32 Uno - Getting Started before you continue.
  2. Build the circuit by following the wiring table above: resistor first, then the shield.
  3. Plug in the USB Type-C cable between the ESP32 Uno and your computer.
  4. Select the board in Arduino IDE: pick ESP32 Dev Module and the COM port of the board (CH340).
  5. Check the library. LiquidCrystal ships with the Arduino IDE. If the compiler cannot find it, search LiquidCrystal in the Library Manager and install it.
  6. Paste the code above into a new sketch.
  7. Upload while holding RIGHT. Click Upload, and keep the RIGHT key pressed until the IDE prints "Writing at...".
  8. Press each key one after another.
  9. Read the LCD. You see the key name, or "Press key!" when your fingers are off the keys.
  10. Pro tip: turn the contrast trimmer until the empty boxes of row 2 just disappear. That is the sharpest setting at 5V, and it saves you from a "blank screen" scare later.

The Serial Monitor (9600 baud) shows one line after each reset:

∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32 Dev Module' on 'COM15')
New Line
9600 baud
ESP32 Uno - LCD Keypad Shield
Ln 11, Col 1
ESP32 Dev Module on COM15
2

With the debug line enabled, you see raw values like these (RIGHT, UP, DOWN, LEFT, SELECT, then no key):

∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32 Dev Module' on 'COM15')
New Line
9600 baud
ESP32 Uno - LCD Keypad Shield 0 786 1631 2402 3066 3912
Ln 11, Col 1
ESP32 Dev Module on COM15
2

Screen stays empty? Check in this order:

  1. Press the shield down until all header pins sit fully in the sockets.
  2. Turn the contrast trimmer slowly across its whole range.
  3. Confirm the upload finished and the board has power from the USB cable.

Video Tutorial

See the ESP32 Uno build in action in the video below.

The demo is a small control station driven by the keypad menus. It reads temperature and humidity from a DHT11, moves a servo, switches three LEDs, and has an auto mode that sets the LED color and the servo angle from the temperature. Wire the extra parts like this:

Part ESP32 Uno Pin
DHT11 DATA D2 (GPIO26)
Servo signal D3 (GPIO25)
Red LED (with resistor) D11 (GPIO23)
Yellow LED (with resistor) D12 (GPIO19)
Green LED (with resistor) D13 (GPIO18)

※ NOTE THAT:

Connect the DHT11 module VCC to 3.3V, which keeps its data signal at a safe level. The servo may use 5V, because its signal only leaves the board. Install two libraries from the Library Manager: ESP32Servo by Kevin Harrington (it replaces Servo.h on this board) and DHT sensor library by Adafruit.

/* * This ESP32 Uno code was developed by newbiely.com * * This ESP32 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-uno/esp32-uno-lcd-keypad-shield */ #include <LiquidCrystal.h> #include <ESP32Servo.h> #include <DHT.h> // ---------- pins used on the ESP32 Uno ---------- #define DHT_PIN 26 // D2 (GPIO26) reads the DHT11 data line #define DHT_TYPE DHT11 #define SERVO_PIN 25 // D3 (GPIO25) sends the servo signal #define RED_LED_PIN 23 // D11 (GPIO23) #define YELLOW_LED_PIN 19 // D12 (GPIO19) #define GREEN_LED_PIN 18 // D13 (GPIO18) // shield LCD: RS D8 (GPIO12), E D9 (GPIO13), DB4-DB7 D4 (GPIO17), D5 (GPIO16), D6 (GPIO27), D7 (GPIO14) LiquidCrystal lcd(12, 13, 17, 16, 27, 14); #define KEYPAD_PIN 2 // A0 (GPIO2) button ladder, with the 3.3 kOhm resistor from A0 to GND DHT dht(DHT_PIN, DHT_TYPE); Servo servo; // ---------- key names ---------- enum Button { BUTTON_NONE, BUTTON_RIGHT, BUTTON_UP, BUTTON_DOWN, BUTTON_LEFT, BUTTON_SELECT }; // ---------- screens of the menu ---------- enum State { MAIN_MENU, SENSOR_MENU, TEMPERATURE_VIEW, HUMIDITY_VIEW, SERVO_CONTROL, LED_MENU, RED_LED_CONTROL, YELLOW_LED_CONTROL, GREEN_LED_CONTROL, AUTO_MODE }; State currentState = MAIN_MENU; // ---------- menu item text ---------- const char* mainMenu[] = { "SENSOR", "SERVO", "LED", "AUTO MODE" }; const char* sensorMenu[] = { "TEMPERATURE", "HUMIDITY" }; const char* ledMenu[] = { "RED", "YELLOW", "GREEN" }; int mainMenuIndex = 0; int sensorMenuIndex = 0; int ledMenuIndex = 0; // ---------- servo, LED and sensor values ---------- int servoAngle = 90; bool redLEDState = false; bool yellowLEDState = false; bool greenLEDState = false; float temperature = 0.0; float humidity = 0.0; unsigned long lastSensorRead = 0; unsigned long lastAutoDisplay = 0; const unsigned long sensorInterval = 2000; const unsigned long autoDisplayInterval = 2000; bool autoDisplayPage = false; // ---------- turn the A0 value into a key ---------- Button readButton() { int value = analogRead(KEYPAD_PIN); // 0 to 4095 on the ESP32 Uno if (value < 400) return BUTTON_RIGHT; if (value < 1250) return BUTTON_UP; if (value < 2000) return BUTTON_DOWN; if (value < 2750) return BUTTON_LEFT; if (value < 3450) return BUTTON_SELECT; return BUTTON_NONE; } // ---------- block until the key is let go ---------- void waitButtonRelease() { while (readButton() != BUTTON_NONE) { delay(10); } delay(100); } // ---------- draw the main menu ---------- void displayMainMenu() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("MAIN MENU"); lcd.setCursor(0, 1); lcd.print("> "); lcd.print(mainMenu[mainMenuIndex]); } // ---------- draw the sensor menu ---------- void displaySensorMenu() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("SENSOR"); lcd.setCursor(0, 1); lcd.print("> "); lcd.print(sensorMenu[sensorMenuIndex]); } // ---------- draw the LED menu ---------- void displayLEDMenu() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("LED CONTROL"); lcd.setCursor(0, 1); lcd.print("> "); lcd.print(ledMenu[ledMenuIndex]); } // ---------- get new DHT11 values (keep old ones on error) ---------- void readSensor() { float newTemperature = dht.readTemperature(); float newHumidity = dht.readHumidity(); if (!isnan(newTemperature)) { temperature = newTemperature; } if (!isnan(newHumidity)) { humidity = newHumidity; } } // ---------- temperature screen ---------- void showTemperature() { readSensor(); lcd.setCursor(0, 0); lcd.print("TEMPERATURE "); lcd.setCursor(0, 1); lcd.print(temperature, 1); lcd.print((char)223); lcd.print("C "); } // ---------- humidity screen ---------- void showHumidity() { readSensor(); lcd.setCursor(0, 0); lcd.print("HUMIDITY "); lcd.setCursor(0, 1); lcd.print(humidity, 1); lcd.print("% "); } // ---------- servo angle screen ---------- void displayServoControl() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("SERVO CONTROL"); lcd.setCursor(0, 1); lcd.print("ANGLE: "); lcd.print(servoAngle); lcd.print((char)223); } // ---------- one LED on/off screen ---------- void displayLEDControl(const char* ledName, bool state) { lcd.clear(); lcd.setCursor(0, 0); lcd.print(ledName); lcd.setCursor(0, 1); if (state) { lcd.print("STATUS: ON"); } else { lcd.print("STATUS: OFF"); } } // ---------- auto mode, first screen ---------- void displayAutoPage1() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("HUM:"); lcd.print(humidity, 0); lcd.print("%"); lcd.setCursor(0, 1); lcd.print("TEMP:"); lcd.print(temperature, 1); lcd.print(" C"); } // ---------- auto mode, second screen ---------- void displayAutoPage2() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("LED: "); if (temperature < 25.0) { lcd.print("GREEN"); } else if (temperature < 30.0) { lcd.print("YELLOW"); } else { lcd.print("RED"); } lcd.setCursor(0, 1); lcd.print("SERVO: "); lcd.print(servoAngle); lcd.print(" DEG"); } // ---------- pick LED and servo angle from the temperature ---------- void runAutoMode() { readSensor(); if (temperature < 31.3) { digitalWrite(GREEN_LED_PIN, HIGH); digitalWrite(YELLOW_LED_PIN, LOW); digitalWrite(RED_LED_PIN, LOW); servoAngle = 0; servo.write(servoAngle); } else if (temperature < 31.5) { digitalWrite(GREEN_LED_PIN, LOW); digitalWrite(YELLOW_LED_PIN, HIGH); digitalWrite(RED_LED_PIN, LOW); servoAngle = 90; servo.write(servoAngle); } else { digitalWrite(GREEN_LED_PIN, LOW); digitalWrite(YELLOW_LED_PIN, LOW); digitalWrite(RED_LED_PIN, HIGH); servoAngle = 180; servo.write(servoAngle); } } // ---------- keys on the main menu ---------- void handleMainMenu(Button button) { if (button == BUTTON_UP) { mainMenuIndex--; if (mainMenuIndex < 0) { mainMenuIndex = 3; } displayMainMenu(); } else if (button == BUTTON_DOWN) { mainMenuIndex++; if (mainMenuIndex > 3) { mainMenuIndex = 0; } displayMainMenu(); } else if (button == BUTTON_SELECT) { switch (mainMenuIndex) { case 0: currentState = SENSOR_MENU; sensorMenuIndex = 0; displaySensorMenu(); break; case 1: currentState = SERVO_CONTROL; displayServoControl(); break; case 2: currentState = LED_MENU; ledMenuIndex = 0; displayLEDMenu(); break; case 3: currentState = AUTO_MODE; autoDisplayPage = false; lastSensorRead = millis(); lastAutoDisplay = millis(); runAutoMode(); displayAutoPage1(); break; } } } // ---------- keys on the sensor menu ---------- void handleSensorMenu(Button button) { if (button == BUTTON_UP) { sensorMenuIndex--; if (sensorMenuIndex < 0) { sensorMenuIndex = 1; } displaySensorMenu(); } else if (button == BUTTON_DOWN) { sensorMenuIndex++; if (sensorMenuIndex > 1) { sensorMenuIndex = 0; } displaySensorMenu(); } else if (button == BUTTON_SELECT) { if (sensorMenuIndex == 0) { lcd.clear(); currentState = TEMPERATURE_VIEW; lastSensorRead = millis(); showTemperature(); } else { lcd.clear(); currentState = HUMIDITY_VIEW; lastSensorRead = millis(); showHumidity(); } } else if (button == BUTTON_LEFT) { currentState = MAIN_MENU; displayMainMenu(); } } // ---------- keys on the temperature screen ---------- void handleTemperatureView(Button button) { if (button == BUTTON_LEFT || button == BUTTON_SELECT) { currentState = SENSOR_MENU; displaySensorMenu(); } } // ---------- keys on the humidity screen ---------- void handleHumidityView(Button button) { if (button == BUTTON_LEFT || button == BUTTON_SELECT) { currentState = SENSOR_MENU; displaySensorMenu(); } } // ---------- keys on the servo screen ---------- void handleServoControl(Button button) { if (button == BUTTON_UP) { servoAngle++; if (servoAngle > 180) { servoAngle = 180; } servo.write(servoAngle); displayServoControl(); } else if (button == BUTTON_DOWN) { servoAngle--; if (servoAngle < 0) { servoAngle = 0; } servo.write(servoAngle); displayServoControl(); } else if (button == BUTTON_RIGHT) { servoAngle += 10; if (servoAngle > 180) { servoAngle = 180; } servo.write(servoAngle); displayServoControl(); } else if (button == BUTTON_LEFT) { servoAngle -= 10; if (servoAngle < 0) { servoAngle = 0; } servo.write(servoAngle); displayServoControl(); } else if (button == BUTTON_SELECT) { currentState = MAIN_MENU; displayMainMenu(); } } // ---------- keys on the LED menu ---------- void handleLEDMenu(Button button) { if (button == BUTTON_UP) { ledMenuIndex--; if (ledMenuIndex < 0) { ledMenuIndex = 2; } displayLEDMenu(); } else if (button == BUTTON_DOWN) { ledMenuIndex++; if (ledMenuIndex > 2) { ledMenuIndex = 0; } displayLEDMenu(); } else if (button == BUTTON_SELECT) { switch (ledMenuIndex) { case 0: currentState = RED_LED_CONTROL; displayLEDControl("RED LED", redLEDState); break; case 1: currentState = YELLOW_LED_CONTROL; displayLEDControl("YELLOW LED", yellowLEDState); break; case 2: currentState = GREEN_LED_CONTROL; displayLEDControl("GREEN LED", greenLEDState); break; } } else if (button == BUTTON_LEFT) { currentState = MAIN_MENU; displayMainMenu(); } } // ---------- keys on the red LED screen ---------- void handleRedLED(Button button) { if (button == BUTTON_RIGHT) { redLEDState = !redLEDState; digitalWrite(RED_LED_PIN, redLEDState ? HIGH : LOW); displayLEDControl("RED LED", redLEDState); } else if (button == BUTTON_LEFT) { currentState = LED_MENU; displayLEDMenu(); } } // ---------- keys on the yellow LED screen ---------- void handleYellowLED(Button button) { if (button == BUTTON_RIGHT) { yellowLEDState = !yellowLEDState; digitalWrite(YELLOW_LED_PIN, yellowLEDState ? HIGH : LOW); displayLEDControl("YELLOW LED", yellowLEDState); } else if (button == BUTTON_LEFT) { currentState = LED_MENU; displayLEDMenu(); } } // ---------- keys on the green LED screen ---------- void handleGreenLED(Button button) { if (button == BUTTON_RIGHT) { greenLEDState = !greenLEDState; digitalWrite(GREEN_LED_PIN, greenLEDState ? HIGH : LOW); displayLEDControl("GREEN LED", greenLEDState); } else if (button == BUTTON_LEFT) { currentState = LED_MENU; displayLEDMenu(); } } // ---------- keys in auto mode ---------- void handleAutoMode(Button button) { if (button == BUTTON_LEFT || button == BUTTON_SELECT) { digitalWrite(RED_LED_PIN, LOW); digitalWrite(YELLOW_LED_PIN, LOW); digitalWrite(GREEN_LED_PIN, LOW); redLEDState = false; yellowLEDState = false; greenLEDState = false; servoAngle = 90; servo.write(servoAngle); currentState = MAIN_MENU; displayMainMenu(); } } // ---------- runs one time after power-on or reset ---------- void setup() { // set the ADC attenuation to 11 dB (up to ~3.3V input) analogSetAttenuation(ADC_11db); lcd.begin(16, 2); dht.begin(); servo.attach(SERVO_PIN); servo.write(servoAngle); pinMode(RED_LED_PIN, OUTPUT); pinMode(YELLOW_LED_PIN, OUTPUT); pinMode(GREEN_LED_PIN, OUTPUT); digitalWrite(RED_LED_PIN, LOW); digitalWrite(YELLOW_LED_PIN, LOW); digitalWrite(GREEN_LED_PIN, LOW); lcd.clear(); lcd.setCursor(0, 0); lcd.print("SMART CONTROL"); lcd.setCursor(0, 1); lcd.print("STATION"); delay(2000); displayMainMenu(); } // ---------- runs again and again ---------- void loop() { Button button = readButton(); if (button != BUTTON_NONE) { switch (currentState) { case MAIN_MENU: handleMainMenu(button); break; case SENSOR_MENU: handleSensorMenu(button); break; case TEMPERATURE_VIEW: handleTemperatureView(button); break; case HUMIDITY_VIEW: handleHumidityView(button); break; case SERVO_CONTROL: handleServoControl(button); break; case LED_MENU: handleLEDMenu(button); break; case RED_LED_CONTROL: handleRedLED(button); break; case YELLOW_LED_CONTROL: handleYellowLED(button); break; case GREEN_LED_CONTROL: handleGreenLED(button); break; case AUTO_MODE: handleAutoMode(button); break; } waitButtonRelease(); } // refresh the screen values on a timer if (currentState == AUTO_MODE) { if (millis() - lastSensorRead >= sensorInterval) { lastSensorRead = millis(); runAutoMode(); } if (millis() - lastAutoDisplay >= autoDisplayInterval) { lastAutoDisplay = millis(); autoDisplayPage = !autoDisplayPage; if (autoDisplayPage) { displayAutoPage2(); } else { displayAutoPage1(); } } } if (currentState == TEMPERATURE_VIEW || currentState == HUMIDITY_VIEW) { if (millis() - lastSensorRead >= sensorInterval) { lastSensorRead = millis(); if (currentState == TEMPERATURE_VIEW) { showTemperature(); } else if (currentState == HUMIDITY_VIEW) { showHumidity(); } } } }

ESP32 Uno vs UNO R4 for This Project

This table lists what changes when this shield moves from the UNO R4 to the ESP32 Uno.

Item Arduino UNO R4 ESP32 Uno
Logic voltage 5V 3.3V
A0 key line Direct, 5V is safe Needs 3.3 kΩ from A0 to GND
LCD pins in code 8, 9, 4, 5, 6, 7 12, 13, 17, 16, 27, 14
ADC range and key limits 0 to 1023, limits 50 to 800 0 to 4095, limits 400 to 3450
Extra code line None analogSetAttenuation(ADC_11db)
Servo library in the demo Servo.h ESP32Servo.h
Arduino IDE board Arduino UNO R4 WiFi or Minima ESP32 Dev Module

The shield plugs in at the same spots, and the LCD works as-is. Only the A0 key line needs the resistor, and uploads go smoothly when you hold RIGHT or lift the shield off.

Bonus: Tidier Code With a getKey() Function

After this part, the key reading lives in one function you can copy into bigger projects.

getKey() returns a key constant, and loop() picks the text with a switch. The limits match the first sketch.

/* * This ESP32 Uno code was developed by newbiely.com * * This ESP32 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-uno/esp32-uno-lcd-keypad-shield */ #include <LiquidCrystal.h> // one number for each key const int KEY_RIGHT = 0; const int KEY_UP = 1; const int KEY_DOWN = 2; const int KEY_LEFT = 3; const int KEY_SELECT = 4; const int KEY_NONE = 5; // RS = D8 (GPIO12), E = D9 (GPIO13), DB4..DB7 = D4 (GPIO17), D5 (GPIO16), D6 (GPIO27), D7 (GPIO14) LiquidCrystal lcd(12, 13, 17, 16, 27, 14); #define KEYPAD_PIN 2 // A0 (GPIO2), with the 3.3 kOhm resistor from A0 to GND int getKey() { int analogValue = analogRead(KEYPAD_PIN); // 0 to 4095 on the ESP32 Uno //Serial.println(analogValue); // remove the slashes to see the raw value if (analogValue < 400) return KEY_RIGHT; else if (analogValue < 1250) return KEY_UP; else if (analogValue < 2000) return KEY_DOWN; else if (analogValue < 2750) return KEY_LEFT; else if (analogValue < 3450) return KEY_SELECT; else return KEY_NONE; } void setup() { Serial.begin(9600); // set the ADC attenuation to 11 dB (up to ~3.3V input) analogSetAttenuation(ADC_11db); Serial.println("ESP32 Uno - LCD Keypad Shield"); // print once to check the upload lcd.begin(16, 2); lcd.print("Hello!"); delay(3000); } void loop() { lcd.clear(); int key = getKey(); switch (key) { case KEY_RIGHT: lcd.print("RIGHT"); break; case KEY_UP: lcd.print("UP"); break; case KEY_DOWN: lcd.print("DOWN"); break; case KEY_LEFT: lcd.print("LEFT"); break; case KEY_SELECT: lcd.print("SELECT"); break; default: lcd.print("Press key!"); break; } delay(200); }
Code Part What It Does
KEY_RIGHT to KEY_NONE Give each key a readable name in code
getKey() Reads A0 (GPIO2) once and returns the matching key
switch (key) Prints the text for that key
default branch Shows "Press key!" when no key is pressed

With this layout, adding a new action for a key is one more case line.

Go Wireless: Type a Message on Your Phone, Read It on the LCD

By the end of this idea, the shield screen shows text you send from a phone.

The ESP32 Uno has Bluetooth (Classic and BLE) and WiFi built in. Use a phone chat app to send a short note, and print it on row 2 of the LCD, while the keys scroll or clear it. Pick Bluetooth for this shield: A0 (GPIO2) is an ADC2 pin, so the keys stop working as soon as WiFi starts.

Extra Help

For more display tricks, such as moving the cursor, scrolling text or drawing custom characters, open the Arduino LiquidCrystal LCD tutorial. Every LiquidCrystal function there runs on the ESP32 Uno without changes.

Your keypad LCD shield is ready for menus, timers and small control panels. Enjoy the build!

Troubleshooting

Problem Possible Cause Solution
No COM port shows up CH340 driver missing or charge-only USB cable Install the CH340 driver and use a USB data cable
Upload stops at Connecting or Wrong boot mode A0 (GPIO2) strapping pin held HIGH by the key ladder Hold the RIGHT key during the upload, or remove the shield
Failed to connect with the shield off Board did not enter upload mode Hold the BOOT button while the upload starts
Board does not boot with the shield on A strapping pin (A0 GPIO2 or D8 GPIO12) pulled the wrong way Unplug the shield, power up, upload, then stack it again
Backlight on but no characters Contrast trimmer at one end, or shield not seated Turn the trimmer and press the shield fully onto the headers
Random symbols on the LCD Wrong GPIO list in the LiquidCrystal line Use lcd(12, 13, 17, 16, 27, 14) as in the sketch
Every key shows Press key! Key limits do not fit your shield Print the raw A0 value and move the limits halfway between keys
SELECT and no key look the same Divider resistor or 11 dB attenuation missing Fit 3.3 kΩ from A0 to GND and keep analogSetAttenuation(ADC_11db)
Keys dead after adding WiFi ADC2 is blocked while WiFi runs Use Bluetooth instead, or reroute the key line to GPIO32 or GPIO33
Demo servo shakes or board resets Servo pulls too much current from USB Power the servo from a separate 5V supply with a shared GND

FAQ

Does an Arduino LCD keypad shield fit the ESP32 Uno?

Yes. The ESP32 Uno form board has the Uno header layout, so the shield drops into the same positions. The LCD runs fine with 3.3V signals. Only the A0 key line needs the 3.3 kΩ resistor to GND.

How does one analog pin read five keys?

Each key connects a different resistor to the A0 line, together with a 2 kΩ pull-up. Every key makes a different voltage. The sketch reads that voltage with analogRead() and compares it with fixed limits to know which key it is.

Is 3.3 kΩ the only resistor value that works?

It is the best common value. With 3.3 kΩ, the "no key" voltage is about 3.1V, just under 3.3V, and the keys stay well apart. A 3 kΩ resistor also works, but then all values drop a little, so check the limits with the Serial Monitor.

Why are the key limits so different from the UNO R4 sketch?

The UNO R4 ADC returns 0 to 1023 and reads A0 straight at 5V. The ESP32 Uno ADC returns 0 to 4095, and the divider resistor lowers every key voltage. So the limits become 400, 1250, 2000, 2750 and 3450 instead of 50, 200, 400, 600 and 800.

What is analogSetAttenuation(ADC_11db) doing in setup()?

The line widens the ESP32 Uno ADC window to roughly 0 to 3.3V, which spreads the whole key ladder across 0 to 4095. With a smaller range, the readings saturate early and several keys read the same. An UNO R4 sketch has no such line. Keep A0 at 3.3V or less.

Why do I have to hold RIGHT when I upload?

A0 is GPIO2, a boot strapping pin that must be LOW or floating when the chip enters upload mode. The shield keeps A0 near 3.1V. Pressing RIGHT shorts A0 to GND, so the upload can start. After "Writing at..." appears, let go.

Why are the LCD pins written as 12, 13, 17, 16, 27, 14?

The sketch uses GPIO numbers. Follow the header labels: RS lands on D8 (GPIO12), E on D9 (GPIO13), and DB4 to DB7 on D4 to D7 (GPIO17, GPIO16, GPIO27, GPIO14).

Can the sketch detect the Reset key?

No. The Reset key goes to the RST (EN) pin and restarts the board. Only Up, Down, Left, Right and Select are read on A0.

Can I use the keys and WiFi in the same sketch?

Not on A0. A0 (GPIO2) is an ADC2 pin, and ADC2 cannot be read while WiFi is on. Bluetooth does not block it. For WiFi, bend the shield's A0 pin out, wire it to GPIO32 or GPIO33 (ADC1), keep the 3.3 kΩ resistor on that line, and change KEYPAD_PIN in the code.

Do I need a CH340 driver for this board?

The ESP32 Uno uses a CH340 USB-to-serial chip. Windows 10 and 11 often install it alone. If no COM port appears in the Arduino IDE, install the CH340 driver and try again.

What if my build needs more pins than the Uno headers offer?

The shield covers most headers, but the ESP32 Uno has extra holes above the power header. GPIO32 and GPIO33 are I/O, PWM and analog on ADC1, so they also read while WiFi is on. GPIO15 is I/O, PWM and analog on ADC2. Add pin headers there with a soldering iron, then type the GPIO number in your sketch. For this shield, GPIO32 or GPIO33 is the best spot for an extra sensor or a WiFi-safe key line. Two warnings: GPIO15 and GPIO0 (the IO0 pin at the unused Uno NC position) are boot strapping pins, and a wrong connection can stop the board from booting or uploading code, so use GPIO32/33 first. Never use the six holes under the digital header (SD0 to SD3, CMD, CLK = GPIO6 to GPIO11), because they are wired to the flash chip and using them crashes the board.

※ OUR MESSAGES

  • As freelancers, We are AVAILABLE for HIRE. See how to outsource your project to us
  • Please feel free to share the link of this tutorial. However, Please do not use our content on any other websites. We invested a lot of effort and time to create the content, please respect our work!