ARTICLE DETAIL

资讯详情

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

STM32F103驱动OV7670:从FIFO缓冲到图像稳定显示的完整指南

STM32F103驱动OV7670:从FIFO缓冲到图像稳定显示的完整指南 简介STM32F103驱动OV7670摄像头实时显示到OLED的完整工程资料包面向嵌入式入门开发者与物联网项目设计者解决摄像头采集、图像传输与屏幕显示之间的联调问题。压缩包共101个文件约7MB主体包含20个.h头文件与17个.c源文件core_cm3、usmart、oled、sys等标准库与驱动模块一应俱全另有.o、.d、.crf等编译中间文件、Keil工程文件uvprojx以及可直接烧录的hex固件整体是结构清晰的MDK工程。这份资源已有13966人学习/下载在同类STM32摄像头显示项目中具有较高参考热度。工程内预置OV7670寄存器配置、DMA采集、图像缩放及色彩转换逻辑同时配备ILI93xx与OLED驱动源码和usmart串口调试组件可帮助读者快速打通摄像头数据链路缩短在Cortex-M3平台上的移植与调试周期也便于后续扩展无线传输或图像处理功能。 OV7670这颗传感器放在今天来看确实有点老但它依然是很多人进入单片机图像采集的第一颗摄像头。原因很直接便宜、资料多、接口是并口不需要复杂协议。可现实是很多人在F103上点它插上电要么黑屏要么满屏雪花折腾几天就放弃了。这篇文章把这套东西从方案选型到硬件接线、从SCCB配置到图像搬运的完整流程梳理一遍包括一些不踩一遍根本发现不了的坑。做通了之后你就有一个稳定的图像输入源后面做二值化、轮廓识别、甚至简单的帧差检测都会顺手很多。1. 起步前必须想清楚的事用FIFO还是直连GPIO1.1 F103为何不能直接接了就显示STM32F103这颗单片机没有DVP或者DCMI这种专门接摄像头并口的硬件外设只有一组一组通用GPIO。OV7670的输出数据线有8根像素时钟PCLK在VGA模式下能跑到24MHz左右就算降到QVGA也常有12MHzF103主频72MHz理论上能跟上但CPU几乎要被占满而且图像数据的连续性要求很高稍微遇到中断抖动整行像素就错位了。更大的问题是内存。QVGA分辨率320x240RGB565格式下一帧数据是320乘以240再乘以2约150KB。STM32F103C8T6标配只有20KB RAM完整保存一帧图像根本没戏只能边读边处理。所以在没有专用接口和足够内存的情况下靠GPIO硬啃OV7670并不是不行但对新手来说一个中断延迟就能让画面裂开调试成本极高。我最初也试过直接用GPIO抢PCLK去买数据结果满屏的横向错位条纹改了十几次中断优先级还是不稳。后来换了FIFO缓冲方案画面立刻稳定了CPU占用也降下来了。这是整个项目里最值得做的决定。1.2 AL422B缓冲芯片为什么能救场AL422B是一颗384KB的异步FIFO芯片相当于一个先入先出的数据仓库。OV7670只管把像素数据往里面写F103有空的时候再从里面读出来两边读写完全独立频率不匹配、突发时序波动都能被缓冲掉。QVGA一帧150KB384KB能存两帧还多所以不会出现写到一半被覆盖的问题。具体到硬件上OV7670的PCLK接AL422B的写时钟WCKHREF接写使能WEVSYNC到来后把写指针复位然后每个PCLK周期写进一个字节。F103这一侧通过两个引脚控制读时钟RCK和读复位RRST就能把数据一行一行拖走。市面上很多带FIFO的OV7670模块比如常见的OV7670带FIFO摄像头模块内部已经把这些信号接好了外部只留出RCK、RRST、VSYNC、SCCB和D0-D7用起来省心很多。买模块的时候要看清型号裸的OV7670模块引脚是PCLK、HREF、VSYNC、D0-D7这种需要自己外接AL422B带FIFO的模块引脚是FIFO_RCK、FIFO_RRST、VSYNC这种。新手优先选带FIFO的版本省掉一大堆时序调试。2. 硬件信号链路每一根线都在干什么2.1 完整接线表和信号流向理解OV7670的工作方式可以先把它想象成一个扫描设备感光阵列逐行曝光读出转成数字信号在PCLK的节拍下从D0-D7输出同时用HREF表示当前像素是否处于有效行内用VSYNC表示一帧图像是否结束。这三个信号是同步图像的骨架。我用带FIFO模块做主线F103最小系统板作为主控LCD用ILI9341串口屏做显示。接线如下信号OV7670模块引脚F103引脚说明电源3.3VVCC3.3V供电要求稳定见下文地GNDGND与F103共地SCCB时钟SIOCPB6I2C1时钟开漏加上拉SCCB数据SIODPB7I2C1数据开漏加上拉时钟输入XCLKPA8定时器PWM输出24MHz帧同步VSYNCPA0外部中断输入FIFO读时钟FIFO_RCKPA1定时器/GPIO控制FIFO读复位FIFO_RRSTPA2每帧开始时复位读指针FIFO数据D0-D7PB0-PB78位像素数据输入如果是无FIFO的裸模块需要把OV7670的PCLK接到AL422B的WCKHREF接到WEVSYNC接到F103的PA0同时AL422B的D0-D7输出再接F103的PB0-PB7。原理一样只是需要自己把这些线连起来。2.2 电源和时钟这两个坑十个人有五个中招OV7670对供电很敏感。它的工作电流大约10mA左右看起来不大但像素时钟切换瞬间会产生电流尖峰。如果摄像头和F103共用一个LDO并且LDO余量不足图像就会出现波纹状的明暗条纹甚至直接黑屏。我实验时单独用一片AMS1117-3.3给摄像头供电同时VCC和GND之间并联一个10uF电解电容和一个0.1uF瓷片电容问题立刻改善。如果你的板子上有电机、舵机这类负载不要把摄像头和它们共用电源。XCLK时钟是另一个大坑。OV7670需要一个外部时钟才能工作工业上常用24MHz有源晶振但在F103上很多人图省事直接用内部RC振荡器或者软件翻转GPIO来凑。内部RC振荡器的频率误差可以达到2%以上对SCCB配置影响不大但会让图像出现不规则的左右偏移颜色也会发飘。正确的做法是用定时器PWM输出一个稳定的24MHz时钟。我的配置是TIM1的PSC设为0ARR设为2CCR设为172MHz除以3等于24MHz占空比正好50%。PA8复用为TIM1_CH1输出后接XCLK即可。还要注意SCCB的SIOC和SIOD是开漏结构模块内部一般已经有上拉电阻但如果你买的模块没有就需要在F103的PB6和PB7外部各加一个4.7k上拉电阻到3.3V否则SCCB通信会时好时坏。3. 摄像头初始化SCCB时序与关键寄存器3.1 SCCB是I2C的亲戚但别完全按I2C来OV7670的所有配置都是通过SCCB总线写入寄存器实现的。SCCB的物理层几乎和I2C一样时钟线SIOC、数据线SIOD起始条件、停止条件、应答位都相同所以很多STM32工程直接拿I2C外设去写。区别在于SCCB读操作的格式先发送设备写地址加寄存器地址产生一个停止条件然后再发送设备读地址连续读取出数据。标准I2C外设处理这种写寄存器地址后重新发起始的方式有时会多出一些组合所以更稳妥的是用GPIO模拟时序。设备地址方面OV7670的7位地址是0x21左移一位后写地址是0x42读地址是0x43。网上有些代码会写成0x21或0x42混用其实就是这个转换关系。用GPIO模拟时写一个字节的流程是拉低SIOC、稳定后移出最高位、拉高SIOC、保持一小段时间、再拉低。这一小段时间至少几百纳秒我一般用几个空循环来延迟。一段最简单的SCCB写寄存器函数大概是这样的void SCCB_Start(void) { SIOD_H; SIOC_H; delay_us(2); SIOD_L; delay_us(2); SIOC_L; } uint8_t SCCB_WriteByte(uint8_t dat) { uint8_t i; for (i 0; i 8; i) { if (dat 0x80) SIOD_H; else SIOD_L; dat 1; SIOC_H; delay_us(1); SIOC_L; delay_us(1); } SIOD_H; // 释放数据线准备接收应答 SIOC_H; delay_us(1); // 读取SIOD电平低电平表示正确应答 SIOC_L; delay_us(1); return 0; }这里最关键的是时序延迟太快会导致寄存器写入失败。实测在72MHz主频下用三个空循环做delay_us的辅助基本能稳定跑通。如果初始化之后图像颜色怪异或者寄存器读写返回值全是0xFF优先怀疑SCCB时序太快。3.2 让OV7670输出QVGA RGB565的关键寄存器配置网上能找到很多OV7670初始化数组但含义不搞清楚出了问题根本不知道从哪下手。按重要性来看这几个寄存器必须理解COM7地址0x12最高位0x80是软复位低三位决定输出格式和分辨率。设置成QVGA加RGB输出时需要把bit2置1QVGA并选择RGB格式。COM15地址0x40设置RGB输出位宽。默认可能是RGB444要改成RGB565这里不配对颜色会错得离谱。CLKRC地址0x11内部PLL分频配置。输入XCLK是24MHz时通过分频让PCLK落在合适区间QVGA下一般配到12MHz左右比较稳。缩放寄存器组比如0x3A、0x3B、0x3C、0x3D把VGA的原始窗口缩小到QVGA如果只改COM7而不配缩放输出很可能还是VGA尺寸帧中断和显示宽度全乱。HSTART/HSTOP、VSTRT/VSTOP地址0x17到0x1A图像窗口边界。大多数模组用默认值能出画但部分批次需要微调否则画面偏左或者偏右。我当前工程用的初始化片段长这样先复位再设格式然后配缩放ov7670_write_reg(0x12, 0x80); // COM7: 软复位 HAL_Delay(50); ov7670_write_reg(0x12, 0x04); // COM7: QVGA, RGB ov7670_write_reg(0x40, 0xD0); // COM15: RGB565 ov7670_write_reg(0x1E, 0x07); // MVFP: 镜像 ov7670_write_reg(0x3A, 0x04); // 水平缩放 ov7670_write_reg(0x3B, 0x00); // 垂直缩放 ov7670_write_reg(0x3C, 0x0C); // 缩放控制 ov7670_write_reg(0x3D, 0x84); // 缩放控制请注意不同来源的模组同一寄存器地址的值可能有差异尤其是缩放和窗口寄存器。我的经验是先去找到一份和你手里的模组同型号的工程用它的数组跑通再去根据实际画面微调。如果是市面上常见的OV7670 带FIFO 模块这个配置一般能直接点亮。4. 从FIFO到LCD数据搬运的核心逻辑4.1 帧同步每次读取都要重新对齐图像数据是流式进来的F103必须知道什么时候是一帧的开始。硬件上OV7670在每一帧开始时会产生一个VSYNC脉冲F103就把这个引脚配置成外部中断下降沿时进入中断服务函数。在中断里做两件事给FIFO的读指针做一次复位然后把一个标志位置1通知主循环可以开始读数据了。这里有个细节FIFO的读指针复位时序不能太莽。AL422B的RRST引脚需要先拉低一段时间再拉高才能把读指针归零。如果复位脉冲太短读指针没有完全归零图像就会从中间某个位置开始读表现为画面内容整体错位。调试中看到画面不是从左上角开始先检查这里的延时。读数据采用边读边刷的方式因为F103的RAM装不下整帧150KB但装下一行640字节绰绰有余。每次读一行直接把这一行写入LCD的GRAM地址区间然后继续下一行。这样只需要一个640字节的buffer显示流程如下void display_frame(void) { if (!frame_ready) return; frame_ready 0; fifo_reset_read_ptr(); // RRST拉低10us再拉高 for (uint16_t line 0; line 240; line) { read_line_from_fifo(rgb_buf, 320); // 从PB口读640字节 lcd_set_window(0, line, 319, line); // 设置LCD显示窗口 lcd_write_data_bulk(rgb_buf, 320); // 向LCD写320个像素 } }4.2 LCD刷屏的带宽瓶颈和优化方向很多人忽略了一个问题F103从FIFO读数据的速度和LCD的刷屏速度决定了最终帧率。如果显示用的是SPI接口的LCDSPI时钟一般工作在20MHz左右一帧RGB565数据150KB光传输就要约60ms加上等待和窗口设置实际帧率大概在5到8fps。这个帧率看静态图像没问题但连续画面会有明显卡顿。想提高帧率有两个方向。第一个是改用FSMC驱动的并口LCD8080接口的数据总线可以做到接近读写内存的速度一帧图像传输时间能压到10ms以内配合DMA搬运可以实现15到20fps。第二个是降低分辨率把显示窗口切到QQVGA 160x120数据量一下降成原来的四分之一帧率自然就上去了。我用串口屏做完第一版后果断换成FSMC并口屏。虽然接线变多但画面连贯性完全不是一个体验。做这个项目如果预算允许推荐直接买一块带FSMC接口的3.5寸或2.8寸并口屏省得二次折腾。5. 实测中翻车最多的排查点5.1 画面花屏不是摄像头坏了先查这四处花屏是最常见的故障表现形式五花八门满屏雪花、横向切片、规则条纹错位。我第一次遇到时以为是摄像头坏了换了模块还是没用最后发现是FIFO的读指针复位延时不够。排查花屏的路径我习惯这样走第一确认XCLK有没有输出。用示波器量PA8或者XCLK引脚没有24MHz方波就查定时器配置和引脚复用。第二检查PCLK信号线长度。用杜邦线直连时PCLK这根线超过10cm就很容易引入干扰导致数据读取错乱尝试缩短线距或者用屏蔽线。第三看FIFO读复位脉冲是否满足要求。RRST拉低时间至少需要10微秒太短读指针复位不彻底。第四怀疑DMA搬运配置。如果用了DMA把PB口数据搬进内存外设地址必须是GPIO IDR的地址内存地址按字节递增半字搬运时两个字节的排列顺序会影响最终颜色但不至于花屏。最后还有一个隐蔽问题OV7670的PCLK和HREF的相对时序在部分模组上要通过寄存器调整。如果你用的是裸模块加外部AL422B图像出现行错位多半是这个原因可以调一下0x3D的PCLK延时设置。5.2 颜色发绿、红蓝互换基本是格式不对颜色异常通常集中在两个原因。一个原因是COM15没有配置成RGB565。如果输出是RGB444或者RGB555但F103按RGB565解析画面整体色调就完全错乱尤其会显得偏绿。另一个原因是像素字节的高低位顺序反了。OV7670在RGB565模式下每个像素的两个字节先输出高字节还是低字节不同模组有差异。交换两个字节后再写入LCD颜色就正常了。如果画面颜色偏暗、偏蓝紫色还要检查自动增益控制和自动白平衡相关寄存器有没有打开。许多OEM模组出厂配置是关闭AWB的输出颜色发冷打开自动白平衡后色调会自然很多。另外供电不足也会导致颜色失真表现为画面有间歇性水波纹。5.3 只有半屏画面问题出在窗口寄存器和扫描方向半屏画面或者画面整体偏移通常是OV7670的HSTART、HSTOP这些窗口寄存器和LCD扫描方向冲突。比如OV7670默认输出窗口是从第12行开始的如果你的LCD面板扫描方向跟它不一致就会出现显示区域起始位置不对。解决方法是先确认摄像头输出窗口。通过寄存器把窗口起点校准到0然后检查LCD的扫描方向。ILI9341这类屏幕通过设置MADCTL寄存器可以调整扫描方向把屏幕横竖方向和摄像头的图像方向匹配起来一般都会要求摄像头画面横向显示屏幕也设置成横向扫描。我遇到过画面左右对称翻转、上下颠倒的情况处理方式就是改LCD的扫描方向和OV7670的MVFP镜像寄存器比重新调整物理安装方向方便得多。5.4 帧率低到没法看别立刻怀疑CPU如果显示出来了但帧率只有一两帧大部分人的第一反应是代码太慢。实际瓶颈多半在串口屏的SPI传输和LCD的写窗口等待上。我用串口屏时即使主循环什么都不干帧率也只有4fps左右换了FSMC并口屏后才到12fps以上。还有一个小技巧把LCD的写数据命令改成连续突发写入模式在GRAM地址连续的情况下不需要每次写像素都重新设置窗口。一次设置好连续写完一行效率能提高30%以上。DMA在这里也能帮上忙从FIFO读一行数据时用DMA数据交到LCD前也通过DMA搬运CPU只在行与行之间做窗口切换帧率提升非常明显。6. 下一步能玩的东西和选型边界6.1 从显示到处理的路线选择图像能稳定显示之后下一步通常是做处理。F103能做的处理有限但二值化、边缘检测这类轻量算法可以跑。常用的做法是把RGB565转成灰度再做阈值分割之后用轮廓搜索算法提取目标区域。注意F103的20KB RAM一帧150KB塞不下所以处理时也用行处理的思路或者先降到QQVGA再处理。如果想做更复杂的颜色识别、Hough变换、深度学习推理F103就不合适了。这时可以换OV2640体积更小、支持JPEG输出或者干脆把主控换成ESP32-S3、树莓派这类带更多内存和更高主频的平台。OV7670的价值在于帮你把图像信号是如何从头到尾流动的这件事彻底搞明白这个认知不换平台都适用。6.2 FreeRTOS下的任务划分和低功耗扩展接上FreeRTOS之后建议把采集显示拆成两个任务一个任务等帧中断负责读FIFO和刷屏另一个任务处理图像或者响应串口指令。两任务之间用二值信号量同步帧中断里只释放信号量不在中断里做耗时操作。这样即使将来增加网络传输或者按键交互也不会影响图像刷新。如果想做低功耗场景可以在画面没有变化时让F103进入停机模式用RTC或者外部按键唤醒。唤醒后再重新初始化LCD和摄像头。OV7670断电后重新上电必须要重新做一遍SCCB寄存器配置不能只恢复时钟否则图像会花。整个采集流程里把初始化寄存器封装成一个独立的函数是低功耗设计里非常实用的习惯。最后分享一个我自己的调试习惯手头至少准备两块同样的OV7670模块和一块AL422B模块花屏时如果嫌示波器麻烦直接换模块是最快的排除手段。我靠这个办法省下过好几个小时的排查时间。图像采集这类项目硬件的变量永远比软件多先把硬件不确定性清零再去调寄存器和DMA就会顺很多。本文还有配套的精品资源点击获取
返回列表