在只支持单图层的LTDC屏幕上同时显示LVGL GUI和摄像头视频流

系统环境

  • 芯片:STM32F429IGT6
  • CubeMX版本:6.9.2
  • Keil版本:5.36
  • LTDC接口16位颜色
  • LVGL 8.3.10

一、理论

此篇暂不讨论如何配置LTDC屏幕,LVGL等内容,若如下前置功能还存在问题请先解决

1.确保前置功能调通

  1. 不带LVGL能否在显示屏上显示视频流
  2. 不考虑摄像头,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_COLORLV_IMG_CF_INDEXED_2BIT。在颜色格式部分查看完整列表。

实现LVGL内显示摄像头画面的核心就是这个缓冲区

二、实操

为此我们需要解决两个问题

  1. 设置lvgl canvas对象缓冲区地址为dcmi dma通道地址
  2. 摄像头画面不断更新需要频繁刷新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%,此时也仅仅是可用的状态,大幅晃动摄像头会导致图像撕裂问题

四、参考链接

Canvas (lv_canvas) — LVGL documentation

文章作者:四文鱼Max

本文链接:https://blog.awolon.fun/archives/stm32-show-video-in-lvgl.html

许可协议:CC BY-SA 4.0

标签: stm32, lvgl

已有 4 条评论

  1. 清明

    是否可以选择在VsyncEventCallback里调用lv_event_send ,然后在注册后的事件回调函数里处理?这种逻辑应该可以避免多线程操作GUI界面。
    还有为什么选择VsyncEventCallback而非FrameEventCallback呢?

    1. 四文鱼Max

      正点原子例程里jpeg模式使用的frame中断回调,我没研究过jpeg模式,估摸是开启jpeg模式后会开启frame中断;中断里调用lv_event_send应该也是没问题的,另外用lv_async_call异步调用会更稳妥,你可以参考下lvgl的官方文档https://docs.lvgl.io/master/details/main-components/timer.htm ,我不用的原因主要是任务调度和lvgl事件处理时间不稳定可能会影响屏幕刷新摄像头画面的及时性,长时间使用后只发现lvgl会报警告没有其他问题就照这个方案使用了

      1. 清明

        好的,感谢回复。

        1. 四文鱼Max

          不客气,共同进步

添加新评论