极速进行项目开发,只需要懂一款芯片架构+一个操作系统+一个GUI。各种部件程序全靠抄

,成为究极缝合怪。本文用stm32f407+FreeRTOS+lvgl演示一些demo。
原文链接:STM32F4+FreeRTOS+LVGL实现快速开发(缝合怪)
lvgl官方的音乐播放器demo:
百问网的2048小游戏:

1.STM32F407和FreeRTOS
STM32F407这款芯片就不多介绍了,挺老的MCU,架构为ARM_CM4F。随便一搜就有非常非常多的例程和项目。
会缝合的基础是对芯片架构非常了解,刚入门的同学建议先从基础学起,推荐学习ARM官方的权威指南。
在家中找到一个早之前的开发板,个人还挺喜欢的,只有最小系统,把pin引出来了,没有乱七八糟的外设,还找到一个240*320的LCD屏幕,ILI9341驱动。

至于FreeRTOS,之前讲了FreeRTOS在STM32F4上的移植:STM32F4移植FreeRTOS光是MCU加上FreeRTOS就已经能做很多东西了,github上也有非常多的项目,直接git clone再随便改改就能做出很多东西。
FreeRTOS的系列讲解:FreeRTOS全解析-1.引入与RTOS简介
2.LVGL和FreeRTOS结合
都有显示屏了,当然得显示一下,增加一下逼格,但是自己画肯定不好看,也也没有那个必要,这就需要借助开源图形库了。
嵌入式GUI有非常多,LVGL是其中之一,很低的配置就可以实现非常好的效果。介绍就没必要了,就是一个C语言写的图形库,需要学习的可以去官网:https://lvgl.io/看手册。
官网的一个demo:

我对LVGL了解不多,但通过手册可以知道,LVGL的底层是通过定时器来循环调用lv_tick_inc();函数,以获知系统时间,称为LVGL的心跳。再通过lv_task_handler();(新版的是lv_timer_handler())来调度LVGL的各种任务,包括显示、输入,各种事件。搞明白这个,我们就可以非常容易得将他与FreeRTOS结合了。
2.1下载LVGL源码,并加入keil工程
去官网找到源码,找个需要的版本,我这里用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]*/
内存和CPU监控开一下,方便看效果,就是我上面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中的已经移植好FreeRTOS的keil工程为模板
新建文件夹

LVGL放入src文件,LVGL_PORT放入porting中的文件,LVGL_APP放入example中随便复制的demo
然后在keil中把所有.c文件添加进去,这个过程就比较麻烦,要,需要一个个点。

2.2调整显示接口
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中初始化函数disp_init中,加上自己的LCD初始化函数。
static void disp_init(void)
{
/*You code here*/
LCD_Init();
}
刷屏函数中加上自己的画点函数,官方中的例子是这样的
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的画点函数,不过这样很慢,推荐直接使用区域绘制
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,速度已经够快了,但是我想更快一点,就用DMA(提升不会特别大)。在这个函数中只放一个DMA传输函数。
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完成中断函数中通知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选择一种显存,在这个函数:
void lv_port_disp_init(void)
中,提供了三种显存方案,第一种是十行,第二种是双十行显存,第三种是双全屏显存,stm32f407sram只有192k,开不了全屏显存。我选择第二种,双显存,因为我开启了DMA,传输的时候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在这个函数中,还需要注册一下,新的不用
/*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调整输入接口
lv_port_indev.c为lvgl的显示接口第一步就是把它和lv_port_indev.h头文件中的第一行if 0改为1,使他们生效。
lvgl支持触摸屏,按键,编码器等输入,我用触摸屏,只需要修改touchpad相关
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];
}
分别是初始化、扫描确实是否有按下,和获取按下的点的值。
我的触摸屏是电阻屏,驱动从正点原子的触摸屏实验例程中来,该驱动中用到了SysTick定时器来延时微秒,我们的工程是含有FreeRTOS的,会造成冲突,我也懒得优化细改了,用了个比较粗糙简单的方法,直接在延时前把几个寄存器保存一下,延时后再恢复。
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; //Çå¿Õ¼ÆÊýÆ÷
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //¿ªÊ¼µ¹Êý
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //µÈ´ýʱ¼äµ½´ï
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //¹Ø±Õ¼ÆÊýÆ÷
SysTick->VAL =0X00; //Çå¿Õ¼ÆÊýÆ÷
SysTick->LOAD = tickload;
SysTick->VAL = tickval;
SysTick->CTRL = tickctrl;
}
读数据TP_Read_AD中加入临界区保护,就可以完美使用。
u16 TP_Read_AD(u8 CMD)
{
taskENTER_CRITICAL();
略
taskEXIT_CRITICAL();
return(Num);
}
2.4main函数
开头说了,将LVGL与FreeRTOS结合的重点其实就是再讲lv_tick_inc()和lv_task_handler()两个函数与FreeRTOS结合起来。
FreeRTOS也有心跳Tick,我们在FreeRTOS的FreeRTOSConfig.h中开启TICK钩子:
#define configUSE_TICK_HOOK 1
每次FreeRTOS发生tick都会去调用钩子函数,在钩子函数中放入LVGL的心跳就可以了:
void vApplicationTickHook(void)
{
lv_tick_inc(1);
}
数字1的意思是告诉LVGL每1ms执行一次。
至于lv_task_handler(),我们开一个任务去执行它
因为LVGL线程不安全,所以调用到LVGL的API时需要加互斥锁。
主函数:
MutexSemaphore=xSemaphoreCreateMutex();
xTaskCreate(lvgl_handler, "lvgl_handler", 1000, NULL, 4, NULL);
任务:
static void lvgl_handler( void *pvParameters )
{
for( ;; )
{
xSemaphoreTake(MutexSemaphore,portMAX_DELAY);
lv_task_handler();
xSemaphoreGive(MutexSemaphore);
// vTaskDelay(pdMS_TO_TICKS(20));
}
}
到这了就是完全移植成功了。
现在你需要的就是个应用代码,github上有特别多的LVGL相关例子,官方也有很多,随便一抄,稍微修改,就可以完成一个小项目,这就是缝合的威力。
lvgl官方的压力测试demo:

STM32F4+FreeRTOS+LVGL实现快速开发(缝合怪)