ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32驱动OV2710摄像头:从DCMI配置到DMA图像采集实战

STM32驱动OV2710摄像头:从DCMI配置到DMA图像采集实战 简介STM32驱动OV2710是一套基于STM32F429与OV2710摄像头模组的嵌入式图像处理工程源码面向有一定ARM Cortex-M4基础、希望学习摄像头驱动与MIPI CSI-2接口开发的工程师。压缩包共135个文件包含53个C源码、63个头文件、5个文本说明文件以及Keil工程文件.uvprojx和hex固件源码按HAL库、LCD、ADC、Flash等外设模块划分可直接在开发板上编译验证整个资源包仅684KB结构清晰简洁。当前已有545人学习下载。资料围绕OV2710寄存器配置、PCLK像素时钟匹配与MIPI CSI-2协议展开深入展示了DMA图像搬运、FreeRTOS任务调度以及摄像头初始化到图像输出的完整链路还附有工程调试配置文件。读者可对照代码快速理解硬件交互细节并在此基础上扩展帧率调节、图像处理或网络传输功能非常适合作为STM32摄像头项目的起步参考。 如果你已经在STM32上玩过OV7670、OV2640这类入门摄像头再去看OV2710的手册第一感觉会是这颗芯片有点“硬核”。它是OmniVision旗下主打1080p全高清输出的CMOS图像传感器在行车记录仪、安防监控、工业视觉里出镜率很高。但在STM32上驱动OV2710属于“能做、但没那么轻松”的事——它没有MIPI接口只有DVP并行输出这反而是STM32能接它的前提可一旦输出全分辨率数据量会立刻变成一场内存和带宽的考验。这篇文章把我实际调通OV2710的完整过程拆开讲清楚从传感器特性、硬件连接、寄存器配置到DCMIDMA采集以及一堆新人容易踩的坑。适合手上有STM32F4/F7开发板、想上一颗真正1080p级别摄像头的朋友。如果你只是想随手点个灯可能用不上但如果你在做视觉入门、工业检测或者行车记录仪相关项目这篇值得收藏。1. 项目概述与方案选型1.1 OV2710是一颗什么样的传感器OV2710是豪威OmniVision推出的1/3英寸CMOS图像传感器最高支持1920x108030fps全高清输出像素阵列为1920x1080支持YUV422、RGB565、RAW等不同输出格式。相比OV7670、OV2640这些入门级模组OV2710的物理分辨率更高、靶面更大弱光表现也更好因此在行车记录仪、安防摄像头、工业视觉检测里大量出现。但从驱动角度讲它比OV2640“难搞”的地方主要在于三点一是寄存器数量多而且不同批次/模组的初始化序列有差异二是它对电源和时钟要求更敏感布线不好容易出现图像横纹或花屏三是全分辨率下数据量很大对MCU的DMA带宽和内存都是压力。我手里的模组是OV2710-A86ADVP 10-bit并行输出控制接口是SCCB和I2C兼容。默认输出1920x1080可以通过寄存器把分辨率降到720p、VGA甚至QCIF。这个降分辨率能力后面会被反复用到。1.2 主控选型为什么必须是带DCMI的STM32F4很多人拿到OV2710后第一反应是“我用STM32F103行不行”答案是不行。F1系列没有DCMI外设只能靠GPIO模拟并行数据采集。OV2710在1080p30fps时PCLK可以到50MHz以上F103的GPIO翻转速度根本扛不住即使强行降帧率、降分辨率也会把CPU占满实测意义不大。真正适合的是带DCMIDigital Camera Interface的型号比如STM32F407、F429、F7系列。DCMI外设可以接收8位、10位、12位、14位并行数据自动解析HSYNC、VSYNC和PCLK时序并通过DMA把数据直接搬到内存里CPU全程不参与搬运。我这次用的主控是STM32F407ZGT6168MHz主频192KB SRAM带DCMI外设。这块板子做图像采集够用但内存空间比较局促后面我会专门讲怎么绕开“内存不够”这个坑。如果你要做1080p全分辨率实时显示更推荐STM32F429/439因为F429带FMC控制器可以外扩SDRAMDCMI采集的数据可以直接DMA到SDRAM的帧缓冲里图像处理空间会充裕很多。1.3 先算一笔账1080p数据量到底有多大这个点值得单独说因为它决定你的方案能不能落地。以RGB565输出为例一帧1920x1080的裸数据大小是1920 x 1080 x 2字节 4,147,200字节约3.95MB如果跑30fps每秒数据量约118MB/s。STM32F407的SRAM一共才192KB连一帧的零头都放不下。就算用DMA边采边处理要做好1080p30fps的实时全帧缓存基本必须外接SDRAM。所以我实际调通OV2710时并没有一上来就奔着1080p去而是先把传感器配置成QCIF或QVGA输出让整条链路先跑通确认图像正常后再用支持SDRAM的平台去做全分辨率采集。这个思路也建议你复制驱动OV2710的重点是先“能出图”然后再谈“出多大图”。2. 硬件连接与关键时序2.1 引脚连接参考表OV2710模组通常引出XVCLK、PCLK、HSYNC、VSYNC、D0-D9、SCCB_SCL、SCCB_SDA等信号。实际项目中我不建议用杜邦线飞线PCLK频率较高时信号完整性会出问题最好用短排线连接或者画PCB。我这次在STM32F407ZGT6板上的接法如下OV2710信号STM32F407引脚说明XVCLKPC9MCO1给传感器提供主时钟实际用模组自带24MHz晶振更省事PCLKPH7DCMI像素时钟输入HSYNCPH8行同步信号VSYNCPH9帧同步信号D0-D7PC6-PC9、PC11、PC12、PD3、PD68位数据输入D8-D9PE5、PE610位模式才需要8位模式可悬空SCCB_SCLPB6软件模拟I2C时钟SCCB_SDAPB7软件模拟I2C数据PWDN接GND上电工作模式RESET接IO控制低电平复位后释放ST官方DCMI引脚复用关系请以具体型号数据手册的Alternate Function Mapping表为准不能盲抄。2.2 SCCB控制通道用GPIO模拟比硬件I2C更省心OV2710的寄存器配置通过SCCB接口完成。SCCB协议和I2C基本兼容OV2710的7位从机地址是0x21读写时序可以直接按I2C写。第一次调的时候我图省事直接用了STM32硬件I2C外设结果发现两个问题一是OV2710的SCCB时序要求比较严格硬件I2C在某些边界情况下会丢ACK二是硬件I2C一旦卡在busy状态排查起来很麻烦。后来我把SCCB改成GPIO软件模拟靠延时控制时钟问题一下就少了。软件模拟SCCB的读写函数核心代码大概长这样// 软件模拟SCCB起始信号 void SCCB_Start(void) { SCCB_SDA_H(); SCCB_SCL_H(); delay_us(5); SCCB_SDA_L(); delay_us(5); SCCB_SCL_L(); } // 写一个字节返回ACK状态 uint8_t SCCB_WriteByte(uint8_t dat) { uint8_t i, ack; for (i 0; i 8; i) { if (dat 0x80) SCCB_SDA_H(); else SCCB_SDA_L(); dat 1; SCCB_SCL_H(); delay_us(2); SCCB_SCL_L(); delay_us(2); } SCCB_SDA_IN(); // 释放SDA读取从机ACK SCCB_SCL_H(); delay_us(2); ack SCCB_SDA_READ(); SCCB_SCL_L(); SCCB_SDA_OUT(); return ack; } // 读一个字节 uint8_t SCCB_ReadByte(void) { uint8_t i, dat 0; SCCB_SDA_IN(); for (i 0; i 8; i) { SCCB_SCL_H(); delay_us(2); dat (dat 1) | SCCB_SDA_READ(); SCCB_SCL_L(); delay_us(2); } SCCB_SDA_OUT(); return dat; }SCCB时钟频率我控制在100kHz以下太快的时序在某些模组上不稳定慢一点对排障也友好。2.3 时钟与上电顺序别忽略OV2710工作需要外部时钟输入。大部分模组板上自带24MHz晶振不需要MCU额外提供XVCLK这种模组最省心。如果没有自带晶振可以用STM32的MCO输出提供时钟但MCO输出的频率不一定刚好是24MHz需要根据主时钟配置调整这里容易出问题。上电顺序方面OV2710对电源和时钟的先后关系没有特别苛刻的要求但还是建议按这样的顺序来先给传感器供电VDD和IOVDD电压按模组要求来通常是2.8V和1.8V或2.8V等待电源稳定后再给XVCLKXVCLK稳定后再释放复位引脚最后通过SCCB写寄存器配置。如果一上电就去读传感器ID大概率读不到或者读到0xFF先软复位一次再操作成功率会高很多。3. 代码级实操寄存器配置与DCMI采集3.1 初始化OV2710的寄存器配置OV2710的寄存器数量很多但拿来做基础驱动核心就几个方向和寄存器软复位、PLL时钟配置、输出分辨率、像素格式、自动曝光/白平衡。不同批次传感器的寄存器地址可能有差异最稳的方法是让模组厂家提供标准初始化序列文章里给的是我在A86A模组上实际验证过的一组关键配置。// OV2710软复位 void OV2710_SoftReset(void) { OV2710_WriteReg(0x3008, 0x82); // 软复位 delay_ms(50); OV2710_WriteReg(0x3008, 0x42); // 恢复运行 delay_ms(50); } // 基础配置QCIF RGB565用于F407调试 void OV2710_Init(void) { OV2710_SoftReset(); // 系统时钟配置 OV2710_WriteReg(0x3103, 0x11); // PLL分频/倍频设置具体值据模组晶振调整 // 输出分辨率QCIF 176x144 OV2710_WriteReg(0x3808, 0x01); // 水平输出尺寸高8位 OV2710_WriteReg(0x3809, 0x50); // 水平输出尺寸低8位 OV2710_WriteReg(0x380A, 0x00); // 垂直输出尺寸高8位 OV2710_WriteReg(0x380B, 0x90); // 垂直输出尺寸低8位 // 像素格式RGB565 OV2710_WriteReg(0x5000, 0xFF); // ISP控制允许色彩输出 OV2710_WriteReg(0x5001, 0x00); OV2710_WriteReg(0x5002, 0x00); // 输出时序设置 OV2710_WriteReg(0x4601, 0x00); // PCLK分频/极性按实际帧率调整 delay_ms(100); }这里要说明一点0x3103、0x3808这类寄存器在不同版本芯片上定义基本一致但具体倍频系数和分频值必须结合模组电路来调。如果PLL配错了表现就是没有PCLK输出或者图像强闪排障时优先怀疑这个地方。3.2 DCMI DMA双缓冲配置OV2710的PCLK、HSYNC、VSYNC直接接到DCMI外设后DCMI在硬件同步模式下自动完成一行一行的数据采集。为了让CPU不参与数据搬运必须接DMA2的Stream1这是DCMI的专用DMA通道。// DCMI初始化外置同步、8位数据模式 void DCMI_Init_Config(void) { DCMI_InitTypeDef DCMI_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DCMI, ENABLE); DCMI_DeInit(); DCMI_InitStructure.DCMI_CaptureMode DCMI_CaptureMode_Continuous; // 连续采集 DCMI_InitStructure.DCMI_SynchroMode DCMI_SynchroMode_Hardware; // 硬件同步 DCMI_InitStructure.DCMI_PCKPolarity DCMI_PCKPolarity_Rising; // PCLK上升沿采样 DCMI_InitStructure.DCMI_VSPolarity DCMI_VSPolarity_Low; // VSYNC低有效 DCMI_InitStructure.DCMI_HSPolarity DCMI_HSPolarity_Low; // HSYNC低有效 DCMI_InitStructure.DCMI_CaptureRate DCMI_CaptureRate_All_Frame; // 采集所有帧 DCMI_InitStructure.DCMI_ExtendedDataMode DCMI_ExtendedDataMode_8b; // 8位数据 DCMI_Init(DCMI_InitStructure); DCMI_ITConfig(DCMI_IT_FRAME, ENABLE); DCMI_Cmd(ENABLE); }DMA配置使用双缓冲模式这样一帧数据在写第二缓冲的时候CPU可以同时处理第一缓冲的图像避免丢帧。以QCIF分辨率RGB565为例单帧大小为176 x 144 x 2 50688字节双缓冲约100KBF407勉强放得下。#define IMAGE_WIDTH 176 #define IMAGE_HEIGHT 144 #define IMAGE_SIZE (IMAGE_WIDTH * IMAGE_HEIGHT * 2) static uint16_t image_buf0[IMAGE_WIDTH * IMAGE_HEIGHT]; static uint16_t image_buf1[IMAGE_WIDTH * IMAGE_HEIGHT]; void DCMI_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); DMA_DeInit(DMA2_Stream1); DMA_InitStructure.DMA_Channel DMA_Channel_1; // DCMI专用通道 DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)DCMI-DR; DMA_InitStructure.DMA_Memory0BaseAddr (uint32_t)image_buf0; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralToMemory; DMA_InitStructure.DMA_BufferSize IMAGE_SIZE / 2; // 半字为单位 DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; // 循环模式 DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_FIFOMode DMA_FIFOMode_Disable; DMA_Init(DMA2_Stream1, DMA_InitStructure); DMA_DoubleBufferModeConfig(DMA2_Stream1, (uint32_t)image_buf1, DMA_Memory_0); DMA_DoubleBufferModeCmd(DMA2_Stream1, ENABLE); DMA_ITConfig(DMA2_Stream1, DMA_IT_TC, ENABLE); DMA_Cmd(DMA2_Stream1, ENABLE); }DMA传输完成后在DMA2_Stream1的中断服务函数里切换处理当前缓冲即可。这里有个经验不要在中断里做耗时操作只设置标志位主循环里再处理图像数据。3.3 图像数据怎么用显示到LCD或上传PC调通DCMI和DMA之后最简单的验证方式就是把采集到的RGB565数据直接丢到LCD上显示。如果用的是带LCD的F407板子可以这样做void LCD_ShowImage(uint16_t *img) { uint16_t x, y; for (y 0; y IMAGE_HEIGHT; y) { for (x 0; x IMAGE_WIDTH; x) { LCD_FastDrawPoint(x, y, img[y * IMAGE_WIDTH x]); } } }这段代码只适合小分辨率比如QCIF。如果分辨率大了循环刷屏会非常慢需要改成LCD的DMA刷屏或者直接用LTDC显示。如果手上没有LCD也可以用串口把BMP图像发到PC端上位机查看。方法是先在MCU里生成BMP文件头再把RGB565转成RGB888追加到像素数据后面通过串口发送PC端用串口助手保存成bin文件就能直接看图。这个方式对排障特别有用因为你能看完整的图像而不只是看LCD上的局部。4. 常见问题与排障实录4.1 SCCB读不到ID先按这个顺序排查这是整个项目里出现频率最高的问题。OV2710的7位地址是0x21读ID寄存器0x300A/0x300B得到0x27和0x10如果读不到按下面的顺序查排查点具体操作供电是否正常用万用表量传感器VDD和IOVDD确认电压在手册范围内SCCB上拉电阻SCL和SDA接4.7kΩ上拉到IOVDD不能省从机地址确认是0x21不是0x20也不是0x22时钟频率SCCB时钟不要超过100kHz降到50kHz更稳复位/上电顺序先软复位一次再读ID很多模组需要延时后才响应还有一个容易忽略的问题OV2710模组上的SCCB引脚可能和别的外设共用导致总线上有其他设备干扰排查时可以把其他设备先断开。4.2 花屏、错位、全黑全白多半是极性配置问题图像花屏或者错位大概率是DCMI的PCLK、HSYNC、VSYNC极性配置和OV2710的实际输出不匹配。OV2710出厂默认的PCLK是上升沿输出数据HSYNC和VSYNC默认高有效。我在代码里配成了PCLK上升沿采样、VSYNC低有效、HSYNC低有效是因为我在寄存器里改了输出极性。如果你用的模组和初始化序列不同实际输出极性可能不一样最直接的排障办法是把DCMI的四个极性项挨个翻转测试图像正常的那组就是对的。全黑图像还有一个常见原因是DCMI的ExtendedDataMode配错了。OV2710输出10位数据但D0-D7接了高8位时DCMI不能直接用10位模式去读否则每一行数据会错位。8位模式接D0-D7是最简单的方案先把图像调出来再考虑更高位宽。4.3 颜色不对、偏紫、偏绿怎么办颜色偏色通常是像素格式配置和DCMI数据宽度不匹配导致的。OV2710输出RGB565时每个像素分两个字节输出如果DCMI用8位模式采集得到的就是完整的RGB565但如果配置成YUV422输出却按RGB565解释图像会明显偏色或者出现奇怪的彩色条纹。建议直接锁定一个输出格式来调试。初期用RGB565最直观看到的颜色和真实场景最接近。确认图像结构正常后再尝试切换到YUV422做进一步图像处理。帧率不稳的问题则和PCLK分频有关。OV2710的0x4601寄存器控制PCLK分频值越大输出帧率越低。调试时可以把帧率降到5fps左右方便观察图像等图像稳定后再逐步提速。4.4 一个一直让我头疼的问题DMA中断进不去这个坑我在调试时卡了快半天。DMA初始化顺序不对或者忘记使能DCMI时钟会导致DMA一直没有数据传输中断自然进不去。DCMI和DMA2的时钟都要先打开且DMA2_Stream1的通道号必须配成DMA_Channel_1。另外DMA双缓冲模式下有两个内存地址要配对如果第二个缓冲区地址没有正确传入DMA_DoubleBufferModeConfig也会导致传输错过中断。每次修改缓冲数组后最好把DMA失能再重新配置一次。写在最后的几点体会OV2710这颗传感器的驱动难度其实不在DCMI外设而在对传感器的时钟、极性和数据格式的理解。只要把这三件事搞明白了图像就出来了搞不明白就会在花屏和黑屏之间反复折腾。我个人在实际调试中的建议是第一永远从小分辨率开始调通QCIF能出图后再逐步增加分辨率第二学会用串口传BMP到电脑上观察完整图像这比盯着LCD局部判断问题高效太多第三所有极性相关的配置都做成宏定义方便反复切换测试别在代码里硬编码。如果做1080p实时采集建议直接上带SDRAM的F429平台DCMI配置思路完全一样只是目标地址换到外部内存数据量大了以后要注意DMA带宽和SDRAM刷新冲突。最后再分享一个小技巧OV2710的软复位后不要马上写寄存器等50ms以上再操作很多“莫名其妙”的初始化失败都是时序太着急导致的。驱动摄像头这件事慢一点反而更快。本文还有配套的精品资源点击获取
返回列表