By Toradex胡珊逢
LVGL (Light and Versatile Graphics Library)是一個輕量級的開源圖形庫闷叉,采用?C?或者?MicroPython?語言開發(fā)消痛〉Eィ可以在資源有限的?MCU?上輕松地繪制圖形界面。Verdin iMX8M Plus?模塊的處理器除了?Cortex-A53?核心外榄鉴,還具有一個?Cortex-M7?核心加勤,其可以運行諸如?FreeRTOS?的實時操作系統(tǒng)。本文接下來就將介紹如何移植LVGL?到Verdin iMX8M Plus?的Cortex-M7?核上扰才。
本次演示采用一塊SPI?接口的LCD?允懂,屏幕控制器為?ILI9341。除了VCC?衩匣、GND?和背光外蕾总,和Verdin iMX8M Plus?連接的引腳主要有下面四個。
ILI9341Verdin iMX8M Plus
CSSPI?片選ECSPI1_SS0SODIMM202
RESET復位GPIO1_IO001SODIMM208
DC命令/數(shù)據(jù)GPIO1_IO00SODIMM206
MOSISPI MOSIECSPI1_MOSISODIMM200
SCKSPI?時鐘ECSPI1_SCLKSODIMM196
表一:Verdin iMX8M Plus?連接 ?ILI9341
注意?Verdin iMX8M Plus?的?SoC?使用?1.8V IO琅捏,在連接?SPI LCD?時需要使用?3.3V – 1.8V?電壓轉(zhuǎn)換電路生百。
LVGL?庫分為兩部分。第一部分是圖形實現(xiàn)柄延,包括繪制各類形狀蚀浆、色彩管理、動畫事件、定時器等市俊,第二部分是硬件驅(qū)動實現(xiàn)?lvgl_drivers杨凑。LVGL?將每一幀繪制好的圖片數(shù)據(jù)保存在RAM?中,lvgl_drivers?負責將數(shù)據(jù)傳輸?shù)酵獠匡@示設備摆昧。lvgl_drivers?支持多種顯示器撩满,如?TFT、電子墨水屏绅你、OLED?等伺帘。這里是?lvgl_drivers?支持的常見顯示控制器。LVGL?移植時通常只需要修改?lvgl_drivers忌锯。例如在本次演示使用了SPI?接口的顯示屏曼追。Verdin iMX8M Plus?可以提供連接顯示屏所需的SPI Master?功能。移植任務主要是適配lvgl_drivers?中ILI9341?的SPI?數(shù)據(jù)傳輸以及LVGL?圖形庫的幾個重要定時任務汉规。
首先安裝iMX8M Plus M7?開發(fā)所需的?SDK礼殊,如?SDK_2_12_1_MIMX8ML8xxxKZ。將該工程下載到SDK?安裝目錄的SDK_2_12_1_MIMX8ML8xxxKZ/boards/evkmimx8mp/rtos_examples/freertos_ecspi/?位置针史。這個工程已經(jīng)包含了下面提到的修改內(nèi)容晶伦。
在工程目錄的armgcc/CmakeLists.txt?添加lvgl?和?lvgl_drivers。這里指定v8.3.7啄枕,其他的版本可能發(fā)生API?變更婚陪,需要做對應的修改。設置?lvgl?和?lvgl_drivers?的?github?下載源频祝。
---------------------------------------
# Fetch LVGL from GitHubFetchContent_Declare(lvgl GIT_REPOSITORY https://github.com/lvgl/lvgl.git GIT_TAG v8.3.7)FetchContent_MakeAvailable(lvgl)FetchContent_Declare(lv_drivers?????????????????????GIT_REPOSITORY https://github.com/lvgl/lv_drivers GIT_TAG v8.3.0)FetchContent_MakeAvailable(lv_drivers)
---------------------------------------
將lvgl::lvgl?和lvgl::drivers?編譯到工程中泌参。
---------------------------------------
target_link_libraries(${MCUX_SDK_PROJECT_NAME} PRIVATE lvgl::lvgl lvgl::drivers)
---------------------------------------
在?add_executable(${MCUX_SDK_PROJECT_NAME} ?添加下面兩個頭文件。
---------------------------------------
"${ProjDirPath}/../lv_drv_conf.h""${ProjDirPath}/../lv_conf.h"
---------------------------------------
設置變量LV_CONF_PATH常空,這是lvgl?的配置文件lv_conf.h沽一,里面包含屏幕分辨率和lvgl?圖形庫參數(shù)。
---------------------------------------
# Specify path to own LVGL config headerset(LV_CONF_PATH????${CMAKE_CURRENT_SOURCE_DIR}/../lv_conf.h????CACHE STRING "" FORCE)
---------------------------------------
FETCHCONTENT_UPDATES_DISCONNECTED?允許每次編譯的時候不必重新下載?lvgl?代碼漓糙。
---------------------------------------
SET(FETCHCONTENT_UPDATES_DISCONNECTED ON)
---------------------------------------
在工程目錄下的?lv_conf.h?設置SPI TFT?屏幕分辨率240*320铣缠。
---------------------------------------
#define LV_HOR_RES_MAX 240#define LV_VER_RES_MAX 320
---------------------------------------
在工程目錄下的?lv_drv_conf.h?設置?LVGL?硬件驅(qū)動相關(guān)參數(shù)。
LV_DRV_DELAY_US()?和?LV_DRV_DELAY_MS()?需要在自己的代碼中實現(xiàn)(位于freertos_ecspi_loopback.c)昆禽。
---------------------------------------
/*********************?* DELAY INTERFACE?*********************/#define LV_DRV_DELAY_INCLUDE ?<stdint.h> ???????????/*Dummy include by default*/#define LV_DRV_DELAY_US(us) ?LVGL_DELAY_MS((1)) ??????/*Delay the given number of microseconds*/#define LV_DRV_DELAY_MS(ms) ?LVGL_DELAY_MS((ms)) ??????/*Delay the given number of milliseconds*/
---------------------------------------
延時函數(shù)在每個平臺上的實現(xiàn)方法都不同蝗蛙,有的可以使用while()?或for()?循環(huán),在運行操作系統(tǒng)的平臺上可以利用系統(tǒng)提供的API醉鳖,例如Verdin iMX8M Plus M7?的FreeRTOS?中使用??vTaskDelay()捡硅。
---------------------------------------
void LVGL_DELAY_MS(uint8_t ms){?????vTaskDelay( ms / portTICK_PERIOD_MS );}
---------------------------------------
SPI TFT LCD?采用了?ILI9341?控制器,因此設置 ?USE_ILI9341?宏定義盗棵,以及分辨率參數(shù)壮韭。
---------------------------------------
#ifndef USE_ILI9341# ?define USE_ILI9341 ??????1#endif# ?define LV_HOR_RES ???????????240 ??????# ?define LV_VER_RES ???????????320 ???
---------------------------------------
除了上面的延時函數(shù)外北发,用于控制ILI9341?數(shù)據(jù)/命令引腳?的LV_DRV_DISP_CMD_DATA()?、復位ILI9341?的LV_DRV_DISP_RST()?和SPI?傳輸一個字節(jié)泰涂、多個字節(jié)的函數(shù)spi_transaction_one_byte(),spi_transaction_array ()也需要自己實現(xiàn)辐怕。在?lv_drv_conf.h?里定義 ?lv_diplay_cmd_data()?和?lv_diplay_reset()逼蒙。
---------------------------------------
#define LV_DRV_DISP_INCLUDE ????????<stdint.h> ??????????/*Dummy include by default*/#define LV_DRV_DISP_CMD_DATA(val) ?lv_diplay_cmd_data((val)) ???/*Set the command/data pin to 'val'*/#define LV_DRV_DISP_RST(val) ??????lv_diplay_reset((val)) ???/*Set the reset pin to 'val'*/
---------------------------------------
由于?iMX8M Plus?的?SPI?在收發(fā)時會自動控制?CS?引腳,因此?LV_DRV_DISP_SPI_CS(val)?可以設置為空函數(shù)寄疏。
---------------------------------------
#define LV_DRV_DISP_SPI_CS(val) ?????????/*spi_cs_set(val)*/ ????/*Set the SPI's Chip select to 'val'*/#define LV_DRV_DISP_SPI_WR_BYTE(data) ???spi_transaction_one_byte((data))/*spi_wr(data)*/ ???????/*Write a byte the SPI bus*/#define LV_DRV_DISP_SPI_WR_ARRAY(adr, n) spi_transaction_array((adr), (n))/*spi_wr_mem(adr, n)*/ ?/*Write 'n' bytes to SPI bus from 'adr'*/
---------------------------------------
在freertos_ecspi_loopback.c?中實現(xiàn)lv_diplay_cmd_data()?是牢,lv_diplay_reset(),spi_transaction_one_byte()陕截,spi_transaction_array()驳棱。
---------------------------------------
void lv_diplay_cmd_data(uint8_t val){????GPIO_PinWrite(GPIO_PAD, LCD_CMD_DATA, val);}void lv_diplay_reset(uint8_t val){????GPIO_PinWrite(GPIO_PAD, LCD_RESET, val);}
---------------------------------------
LCD_CMD_DATA ?和LCD_RESET?分別定義如下,用于控制ILI9341?的?命令/數(shù)據(jù)和復位引腳农曲。
---------------------------------------
#define GPIO_PAD ???????GPIO1#define LCD_CMD_DATA ???0U#define LCD_RESET ??????1U
---------------------------------------
SPI?數(shù)據(jù)傳輸采用列隊形式發(fā)送社搅。spi_transaction_one_byte()?和spi_transaction_array()?均采用xQueueSend()?將需要發(fā)送的數(shù)據(jù)加入到spi_queue?列隊中,該列隊長度為128?字節(jié)乳规。然后運行一個高優(yōu)先級的任務ecspi_task()?將數(shù)據(jù)從列隊中通過ECSPI_RTOS_Transfer()?發(fā)送到ILI9341?控制器形葬。由于發(fā)送數(shù)據(jù)的任務優(yōu)先級高于寫入列隊的,所以spi_queue?列隊中保存的數(shù)據(jù)會被很快發(fā)送出去暮的。
---------------------------------------
void spi_transaction_one_byte(uint8_t data){????BaseType_t xStatus;????uint32_t data_to_queue;????data_to_queue = (uint32_t)data;????xStatus = xQueueSend(spi_queue, &data_to_queue, portMAX_DELAY);????if( xStatus != pdPASS )????{????????PRINTF( "Could not send to the queue.\r\n" );????}}
---------------------------------------
本演示中笙以,采用不同優(yōu)先級的任務來實現(xiàn)相應的工作。優(yōu)先級數(shù)字越大便是優(yōu)先級越高冻辩。為了保證?SPI?及時發(fā)送到?ILI9341猖腕,將其設置為最高優(yōu)先級。
任務函數(shù)優(yōu)先級功能描述
draw_lvgl_ui2LVGL UI
lv_task_hander_task1調(diào)用?lv_task_handler
init_task3SPI恨闪、lv_init, hal_init?初始化
ecspi_task4發(fā)送?SPI?數(shù)據(jù)到ILI9341
vApplicationTickHookexecuted every tick調(diào)用?lv_tick_inc
表二:FreeRTOS?任務描述
draw_lvgl_ui()?中繪制需要顯示的?LVGL UI?內(nèi)容倘感,本演示中將顯示一個動態(tài)伸縮變化的彩色柱。
lv_task_hander_task()?將每隔?5ms?調(diào)用?lv_task_handler()咙咽,該函數(shù)會每?5ms?處理?lvgl?相關(guān)任務侠仇。
init_task()?中完成?SPI、ILI9341?的初始化犁珠,以及?LVGL?圖形庫的相關(guān)初始化逻炊。為了防止在初始化完成前調(diào)用lv_task_handler?和UI?繪制,該任務運行時使用?vTaskSuspend?暫時停止draw_lvgl_ui?和lv_task_hander_task?兩個任務犁享。但ecspi_task?繼續(xù)運行余素。
---------------------------------------
void init_task(void *pvParameters){????vTaskSuspend(xUITaskHandle); ?//suspend ui task untill init task finisded.????vTaskSuspend(xLVTaskHandle);????????spi_init();????ili9341_init();????lv_init();????hal_init();????PRINTF("Init finised. resume xUI and XLV tasks\r\n");????vTaskResume(xUITaskHandle);????vTaskResume(xLVTaskHandle);
---------------------------------------
vApplicationTickHook?并不是一個單獨的FreeRTOS?任務,而是在每個tick?都會被執(zhí)行炊昆。因此桨吊,lv_tick_inc?將在每2ms?運行威根。該函數(shù)向LVGL?動畫和其他任務提供已經(jīng)運行的時間信息,需要保證其運行的準確性和粒度视乐。
---------------------------------------
void vApplicationTickHook(void){????static uint32_t ulCount = 0;????ulCount++;????if (ulCount >= 2UL)????{????????lv_tick_inc(2); ??//calling every 2 milliseconds.????????ulCount = 0UL;????}}
---------------------------------------
修改?FreeRTOSConfig.h?中的下面參數(shù)洛搀,實現(xiàn)每個?TICK?為?1ms,以及啟用上面提到的TICK_HOOK佑淀。
---------------------------------------
#define configTICK_RATE_HZ ?????????????????????((TickType_t)1000)#define configUSE_TICK_HOOK ????????????????????1
---------------------------------------
由于LVGL?運行需要較大的RAM?空間留美,因此該演示的?M7?固件會被加載到DDR RAM?上運行。在編譯的時候使用build_ddr_release.sh?腳本伸刃。
---------------------------------------
export ARMGCC_DIR=/opt/gcc-arm-none-eabi-10.3-2021.10cd armgcc./build_ddr_release.sh
---------------------------------------
在?U-Boot?里面設置?m7bootddr?參數(shù)谎砾,將上面編譯好的?M7?固件加載到地址為?0x80000000?的?DDR RAM?中。
---------------------------------------
Verdin iMX8MP # print m7bootddrm7bootddr=tftp 0x80000000 m7.bin; dcache flush; bootaux 0x80000000
---------------------------------------
啟動時在?U-Boot?中運行下面命令捧颅。
---------------------------------------
run m7bootddr
---------------------------------------
運行效果如下景图。
總結(jié)
本文介紹為?Verdin iMX8M Plus M7?移植?LVGL?的步驟和創(chuàng)建對應 ?FreeRTOS?任務。在項目中需要為實際使用的外設和業(yè)務設置合適的任務優(yōu)先級碉哑,保證圖形流暢顯示以及數(shù)據(jù)及時處理挚币。在?device tree?也需要把?M7?所使用的外設禁用,避免和?Linux?系統(tǒng)的沖突扣典。