I2C LCD with ESP32 using ESP-IDF

In this tutorial, we will learn how to interface I2C LCD with ESP32 and how to display simple text/numbers on the I2C LCD using ESP-IDF. This I2C LCD is a 16×2 device which means it can display 16 columns by two rows of alphanumeric characters on this display. The LCD has the usual type of hd44780 controller, and it also has an I2C circuit connected with it which makes it easy to connect to the ESP32 board. A typical 16×2 LCD has sixteen pins that need to be connected with the ESP32 board. Using an I2C LCD, the pins are limited to only 4 from which two are power pins (VCC, GND) and the other two are I2C pins (SCL, SDA). Hence, it becomes very easy and handy to work with this 16×2 I2C LCD instead of the generic 16×2 LCD.

Before we move ahead, make sure you have the latest version of VS Code installed on your system with the ESP-IDF extension configured.

Interface I2C LCD with ESP32

The I2C controller module as shown below has 16 pins that connect with the 16×2 LCD pins. The variable resistor found on the module is used to modify the brightness of the LCD. This potentiometer is very handy when you are using this display module in different light conditions. Moreover, you can view the external 4 pins on the module namely GND, VCC, SDA, and SCL that connect with the ESP32 development board so that the 16×2 LCD and the microcontroller can communicate via I2C protocol.

I2C LCD

ESP32 I2C Pins

The diagram below shows the pinout for the ESP32, where the default I2C pins are highlighted in red. These are the default I2C pins of the ESP32 DEVKIT module. However, if you want to use other GPIO pins as I2C pins then you will have to set them in code.

ESP32 Pinout I2C Pins
ESP32 I2C Pins

Refer to the following article to know more about ESP32 GPIO pins:

Interfacing I2C LCD with ESP32

Interface I2C LCD with ESP32 ESP-IDF

We will need the following components to connect our ESP32 board with the I2C LCD.

  1. ESP32 board
  2. I2C LCD
  3. Connecting Wires
  4. Breadboard

The connection of I2C LCD with the ESP32 board is very simple as it just involves the connection of the 4 pins (GND, VCC, SDA, and SCL) with ESP32. We have to connect the VCC terminal with Vin pin, ground with the ground (common ground), SCL of the sensor with SCL of ESP32 board, and SDA of the sensor with the SDA pin of the ESP32 board.

In ESP32, the default I2C pin for SDA is GPIO21 and for SCL is GPIO22.

The connections between the devices are specified in the table below:

I2C LCDESP32
GNDGND
VCCVIN
SDAGPIO21(I2C SDA)
SCLGPIO22 (I2C SCL)

ESP32 with I2C LCD Schematic Diagram

Follow the schematic diagram below to connect the ESP32 module with the I2C LCD.

ESP32 with I2C LCD schematic diagram

As seen in the schematic diagram above, the I2C LCD is powered by the Vin pin of ESP32. Both the grounds of the devices are connected in common. The SCL pin of I2C LCD is connected with GPIO 22 which is the default SCL pin of the ESP32 board. Similarly, the SDA pin of the I2C LCD is connected with GPIO21 which is the default SDA pin of the ESP32 board.

If you are using Arduino instead of ESP-IDF, you can refer to this article:

ESP32 I2C LCD with ESP-IDF

Open your VS Code and create a new ESP-IDF project. Now head over to the main.c file. We will define the functions and the program code here.

EPS32 Display Alphanumeric characters on I2C LCD Code

ESP32 with I2C LCD ESP-IDF demo

We will use this code to display both letters and numerals on the I2C LCD. We will display the message “16×2 I2C LCD” on the screen for 3 seconds. Then the screen will clear. Another message “Lets Count 0-10!” will be displayed for three seconds. After that, the LCD will clear again and display numbers from 0 to 10 after every second. This loop will repeat continuously.

#include <driver/i2c.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <stdio.h>
#include "sdkconfig.h"
#include "HD44780.h"

#define LCD_ADDR 0x27
#define SDA_PIN  21
#define SCL_PIN  22
#define LCD_COLS 16
#define LCD_ROWS 2

void LCD_DemoTask(void* param)
{
    char num[20];
    while (true) {
        LCD_home();
        LCD_clearScreen();
        LCD_writeStr("16x2 I2C LCD");
        vTaskDelay(3000 / portTICK_RATE_MS);
        LCD_clearScreen();
        LCD_writeStr("Lets Count 0-10!");
        vTaskDelay(3000 / portTICK_RATE_MS);
        LCD_clearScreen();
        for (int i = 0; i <= 10; i++) {
            LCD_setCursor(8, 2);
            sprintf(num, "%d", i);
            LCD_writeStr(num);
            vTaskDelay(1000 / portTICK_RATE_MS);
        }
  
    }
}

void app_main(void)
{
    LCD_init(LCD_ADDR, SDA_PIN, SCL_PIN, LCD_COLS, LCD_ROWS);
    xTaskCreate(&LCD_DemoTask, "Demo Task", 2048, NULL, 5, NULL);
    
}

How Code Works?

Firstly, we will start by including the necessary libraries for this project. This includes the i2c driver, ESP32_HD44780, and FreeRTOS libraries. The driver/i2c.h library will enable the ESP32 to communicate with other I2C devices, in our case it is the LCD. The HD44780 is the universal ESP-IDF driver for HD44780 LCD to display texts and numbers. Hence, we include the header file “HD44780.h” as well.

In your project directory create a component folder and place HD44780 folder there. To obtain the HDD44780 folder, follow this link.

#include <driver/i2c.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <stdio.h>
#include "sdkconfig.h"
#include "HD44780.h"

I2C LCD Parameters

Next, we will define the address of our I2C LCD. In our case it is ‘0x27’ which is usually the default address of the I2C LCD.

#define LCD_ADDR 0x27

Specify the SDA and SCL pins of the ESP32 being used for I2C communication. In our case, we are using the default I2Cpins, however, if you want to use other GPIO pins of ESP32 for I2C communication, you will have to define them here.

#define SDA_PIN  21
#define SCL_PIN  22

Moreover, we will also specify the rows and columns of our LCD. In our case, we are using a 16×2 LCD hence the number of columns are 16 and the number of rows are 2.

#define LCD_COLS 16
#define LCD_ROWS 2

Task Callback

Here we are defining the LCD_DemoTask() function which acts as a task callback. It will be used to display the different texts on the LCD screen.

void LCD_DemoTask(void* param)
{
    char num[20];
    while (true) {
        LCD_home();
        LCD_clearScreen();
        LCD_writeStr("16x2 I2C LCD");
        vTaskDelay(3000 / portTICK_RATE_MS);
        LCD_clearScreen();
        LCD_writeStr("Lets Count 0-10!");
        vTaskDelay(3000 / portTICK_RATE_MS);
        LCD_clearScreen();
        for (int i = 0; i <= 10; i++) {
            LCD_setCursor(8, 2);
            sprintf(num, "%d", i);
            LCD_writeStr(num);
            vTaskDelay(1000 / portTICK_RATE_MS);
        }
  
    }
}
Clear Screen

Inside our while loop, we will start off by clearing our LCD screen by calling the LCD_clearScreen() function.

LCD_clearScreen();
Set Cursor Position at (0,0)

Next, call the LCD_home() function. This will ensure that the cursor location is set at (column = 0, row = 0) which denotes the upper left hand corner of the screen.

LCD_home();
Display String

Then, using the LCD_writeStr() function, we will pass the string that we want to display on the LCD as a parameter inside it. In our case, we want to print “16×2 I2C LCD.”

LCD_writeStr("16x2 I2C LCD");

We will add a delay of 3 seconds using the vTaskDelay() function and then clear the screen again using LCD_clearScreen() function.

vTaskDelay(3000 / portTICK_RATE_MS);
LCD_clearScreen();

Next, we will display the text “Lets Count 0-10!” on the LCD screen. The cursor position is unchanged so the text will be displayed at (0,0). Again add a delay of 3 seconds and then clear the screen.

LCD_writeStr("Lets Count 0-10!");
vTaskDelay(3000 / portTICK_RATE_MS);
LCD_clearScreen();
Set Position of Cursor and Display Numerals

Now using a for loop we will display the numerals from 0 to 10 after every second. First, we will set the cursor position to column 8 and row 2. This is done by using the LCD_setCursor() function and specifying the column position as the first parameter and the row position as the second parameter. Then we will convert the integer variable ‘i’ to a string and store the values in the array of characters called ‘num’. Note that ‘i’ is being incremented in the for loop from 0 to 10. This will then be displayed on the LCD using the LCD_write() function where we specify ‘num’ as the parameter inside it. Moreover, we display each number after a delay of delay of 1 second.

      for (int i = 0; i <= 10; i++) {
            LCD_setCursor(8, 2);
            sprintf(num, "%d", i);
            LCD_writeStr(num);
            vTaskDelay(1000 / portTICK_RATE_MS);
        }

Inside the main function, we will initialize the I2C LCD by calling the LCD_init() function. It takes in five parameters which are the I2C bus address of the LCD, SDA pin being used, SCL pin being used, the number of LCD columns and the number of LCD rows respectively. All of these variables were previously defined in the code. Then we will create our FreeRTOS task using the xTaskCreate() function and specify its parameters as mentioned in the code below:

void app_main(void)
{
    LCD_init(LCD_ADDR, SDA_PIN, SCL_PIN, LCD_COLS, LCD_ROWS);
    xTaskCreate(&LCD_DemoTask, "Demo Task", 2048, NULL, 5, NULL);
    
}

Compiling the Sketch

To flash your chip, type the following command in the serial terminal. Remember to replace the COM port with the one through which your board is connected.

idf.py -p COMX flash monitor
ESP32 with I2C LCD using ESP-IDF flash chip

After the code flashes successfully, the I2C LCD will start displaying the messages as shown in the video below:

You may also like to read:

4 thoughts on “I2C LCD with ESP32 using ESP-IDF”

  1. Great content, but I’m stucked at the HD44780 folder.

    I have no idea how do download it from GitHub, copy/paste it and import it to the right place in IDF.

    If you could add this part it would be great.

    Thanks.

    Reply

Leave a Comment