This code will work with the latest master branch of the ESP-IDF and also the latest master branch of LVGL…
This is pseudo code and has not been tested. There are bound to be some small errors and typos that will need to be corrected. It gives a general idea of how to use the internal mechanics of the ESP-IDF to achieve the best possible performance. This enables DMA transfers and double buffering which will greatly improve the speed of your application.
#include <stdio.h>
#include <unistd.h>
#include <sys/lock.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_st7789.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_check.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lvgl.h"
static const char *TAG = "MyDisplay";
/* LCD size */
#define DISP_HOR_RES 240
#define DISP_VER_RES 320
/* LCD settings */
#define DISP_DRAW_BUFF_HEIGHT 50
/* LCD pins */
#define DISP_SPI_NUM SPI3_HOST
#define DISP_GPIO_SCLK GPIO_NUM_41 // GPIO_NUM_6
#define DISP_GPIO_MOSI GPIO_NUM_40 // GPIO_NUM_7
#define DISP_GPIO_RST GPIO_NUM_39 // GPIO_NUM_8
#define DISP_GPIO_DC GPIO_NUM_44 // GPIO_NUM_4
#define DISP_GPIO_CS GPIO_NUM_42 // GPIO_NUM_5
#define DISP_GPIO_BL GPIO_NUM_1 // GPIO_NUM_15
/* Touch settings */
#define DISP_TOUCH_I2C_NUM I2C_NUM_1
#define DISP_TOUCH_I2C_CLK_HZ 400000 // 400000
/* LCD touch pins */
#define TOUCH_I2C_SCL GPIO_NUM_21 // GPIO_NUM_21 9
#define TOUCH_I2C_SDA GPIO_NUM_14 // GPIO_NUM_14 10
#define TOUCH_GPIO_INT GPIO_NUM_38 // GPIO_NUM_38
#define TOUCH_GPIO_RST GPIO_NUM_11 // GPIO_NUM_11 // dummy
#define BUFFER_SIZE (DISP_HOR_RES * DISP_VER_RES * sizeof(uint16_t) / 10)
static esp_lcd_panel_handle_t panel_handle = NULL;
static esp_lcd_panel_io_handle_t io_handle = NULL;
static lv_display_t *display = NULL;
static void* buf1 = NULL;
static void* buf2 = NULL;
// this gets called when the DMA transfer of the buffer data has completed
static bool notify_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
lv_display_t *disp = (lv_display_t *)user_ctx;
lv_display_flush_ready(disp);
return false;
}
static void flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
int x1 = area->x1;
int x2 = area->x2;
int y1 = area->y1;
int y2 = area->y2;
// uncomment the following line if the colors are wrong
// lv_draw_sw_rgb565_swap(px_map, (x2 + 1 - x1) * (y2 + 1 - y1));
esp_lcd_panel_draw_bitmap((esp_lcd_panel_handle_t)lv_display_get_user_data(disp), x1, y1, x2 + 1, y2 + 1, px_map);
}
static void lvgl_tick_increment(void *arg)
{
// Tell LVGL how many milliseconds have elapsed
lv_tick_inc(2); // tjek
}
static esp_err_t lvgl_tick_init(void)
{
esp_timer_handle_t tick_timer;
// Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &lvgl_tick_increment,
.name = "LVGL tick",
};
ESP_RETURN_ON_ERROR(esp_timer_create(&lvgl_tick_timer_args, &tick_timer), TAG, "Creating LVGL timer filed!");
return esp_timer_start_periodic(tick_timer, 2 * 1000); // 2 ms
}
static esp_err_t lvgl_init(void)
{
lv_init();
display = lv_display_create(DISP_HOR_RES, DISP_VER_RES);
buf1 = (void *)spi_bus_dma_memory_alloc(DISP_SPI_NUM, BUFFER_SIZE, 0);
buf2 = (void *)spi_bus_dma_memory_alloc(DISP_SPI_NUM, BUFFER_SIZE, 0);
lv_display_set_buffers(display, buf1, buf2, BUFFER_SIZE, LV_DISPLAY_RENDER_MODE_PARTIAL);
lv_display_set_user_data(display, panel_handle);
lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565);
lv_display_set_flush_cb(display, flush_cb);
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = notify_flush_ready,
};
/* Register done callback */
ESP_ERROR_CHECK(esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, display));
return ESP_OK;
}
static esp_err_t display_init(void)
{
/* LCD backlight and DC */
gpio_config_t gpio_cfg = {
.pin_bit_mask = (1ULL << DISP_GPIO_BL),
.mode = GPIO_MODE_OUTPUT,
};
ESP_ERROR_CHECK(gpio_config(&gpio_cfg));
/* LCD initialization */
ESP_LOGD(TAG, "Initialize SPI bus");
spi_bus_config_t buscfg = {
.sclk_io_num = DISP_GPIO_SCLK,
.mosi_io_num = DISP_GPIO_MOSI,
.miso_io_num = GPIO_NUM_NC,
.quadwp_io_num = GPIO_NUM_NC,
.quadhd_io_num = GPIO_NUM_NC,
.max_transfer_sz = BUFFER_SIZE
};
ESP_RETURN_ON_ERROR(spi_bus_initialize(DISP_SPI_NUM, &buscfg, SPI_DMA_CH_AUTO), TAG, "SPI init failed");
esp_lcd_panel_io_spi_config_t io_config = {
.dc_gpio_num = DISP_GPIO_DC,
.cs_gpio_num = DISP_GPIO_CS,
.pclk_hz = 80 * 1000 * 1000,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.spi_mode = 0,
.trans_queue_depth = 10,
};
ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)DISP_SPI_NUM, &io_config, &io_handle), "SPI init failed");
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = DISP_GPIO_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
};
ESP_RETURN_ON_ERROR(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle), "Display init failed");
return ESP_OK;
}
void app_main() {
vTaskDelay(5000/portTICK_PERIOD_MS);
esp_err_t ret = display_init(void);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "ST7796 failed to initilize");
while (1);
}
ret = lvgl_init(void);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "LVGL Display failed to initialize");
while (1);
}
ret = lvgl_tick_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Timer failed to initialize");
while (1);
}
// Create a simple label
lv_obj_t *label = lv_label_create(lv_screen_active());
lv_label_set_text(label, "Hello, LVGL!");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
gpio_set_level(DISP_GPIO_BL, 1); // Turn on backlight
long curtime = esp_timer_get_time()/1000;
int counter = 0;
// Handle LVGL tasks
while (1) {
vTaskDelay(pdMS_TO_TICKS(10));
lv_task_handler();
if (esp_timer_get_time() / 1000 - curtime > 1000) {
curtime = esp_timer_get_time() / 1000;
char textlabel[20];
sprintf(textlabel, "Running: %u\n", counter);
printf(textlabel);
lv_label_set_text(label, textlabel);
counter++;
}
}
}