前言
恩智浦“FRDM-MCXN947”評(píng)測(cè)活動(dòng)由安富利和與非網(wǎng)協(xié)同舉辦。本篇內(nèi)容由與非網(wǎng)用戶發(fā)布,已獲轉(zhuǎn)載許可。原文可在與非網(wǎng)(eefocus)工程師社區(qū)查看。
背景
上一期【用戶測(cè)評(píng)(六):NXP FRDM-MCXN947 FLEXIO_SPI驅(qū)動(dòng)TFT LCD】以FLEXIO_SPI驅(qū)動(dòng)了TFT LCD,此貼移植LVGL,測(cè)試刷屏速度。
LVGL移植
01引入SDK組件
在MCUXpresso For VS Code開發(fā)環(huán)境中,引入NXP SDK組件的方法很簡(jiǎn)單,如下圖所示:
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庫(kù)文件中;
lv_support.c是適配接口實(shí)現(xiàn)文件;
lv_support.h是適配接口對(duì)外的接口文件,包括宏定義、函數(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. 注冊(cè)disp_drv.flush_cb = DEMO_FlushDisplay,需要實(shí)現(xiàn)自己的DEMO_FlushDisplay()函數(shù);
4. 最后注冊(cè)顯示驅(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è)簡(jiǎn)單的做法是把LVGL心跳放到vApplicationTickHook()中,非常簡(jiǎn)單快捷。但是需要注意,需要在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ì)對(duì)所有的依賴此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)行速度對(duì)比:
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太簡(jiǎn)短了,不能讓人一眼就明白用途,且各個(gè)組件的依賴關(guān)系也沒有說明,不易輕松上手。
建議:Descriptiong文字描述的詳細(xì)些;各個(gè)組件的依賴關(guān)系也明確的寫出來。
02MCUXpresso For VS Code的不足與建議
不足之處:
1. 在VS Code開發(fā)環(huán)境中添加組件是很方便,但是對(duì)于新手不友好,特別是不熟悉CMake的人來說,編譯找不到頭文件、函數(shù)未定義,讓新手望而卻步。
2. 某些組件有core和porting層,其中core層可以所有項(xiàng)目通用,但是porting對(duì)于每個(gè)項(xiàng)目來說可能不一樣;當(dāng)前工程添加了這個(gè)組件只是在CMake標(biāo)記了組件的core和porting層都納入編譯,但是它們依然存放在SDK目錄下,改動(dòng)一處影響所有其他工程,這很不好。
建議:添加組件把組件的porting層拷貝到當(dāng)前工程,這樣每個(gè)工程都有自己的適配層,互不影響。
-
恩智浦
+關(guān)注
關(guān)注
14文章
5956瀏覽量
114091 -
移植
+關(guān)注
關(guān)注
1文章
395瀏覽量
28607 -
SPI
+關(guān)注
關(guān)注
17文章
1788瀏覽量
95006 -
開發(fā)環(huán)境
+關(guān)注
關(guān)注
1文章
240瀏覽量
17062 -
LVGL
+關(guān)注
關(guān)注
1文章
102瀏覽量
3580
原文標(biāo)題:用戶測(cè)評(píng)(七):移植LVGL跑benchmark
文章出處:【微信號(hào):AvnetAsia,微信公眾號(hào):安富利】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
TFT適配LVGL實(shí)踐分享

恩智浦MCX N23的官方評(píng)估板FRDM-MCXN236詳解

在恩智浦FRDM-MCXN947開發(fā)板部署DeepSeek大語言模型

FRDM-MCXN947的純Linux命令行環(huán)境搭建
關(guān)于將Flash寫入FRDM-MCXN947的問題求解
FRDM-MCXN947在初始化lpI2C時(shí), I2C無法正常工作怎么解決?
富昌電子推薦兩款恩智浦的MCX A和MCX N系列微控制器
【上海】5月25日-基于恩智浦MCX N系列MCU結(jié)合RT-Thread的應(yīng)用與實(shí)踐 線下培訓(xù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)燈

評(píng)論