前言
恩智浦“FRDM-MCXN947”評測活動(dòng)由安富利和與非網(wǎng)協(xié)同舉辦。本篇內(nèi)容由與非網(wǎng)用戶發(fā)布,已獲轉(zhuǎn)載許可。原文可在與非網(wǎng)(eefocus)工程師社區(qū)查看。
背景
上一期【用戶測評(六):NXP FRDM-MCXN947 FLEXIO_SPI驅(qū)動(dòng)TFT LCD】以FLEXIO_SPI驅(qū)動(dòng)了TFT LCD,此貼移植LVGL,測試刷屏速度。
LVGL移植
01引入SDK組件
在MCUXpresso For VS Code開發(fā)環(huán)境中,引入NXP SDK組件的方法很簡單,如下圖所示:

1. 在VS Code側(cè)邊欄打開MCUXpresso插件圖標(biāo),點(diǎn)擊打開;
2. 鼠標(biāo)右鍵單擊項(xiàng)目,展開選項(xiàng);
3. 選擇Configure展開子菜單欄;
4. 選擇Manage Components彈開組件選擇窗口;
在彈出的組件選擇框中查找或者輸入lvgl并勾選,導(dǎo)入LVGL組件,如下圖所示。

需要注意此方法導(dǎo)入的SDK組件并不會(huì)把源碼拷貝到當(dāng)前工程目錄中,只是修改了文件armgcc/config.cmake文件,如下:
set(CONFIG_USE_xxxx true)# 其他組件 # 引入 LVGL 時(shí)添加的兩行 set(CONFIG_USE_middleware_lvgltrue) set(CONFIG_USE_middleware_lvgl_templatetrue)
02添加LVGL顯示適配層
參考LVGL官方的移植文檔,LVGL移植主要干以下兩件事:
1.LVGL初始化,LCD硬件初始化;
2.LVGL刷新緩沖區(qū)接口的實(shí)現(xiàn);
1. 新增文件
新增3個(gè)文件,如下所示:
bsp/lvgl_port/
lv_conf.h
lvgl_support.c
lvgl_support.h
lv_conf.h是LVGL配置頭文件,配置選項(xiàng)非常多,這里僅介紹幾個(gè)重要的配置選項(xiàng):LV_COLOR_DEPTH設(shè)置顏色深度;LV_MEM_SIZE設(shè)置LVGL內(nèi)部使用的內(nèi)存分配池的大小;LV_USE_XXX使能widgets組件;LV_BUILD_EXAMPLES允許編譯內(nèi)置的示例到LVGL庫文件中;
lv_support.c是適配接口實(shí)現(xiàn)文件;
lv_support.h是適配接口對外的接口文件,包括宏定義、函數(shù)聲明等;
2. lv_port_disp_init()
此函數(shù)做了以下幾件事:
1. 初始化LVGL顯示緩沖區(qū)內(nèi)存,調(diào)用lv_disp_draw_buf_init();
2. 初始化LCD硬件,但是已經(jīng)在外部調(diào)用LCD_init(),此處無需調(diào)用;
3. 注冊disp_drv.flush_cb = DEMO_FlushDisplay,需要實(shí)現(xiàn)自己的DEMO_FlushDisplay()函數(shù);
4. 最后注冊顯示驅(qū)動(dòng)lv_disp_drv_register(&disp_drv);
voidlv_port_disp_init(void)
{
staticlv_disp_draw_buf_tdisp_buf;
memset(s_frameBuffer,0,sizeof(s_frameBuffer));
lv_disp_draw_buf_init(&disp_buf, (void*)s_frameBuffer[0], (void*)s_frameBuffer[1], LCD_VIRTUAL_BUF_SIZE);
/*-------------------------
* Initialize your display
* -----------------------*/
//NOTE:已在其他位置調(diào)用 LCD_Init() ,此處無需調(diào)用
/*-----------------------------------
* Register the display in LittlevGL
*----------------------------------*/
staticlv_disp_drv_tdisp_drv;/*Descriptor of a display driver*/
lv_disp_drv_init(&disp_drv); /*Basic initialization*/
/*Set up the functions to access to your display*/
/*Set the resolution of the display*/
disp_drv.hor_res =LCD_WIDTH;
disp_drv.ver_res =LCD_HEIGHT;
/*Used to copy the buffer's content to the display*/
disp_drv.flush_cb =DEMO_FlushDisplay;
/*Set a display buffer*/
disp_drv.draw_buf =&disp_buf;
/*Finally register the driver*/
lv_disp_drv_register(&disp_drv);
}
3. lvgl刷新緩沖區(qū)接口的實(shí)現(xiàn)
在bsp/lvgl_port/lvgl_support.c中實(shí)現(xiàn)如下函數(shù),最終調(diào)用LCD中的函數(shù)LCD_DrawBitmap()實(shí)現(xiàn)把LVGL緩沖區(qū)內(nèi)容刷新到屏幕上。
/* 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_flush_ready()' has to be called when finished * This function is required only when LV_VDB_SIZE != 0 in lv_conf.h*/ staticvoidDEMO_FlushDisplay(lv_disp_drv_t*disp_drv,constlv_area_t*area,lv_color_t*color_p) { lv_coord_tx1 = area->x1; lv_coord_ty1 = area->y1; lv_coord_tx2 = area->x2; lv_coord_ty2 = area->y2; int32_tlength = (x2 - x1 +1) * (y2 - y1 +1) *LCD_FB_BYTE_PER_PIXEL; LCD_DrawBitmap(x1, y1, x2, y2, (uint16_t*)color_p); /* IMPORTANT!!! * Inform the graphics library that you are ready with the flushing*/ lv_disp_flush_ready(disp_drv); }
LCD_DrawBitmap()函數(shù)實(shí)現(xiàn)非常重要,如果速度慢導(dǎo)致卡頓,如果速度快則可以流暢地刷屏。在調(diào)試過程中實(shí)現(xiàn)了兩個(gè)版本:
1. 逐個(gè)打點(diǎn),調(diào)用LCD_WriteData_16Bit(),刷屏卡成PPT;
2. 利用EDMA直接發(fā)送整個(gè)緩沖區(qū),速度很快,達(dá)到31FPS;
voidLCD_DrawBitmap(uint16_txstart,uint16_tystart,uint16_txend,uint16_tyend,uint16_t*color)
{
LCD_SetWindows(xstart, ystart, xend, yend);
uint16_twidth = xend - xstart +1;
uint16_theight = yend - ystart +1;
uint16_tsize = width * height;
#if0
//NOTE:逐個(gè)打點(diǎn),非常慢--卡成 PPT
for(uint16_ti = ystart; i <= yend; i++) {
? ??for?(uint16_t?j = xstart; j <= xend; j++) {
? ? ??Lcd_WriteData_16Bit(*color);
? ? ? color++;
? ? }
? }
#else
??//?NOTE:?EDMA 發(fā)送一個(gè)緩沖幀,速度很快 -- 31FPS
??Lcd_WriteData_16BitArray(color, size);
#endif
}
EDMA發(fā)送整個(gè)緩沖幀的實(shí)現(xiàn)
逐個(gè)打點(diǎn)的速度太慢,這里就不展示了。在移植LCD驅(qū)動(dòng)時(shí)就發(fā)現(xiàn)刷屏非常慢,迫切地需要實(shí)現(xiàn)一個(gè)利用EDMA發(fā)送緩沖區(qū)地函數(shù),在LVGL刷屏?xí)r正好可以用到。
函數(shù)LCD_WriteData_16BitArray()如下,利用EDMA的特性,一下子傳輸整個(gè)緩沖區(qū)比逐個(gè)打點(diǎn)快很多。
這里遇到了一個(gè)坑,LVGL配置顏色深度為16bit,一個(gè)像素點(diǎn)占據(jù)兩個(gè)字節(jié),寫代碼時(shí)腦子短路了,知道這個(gè)細(xì)節(jié),但是在配置EDMA傳輸參數(shù)時(shí)沒有反映過來,一開始屏幕沒有顯示,后來debug才發(fā)現(xiàn)EDMA傳輸啟動(dòng)就卡主了,原來下面的xfer.dataSize配置需要注意size * 2。
/** * @brief SPI 發(fā)送一個(gè)數(shù)組 * * @param Data * @param size */ voidLcd_WriteData_16BitArray(uint16_t*Data,uint32_tsize) { flexio_spi_transfer_txfer = {0}; LCD_CS_CLR; LCD_RS_SET; xfer.txData = (uint8_t*)Data; xfer.rxData =NULL; xfer.dataSize = size *2;//NOTE:16bit顏色,一個(gè)像素點(diǎn)占據(jù)2個(gè)字節(jié) (這里差點(diǎn)坑哭了!!!) xfer.flags = kFLEXIO_SPI_16bitMsb; FLEXIO_SPI_MasterTransferCreateHandleEDMA(&spiDev, &g_spiHandle, spi_master_completionCallback,NULL, &txHandle, &rxHandle); FLEXIO_SPI_MasterTransferEDMA(&spiDev, &g_spiHandle, &xfer); while(!completeFlag); completeFlag =false; LCD_CS_SET; }
03創(chuàng)建LVGL任務(wù)
改造上一版的程序,把LCD和LVGL初始化拎出來放到一個(gè)單獨(dú)的GUI Task中,如下所示:
/**
*@briefLVGL Core 線程
*
*@parampvParameters
*/
staticvoidgui_task_entry(void*pvParameters)
{
lv_port_pre_init();
lv_init();
lv_port_disp_init();
lv_port_indev_init();
s_lvgl_initialized =true;
lv_demo_benchmark();
while(1) {
lv_task_handler();
vTaskDelay(pdMS_TO_TICKS(5));
}
}
voidgui_task_create(void)
{
// LCD init
LCD_Init();
// LVGL init
if(xTaskCreate(gui_task_entry,"gui_task",
ZYGOTE_TASK_STACK_SIZE,NULL,ZYGOTE_TASK_PRIORITY,NULL) != pdPASS)
{
PRINTF("Task creation failed!.
");
while(1);
}
}
04添加LVGL心跳
上面的gui_task_entry()在死循環(huán)中每隔5毫秒進(jìn)入一次lvgl任務(wù),但是還沒有更新LVGL心跳,是不會(huì)刷新屏幕的。
當(dāng)前使用了FreeRTOS,一個(gè)簡單的做法是把LVGL心跳放到vApplicationTickHook()中,非常簡單快捷。但是需要注意,需要在FreeRTOSConfig.h中使能宏定義configUSE_TICK_HOOK = 1才能使這個(gè)vApplicationTickHook()函數(shù)生效。
/*!
* @brief FreeRTOS tick hook.
*/
voidvApplicationTickHook(void)
{
if(s_lvgl_initialized)
{
lv_tick_inc(1);
}
}
05編譯
編譯出錯(cuò)undefined reference to `lv_demo_benchmark'
第一反映是查看lv_conf.h文件
1.LV_BUILD_EXAMPLES宏定義是否啟用了;
2.LV_USE_DEMO_BENCHMARK宏定義是否啟用了;

檢查過了,確認(rèn)過來,啟用了,但是還是鏈接失敗,最后不到lv_demo_benchmark符號(hào)。
找到SDK的cmake模塊文件
找到SDK的cmake文件,如下lvgl拆分成了多個(gè)模塊文件:
middleware_lvgl.cmake是lvgl核心組件的cmake模塊文件;
middleware_lvgl_unused_files.cmake其實(shí)是一個(gè)沒有什么實(shí)際意義的cmake模塊文件;
middleware_lvgl_demo_widgets.cmake是把lvgl demo widgets示例的源碼加入編譯;
middleware_lvgl_demo_stress.cmake是把lvgl demo stress示例的源碼加入編譯;
middleware_lvgl_demo_benchmark.cmake是把 lvgl demo benchmark示例的源碼加入編譯;
middleware_lvgl_template.cmake是把lvgl_sdk目錄下的lvgl_support.c等三個(gè)文件添加到編譯中;

這里就發(fā)現(xiàn)了MCUXPresso for VS Code的兩個(gè)漏洞:
1. SDK組件管理器中添加了LVGL組件,但是沒有自動(dòng)把其中的3個(gè)demo相關(guān)的模塊加入到源碼中;
2. 雖然把middleware_lvgl_template.cmake所在的組件加了進(jìn)來,但是并沒有把源碼拷貝過來,難道需要用戶手動(dòng)去改SDK目錄下的lvgl_sdk目錄?可以是這樣一改會(huì)對所有的依賴此SDK的工程都產(chǎn)生影響?
3. middleware_lvgl.cmake模塊,居然依賴于middleware_lvgl_template.cmake模塊,這個(gè)是我不能理解的。就是第2點(diǎn),LVGL適配層可以字節(jié)寫,但是依賴middleware_lvgl_template組件,那么所有用戶工程都依賴同一份SDK中的lvgl_sdk/template模塊,不合理。
middleware_lvgl.cmake文件大致如下:
# 從這里看出 lvgl core 居然依賴 middleware_lvgl
if(CONFIG_USE_middleware_lvgl_template)
# 添加 lvgl core 代碼到編譯系統(tǒng),添加頭文件路徑為公共頭文件搜索路徑
else()
# 報(bào)錯(cuò)
message(SEND_ERROR"middleware_lvgl dependency does not meet, please check ${CMAKE_CURRENT_LIST_FILE}.")
endif()
我的解決辦法
1. 修改armgcc/config.cmake文件,手動(dòng)增加middleware_lvgl_demo_benchmark組件;
2. 把middleware_lvgl_template禁用;
3. (臨時(shí)解決方案)同時(shí)修改sdk/middleware/lvg/middleware_lvgl.cmake文件,去掉CONFIG_USE_middleware_lvgl_template,強(qiáng)行設(shè)置為TRUE;
config.cmake文件中最終關(guān)于LVGL的配置如下:

middleware_lvgl.cmake文件修改如下:

編譯成功
最后編譯成功,運(yùn)行成功,lvgl_demo_benchmark運(yùn)行成功。
運(yùn)行
運(yùn)行速度對比:
debug,刷屏打點(diǎn),卡成PPT,沒有拍視頻;
debug,EDMA發(fā)送緩沖區(qū),19FPS;
release,EDMA發(fā)送緩沖區(qū),31FPS;

演示視頻見B站:
https://www.bilibili.com/video/BV13pUbYhEPx/?spm_id_from=888.80997.embed_other.whitelist&t=1.46976&bvid=BV13pUbYhEPx
總結(jié)
01MCUXpresso IDE的不足與建議
其實(shí)我最早使用的就是MCUXpresso IDE,發(fā)現(xiàn)添加組件不是那么順利,例如在一個(gè)no-os的工程中添加FreeRTOS組件,發(fā)現(xiàn)FreeRTOS源碼的確拷貝到了當(dāng)前工程中,但是FreeROTS的porting層的源碼卻沒有拷貝過來,當(dāng)時(shí)沒有搞清楚機(jī)制,編譯失敗就放棄了。
后來幾經(jīng)嘗試之后才發(fā)現(xiàn)SDK組件管理器中關(guān)于FreeROTS有很多零碎的組件,如下圖:
1. 標(biāo)號(hào)(1)是FreeRTOS內(nèi)核源碼,但是缺少porting層源碼;
2. 標(biāo)號(hào)(2)是FreeRTOS的適配層,內(nèi)存管理驅(qū)動(dòng);
3. 標(biāo)號(hào)(3)是多核通信的FreeRTOS實(shí)現(xiàn)方法;
總之關(guān)于FreeRTOS有很多零碎的組件分散在各個(gè)單元,每個(gè)組件的Description太簡短了,不能讓人一眼就明白用途,且各個(gè)組件的依賴關(guān)系也沒有說明,不易輕松上手。
建議:Descriptiong文字描述的詳細(xì)些;各個(gè)組件的依賴關(guān)系也明確的寫出來。

02MCUXpresso For VS Code的不足與建議
不足之處:
1. 在VS Code開發(fā)環(huán)境中添加組件是很方便,但是對于新手不友好,特別是不熟悉CMake的人來說,編譯找不到頭文件、函數(shù)未定義,讓新手望而卻步。
2. 某些組件有core和porting層,其中core層可以所有項(xiàng)目通用,但是porting對于每個(gè)項(xiàng)目來說可能不一樣;當(dāng)前工程添加了這個(gè)組件只是在CMake標(biāo)記了組件的core和porting層都納入編譯,但是它們依然存放在SDK目錄下,改動(dòng)一處影響所有其他工程,這很不好。
建議:添加組件把組件的porting層拷貝到當(dāng)前工程,這樣每個(gè)工程都有自己的適配層,互不影響。
-
恩智浦
+關(guān)注
關(guān)注
14文章
6095瀏覽量
147140 -
移植
+關(guān)注
關(guān)注
1文章
414瀏覽量
29388 -
SPI
+關(guān)注
關(guān)注
17文章
1885瀏覽量
101214 -
開發(fā)環(huán)境
+關(guān)注
關(guān)注
1文章
270瀏覽量
17637 -
LVGL
+關(guān)注
關(guān)注
2文章
124瀏覽量
4552
原文標(biāo)題:用戶測評(七):移植LVGL跑benchmark
文章出處:【微信號(hào):AvnetAsia,微信公眾號(hào):安富利】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
TFT適配LVGL實(shí)踐分享
恩智浦MCX N23的官方評估板FRDM-MCXN236詳解
在恩智浦FRDM-MCXN947開發(fā)板部署DeepSeek大語言模型
使用恩智浦FRDM-MCXN947開發(fā)板LVGL移植觸摸屏
FRDM-MCXN947的純Linux命令行環(huán)境搭建
關(guān)于將Flash寫入FRDM-MCXN947的問題求解
FRDM-MCXN947在初始化lpI2C時(shí), I2C無法正常工作怎么解決?
富昌電子推薦兩款恩智浦的MCX A和MCX N系列微控制器
《恩智浦FRDM-MCXN947開發(fā)實(shí)踐指南》上線啦
使用VSCode調(diào)試FRDM MCXN947開發(fā)板
恩智浦新品MCX N系列線下培訓(xùn)來啦!LVGL、AI等超多精彩Demo演示,快來報(bào)名吧!
基于Label CIFAR10 image on FRDM-MCXN947例程實(shí)現(xiàn)鞋和帽子的識(shí)別
使用NXP MCX-N板卡搭建環(huán)境及點(diǎn)燈
使用恩智浦FRDM-MCXN947開發(fā)板移植LVGL跑benchmark
評論