I think this will help.
/*
* display_mng.c
*
* Created on: Jan 26, 2024
* Author: xpress_embedo
*/
#include "esp_heap_caps.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "ili9341.h"
#include "lvgl.h"
#include "display_mng.h"
// Defines
#define LV_TICK_PERIOD_MS (2)
#define DISP_BUFFER_SIZE (TFT_BUFFER_SIZE)
#define BYTES_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565))
// Private Function Declarations
static void display_mng(void *pvParameter);
static void display_flush_cb(lv_display_t * disp, const lv_area_t *area, lv_color_t *color_map);
static void display_flush_slow_cb(lv_display_t * disp, const lv_area_t *area, lv_color_t *color_map);
static void display_flush_swap_cb(lv_display_t * disp, const lv_area_t *area, lv_color_t *color_map);
static void display_input_read(lv_indev_t * indev, lv_indev_data_t*data);
static void lvgl_tick(void *arg);
// Private Variables
// Creates a semaphore to handle concurrent call to lvgl stuff, If we wish to
// call *any* lvgl function from other threads/tasks we should lock on the very
// same semaphore!
SemaphoreHandle_t lvgl_semaphore;
// Public Function Definitions
/**
* @brief Display Initialize
* Initialize the Display Controller, Touch, and Initialize LVGL Library
* Also link draw buffer.
* @param None
*/
void display_init( void )
{
// for LVGL 8.3.11
// static lv_disp_draw_buf_t draw_buf; // contains internal graphics buffer called draw buffer
// static lv_disp_drv_t disp_drv; // contains callback functions
// static lv_indev_drv_t indev_drv; // input device drivers
tft_init();
xpt2046_init();
lv_init();
// lvgl 9.2
lv_display_t * disp = lv_display_create(TFT_HOR_RES_MAX, TFT_VER_RES_MAX);
lv_display_set_flush_cb(disp, display_flush_cb);
lv_display_set_flush_wait_cb(disp, NULL);
size_t DRAW_BUF_SIZE = 0;
DRAW_BUF_SIZE = TFT_HOR_RES_MAX * TFT_VER_RES_MAX / 10 * sizeof(lv_color_t);
lv_color_t * drawBuf1[DRAW_BUF_SIZE / 4];
lv_display_set_buffers(disp, drawBuf1, NULL, DRAW_BUF_SIZE, LV_DISPLAY_RENDER_MODE_PARTIAL);
// configuring input devices
lv_indev_t *indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_long_press_time(indev, 150);
lv_indev_set_read_cb(indev, display_input_read);
// initialize the lvgl library
// Tick Interface for LVGL using esp_timer to generate 2ms periodic event
lvgl_semaphore = xSemaphoreCreateMutex();
const esp_timer_create_args_t lvgl_tick_timer_args =
{
.callback = &lvgl_tick,
.name = "lvgl_tick"
};
esp_timer_handle_t lvgl_tick_timer;
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, LV_TICK_PERIOD_MS * 1000)); // here time is in micro seconds
//xTaskCreatePinnedToCore(&display_mng, "display mng", 4096*4, NULL, 5, NULL, 0);
xTaskCreatePinnedToCore(&display_mng, "display mng", 4096*4, NULL, 5, NULL, 1);
// NOTE: I checked the flush timing with pinning and without pinning to core is same
}
// Private Function Definitions
/**
* @brief Display Manager Function which calls the lvgl timer handler function
* @param *pvParameter task parameter
*/
static int max_flushing_time = 0;
static void display_mng(void *pvParameter)
{
int64_t start_time = 0;
while(1)
{
vTaskDelay(pdMS_TO_TICKS(20));
if( pdTRUE == xSemaphoreTake(lvgl_semaphore, portMAX_DELAY) )
{
start_time = esp_timer_get_time();
lv_timer_handler();
// Semaphore is released when flushing is completed, this is checked using
// tft_flush_status function, and then we release the semaphore
// xSemaphoreGive(lvgl_semaphore);
}
// check flushing status
if( tft_flush_status() == true )
{
// printf("Flushing Time: %d" PRId64 ", %" PRId64 "\n", esp_timer_get_time(), start_time);
int time_taken = (int32_t)((esp_timer_get_time() - start_time)/1000);
if( time_taken > max_flushing_time )
{
max_flushing_time = time_taken;
printf("Flushing Time: %d ms\n", max_flushing_time );
}
xSemaphoreGive(lvgl_semaphore);
}
}
}
/**
* @brief Flush the data to the display controller
* This function is a fast function, further improvements can be done in
* the SPI driver layer.
* @note The ILI9341 is working in 8-bit SPI mode, and hence in LVGL configuration
* Data SWAP must be enabled, else this function will not display data
* properly, and if u want that use the flush_swap function, but for sure
* the flush_swap function is a slow function.
* @param drv lvgl display drivers
* @param area lvgl area to be updated
* @param color_map pixel information
*/
static void display_flush_cb(lv_display_t * disp, const lv_area_t *area, lv_color_t *color_map)
{
size_t width = (area->x2 - area->x1 + 1);
size_t height = (area->y2 - area->y1 + 1);
size_t len = width * height * 2;
ili9341_set_window(area->x1, area->y1, area->x2, area->y2);
// transfer frame buffer
// the problem with this command is that it uses the polling method
// tft_send_cmd(ILI9341_GRAM, (uint8_t*)color_map, len);
// while here the first command i.e. ILI9341_GRAM using polling method
// while the send data method using spi_device_transmit function
tft_send_cmd(ILI9341_GRAM, 0, 0);
tft_send_data((uint8_t*)color_map, len);
lv_disp_flush_ready(disp);
}
/**
* @brief Flush the data to the display controller
* This function is so slow that it will create a watchdog reset.
* This function is just written to test some stuff
* @param drv lvgl display drivers
* @param area lvgl area to be updated
* @param color_map pixel information
*/
static void display_flush_slow_cb(lv_display_t *disp, const lv_area_t *area, lv_color_t *color_map)
{
uint16_t x, y;
lv_color_t temp;
for(y = area->y1; y <= area->y2; y++)
{
for(x = area->x1; x <= area->x2; x++)
{
temp = *color_map;
ili9341_draw_pixel(x, y, lv_color_to_u16(temp));
color_map++;
}
}
lv_disp_flush_ready(disp);
}
/**
* @brief Flush the data to the display controller
* This function is swaps the data before transmitting unlike the above
* flush function, hence it is slow, it is recommended to use the above
* function and not this one.
* @param drv lvgl display drivers
* @param area lvgl area to be updated
* @param color_map pixel information
*/
static void display_flush_swap_cb(lv_display_t *disp, const lv_area_t *area, lv_color_t *color_map)
{
size_t idx = 0;
size_t width = (area->x2 - area->x1 + 1);
size_t height = (area->y2 - area->y1 + 1);
size_t len = width * height;
uint8_t data[2];
uint16_t temp;
ili9341_set_window(area->x1, area->y1, area->x2, area->y2);
// transfer frame buffer
tft_send_cmd(ILI9341_GRAM, 0, 0);
for( idx = 0; idx < len; idx++ )
{
// temp = *color_map;
temp = lv_color_to_u16(*color_map);
data[0] = (temp)>>8;
data[1] = (temp) & 0xFF;
tft_send_data(data, 2);
color_map++;
}
lv_disp_flush_ready(disp);
}
/**
* @brief Read the touch coordinates from the touch controller
* @param drv pointer to input device driver structure
* @param data pointer to data
*/
static void display_input_read(lv_indev_t * indev, lv_indev_data_t * data)
{
static int16_t x = 0;
static int16_t y = 0;
// check if we have a touch detected or not
if( xpt2046_read(&x, &y) )
{
// we are here means touch is detected
data->point.x = x;
data->point.y = y;
data->state = LV_INDEV_STATE_PRESSED;
}
else
{
data->state = LV_INDEV_STATE_RELEASED;
}
}
/**
* @brief LVGL Tick Function Hook
* LVGL need to call function lv_tick_inc periodically @ LV_TICK_PERIOD_MS
* to keep timing information.
* @param arg
*/
static void lvgl_tick(void *arg)
{
(void) arg;
lv_tick_inc(LV_TICK_PERIOD_MS);
}
main.c
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "lvgl.h"
#include "display_mng.h"
// Private Variables
static uint8_t button_counter = 0;
static lv_obj_t * count_label;
// Private Function Declarations
void lv_button_demo(void);
static void event_handler(lv_event_t * e);
void app_main(void)
{
display_init();
lv_button_demo();
while (true)
{
sleep(3);
printf("LVGL Demo Running\n");
}
}
// Private Function Definitions
/**
* @brief Simple Button Screen containing two labels and two buttons
* @param None
*/
void lv_button_demo(void)
{
lv_obj_t * label;
lv_obj_t * btn1 = lv_btn_create(lv_scr_act());
lv_obj_add_event_cb(btn1, event_handler, LV_EVENT_ALL, NULL);
lv_obj_align(btn1, LV_ALIGN_CENTER, 0, -10);
label = lv_label_create(btn1);
lv_label_set_text(label, "Button");
lv_obj_center(label);
count_label = lv_label_create(lv_scr_act());
lv_obj_align(count_label, LV_ALIGN_CENTER, 0, -60);
lv_label_set_text(count_label, "Counts: 0");
lv_obj_t * btn2 = lv_btn_create(lv_scr_act());
lv_obj_add_event_cb(btn2, event_handler, LV_EVENT_ALL, NULL);
lv_obj_align(btn2, LV_ALIGN_CENTER, 0, 40);
lv_obj_add_flag(btn2, LV_OBJ_FLAG_CHECKABLE);
lv_obj_set_height(btn2, LV_SIZE_CONTENT);
label = lv_label_create(btn2);
lv_label_set_text(label, "Toggle");
lv_obj_center(label);
}
/**
* @brief Event Handler Function for Button Clicked Events
* @param e lvgl event
*/
static void event_handler(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
if( (code == LV_EVENT_CLICKED) || (code == LV_EVENT_LONG_PRESSED_REPEAT) )
{
if ( code == LV_EVENT_CLICKED)
LV_LOG_USER("Click Event");
else if( code == LV_EVENT_LONG_PRESSED_REPEAT )
LV_LOG_USER("Press and Hold Event");
button_counter++;
lv_label_set_text_fmt(count_label, "Count: %d", button_counter);
}
else if(code == LV_EVENT_VALUE_CHANGED)
{
LV_LOG_USER("Toggle Event");
}
}