Step 1 — 准備移植所需文件
從lvgl官網下載到的文件中,主要需要以下選中的文件或文件夾:
在自己的工程中新建文件夾,命名為lvgl,將上述選中文件全部提取至該文件夾中,完整的工程文件樹如下:
其中:
- Bsp文件夾中的問價為ST7789的驅動文件,詳情請參考ST7789驅動;
- lvgl/example文件夾中包含了官方給出的各類例子,之所以建議將之拷貝至工程中主要是方便日后隨時查閱,同時,其中的porting文件夾中包含了移植的關鍵性接口文件的模板;
- lvgl/src文件夾中包含了GUI庫的所有核心文件;
- lvgl.h中包含了各類函數的聲明,方便調用,lv_conf_template.h為lvgl配置的模板。
Step 2 — 修改和補充相關的文件
1. porting文件夾中的文件
porting文件夾中個包含以下文件:
其中:
- disp的模板文件為顯示相關的接口函數的注冊文件;
- fs的模板文件為文件系統相關的接口函數的注冊文件;
- indev的模板文件為輸入設備的接口函數的注冊文件。
本隨筆中將重點討論GUI系統的移植,因此此處僅說明disp的模板文件的修改:
- 將disp相關的模板文件創建副本,並重命名,去掉_template后綴;
- 將h文件中的開始處的
#if 0
改為#if 1
使能該文件; - 在h文件中聲明外部調用初始化端口的函數:
/**
* LVGL requires a buffer where it internally draws the widgets.
* Later this buffer will passed to your display driver's `flush_cb` to copy its content to your display.
* The buffer has to be greater than 1 display row
*
* There are 3 buffering configurations:
* 1. Create ONE buffer:
* LVGL will draw the display's content here and writes it to your display
*
* 2. Create TWO buffer:
* LVGL will draw the display's content to a buffer and writes it your display.
* You should use DMA to write the buffer's content to the display.
* It will enable LVGL to draw the next part of the screen to the other buffer while
* the data is being sent form the first buffer. It makes rendering and flushing parallel.
*
* 3. Double buffering
* Set 2 screens sized buffers and set disp_drv.full_refresh = 1.
* This way LVGL will always provide the whole rendered screen in `flush_cb`
* and you only need to change the frame buffer's address.
*/
/* Example for 1) */
#define MY_DISP_HOR_RES 240
static lv_disp_draw_buf_t draw_buf_dsc_1;
static lv_color_t buf_1[MY_DISP_HOR_RES * 10]; /*A buffer for 10 rows*/
lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/
// /* Example for 2) */
// static lv_disp_draw_buf_t draw_buf_dsc_2;
// static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10]; /*A buffer for 10 rows*/
// static lv_color_t buf_2_2[MY_DISP_HOR_RES * 10]; /*An other buffer for 10 rows*/
// lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/
//
// /* Example for 3) also set disp_drv.full_refresh = 1 below*/
// static lv_disp_draw_buf_t draw_buf_dsc_3;
// static lv_color_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*A screen sized buffer*/
// static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*Another screen sized buffer*/
// lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2, MY_DISP_VER_RES * LV_VER_RES_MAX); /*Initialize the display buffer*/
- 修改分辨率,如下:
disp_drv.hor_res = 240;
disp_drv.ver_res = 240;
- 補充初始化函數,如下:
/*Initialize your display and the required peripherals.*/
static void disp_init(void)
{
st7789_init();
}
- 補充flush函數,注意,如果采用寫點函數來做,刷屏會很慢,所以推薦采用
st7789_flush
函數(見ST7789驅動):
/*Flush the content of the internal buffer the specific area on the display
*You can use DMA or any hardware acceleration to do this operation in the background but
*'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
/*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
// int32_t x;
// int32_t y;
// for(y = area->y1; y <= area->y2; y++) {
// for(x = area->x1; x <= area->x2; x++) {
// /*Put a pixel to the display. For example:*/
// /*put_px(x, y, *color_p)*/
// color_p++;
// }
// }
st7789_flush(area->x1, area->y1, area->x2, area->y2, ((uint16_t*)color_p));
/*IMPORTANT!!!
*Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);
}
2. 修改lv_conf.h
文件
- 將
lv_conf_template.h
創建副本並重命名為lv_conf.h
; - 將
#if 0
修改為#if 1
以使能該文件; - 將
LV_COLOR_16_SWAP
宏定義為1,以解決注冊的flush
函數顏色錯誤問題。
3. 添加時基
聽聞LVGL自帶非搶占式操作系統,需要時基支持,此處可采用系統自帶的滴答定時器中斷來實現,如下:
/**
* @brief This function handles System tick timer.
*/
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
lv_tick_inc(1);
/* USER CODE END SysTick_IRQn 1 */
}
Step 3 — 工程屬性設置修改
在路徑中加入相關的文件路徑,如下:
在源文件位置中添加相關文件夾,如下:
Step 4 — LVGL相關函數的調用
首先需經過初始化,如下:
/* USER CODE BEGIN 2 */
lv_init();
lv_port_disp_init();
/* USER CODE END 2 */
其次,非常重要的一步,需要在while (1)
循環中調用lv_task_handler()
函數,如下:
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
lv_task_handler();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
考慮到函數的使用相對復雜,因此此時庫中的例子將有非常重要的參考意義,在使用自帶的例子時,只需包含頭文件lv_examples.h
即可調用其自帶的例子,例如:
/* USER CODE BEGIN 2 */
lv_init();
lv_port_disp_init();
lv_example_keyboard_1();
/* USER CODE END 2 */
最終顯示效果如下:
Step 5 — LVGL庫的剪裁
在實際使用中,完成上述移植后,系統的Flash占用率非常高,如下:
為此,應考慮進行合理的剪裁。很遺憾,我嘗試過,但並沒有成功。
補充
LVGL的版本更新很快,版本更迭很多配置都有所變化。但是:
添加文件——>編譯——>報錯——>解決報錯——>編譯——>報錯——>解決報錯...
這一迭代式移植方法,在各種庫的移植中,都是屢試不爽的,LVGL也一樣。