How to interface SPI LCD with ESP32 using builtin library

This is the 10th tutorial in the ESP32 series using the espressif-IDF and today we will start a mini series covering the interfacing of displays with ESP32. In this mini series we will first see how to interface the SPI based displays with ESP32 using the ESP32’s built in LCD library. Then we will implement the LVGL functionality to our project. Finally we will also implement the touch function in the displays that supports it.

Later we will also use the similar method for interfacing some monochrome displays in the future tutorials.

Today we will see how to connect the 2.8″ ILI9341 display with ESP32 and how to get it working using the ESP32’s builtin LCD library. I am using ESP32 Wroom development board, but the procedure remains same for other dev boards as well.

Connection

Below is the image showing the connection between ESP32 and ILI9341.

I am going to use the VSPI_HOST for the SPI. The SPI pins in the image above are defined according to it. You can check the SPI tutorial to know more about the available instances on ESP32.

The LCD is powered with 3.3V from the MCU itself. The rest of the pins are as follows:

  • CS is connected to GPIO5.
  • RESET is connected to GPIO22.
  • DC is connected to GPIO21.
  • SDI(MOSI) is connected to GPIO23.
  • SCK is connected to GPIO18.
  • LED is connected to GPIO4. You can also connect it to 3.3V, if you do not want to control the backlight.
  • SDO(MISO) is connected to GPIO19.


Initial Setup

We will first create a basic project in the Espressif IDE.

Now go to the https://components.espressif.com/ and search for the display controller (ILI9341 in this case). You will get a lot of results, but make sure to open one with esp_lcd_**** as shown in the image below.

Download the component archive and extract it.

Now copy this extracted folder inside the project folder. I have created a new folder (components), where I will place all the components used in the project.

We need to link this component to our project. To do so, we will create a new file (idf_component.yml) inside the main folder.

Now add the dependency of the lcd component in this file.

dependencies:
 esp_lcd_ili9341: "^2.0.0"

Note:- make sure there is only single space in the 2nd line.

Here the 2.0.0 is the component library version and it can be found in the component page itself.



The code

First we need to include the necessary files.

#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include "driver/gpio.h"
#include "esp_lcd_ili9341.h"
#include "esp_lcd_panel_commands.h"
#include "esp_lcd_panel_dev.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_vendor.h"
#include "hal/gpio_types.h"
#include "hal/spi_types.h"

Here I have included the esp_lcd_ili9341.h file from the component. The rest of the inclusions starting with esp_lcd_panel_** are needed for the LCD operations. All of them might not be needed, but I have included all available.

Now we will define the pins used in this project.

#define LCD_HOST	VSPI_HOST
#define PIN_NUM_SCLK    18
#define PIN_NUM_MOSI    23
#define PIN_NUM_MISO    19
#define PIN_NUM_LCD_CS  5
#define PIN_NUM_BKL     4
#define PIN_NUM_RST     22
#define PIN_NUM_LCD_DC  21

As I mentioned earlier, I am going to use the VSPI_HOST for the LCD. The pins are defined according to the connection diagram shown in the beginning of the article.

Next define some LCD related constants we will be using.

#define LCD_H_RES	240
#define LCD_V_RES	320
#define LCD_PIXEL_CLOCK_HZ	20*1000*1000
#define LCD_CMD_BITS	8
#define LCD_PARAM_BITS	8

H_RES and V_RES are the Horizontal and Vertical resolution of the display. The LCD_PIXEL_CLOCK_HZ is the Piexl clock frequency in Hertz. The pixel clock determines how quickly the ESP32 can send data to update the LEDs, thus controlling the refresh rate and animation speed. I have set it to 20MHz.

LCD_CMD_BITS and LCD_PARAM_BITS sets the bit width of the command and parameter that recognised by the LCD controller chip. This is chip specific, and ILI9341 uses 8 bits for it.

We will create a separate function to initialise the display driver. We just need to follow the steps mentioned in this guide.

static void display_init (void)
{
	gpio_set_direction(PIN_NUM_BKL, GPIO_MODE_OUTPUT);
	gpio_set_level(PIN_NUM_BKL, 1);

First of all I am setting the LCD backlight pin as output and the level of this pin is set to high. If you are connecting the backlight pin to 3.3V, you do not need to do this.

STEP1. Create a SPI bus.

       spi_bus_config_t buscfg = {
    .sclk_io_num = PIN_NUM_SCLK,
    .mosi_io_num = PIN_NUM_MOSI,
    .miso_io_num = PIN_NUM_MISO,
    .quadwp_io_num = -1,
    .quadhd_io_num = -1,
    .max_transfer_sz = LCD_H_RES * 80 * sizeof(uint16_t), // transfer 80 lines of pixels (assume pixel is RGB565) at most in one SPI transaction
};
ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO)); // Enable the DMA feature

Here we will simply define the CLK, MISO and MOSI pins for the SPI. Since we are not using QUADSPI, the respective pins are set to -1. The maximum transfer size in one SPI transaction is set to LCD_H_RES * 80 * 2 bytes. Basically we can transfer upto 80 lines data in one transaction.

STEP 2. Allocate an LCD IO device handle from the SPI bus.

esp_lcd_panel_io_spi_config_t io_config = {
    .dc_gpio_num = PIN_NUM_LCD_DC,
    .cs_gpio_num = PIN_NUM_LCD_CS,
    .pclk_hz = LCD_PIXEL_CLOCK_HZ,
    .lcd_cmd_bits = LCD_CMD_BITS,
    .lcd_param_bits = LCD_PARAM_BITS,
    .spi_mode = 0,
    .trans_queue_depth = 10,
};
// Attach the LCD to the SPI bus
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));

Here we will allocate the CS and DC pins along with the Pixel Clock and command and parameter bits. The SPI Mode is set to Mode 0 and the queue depth is set to 10. This is basically the  depth of the SPI transaction queue.

STEP 3. Install the LCD controller driver. The LCD controller driver is responsible for sending the commands and parameters to the LCD controller chip.

esp_lcd_panel_dev_config_t panel_config = {
    .reset_gpio_num = PIN_NUM_RST,
    .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
    .bits_per_pixel = 16,
};
// Create LCD panel handle for ILI9341, with the SPI IO device handle
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(io_handle, &panel_config, &lcd_panel_handle));

The rgb_ele_order sets the R-G-B element order of each color data. It is set to BGR in case of ILI9341. If there is some colour related issue, you can set it back to RGB.

bits_per_pixel sets the bit width of the pixel color data. The LCD driver uses this value to calculate the number of bytes to send to the LCD controller chip. ILI9341 uses RGB565, which requires 2 bytes per pixel, hence it is set to 16 bits.

The function esp_lcd_new_panel_ili9341 will attach the above configuration to the ILI9341. This function is present in the component library we downloaded. In fact a similar function is present in each LCD component library. So say for example in case of ST7735, the function will change to esp_lcd_new_panel_st7735.

STEP 4. Perform the LCD IO Operations.

So far we have only attached the LCD to the SPI. Now we will perform the initialization for the display.

esp_lcd_panel_reset(lcd_panel_handle);
esp_lcd_panel_init(lcd_panel_handle);
esp_lcd_panel_disp_on_off(lcd_panel_handle, true);
}

Here we will first reset the LCD. The initialise it and finally turn the display on.

The main function

Inside the main function, we will first initialise the display and then draw some bitmap on it. This display driver is created to be used with libraries like LVGL, hence it does not provide many functions to draw on display. Therefore we only have a function to draw the bitmap on the display.

void app_main(void)
{
    display_init();
    esp_lcd_panel_swap_xy(lcd_panel_handle, true);
    esp_lcd_panel_draw_bitmap(lcd_panel_handle, 0, 0, 100, 50, hello_map);
    while (true) {
        sleep(1);
    }
}

After initialising the display, I am using the swap function to swap the X and Y Axes of the display. Basically you can use a combination of esp_lcd_panel_swap_xy and esp_lcd_panel_mirror function to rotate the display.

I am using a small bitmap of resolution 100×50. The drawing starts at the beginning of the display (0,0). The bitmap array is defined as hello_map, which you can find in the final project itself.

Any other SPI display can be interfaced using the same method. There are only few things which needs to be taken care of.

  • The function to Create LCD panel handle, esp_lcd_new_panel_ili9341 will change according to the display.
  • The rgb_ele_order might change based on the display color endianness. You can try keeping the same order, but if the colours are different than the actual image, change this order. There are only 2 orders supported, RGB and BGR.
  • The bits_per_pixel might change based on the display. Check if the display controller supports RGB565(16 bits) or RGB888(24 bits) or XRGB8888(32 bits).
  • The LCD_H_RES and LCD_V_RES will definitely change based on the display size.
  • The LCD_CMD_BITS and LCD_PARAM_BITS might change based on the display. Do check the datasheet of the display controller.


Result

Below is the image showing the bitmap printed on the display.

The display is rotated because of the sawp function I used before drawing the bitmap.

Check out the Video Below




Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

Subscribe
Notify of

0 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments