Updated on 2023-03-11
Initializing the ESP LCD Panel API is a chore. Here's some boilerplate code to make it easier.
Update: This code is fine, but if you want a PlatformIO project and, several drivers with this code (plus slightly updated functionality), you can download these bits here.
I hate writing the same code more than once, especially when I find I need it in project after project, and yet some things are not worth abstracting, or the cost of abstracting is unrealistic.
Setting up Espressif's ESP LCD Panel API is one of those things. If you need the code once, chances are you'll need it over and over again in future projects, slightly changed for the particular hardware you are using.
To that end, I've provided a bunch of boilerplate code wherein you simply set some #defines and call lcd_panel_init() and Bob's your uncle. Instant initialization code for your device.
This code is public domain and copyleft. It requires no attribution or license, but you use it at your own risk.
Copy the code below into your project, and modify the defines to fit your hardware. I've provided several examples.
LCD_SPI_HOST is for SPI devices only and indicates which ESP32 SPI host is used for the LCD.
LCD_BK_LIGHT_ON_LEVEL should be 1 for most devices, but some turn the backlight on when the pin is low, so setting this to zero will work for those devices.
PIN_NUM_XXXXX are GPIO assignments for the various connections.
LCD_PANEL indicates the function to create a new driver instance for the display's controller.
LCD_FLUSH_CALLBACK is the routine that will be called when the DMA transfer is complete.
LCD_TRANSFER_SIZE is the maximum size of the DMA buffer that can be sent.
The rest of the LCD_XXXXX functions are various details and minor errata about your hardware.
I've provided examples for several devices - the Lilygo TTGO T1 Display, the Makerfabs ESP Display S3 Parallel w/ Touch, the Espressif ESP_WROVER_KIT 4.1, the M5Stack Core2, and the M5Stack Fire.
I'm posting the code in two parts. The first part is the license. I don't require you use it but it has been asked for, so I'm putting it here.
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute
this software, either in source code form or as a compiled binary, for any
purpose, commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of
this software dedicate any and all copyright interest in the software to
the public domain. We make this dedication for the benefit of the public
at large and to the detriment of our heirs and successors. We intend this
dedication to be an overt act of relinquishment in perpetuity of all
present and future rights to this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to https://unlicense.org
*/
#ifdef TTGO_T1
#define LCD_SPI_HOST SPI3_HOST
#define LCD_BK_LIGHT_ON_LEVEL 1
#define LCD_BK_LIGHT_OFF_LEVEL !LCD_BK_LIGHT_ON_LEVEL
#define PIN_NUM_MOSI 19
#define PIN_NUM_CLK 18
#define PIN_NUM_CS 5
#define PIN_NUM_DC 16
#define PIN_NUM_RST 23
#define PIN_NUM_BCKL 4
#define LCD_PANEL esp_lcd_new_panel_st7789
#define LCD_HRES 135
#define LCD_VRES 240
#define LCD_COLOR_SPACE ESP_LCD_COLOR_SPACE_RGB
#define LCD_PIXEL_CLOCK_HZ (40 * 1000 * 1000)
#define LCD_GAP_X 40
#define LCD_GAP_Y 52
#define LCD_MIRROR_X false
#define LCD_MIRROR_Y true
#define LCD_INVERT_COLOR true
#define LCD_SWAP_XY true
#define LCD_TRANSFER_SIZE (32*1024)
#define LCD_FLUSH_CALLBACK lcd_flush_ready
#endif // TTGO_T1
#ifdef ESP_WROVER_KIT
#define LCD_BK_LIGHT_ON_LEVEL 0
#define LCD_BK_LIGHT_OFF_LEVEL !LCD_BK_LIGHT_ON_LEVEL
#define LCD_SPI_HOST HSPI_HOST
#define PIN_NUM_MISO 25
#define PIN_NUM_MOSI 23
#define PIN_NUM_CLK 19
#define PIN_NUM_CS 22
#define PIN_NUM_DC 21
#define PIN_NUM_RST 18
#define PIN_NUM_BCKL 5
#define LCD_PANEL esp_lcd_new_panel_ili9341
#define LCD_HRES 240
#define LCD_VRES 320
#define LCD_COLOR_SPACE ESP_LCD_COLOR_SPACE_BGR
#define LCD_PIXEL_CLOCK_HZ (40 * 1000 * 1000)
#define LCD_GAP_X 0
#define LCD_GAP_Y 0
#define LCD_MIRROR_X false
#define LCD_MIRROR_Y false
#define LCD_INVERT_COLOR false
#define LCD_SWAP_XY true
#define LCD_TRANSFER_SIZE (32*1024)
#define LCD_FLUSH_CALLBACK lcd_flush_ready
#endif // ESP_WROVER_KIT
#ifdef ESP_DISPLAY_S3
#define LCD_BK_LIGHT_ON_LEVEL 1
#define LCD_BK_LIGHT_OFF_LEVEL !LCD_BK_LIGHT_ON_LEVEL
#define PIN_NUM_CS 37
#define PIN_NUM_WR 35
#define PIN_NUM_RD 48
#define PIN_NUM_RS 36
#define PIN_NUM_D00 47
#define PIN_NUM_D01 21
#define PIN_NUM_D02 14
#define PIN_NUM_D03 13
#define PIN_NUM_D04 12
#define PIN_NUM_D05 11
#define PIN_NUM_D06 10
#define PIN_NUM_D07 9
#define PIN_NUM_D08 3
#define PIN_NUM_D09 8
#define PIN_NUM_D10 16
#define PIN_NUM_D11 15
#define PIN_NUM_D12 7
#define PIN_NUM_D13 6
#define PIN_NUM_D14 5
#define PIN_NUM_D15 4
#define PIN_NUM_BCKL 45
#define LCD_PANEL esp_lcd_new_panel_ili9488
#define LCD_HRES 320
#define LCD_VRES 480
#define LCD_COLOR_SPACE ESP_LCD_COLOR_SPACE_BGR
#define LCD_PIXEL_CLOCK_HZ (20 * 1000 * 1000)
#define LCD_GAP_X 0
#define LCD_GAP_Y 0
#define LCD_MIRROR_X false
#define LCD_MIRROR_Y false
#define LCD_INVERT_COLOR false
#define LCD_SWAP_XY true
#define LCD_TRANSFER_SIZE (32*1024)
#define LCD_FLUSH_CALLBACK lcd_flush_ready
#endif // ESP_DISPLAY_S3
#ifdef M5STACK_CORE2
#define LCD_SPI_HOST SPI3_HOST
#define LCD_BK_LIGHT_ON_LEVEL 1
#define LCD_BK_LIGHT_OFF_LEVEL !LCD_BK_LIGHT_ON_LEVEL
#define PIN_NUM_MOSI 23
#define PIN_NUM_CLK 18
#define PIN_NUM_CS 5
#define PIN_NUM_DC 15
#define LCD_PANEL esp_lcd_new_panel_ili9342
#define LCD_HRES 240
#define LCD_VRES 320
#define LCD_COLOR_SPACE ESP_LCD_COLOR_SPACE_BGR
#define LCD_PIXEL_CLOCK_HZ (40 * 1000 * 1000)
#define LCD_GAP_X 0
#define LCD_GAP_Y 0
#define LCD_MIRROR_X false
#define LCD_MIRROR_Y false
#define LCD_INVERT_COLOR true
#define LCD_SWAP_XY true
#define LCD_TRANSFER_SIZE (32*1024)
#define LCD_FLUSH_CALLBACK lcd_flush_ready
#endif
#ifdef M5STACK_FIRE
#define LCD_SPI_HOST SPI3_HOST
#define LCD_BK_LIGHT_ON_LEVEL 1
#define LCD_BK_LIGHT_OFF_LEVEL !LCD_BK_LIGHT_ON_LEVEL
#define PIN_NUM_MOSI 23
#define PIN_NUM_CLK 18
#define PIN_NUM_CS 14
#define PIN_NUM_DC 27
#define PIN_NUM_RST 33
#define PIN_NUM_BCKL 32
#define LCD_PANEL esp_lcd_new_panel_ili9342
#define LCD_HRES 240
#define LCD_VRES 320
#define LCD_COLOR_SPACE ESP_LCD_COLOR_SPACE_BGR
#define LCD_PIXEL_CLOCK_HZ (40 * 1000 * 1000)
#define LCD_GAP_X 0
#define LCD_GAP_Y 0
#define LCD_MIRROR_X false
#define LCD_MIRROR_Y false
#define LCD_INVERT_COLOR true
#define LCD_SWAP_XY true
#define LCD_TRANSFER_SIZE (32*1024)
#define LCD_FLUSH_CALLBACK lcd_flush_ready
#endif
// global so it can be used after init
esp_lcd_panel_handle_t lcd_handle
// initialize the screen using the esp lcd panel API
void lcd_panel_init() {
#ifdef PIN_NUM_BCKL
pinMode(PIN_NUM_BCKL, OUTPUT);
#endif // PIN_NUM_BCKL
#ifdef LCD_SPI_HOST // 1-bit SPI
spi_bus_config_t bus_config;
memset(&bus_config, 0, sizeof(bus_config));
bus_config.sclk_io_num = PIN_NUM_CLK;
bus_config.mosi_io_num = PIN_NUM_MOSI;
#ifdef PIN_NUM_MISO
bus_config.miso_io_num = PIN_NUM_MISO;
#else
bus_config.miso_io_num = -1;
#endif // PIN_NUM_MISO
#ifdef PIN_NUM_QUADWP
bus_config.quadwp_io_num = PIN_NUM_QUADWP;
#else
bus_config.quadwp_io_num = -1;
#endif
#ifdef PIN_NUM_QUADHD
bus_config.quadhd_io_num = PIN_NUM_QUADHD;
#else
bus_config.quadhd_io_num = -1;
#endif
bus_config.max_transfer_sz = LCD_TRANSFER_SIZE + 8;
// Initialize the SPI bus on LCD_SPI_HOST
spi_bus_initialize(LCD_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO);
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_spi_config_t io_config;
memset(&io_config, 0, sizeof(io_config));
io_config.dc_gpio_num = PIN_NUM_DC,
io_config.cs_gpio_num = PIN_NUM_CS,
io_config.pclk_hz = LCD_PIXEL_CLOCK_HZ,
io_config.lcd_cmd_bits = 8,
io_config.lcd_param_bits = 8,
io_config.spi_mode = 0,
io_config.trans_queue_depth = 10,
io_config.on_color_trans_done = LCD_FLUSH_CALLBACK;
// Attach the LCD to the SPI bus
esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI_HOST,
&io_config, &
io_handle);
#elif defined(PIN_NUM_D07) // 8 or 16-bit i8080
pinMode(PIN_NUM_RD,OUTPUT);
digitalWrite(PIN_NUM_RD,HIGH);
esp_lcd_i80_bus_handle_t i80_bus = NULL;
esp_lcd_i80_bus_config_t bus_config;
memset(&bus_config,0,sizeof(bus_config));
bus_config.clk_src = LCD_CLK_SRC_PLL160M;
bus_config.dc_gpio_num = PIN_NUM_RS;
bus_config.wr_gpio_num = PIN_NUM_WR;
bus_config.data_gpio_nums[0] = PIN_NUM_D00;
bus_config.data_gpio_nums[1] = PIN_NUM_D01;
bus_config.data_gpio_nums[2] = PIN_NUM_D02;
bus_config.data_gpio_nums[3] = PIN_NUM_D03;
bus_config.data_gpio_nums[4] = PIN_NUM_D04;
bus_config.data_gpio_nums[5] = PIN_NUM_D05;
bus_config.data_gpio_nums[6] = PIN_NUM_D06;
bus_config.data_gpio_nums[7] = PIN_NUM_D07;
#ifdef PIN_NUM_D15
bus_config.data_gpio_nums[8] = PIN_NUM_D08;
bus_config.data_gpio_nums[9] = PIN_NUM_D09;
bus_config.data_gpio_nums[10] = PIN_NUM_D10;
bus_config.data_gpio_nums[11] = PIN_NUM_D11;
bus_config.data_gpio_nums[12] = PIN_NUM_D12;
bus_config.data_gpio_nums[13] = PIN_NUM_D13;
bus_config.data_gpio_nums[14] = PIN_NUM_D14;
bus_config.data_gpio_nums[15] = PIN_NUM_D15;
bus_config.bus_width = 16;
#else
bus_config.bus_width = 8;
#endif // PIN_NUM_D15
bus_config.max_transfer_bytes = LCD_TRANSFER_SIZE;
esp_lcd_new_i80_bus(&bus_config, &i80_bus);
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_i80_config_t io_config;
memset(&io_config,0,sizeof(io_config));
io_config.cs_gpio_num = PIN_NUM_CS;
io_config.pclk_hz = LCD_PIXEL_CLOCK_HZ;
io_config.trans_queue_depth = 20;
io_config.dc_levels.dc_idle_level=0;
io_config.dc_levels.dc_idle_level = 0;
io_config.dc_levels.dc_cmd_level = 0;
io_config.dc_levels.dc_dummy_level = 0;
io_config.dc_levels.dc_data_level = 1;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
io_config.on_color_trans_done = LCD_FLUSH_CALLBACK;
io_config.user_ctx = nullptr;
io_config.flags.swap_color_bytes = true;
io_config.flags.cs_active_high = false;
io_config.flags.reverse_color_bits = false;
esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle);
#endif // PIN_NUM_D15
lcd_handle = NULL;
esp_lcd_panel_dev_config_t panel_config;
memset(&panel_config, 0, sizeof(panel_config));
#ifdef PIN_NUM_RST
panel_config.reset_gpio_num = PIN_NUM_RST;
#else
panel_config.reset_gpio_num = -1;
#endif
panel_config.color_space = LCD_COLOR_SPACE;
panel_config.bits_per_pixel = 16;
// Initialize the LCD configuration
LCD_PANEL(io_handle, &panel_config, &lcd_handle);
#ifdef PIN_NUM_BCKL
// Turn off backlight to avoid unpredictable display on
// the LCD screen while initializing
// the LCD panel driver. (Different LCD screens may need different levels)
digitalWrite(PIN_NUM_BCKL, LCD_BK_LIGHT_OFF_LEVEL);
#endif // PIN_NUM_BCKL
// Reset the display
esp_lcd_panel_reset(lcd_handle);
// Initialize LCD panel
esp_lcd_panel_init(lcd_handle);
esp_lcd_panel_swap_xy(lcd_handle, LCD_SWAP_XY);
esp_lcd_panel_set_gap(lcd_handle, LCD_GAP_X, LCD_GAP_Y);
esp_lcd_panel_mirror(lcd_handle, LCD_MIRROR_X, LCD_MIRROR_Y);
esp_lcd_panel_invert_color(lcd_handle, LCD_INVERT_COLOR);
// Turn on the screen
esp_lcd_panel_disp_off(lcd_handle, false);
#ifdef PIN_NUM_BCKL
// Turn on backlight (Different LCD screens may need different levels)
digitalWrite(PIN_NUM_BCKL, LCD_BK_LIGHT_ON_LEVEL);
#endif // PIN_NUM_BCKL
}