Project 5: Dino Game on LCD
Share
Part 1: How to use LCD to display what we want and use of push button
Step 1: Overview
In this tutorial, we will create a simple circuit and code that shows a message on an LCD screen when a button is pressed. The message toggles between two different texts on the LCD each time the button is pressed.
Step 2: Components Required
- ESP32 Development Board
- 16x2 LCD Display (I2C Module — PCF8574)
- Puss Button
- Breadboard & Jumper Wires
Step 3: Circuit Connections

LCD Connections:
- VCC: Connect to 5V on the ESP32.
- GND: Connect to GND on the ESP32.
- SDA: Connect to GPIO 21 (or the default SDA pin on your ESP32).
- SCL: Connect to GPIO 22 (or the default SCL pin on your ESP32).
Button Connections:
- One side of the button should be connected to GPIO 12 (or any other GPIO you choose).
- The other side should be connected to 3v3.

Step 5: Upload the ESP32 Code
Copy and paste the following code into the Arduino IDE and upload it to your ESP32.
#include <Wire.h>
#include <LiquidCrystal_PCF8574.h>
#define BUTTON_PIN 12 // GPIO pin for the button
#define LCD_ADDR 0x27
LiquidCrystal_PCF8574 lcd(LCD_ADDR);
bool buttonState = false; // Tracks button press
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLDOWN); // Using internal pull-down
lcd.begin(16, 2);
lcd.setBacklight(255);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Hello, Techsage!");
lcd.setCursor(0, 1);
lcd.print("Press Button...");
}
void loop() {
if (digitalRead(BUTTON_PIN) == HIGH) { // Button is pressed (HIGH due to pull-down)
delay(50); // Debounce
if (digitalRead(BUTTON_PIN) == HIGH) { // Still pressed
buttonState = !buttonState; // Toggle state
lcd.clear();
if (buttonState) {
lcd.setCursor(0, 0);
lcd.print("Button Pressed!");
} else {
lcd.setCursor(0, 0);
lcd.print("Hello, Techsage!");
}
delay(300); // Avoid multiple rapid changes
}
}
}
Explanation of Code:
Library Inclusions:
- We include
Wire.h
for I2C communication andLiquidCrystal_PCF8574.h
for controlling the LCD screen.
Pin Definitions:
-
BUTTON_PIN
: Defines the GPIO pin (GPIO 12 in this case) where the push-button is connected. -
LCD_ADDR
: Defines the I2C address of the LCD (0x27 is commonly used for many LCDs with I2C).
Button and LCD Setup:
- We set the button pin as an input with a pull-down resistor to ensure the button state is LOW when not pressed.
- The LCD is initialized with 16 columns and 2 rows (
lcd.begin(16, 2)
). - Initially, the LCD shows the message “Hello, Techsage!”.
Main Loop:
- Button press detection: When the button is pressed (i.e., the GPIO pin reads HIGH), a debounce delay is added to avoid multiple triggers.
-
Button state toggle: The
buttonState
variable toggles betweentrue
andfalse
every time the button is pressed. - Message change: Based on the button state, the message on the LCD changes:
- If
buttonState
istrue
, it shows "Button Pressed!". - If
buttonState
isfalse
, it shows "Hello, Techsage!".
Debounce:
- After detecting a button press, the code waits for 50ms to debounce the button, and a 300ms delay after toggling the message ensures the LCD doesn’t change too rapidly.
How It Works:
- When the ESP32 is powered on, the LCD displays “Hello, Techsage!” on the first row and “Press Button…” on the second row.
- Pressing the button once toggles the display to show “Button Pressed!”.
- Pressing the button again toggles back to “Hello, Techsage!”.
- This cycle continues each time the button is pressed.
Part 2 : Dino Game on LCD with ESP32
Overview
In this project, we will create a Dino Run Game on a 16x2 LCD using an ESP32. The game mimics the famous Chrome Dino Game, where the dinosaur jumps over obstacles (cacti) using a push button. The score increases as the Dino successfully avoids obstacles.
Features:
Dino jumps when the button is pressed
Cactus moves across the screen
Score increments when Dino avoids cacti
Game Over screen if Dino collides with a cactus
Just Upload the Code Given Below in same circuit used above:
#include <Wire.h>
#include <LiquidCrystal_PCF8574.h>
/////// DEFINES ////////////////////////////////////
#define BASE_SPEED 70
#define INPUT_BUTTON_PIN 12 // Button at GPIO 12
#define LCD_ADDR 0x27
#define LCD_COLS 16
#define LCD_ROWS 2
#define LCD_TOP_ROW 0,0
#define LCD_BOTTOM_ROW 0,1
#define LCD_SCORE_POS 6,0
#define LCD_SDSCORE_OFFSET 14,0
#define START_STATE 0
#define GAME_STATE 1
#define GAME_OVER_STATE 2
#define DINO_FRAME_A_LCD 0
#define DINO_FRAME_B_LCD 1
#define CACTUS_LCD 2
#define DINO_AIR_TIME 20
#define INPUT_HOLD_TIME 25
#define SPEED_INCREASE_VAL 10
////////////////////////////////////////////////////
////// GLOBAL //////////////////////////////////////
LiquidCrystal_PCF8574 lcd(LCD_ADDR);
int currState = START_STATE;
int gameSpeed = BASE_SPEED;
int inputState = 0;
int inputHoldTimer = 0;
int score = 0;
int dinoY = 1;
int dinoCurrAirTime = 0;
char dinoCurrFrame = 'A';
int currCactusX = 16;
bool buttonPressed = false; // Track button state
const char* startScreenTopStr = "ESP32 Dino Run";
const char* startScreenBottomStr = "Press To Start!";
const char* gameOverTopStr = "Game Over!";
const char* scoreText = "Score:";
// Graphics
byte dinoGfxFrameA[8] =
{
0b00000,0b01110,0b10101,0b10001,
0b10010,0b11110,0b10100,0b01100
};
byte dinoGfxFrameB[8] =
{
0b00000,0b01110,0b10101,0b10001,
0b10010,0b11110,0b10100,0b10010
};
byte cactusGfx[8] =
{
0b00100,0b10101,0b10101,0b10101,
0b01110,0b01110,0b01110,0b01110
};
////////////////////////////////////////////////////
///// FUNCTIONS ////////////////////////////////////
void _initHardware()
{
pinMode(INPUT_BUTTON_PIN, INPUT_PULLDOWN); // Use internal pull-down resistor
lcd.begin(LCD_COLS, LCD_ROWS);
lcd.setBacklight(255);
lcd.clear();
}
void _initGraphics()
{
lcd.createChar(DINO_FRAME_A_LCD, dinoGfxFrameA);
lcd.createChar(DINO_FRAME_B_LCD, dinoGfxFrameB);
lcd.createChar(CACTUS_LCD, cactusGfx);
}
// Function to check button press with debounce
bool isButtonPressed() {
if (digitalRead(INPUT_BUTTON_PIN) == HIGH) {
delay(50); // Debounce delay
if (digitalRead(INPUT_BUTTON_PIN) == HIGH) {
return true;
}
}
return false;
}
void _startStateProcess()
{
if (isButtonPressed())
{
currState = GAME_STATE;
}
}
void _gameStateProcess()
{
// Check if button is pressed to jump
if (isButtonPressed() && dinoCurrAirTime == 0 && inputHoldTimer <= 0)
{
dinoY = 0; // Move dino to the air
dinoCurrAirTime = DINO_AIR_TIME;
inputHoldTimer = INPUT_HOLD_TIME;
}
// Move the cactus
if(currCactusX > 1)
{
currCactusX--;
}
else
{
if(dinoY == 0)
{
score++;
if(score % 10 == 0)
{
gameSpeed -= SPEED_INCREASE_VAL;
}
currCactusX = 16;
}
else
{
gameSpeed = BASE_SPEED;
currState = GAME_OVER_STATE;
return;
}
}
if(dinoCurrAirTime == 1)
{
dinoY = 1;
}
if(dinoCurrAirTime > 0)
{
dinoCurrAirTime--;
}
if(inputHoldTimer > 0)
{
inputHoldTimer--;
}
}
void _gameOverStateProcess()
{
if (isButtonPressed())
{
delay(250);
_resetGame();
}
}
void _resetGame()
{
score = 0;
dinoY = 1;
dinoCurrAirTime = 0;
dinoCurrFrame = 'A';
currCactusX = 16;
currState = START_STATE;
}
// Graphics
void _drawStartScreen()
{
lcd.clear();
lcd.setCursor(LCD_TOP_ROW);
lcd.print(startScreenTopStr);
lcd.setCursor(LCD_BOTTOM_ROW);
lcd.print(startScreenBottomStr);
}
void _drawGameOverScreen()
{
lcd.clear();
lcd.setCursor(LCD_TOP_ROW);
lcd.print(gameOverTopStr);
lcd.setCursor(LCD_BOTTOM_ROW);
lcd.print(scoreText);
lcd.print(score);
}
void _drawGameGraphics()
{
lcd.clear();
lcd.setCursor(0, dinoY);
if(dinoCurrFrame == 'A')
{
lcd.write(DINO_FRAME_A_LCD);
dinoCurrFrame = 'B';
}
else
{
lcd.write(DINO_FRAME_B_LCD);
dinoCurrFrame = 'A';
}
lcd.setCursor(currCactusX, 1);
lcd.write(CACTUS_LCD);
lcd.setCursor(LCD_SCORE_POS);
lcd.print(scoreText);
if(score > 9)
{
lcd.print(score);
}
else
{
lcd.setCursor(LCD_SDSCORE_OFFSET);
lcd.print(score);
}
}
////////////////////////////////////////////////////
/////// MAIN CODE //////////////////////////////////
void setup()
{
_initHardware();
_initGraphics();
}
void loop()
{
switch(currState)
{
case START_STATE:
_startStateProcess();
_drawStartScreen();
break;
case GAME_STATE:
_gameStateProcess();
_drawGameGraphics();
break;
case GAME_OVER_STATE:
_gameOverStateProcess();
_drawGameOverScreen();
break;
default:
break;
}
delay(gameSpeed);
}
How It Works
Game Start: The LCD displays `”ESP32 Dino Run”`. Pressing the button starts the game.
Gameplay:
The Dino jumps when you press the button.
The Cactus moves from right to left.
If the Dino avoids the cactus, the score increases.
Game Over: If the Dino hits a cactus, the LCD displays ”Game Over”. Pressing the button restarts the game.