STM32 LVGL界面内显示DCMI摄像头视频流
在只支持单图层的LTDC屏幕上同时显示LVGL GUI和摄像头视频流
系统环境
- 芯片:STM32F429IGT6
- CubeMX版本:6.9.2
- Keil版本:5.36
- LTDC接口16位颜色
- LVGL 8.3.10
一、理论
此篇暂不讨论如何配置LTDC屏幕,LVGL等内容,若如下前置功能还存在问题请先解决
1.确保前置功能调通
- 不带LVGL能否在显示屏上显示视频流
- 不考虑摄像头,LVGL能否成功显示在显示屏上
2.基础知识回顾
1.STM32 DCMI
该外设使用比较简单,在CubeMX中配置好像素,垂直同步,水平同步极性和跳帧功能,DMA通道等功能后编写程序软件初始化OV5640,配置OV5640为RGB565模式即可。
使用如下API开始摄像头视频流
/**
* @brief Enables DCMI DMA request and enables DCMI capture
* @param hdcmi pointer to a DCMI_HandleTypeDef structure that contains
* the configuration information for DCMI.
* @param DCMI_Mode DCMI capture mode snapshot or continuous grab.
* @param pData The destination memory Buffer address (LCD Frame buffer).
* @param Length The length of capture to be transferred.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_DCMI_Start_DMA(DCMI_HandleTypeDef* hdcmi, uint32_t DCMI_Mode, uint32_t pData, uint32_t Length)
记住 pData 这个数据地址,接着看lvgl canvas控件。
2.LVGL Canvas控件
画布需要一个缓冲区来存储绘制的图像。要为画布分配一个缓冲区,请使用
lv_canvas_set_buffer(canvas, buffer, width, height, LV_IMG_CF_...)
其中buffer
是一个静态缓冲区(不仅仅是一个局部变量),用于保存画布的图像。例如:
static lv_color_t buffer[LV_CANVAS_BUF_SIZE_TRUE_COLOR(width, height)]
LV_CANVAS_BUF_SIZE_...
宏帮助确定不同颜色格式的缓冲区大小。
画布支持所有内置颜色格式,如LV_IMG_CF_TRUE_COLOR
或LV_IMG_CF_INDEXED_2BIT
。在颜色格式部分查看完整列表。
实现LVGL内显示摄像头画面的核心就是这个缓冲区
二、实操
为此我们需要解决两个问题
- 设置lvgl canvas对象缓冲区地址为dcmi dma通道地址
- 摄像头画面不断更新需要频繁刷新canvas对象从而显示视频流
1. 配置OV5640输出尺寸
/**
* @brief 设置图像输出大小
* OV5640输出图像的大小(分辨率),完全由该函数确定
* offx,offy,为输出图像在OV5640_ImageWin_Set设定窗口(假设长宽为xsize和ysize)上的偏移
* 由于开启了scale功能,用于输出的图像窗口为:xsize-2*offx,ysize-2*offy
* width,height:实际输出图像的宽度和高度
* 实际输出(width,height),是在xsize-2*offx,ysize-2*offy的基础上进行缩放处理.
* 一般设置offx和offy的值为16和4,更小也是可以,不过默认是16和4
* @ret 0,设置成功
* 其他,设置失败
*/
uint8_t OV5640_OutSize_Set(uint16_t offx,uint16_t offy,uint16_t width,uint16_t height)
// 拉伸
OV5640_OutSize_Set(300,0,240,290);
2.开启DCMI DMA传输
uint16_t dcmi_framebuf[240][290];
HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_CONTINUOUS,(uint32_t)dcmi_framebuf,240*290*2/4);
首先定义缓冲区,这个变量需要定义为全局,因为你还需要让lvgl canvas对象使用,如果单片机内部没有这么大的内存可以将其指定到外部SDRAM内
3.创建lvgl canvas对象并设置其缓冲地址
// 创建对象
ui->Main_canvas_cam = lv_canvas_create(ui->Main);
// 设置buffer地址
lv_canvas_set_buffer(ui->Main_canvas_cam, dcmi_framebuf, 240, 290, LV_IMG_CF_TRUE_COLOR);
// 填充canvas背景
lv_canvas_fill_bg(ui->Main_canvas_cam, lv_color_hex(0xffffff), 255);
// 设置canvas位置
lv_obj_set_pos(ui->Main_canvas_cam, 0, 30);
// 设置canvas大小
lv_obj_set_size(ui->Main_canvas_cam, 240, 290);
// 设置canvas滚动模式
lv_obj_set_scrollbar_mode(ui->Main_canvas_cam, LV_SCROLLBAR_MODE_OFF);
4.刷新lvgl canvas对象
DCMI摄像头垂直同步中断服务例程即如下回调函数
HAL_DCMI_VsyncEventCallback(DCMI_HandleTypeDef *hdcmi)
{
/// @brief 告知LVGL需要重绘对象
/// @bug !!!潜在BUG!!!
/// LVGL线程不安全,中断会频繁抢占lvgl线程
/// 当lvgl正在刷新屏幕时被抢占调用该函数后LVGL内部会抛出Error
/// 实际测试该问题应该不会造成内存泄露或者系统死机问题
lv_obj_invalidate(ui.Main_canvas_cam);
}
此回调函数即用于通知DCMI一帧数据传输完成,此时告知LVGL canvas数据已经无效了,立即重绘
三、问题
1.摄像头帧率&显示屏帧率&LVGL显示帧率 问题
这三者帧率关系应该为 摄像头帧率≤LVGL显示帧率≤显示屏帧率
另外实际表现性能非常重要,并非显示屏可以跑60帧你就必须把LVGL和摄像头帧率都拉到60帧,你的MCU没有这么强的性能
我实测条件是LVGL全尺寸双缓冲30fps ,屏幕的像素时钟配置也为30fps,摄像头原始尺寸1280*800@15fps
最终是顶层GUI底层摄像头画面的情况下可以稳30fps,CPU占用80-90%,此时也仅仅是可用的状态,大幅晃动摄像头会导致图像撕裂问题
四、参考链接
文章作者:四文鱼Max
本文链接:https://blog.awolon.fun/archives/stm32-show-video-in-lvgl.html
许可协议:CC BY-SA 4.0
是否可以选择在VsyncEventCallback里调用lv_event_send ,然后在注册后的事件回调函数里处理?这种逻辑应该可以避免多线程操作GUI界面。
还有为什么选择VsyncEventCallback而非FrameEventCallback呢?
正点原子例程里jpeg模式使用的frame中断回调,我没研究过jpeg模式,估摸是开启jpeg模式后会开启frame中断;中断里调用lv_event_send应该也是没问题的,另外用lv_async_call异步调用会更稳妥,你可以参考下lvgl的官方文档https://docs.lvgl.io/master/details/main-components/timer.htm ,我不用的原因主要是任务调度和lvgl事件处理时间不稳定可能会影响屏幕刷新摄像头画面的及时性,长时间使用后只发现lvgl会报警告没有其他问题就照这个方案使用了
好的,感谢回复。
不客气,共同进步