I couldn't find any simple examples of working with the ESP-IDF display, so I asked chatGPT to make such an example for me from a LilyGo-Display-IDF demo. All changes affected the main.cpp file.
Here's a simplified version of the main.cpp file:
/**
* @file main.cpp
* @brief Simplified display initialization and LVGL setup for ESP32
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "lvgl.h"
#include "tft_driver.h"
#include "product_pins.h"
static const char *TAG = "main";
#define LVGL_TICK_PERIOD_MS 2
#define LVGL_TASK_STACK_SIZE (4 * 1024)
static lv_disp_draw_buf_t disp_buf;
lv_disp_drv_t disp_drv;
// Flush callback to send buffer to display
static void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
display_push_colors(area->x1, area->y1, area->x2 - area->x1 + 1, area->y2 - area->y1 + 1, (uint16_t *)color_map);
lv_disp_flush_ready(drv);
}
// LVGL tick increment callback
static void lvgl_tick_inc(void *arg)
{
lv_tick_inc(LVGL_TICK_PERIOD_MS);
}
// LVGL task handler
static void lvgl_task(void *arg)
{
while (true) {
lv_task_handler();
vTaskDelay(pdMS_TO_TICKS(LVGL_TICK_PERIOD_MS));
}
}
extern "C" void app_main(void)
{
ESP_LOGI(TAG, "Initialize DISPLAY and LVGL library.");
display_init();
lv_init();
// Allocate display buffers
lv_color_t *buf1 = (lv_color_t *)heap_caps_malloc(AMOLED_HEIGHT * 20 * sizeof(lv_color_t), MALLOC_CAP_DMA);
lv_color_t *buf2 = (lv_color_t *)heap_caps_malloc(AMOLED_HEIGHT * 20 * sizeof(lv_color_t), MALLOC_CAP_DMA);
lv_disp_draw_buf_init(&disp_buf, buf1, buf2, AMOLED_HEIGHT * 20);
// Set up and register display driver
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = AMOLED_HEIGHT;
disp_drv.ver_res = AMOLED_WIDTH;
disp_drv.flush_cb = lvgl_flush_cb;
disp_drv.draw_buf = &disp_buf;
lv_disp_drv_register(&disp_drv);
// Install LVGL tick timer
esp_timer_handle_t lvgl_tick_timer;
const esp_timer_create_args_t tick_timer_args = { .callback = &lvgl_tick_inc, .name = "lvgl_tick" };
esp_timer_create(&tick_timer_args, &lvgl_tick_timer);
esp_timer_start_periodic(lvgl_tick_timer, LVGL_TICK_PERIOD_MS * 1000);
// Create and display label
lv_obj_t *label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Hello, ESP32-S3!");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
// Create LVGL task
xTaskCreate(lvgl_task, "LVGL", LVGL_TASK_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL);
}
Here’s a line-by-line explanation of the simplified main.cpp file:
Includes:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "lvgl.h"
#include "tft_driver.h"
#include "product_pins.h"
These are preprocessor directives that include various libraries and headers:
freertos/FreeRTOS.h
andfreertos/task.h
: Required for using FreeRTOS functions and task management. FreeRTOS is a popular real-time operating system (RTOS) for embedded systems that allows for efficient task management and scheduling. FreeRTOS allows this project to run multiple tasks in parallel, such as handling the display (LVGL) and managing timers, without the tasks interfering with each other.esp_timer.h
: For handling high-resolution timers in ESP-IDF.esp_log.h
: Provides logging capabilities.lvgl.h
: The LVGL library for creating graphical user interfaces. The LVGL (Light and Versatile Graphics Library) is an open-source graphics library designed for embedded systems with limited resources.tft_driver.h
: Header for display driver functions (specific to the hardware).product_pins.h
: Contains pin definitions specific to the product. Also contains definitions of the AMOLED_HEIGHT and AMOLED_WIDTH variables.
TAG Declaration:
static const char *TAG = "main";
Defines a static constant string TAG used as a tag for logging purposes, helping to identify the source of log messages.
Macro Definitions:
#define LVGL_TICK_PERIOD_MS 2
#define LVGL_TASK_STACK_SIZE (4 * 1024)
LVGL_TICK_PERIOD_MS
: Defines the tick period for LVGL in milliseconds, which is 2 ms. This refers to the interval at which the LVGL library's internal clock or "tick" is updated. LVGL uses this tick to manage time-dependent tasks like animations, input handling, and other time-based events within the graphical user interface.LVGL_TASK_STACK_SIZE
: Defines the stack size for the LVGL task, which is 4 KB. This is the amount of memory allocated for the stack of a task (thread) that is dedicated to handling LVGL operations in a FreeRTOS environment. In a multitasking operating system like FreeRTOS, each task (or thread) has its own stack, which is a specific region of memory used to store local variables, function calls, and return addresses.
Display Buffer and Driver Declaration:
static lv_disp_draw_buf_t disp_buf;
lv_disp_drv_t disp_drv;
disp_buf
: A structure to hold display buffer information.disp_drv
: A structure representing the display driver.
Flush Callback Function:
static void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
display_push_colors(area->x1, area->y1, area->x2 - area->x1 + 1, area->y2 - area->y1 + 1, (uint16_t *)color_map);
lv_disp_flush_ready(drv);
}
lvgl_flush_cb
: This function is called by LVGL to send a portion of the display buffer (color_map) to the physical display.display_push_colors
: A function that pushes the color data for a specified area on the display.lv_disp_flush_ready
: Signals LVGL that the flushing operation is complete.
Tick Increment Function:
static void lvgl_tick_inc(void *arg)
{
lv_tick_inc(LVGL_TICK_PERIOD_MS);
}
lvgl_tick_inc
: Increments the LVGL internal tick counter, used for timekeeping and animations.lv_tick_inc
: LVGL function that advances the tick counter by the defined period (2ms).
LVGL Task Function:
static void lvgl_task(void *arg)
{
while (true) {
lv_task_handler();
vTaskDelay(pdMS_TO_TICKS(LVGL_TICK_PERIOD_MS));
}
}
lvgl_task
: A FreeRTOS task that continuously callslv_task_handler
, which processes LVGL tasks like animations and input handling.vTaskDelay
: Causes the task to wait for the next tick period (2 ms) before running again, allowing other tasks to execute.
Main Application Function:
extern "C" void app_main(void)
{
app_main
: The main function where the application starts executing. Theextern "C"
ensures that the function uses C linkage, which is important for compatibility with the ESP-IDF.
Initialization:
ESP_LOGI(TAG, "Initialize DISPLAY and LVGL library.");
display_init();
lv_init();
ESP_LOGI
: Logs an informational message indicating the start of display and LVGL initialization.display_init
: Initializes the display hardware.lv_init
: Initializes the LVGL library.
Display Buffer Allocation and Initialization:
lv_color_t *buf1 = (lv_color_t *)heap_caps_malloc(AMOLED_HEIGHT * 20 * sizeof(lv_color_t), MALLOC_CAP_DMA);
lv_color_t *buf2 = (lv_color_t *)heap_caps_malloc(AMOLED_HEIGHT * 20 * sizeof(lv_color_t), MALLOC_CAP_DMA);
lv_disp_draw_buf_init(&disp_buf, buf1, buf2, AMOLED_HEIGHT * 20);
heap_caps_malloc
: Allocates memory for two display buffers (buf1 and buf2) with specific capabilities (e.g., DMA). The size is calculated based on the display height and a buffer size factor (20). Using two display buffers (buf1 a and buf2) enables the system to prepare the next frame in the background while the current frame is being displayed, leading to smoother and more efficient screen updates without visual artifacts like flickering or tearing.lv_disp_draw_buf_init
: Initializes the display buffer with the allocated memory (buf1, buf2).
Display Driver Setup:
lv_disp_drv_init(&disp_drv); disp_drv.hor_res = AMOLED_HEIGHT; disp_drv.ver_res = AMOLED_WIDTH; disp_drv.flush_cb = lvgl_flush_cb; disp_drv.draw_buf = &disp_buf; lv_disp_drv_register(&disp_drv);
lv_disp_drv_init
: Initializes the display driver structure (disp_drv
).hor_res
andver_res
: Set the horizontal and vertical resolution of the display.flush_cb
: Assigns the flush callback (lvgl_flush_cb
) to the driver.draw_buf
: Links the display buffer to the driver.lv_disp_drv_register
: Registers the display driver with LVGL.
LVGL Tick Timer Setup:
esp_timer_handle_t lvgl_tick_timer;
const esp_timer_create_args_t tick_timer_args = { .callback = &lvgl_tick_inc, .name = "lvgl_tick" };
esp_timer_create(&tick_timer_args, &lvgl_tick_timer);
esp_timer_start_periodic(lvgl_tick_timer, LVGL_TICK_PERIOD_MS * 1000);
esp_timer_handle_t lvgl_tick_timer
: Declares a timer handle.esp_timer_create_args_t tick_timer_args
: Defines timer creation arguments, including the callback function (lvgl_tick_inc
) and the timer's name (lvgl_tick
).esp_timer_create
: Creates the timer with the specified arguments.esp_timer_start_periodic
: Starts the timer to trigger periodically every 2ms (converted to microseconds).
Label Creation and Alignment:
lv_obj_t *label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Hello, ESP32-S3!");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
lv_label_create
: Creates a new label object on the active screen.lv_label_set_text
: Sets the text of the label to "Hello, ESP32-S3!".lv_obj_align
: Aligns the label to the center of the screen.
LVGL Task Creation:
xTaskCreate(lvgl_task, "LVGL", LVGL_TASK_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL);
}
xTaskCreate
: Creates and starts a FreeRTOS task that runs thelvgl_task
function. The task is named "LVGL", has a stack size of 4 KB, and runs at idle priority.

Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.