基于STM32F429的OV7725-NF无缓存摄像头的DCMI图像采集 和LTDC显示
STM32F429具备DCMI摄像头接口,可利用DMA功能实现快速的硬件级图像采集存储,大大降低了纯软件开发的难度。开发教程网络上一大堆,但本人一开始也是经历了九九八十一难,一直不出图像,过程中还是有不少坑需要自己填一下,以此记录。完整的案例代码见以下链接。
CAM2LCDonF429IGT6.rar
0. 首先说明一下时钟配置
1. DCMI配置
1.1 首先点击DCMI进入配置页面后,要选择Mode
不同的mode代表不同的数据格式。下图中,8bits Embedded Synchro是指码流中嵌入同步码元的编码形式,其他的External Synchro都是通过单独的硬线提供同步信号。8、10、12、14是指数据位宽,像OV7725是8位宽数据+硬线同步,所以应该选择“Slave 8 bits External Synchro”。
1.2 然后要进行GPIO settings
根据实际使用的电路连接选择对应的引脚,调整的时候要在“Pinout view”界面中进行修改。
1.3 然后是Parameter settings参数设置
一共就以下5个需要配置的:
前3个参数是和所接摄像头参数对应的,要保持一致,分别是指PCLK触发沿选择、垂直同步信号VSYNC极性、水平同步信号HREF极性,注意OV7725提供的水平同步信号是HREF而不是HSYNC。
从下图OV7725时序图可以看出,PCLK上升沿有效,所以选Active on Rising edge.
但是,从下图帧时序图中看到,当VSYNC低电平,且HREF、HSYNC高电平时,才传输有效数据,DCMI配置选项中的Active High是指高电平时进行同步,官方称为消隐信号,也就是低电平时进行传数。这个一定要选对,不然接收到的数据永远都是0x00。对应OV7725的正确选择应该是:V=Active high; H=Active low。
接下来是“Frequency of frame capture”,这个是指DCMI接收处理的帧频率,通俗点就是说摄像头在一直发送图像数据,可能帧频率是60fps,但我们实际不需要这么高的帧率,可以通过这个选项选择“每处理1帧,丢弃3帧”、“每处理1帧,丢弃1帧”、“全部处理”。根据实际需要选择。
最后一个JPEG mode是指接收的数据流传输的是否为JPEG压缩数据。OV7725输出的VGA、QVGA、CIF格式都是非压缩数据,所以这里要选Disabled。
1.4 DMA settings
DMA mode有normal和circular两个选项,normal指执行一次DMA传输后停止,circular指连续循环执行数据搬移。
Data Width的选择应与实际一致,我们要将OV7725通过DCMI传入的外设数据搬移到内部存储器中,OV7725在传给DCMI时一次传8bits,但DCMI内部会将接收到的摄像头数据放到一个 32 位数据寄存器(DCMI_DR)中,然后通过通用DMA 进行传输。也就是说DCMI接收处理4次放满32bits数据后才会发起一次DMA传输。如果想使用DMA的FIFO,可以选择Use Fifo,并选定Threshold参数。
此处我们的图像编码格式是RGB565,按照上述说明,DCMI输出的32位数据应该是下图这种排列方式,当LTDC读取时也是这样的格式,通过实现发现是可以直接解码的,不需要进行位变换,但这部分内部原理还需要摸清楚,这里暂时不展开。
1.5 需要使用的HAL库函数
-
启动DCMI:HAL_StatusTypeDef HAL_DCMI_Start_DMA (DCMI_HandleTypeDef * hdcmi, uint32_t DCMI_Mode, uint32_t pData, uint32_t Length)
-
结束DCMI:HAL_StatusTypeDef HAL_DCMI_Stop(DCMI_HandleTypeDef * hdcmi)
-
行接收完毕中断:void HAL_DCMI_LineEventCallback(DCMI_HandleTypeDef * hdcmi)
-
帧接收完毕中断:void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef * hdcmi) 或
void HAL_DCMI_VsyncEventCallback(DCMI_HandleTypeDef *hdcmi)
3. OV7725配置寄存器
OV7725按照定义的引脚连接好后就可以工作了,具体各个引脚功能如下表所示:
信号 | 作用描述 | 信号 | 作用描述 | |
---|---|---|---|---|
VCC3.3 | 模块供电脚,接 3.3V 电源[输入] | OV_PCLK | 像素时钟输出[输出] | |
GND | 模块地线[输入] | OV_VSYNC | 帧同步信号[输出] | |
OV_SCL | SCCB 通信时钟信号[输入] | OV_HREF | 行同步信号[输出] | |
OV_SDA | SCCB 通信数据信号[双向] | OV_RESET | 复位信号,低电平复位[输入] | |
SGM_CTRL | 传感器时钟选择控制,高电平时使用模块内部晶振,低电平时使用XCLK_IN外部时钟信号[输入] | OV_D[7:0] | 数据输出(8 位)[输出] | |
XCLK_IN | 外部时钟输入[输入] | 我是用的摄像头内置晶振频率为12MHz |
但是他的参数设置可是多的一批,一共有0x00-0xac个8位寄存器,要想获得优秀画质,就得摸透这些寄存器。下面是官方给出的有用的寄存器配置方案,具体应用时可适当调整:
【注意】:硬件I2C有一些bug,不太好用,总是busy状态,推荐使用软件I2C进行配置通信。
//输出窗口设置
{COM7, 0x46}, //QVGA RGB565
{HSTART, 0x3f}, //水平起始位置
{HSIZE, 0x50}, //水平尺寸
{VSTRT, 0x03}, //垂直起始位置
{VSIZE, 0x78}, //垂直尺寸
{HREF, 0x00},
{HOutSize, 0x50}, //输出尺寸高,QVGA320填0x50; VGA填0xA0;
{VOutSize, 0x78}, //输出尺寸宽,QVGA480填0x78; VGA填0xF0;
//帧率
//30 fps, PCLK = 12Mhz
{CLKRC, 0x01}, //CLKRC, F/2/2;F(internal clock) = F(input clock)/(Bit[5:0]+1)/2
{COM4, 0x41}, //COM4, PLL 4倍频
{EXHCH, 0x00},
{EXHCL, 0x00},
{DM_LNL, 0x00}, //DM_LNL, Dummy Row Low 8 Bits
{DM_LNH, 0x00}, //DM_LNH, Dummy Row High 8 Bits
{ADVFL, 0x00},
{ADVFH, 0x00},
{COM5, 0xf5},//夜晚模式下自动帧率控制开启
//DSP control
{TGT_B, 0x80},//{TGT_B, 0x7f},
{FixGain, 0x00},//0x09
{AWB_Ctrl0, 0xf0},//0xe0
{DSP_Ctrl1, 0x1f},//0xff
{DSP_Ctrl2, 0x00},
{DSP_Ctrl3, 0x10},
{DSP_Ctrl4, 0x00},
//AGC AEC AWB
{COM8, 0x8f},//0xf0
{COM4, 0x41}, //Pll AEC CONFIG
{COM6, 0x43},//0xc5
{COM9, 0x4a},//0x11
{BDBase, 0xfF},//0x7f
{BDMStep, 0x01},//0x03
{AEW, 0x40},
{AEB, 0x30},
{VPT, 0xa1},
{EXHCL, 0x9e},
{AWBCtrl3, 0xaa},
{COM8, 0xff},
//matrix shapness brightness contrast
{EDGE1, 0x08},
{DNSOff, 0x01},
{EDGE2, 0x03},
{EDGE3, 0x00},
{MTX1, 0xb0},
{MTX2, 0x9d},
{MTX3, 0x13},
{MTX4, 0x16},
{MTX5, 0x7b},
{MTX6, 0x91},
{MTX_Ctrl, 0x1e},
{BRIGHT, 0x08},
{CNST, 0x20},
{UVADJ0, 0x81},
{SDE, 0X06},
{USAT, 0x65},
{VSAT, 0x65},
{HUECOS, 0X80},
{HUESIN, 0X80},
//GAMMA config
{GAM1, 0x0c},
{GAM2, 0x16},
{GAM3, 0x2a},
{GAM4, 0x4e},
{GAM5, 0x61},
{GAM6, 0x6f},
{GAM7, 0x7b},
{GAM8, 0x86},
{GAM9, 0x8e},
{GAM10, 0x97},
{GAM11, 0xa4},
{GAM12, 0xaf},
{GAM13, 0xc5},
{GAM14, 0xd7},
{GAM15, 0xe8},
{SLOP, 0x20},
{COM3, 0x40},//Horizontal mirror image;默认0x10,即改变YUV为UVY格式。但是摄像头不是芯片而是模组时,要将0X10中的1变成0,即设置YUV格式
{COM10, 0x00}, //默认VSYNC 低电平有效。如果要兼容OV2640 DCMI的配置这里需要VSYNC 高电平有效
{COM2, 0x01}, //设置输出驱动能力为2倍
4. LTDC显示器显示
LTDC是一种TFT显示屏接口,全称为LCD-TFT display controller,属于显示像素接口的一种,显示控制器提供了一个并行的数字RGB(红、绿、蓝)信号、以及水平/垂直同步信号、像素时钟作为输出,直接与各种LCD和TFT面板连接,且显示面板不需要缓存。一帧开始后,从左向右、从上向下一个像素一个像素输出RGB值,类似VGA逐行扫描刷新。
STM32的LTDC使用非常简单,完成参数配置后,只要定时向图层句柄设置图像数据地址即可,代码量非常少,基本可以看成STM32的显卡。
接口配置
- LTDC主要的接口IO有像素时钟LCD_CLK、水平同步HSYNC、垂直同步VSYNC、数据有效DE和3组RGB数据信号并行线,STM32F429最大支持RGB888显示输出,Display Type选项要根据所使用的显示屏支持的数据格式进行选择。
- configuration中的parameter settings是最核心的配置,其中下图红框中的参数是由所使用的显示屏决定的,通常显示屏datasheet中都会给出,比如我使用的TM043NDH02给出的配置参数见第二张图,只要一一对应匹配即可。
3. signal polarity有效电平配置
这部分有效电平的设置一定要和使用的显示屏相符合,下图为我是用的显示屏datasheet对IO电平的要求。
- layer settings主要是对图层参数进行设置。
STM32F429共提供两个图层,每个图层的配置基本相似,下面是单个图层的配置说明。
完成以上配置后,LTDC就可以工作了。当我们需要把OV7725采集的图像进行显示时,只要在DCMI的帧中断或垂直同步中断中,把帧图像缓存地址向LTDC的图层句柄的起始地址赋值,然后调用一次配置函数即可,这样每接收完一帧图像,即触发一次显示刷新。
void HAL_DCMI_VsyncEventCallback(DCMI_HandleTypeDef *hdcmi)
{
pLayerCfg.FBStartAdress = IMG_ADDR;
if (HAL_LTDC_ConfigLayer(&hltdc, &pLayerCfg, 0) != HAL_OK)
{
Error_Handler();
}
}