極速進行項目開發(fā),只需要懂一款芯片架構(gòu)+一個操作系統(tǒng)+一個GUI。各種部件程序全靠抄

,成為究極縫合怪。本文用stm32f407+FreeRTOS+lvgl演示一些demo。
原文鏈接:STM32F4+FreeRTOS+LVGL實現(xiàn)快速開發(fā)(縫合怪)
lvgl官方的音樂播放器demo:
百問網(wǎng)的2048小游戲:

1.STM32F407和FreeRTOS
STM32F407這款芯片就不多介紹了,挺老的MCU,架構(gòu)為ARM_CM4F。隨便一搜就有非常非常多的例程和項目。
會縫合的基礎(chǔ)是對芯片架構(gòu)非常了解,剛?cè)腴T的同學(xué)建議先從基礎(chǔ)學(xué)起,推薦學(xué)習(xí)ARM官方的權(quán)威指南。
在家中找到一個早之前的開發(fā)板,個人還挺喜歡的,只有最小系統(tǒng),把pin引出來了,沒有亂七八糟的外設(shè),還找到一個240*320的LCD屏幕,ILI9341驅(qū)動。

至于FreeRTOS,之前講了FreeRTOS在STM32F4上的移植:STM32F4移植FreeRTOS光是MCU加上FreeRTOS就已經(jīng)能做很多東西了,github上也有非常多的項目,直接git clone再隨便改改就能做出很多東西。
FreeRTOS的系列講解:FreeRTOS全解析-1.引入與RTOS簡介
2.LVGL和FreeRTOS結(jié)合
都有顯示屏了,當然得顯示一下,增加一下逼格,但是自己畫肯定不好看,也也沒有那個必要,這就需要借助開源圖形庫了。
嵌入式GUI有非常多,LVGL是其中之一,很低的配置就可以實現(xiàn)非常好的效果。介紹就沒必要了,就是一個C語言寫的圖形庫,需要學(xué)習(xí)的可以去官網(wǎng):https://lvgl.io/看手冊。
官網(wǎng)的一個demo:

我對LVGL了解不多,但通過手冊可以知道,LVGL的底層是通過定時器來循環(huán)調(diào)用lv_tick_inc();函數(shù),以獲知系統(tǒng)時間,稱為LVGL的心跳。再通過lv_task_handler();(新版的是lv_timer_handler())來調(diào)度LVGL的各種任務(wù),包括顯示、輸入,各種事件。搞明白這個,我們就可以非常容易得將他與FreeRTOS結(jié)合了。
2.1下載LVGL源碼,并加入keil工程
去官網(wǎng)找到源碼,找個需要的版本,我這里用git下了lvgl8.0。
只需要如下幾個東西
?
把lv_conf_template.h的文件名改成lv_conf.h。這是官方提供的配置樣板,我們要改成適合自己的。開頭的if 0改成1,使它生效。
#if 1 /*Set it to "1" to enable content*/
顯示屏的顏色,我的是16位的
/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 16
顯示和輸入(觸屏、按鈕等)的周期,按需要改。顯示周期20ms就是50幀。
/*Default display refresh period. LVG will redraw changed ares with this period time*/
#define LV_DISP_DEF_REFR_PERIOD 20 /*[ms]*/
?
/*Input device read period in milliseconds*/
#define?LV_INDEV_DEF_READ_PERIOD????30??????/*[ms]*/
內(nèi)存和CPU監(jiān)控開一下,方便看效果,就是我上面demo中左下角和右下角的顯示。
/*1: Show CPU usage and FPS count in the right bottom corner*/
#define LV_USE_PERF_MONITOR 1
?
/*1: Show the used memory and the memory fragmentation in the left bottom corner
* Requires LV_MEM_CUSTOM = 0*/
#define?LV_USE_MEM_MONITOR??????1
examples文件夾中只需要porting文件夾。

里面的文件名中的template,全部刪掉。

以STM32F4移植FreeRTOS中的已經(jīng)移植好FreeRTOS的keil工程為模板
新建文件夾

LVGL放入src文件,LVGL_PORT放入porting中的文件,LVGL_APP放入example中隨便復(fù)制的demo
然后在keil中把所有.c文件添加進去,這個過程就比較麻煩,要,需要一個個點。

2.2調(diào)整顯示接口
lv_port_disp.c為lvgl的顯示接口第一步就是把它和lv_port_disp.h頭文件中的第一行if 0改為1,使他們生效。
lv_port_disp.h文件中添加兩行,表示我LCD的分辨率為240*320
#define MY_DISP_HOR_RES 240
#define MY_DISP_VER_RES 320
lv_port_disp.c中初始化函數(shù)disp_init中,加上自己的LCD初始化函數(shù)。
static void disp_init(void)
{
/*You code here*/
LCD_Init();
}
刷屏函數(shù)中加上自己的畫點函數(shù),官方中的例子是這樣的
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
????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++;
????????}
????}
lv_disp_flush_ready(disp_drv);
}
/*put_px(x,?y,?*color_p)*/改成你LCD的畫點函數(shù),不過這樣很慢,推薦直接使用區(qū)域繪制
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
?? LCD_Color_Fill(area->x1,area->y1,area->x2,area->y2,(u16*)color_p);
????lv_disp_flush_ready(disp_drv);
}
這樣會更快。
我的LCD用FSMC,速度已經(jīng)夠快了,但是我想更快一點,就用DMA(提升不會特別大)。在這個函數(shù)中只放一個DMA傳輸函數(shù)。
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
??LCD_Start_DMA_Transfer(area->x1,area->y1,area->x2,area->y2,(u16*)color_p);
}
在DMA完成中斷函數(shù)中通知LVGL
void DMA2_Stream3_IRQHandler(void)
{
if(DMA_GetITStatus(LCD_DMA_Stream,LCD_DMA_IT_TCIFx)!=RESET)
{
??? DMA_ClearITPendingBit(LCD_DMA_Stream,LCD_DMA_IT_TCIFx);
???????lv_disp_flush_ready(&disp_drv);?
}
}
然后就是為LVGL選擇一種顯存,在這個函數(shù):
void lv_port_disp_init(void)
中,提供了三種顯存方案,第一種是十行,第二種是雙十行顯存,第三種是雙全屏顯存,stm32f407sram只有192k,開不了全屏顯存。我選擇第二種,雙顯存,因為我開啟了DMA,傳輸?shù)臅r候CPU可以去計算,并存入第二塊顯存。用不到的代碼注釋掉就行。如下:
/* Example for 1) */
// 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_1;
static lv_color_t buf_2_1[MY_DISP_HOR_RES * 80]; /*A buffer for 10 rows*/
static lv_color_t buf_2_2[MY_DISP_HOR_RES * 80]; /*An other buffer for 10 rows*/
lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 80); /*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_1[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*An other 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*/
同時,比較舊的lvgl在這個函數(shù)中,還需要注冊一下,新的不用
????/*Set?up?the?functions?to?access?to?your?display*/
/*Set the resolution of the display*/
disp_drv.hor_res = MY_DISP_HOR_RES;
disp_drv.ver_res = MY_DISP_VER_RES;
其實就是加上分辨率。
2.3調(diào)整輸入接口
lv_port_indev.c為lvgl的顯示接口第一步就是把它和lv_port_indev.h頭文件中的第一行if 0改為1,使他們生效。
lvgl支持觸摸屏,按鍵,編碼器等輸入,我用觸摸屏,只需要修改touchpad相關(guān)
static void touchpad_init(void)
{
???/*Your?code?comes?here*/
tp_dev.init();
}
/*Return true is the touchpad is pressed*/
static bool touchpad_is_pressed(void)
{
????tp_dev.scan(0);
if(tp_dev.sta&TP_PRES_DOWN)
??????return?true;
return false;
}
?
/*Get the x and y coordinates if the touchpad is pressed*/
static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y)
{?
(*x) = tp_dev.x[0];
(*y) = tp_dev.y[0];
}
分別是初始化、掃描確實是否有按下,和獲取按下的點的值。
我的觸摸屏是電阻屏,驅(qū)動從正點原子的觸摸屏實驗例程中來,該驅(qū)動中用到了SysTick定時器來延時微秒,我們的工程是含有FreeRTOS的,會造成沖突,我也懶得優(yōu)化細改了,用了個比較粗糙簡單的方法,直接在延時前把幾個寄存器保存一下,延時后再恢復(fù)。
void delay_us(u32 nus)
{
u32 temp;
u32 tickload = SysTick->LOAD;
u32 tickval = SysTick->VAL;
u32 tickctrl = SysTick->CTRL;
SysTick->LOAD=nus*fac_us; //ê±???ó??
SysTick->VAL=0x00; //??????êy?÷
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //?aê?μ1êy
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //μè′yê±??μ?′?
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //1?±???êy?÷
SysTick->VAL =0X00; //??????êy?÷
SysTick->LOAD = tickload;
SysTick->VAL = tickval;
SysTick->CTRL = tickctrl;
}
讀數(shù)據(jù)TP_Read_AD中加入臨界區(qū)保護,就可以完美使用。
u16 TP_Read_AD(u8 CMD)
{????
taskENTER_CRITICAL();
略
??taskEXIT_CRITICAL();?????
return(Num);
}
2.4main函數(shù)
開頭說了,將LVGL與FreeRTOS結(jié)合的重點其實就是再講lv_tick_inc()和lv_task_handler()兩個函數(shù)與FreeRTOS結(jié)合起來。
FreeRTOS也有心跳Tick,我們在FreeRTOS的FreeRTOSConfig.h中開啟TICK鉤子:
#define configUSE_TICK_HOOK 1
每次FreeRTOS發(fā)生tick都會去調(diào)用鉤子函數(shù),在鉤子函數(shù)中放入LVGL的心跳就可以了:
void vApplicationTickHook(void)
{
lv_tick_inc(1);
}
數(shù)字1的意思是告訴LVGL每1ms執(zhí)行一次。
至于lv_task_handler(),我們開一個任務(wù)去執(zhí)行它
因為LVGL線程不安全,所以調(diào)用到LVGL的API時需要加互斥鎖。
主函數(shù):
??MutexSemaphore=xSemaphoreCreateMutex();?
xTaskCreate(lvgl_handler, "lvgl_handler", 1000, NULL, 4, NULL);
任務(wù):
static void lvgl_handler( void *pvParameters )
{
for( ;; )
{
xSemaphoreTake(MutexSemaphore,portMAX_DELAY);
lv_task_handler();
xSemaphoreGive(MutexSemaphore);
// vTaskDelay(pdMS_TO_TICKS(20));
}
}
到這了就是完全移植成功了。
現(xiàn)在你需要的就是個應(yīng)用代碼,github上有特別多的LVGL相關(guān)例子,官方也有很多,隨便一抄,稍微修改,就可以完成一個小項目,這就是縫合的威力。
lvgl官方的壓力測試demo:文章來源:http://www.zghlxwxcb.cn/news/detail-472519.html

STM32F4+FreeRTOS+LVGL實現(xiàn)快速開發(fā)(縫合怪)文章來源地址http://www.zghlxwxcb.cn/news/detail-472519.html
到了這里,關(guān)于STM32F4+FreeRTOS+LVGL實現(xiàn)嵌入式快速開發(fā)(縫合怪)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!