ARTICLE DETAIL

资讯详情

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

STM32F103+OV7725 FIFO摄像头图像采集与串口上位机显示实战

STM32F103+OV7725 FIFO摄像头图像采集与串口上位机显示实战 简介一套基于STM32F103与OV7725摄像头的嵌入式视觉调试方案面向学习STM32驱动开发、图像采集及串口通信的开发者也适合毕业设计或小型视觉项目起步使用。工程覆盖OV7725传感器初始化、SPI/I2C通信配置、图像数据读取与通过UART发送到山外串口调试助手实时显示的完整链路可用于快速验证成像效果并排查通信与解码问题。RAR压缩包共173个文件核心为38个h头文件与36个c源码方便查看寄存器定义和驱动实现另含Keil工程配置uvprojx/uvoptx、编译生成文件hex/axf/map/lst/o/crf及清理脚本bat等整体仅3.16MB结构清晰解压后可直接用Keil打开参考。目前已有682人学习下载说明该工程具备较高参考价值。资料对寄存器配置、串口分包传输、RGB565/JPEG解码及常见丢包、显示异常等调试思路均有实际展示适合需要对照完整工程进行迁移或二次开发的初中级嵌入式开发者。 如果你手头正好有一块OV7725摄像头模块和一块STM32F103开发板想做的第一件事大概率不是“拍出一张能用的照片”而是赶紧在电脑上看到一个稳定的画面。我就是这么过来的当时SPI接口、I2C接口的传感器玩了一圈第一次接触带FIFO的数字摄像头以为接好线、写好SCCB配置就能出图结果在“读ID失败”和“画面花屏”之间反复横跳最后靠山外串口调试助手才把整条链路跑通。这篇内容就复盘我当时用“OV7725_FIFO模块 STM32F103 山外串口调试助手”这套组合从硬件选型、寄存器配置、FIFO读时序到最后上位机显示图像的全过程。适合正在做智能车、图像识别入门或者毕业设计要上OV7725的同学参考。整套方案的代码量不大但每一段时序都值得掰开揉碎讲清楚。1. 方案选型为什么这套组合能省掉DCMI的烦恼1.1 F103没有DCMIFIFO就是那个“中转仓库”STM32F103这颗芯片主频72MHz外设丰富但它没有DCMI数字摄像头接口。DCMI是F4系列才开始标配的硬件接口专门用来对接摄像头传感器输出的DVP并行时序。F103要想直接采集OV7725的PCLK像素时钟、HREF行信号、VSYNC帧信号只能靠GPIO模拟而且速度完全跟不上。打个比方摄像头是一条高速传送带PCLK每来一个脉冲就吐出一个像素。STM32F103则像一台小货车传送带一分钟能送一百件货小货车一分钟只能搬十件中间必须有个仓库暂存。这颗仓库芯片就是模块上常见的AL422B FIFO容量384KB双端口设计写侧跟着摄像头的PCLK自动写入读侧由MCU按自己的节奏读取。这样一来F103不需要时刻盯着像素时钟只需要在一帧图像写完以后找个空闲时间整批搬走。1.2 一颗AL422B能装下多少数据AL422B的384KB容量意味着什么算一笔账就清楚了图像格式单帧大小能缓存帧数VGA灰度 640x480307200字节1帧QVGA灰度 320x24076800字节5帧QVGA RGB565 320x240153600字节2帧QQVGA灰度 160x12019200字节20帧我在项目里用的是QVGA灰度输出一帧76800字节FIFO空间绰绰有余。实际使用中不建议把FIFO塞满好几帧因为读侧如果追不上写侧新帧会把旧帧覆盖画面就花了。更好的做法是靠VSYNC信号控制写使能写完一帧就“关门”等MCU把数据读走再放行下一帧。这也是“FIFO方案”的灵魂所在。1.3 这套方案的现实定位F103OV7725FIFO这套组合帧率瓶颈在串口不在摄像头。传感器本身输出速度很快但F103把图像数据从FIFO搬出来以后无论走串口还是走并口屏传输速度都远低于传感器输出速度。因此这套方案适合的是低速静态场景色块识别、赛道寻迹、条码/二维码识别、简单轮廓检测这些场景对帧率要求不高1~5fps完全够用。如果要做高速目标追踪或者连续视频流监控建议直接换带DCMI的F4平台或者上外部SDRAM缓存大图。2. 硬件接线与SCCB初始化先让传感器“听话”2.1 接线表别让排针定义坑了你OV7725模块的排针定义各家略有差异但核心信号基本一致。接线时重点确认三组SCCB控制线、图像数据线、FIFO控制线。我当时按下面这张表接的功能模块引脚STM32F103引脚备注SCCB时钟SCLPB6软件模拟频率约100kHzSCCB数据SDAPB7双向IO需外部4.7k上拉帧同步VSYNCPA0下降沿触发外部中断行参考HREF可不接模块写FIFO时硬件自动使用像素时钟PCLK可不接模块写FIFO时硬件自动使用读时钟RCLKPD6读FIFO用GPIO翻转读复位RRSTPD5复位读指针输出使能OEPD7置低使能FIFO输出数据线D0~D7PC0~PC78位像素数据电源VCC3.3V模块自带稳压则接5V这里有个常见坑OV7725的SCCB接口SDA引脚内部上拉很弱甚至没有上拉必须在模块外部加4.7k电阻到3.3V否则读ID会时好时坏。另外所有信号线尽量共地电源线上加一个104去耦电容能少很多莫名其妙的干扰问题。2.2 SCCB和I2C的差别别踩“少一位停止位”的坑SCCB时序看起来和I2C几乎一样但有个关键差异SCCB每次“传输阶段”结束时必须发出停止条件尤其是读操作中间多了一个“总线释放阶段”。很多教程直接拿I2C的驱动改巴改巴就上结果读出全是0xFF就是因为读操作的第二阶段少发了停止条件。OV7725的SCCB从机地址是0x21换算成8位地址后写地址是0x42读地址是0x43。读寄存器的典型流程是发送起始条件发送写地址0x42等待ACK发送目标寄存器地址等待ACK发送停止条件这一小段停顿不能省再次发送起始条件发送读地址0x43等待ACK读取一个字节数据发送停止条件读ID的验证代码很直接我习惯先把这部分跑通再继续往下写uint8_t SCCB_ReadReg(uint8_t reg) { uint8_t val 0; SCCB_Start(); SCCB_WriteByte(0x42); // 写地址 SCCB_WriteByte(reg); // 寄存器地址 SCCB_Stop(); delay_us(5); SCCB_Start(); SCCB_WriteByte(0x43); // 读地址 val SCCB_ReadByte(); // 读取数据 SCCB_Stop(); return val; } // 读0x0A和0x0BOV7725的PID应为0x77 uint8_t pid SCCB_ReadReg(0x0A); uint8_t ver SCCB_ReadReg(0x0B);上电后务必延时10ms以上再访问SCCB因为OV7725内部需要时间完成上电初始化太早访问会超时。SCL频率建议控制在100kHz左右F103主频72MHz时用几个简单的delay_ns就能满足。2.3 初始化时最关键的几个寄存器OV7725的寄存器表很长但真正决定输出格式的开关就那几个。我在项目里先整表烧录模块厂商提供的初始化数组再单独调整下面几个关键项寄存器作用常用设置0x12输出格式/分辨率主开关设置QVGA还是VGA、RGB还是YUV0x17水平窗口起始配合分辨率设置0x18水平窗口结束配合分辨率设置0x19垂直窗口起始配合分辨率设置0x1A垂直窗口结束配合分辨率设置0x32HREF输出控制行同步相关注意一点网上流传的OV7725初始化数组非常多正点原子的、野火的、山外的、各种开源项目的寄存器值经常互相冲突。最靠谱的做法是直接用你手上模块卖家提供的初始化表或者用山外调试助手配套例程里的ov7725_init.c。如果你只想跑通灰度图像建议先把输出格式切到YUV422或RGB565再配合串口/屏幕去确认画面方向是否正常。我项目的做法是先读一次PID0x77确认传感器活着然后直接加载一份已验证可用的QVGA灰度初始化表。整个初始化里如果某个寄存器写错了最常见的现象不是完全没画面而是图像错行、偏色、上下颠倒。这类问题后面单独讲。3. 从FIFO里把图像搬出来GPIO模拟和FSMC总线两条路3.1 写侧由硬件自动完成MCU只管“关门”模块上的硬件逻辑会把VSYNC、HREF、PCLK组合成FIFO写时钟和写使能MCU其实不需要干预写入过程。但有一件事必须由MCU控制什么时候允许写、什么时候禁止写。否则FIFO会反复被新帧覆盖读出来的画面是花的。我的处理方式是用VSYNC下降沿触发外部中断在中断里做两件事拉高FIFO_WR或直接让写侧暂停把当前这一帧“锁”在FIFO里置一个标志位通知主循环可以开始读FIFO了主循环读到标志位置位后先把这一帧数据读完再拉低FIFO_WR或者释放写使能让下一帧开始写入。这样FIFO里始终保持“一帧完整图像被稳定读取”的状态。3.2 GPIO模拟读先跑通再优化GPIO读FIFO的核心动作是RRST拉低再拉高复位读指针OE拉低使能输出然后每个RCLK脉冲读出一个字节数据。代码骨架大概是这样void FIFO_ResetPtr(void) { RRST_GPIO_LOW(); delay_us(1); RRST_GPIO_HIGH(); delay_us(1); } uint8_t FIFO_ReadByte(void) { RCLK_GPIO_HIGH(); uint8_t data GPIO_ReadInputData(GPIOC) 0xFF; RCLK_GPIO_LOW(); return data; } void FIFO_ReadFrame(uint8_t *buf, uint16_t size) { OE_GPIO_LOW(); FIFO_ResetPtr(); for (uint16_t i 0; i size; i) { buf[i] FIFO_ReadByte(); } OE_GPIO_HIGH(); }这段代码能跑但效率一般。每读一个字节要操作一次RCLK一帧QVGA灰度76800字节大概需要几万个GPIO操作实测读取本身需要几十毫秒。考虑到主循环里还要做图像处理和串口发送帧率自然不会太高。但GPIO模拟是理解时序最快的方式强烈建议先把它跑通。3.3 FSMCDMA把FIFO当作一块“只读SRAM”如果不想让CPU消耗在逐个字节的读取上F103的FSMC接口是个利器前提是你用的芯片型号带FSMC外设比如STM32F103ZET6。C8T6这类小容量芯片没有FSMC只能用GPIO模拟。FSMC的思路是把AL422B当作一片外部SRAM来读。把FIFO的D0~D7接到FSMC数据线OE接到FSMC的NOE读信号片选接到某个NE引脚地址线随便接一根就行。FSMC在读取地址时会自动产生NOE脉冲而AL422B正好需要OE低脉冲来输出数据配合得天衣无缝。最爽的用法是配合DMA把外设地址设成FSMC映射出来的地址内存地址设成图像缓冲区数据宽度8位传输大小76800字节启动一次DMA传输整帧图像自动搬进内存。传输期间CPU完全空闲可以做别的处理。// FSMC接口被映射到0x60000000起始的Bank1地址范围 #define FIFO_BASE_ADDR ((uint32_t)0x60000000) DMA_Configuration(DMA1_Channel6, (uint32_t)FIFO_BASE_ADDR, (uint32_t)image_buffer, 76800); DMA_Cmd(DMA1_Channel6, ENABLE); // DMA传输完成后触发中断此时image_buffer里就是完整一帧FSMCDMA方案的好处不是“从1fps变成10fps”这么简单而是把读取时间从CPU里完全剥离CPU可以去跑图像算法、去发串口数据、去处理其他逻辑。这对后续扩展特别重要。4. 图像数据上行串口协议与山外调试助手的配合4.1 为什么不用屏幕而是用串口上位机单片机直接驱动TFT屏当然可以但调试阶段有太多不方便屏幕小、看不出细节没法随时截图对比平台一变屏幕驱动就废。用串口把图像数据发给上位机电脑上直接看画面还能保存和分析数据效率和体验完全不一样。山外串口调试助手就是智能车圈子里很多人用的上位机支持灰度图、二值图、RGB图像的远程显示也支持波形、PID调试等功能一套工具能干很多事。4.2 图像显示协议别凭空猜按demo来图像从单片机到上位机本质只是“裸数据格式说明”。上位机需要知道三件事图像多宽、多高、每个像素几个字节。山外调试助手的图像显示协议在不同版本里定义有差异我用的这套约定是帧头 水平分辨率 垂直分辨率 像素格式字段 图像数据 校验位。帧头发送一组特征字节上位机识别后进入图像接收模式。我当时参考的是山外助手配套例程里的发送函数大致是这个风格void SendImageGray(uint8_t *img, uint16_t w, uint16_t h) { uint8_t header[7] {0xA5, 0x5A, 0x01, (uint8_t)(w 8), (uint8_t)(w 0xFF), (uint8_t)(h 8), (uint8_t)(h 0xFF)}; UART_Send(header, 7); UART_Send(img, w * h); UART_SendByte(0x5A); // 帧尾 }必须提醒的是不同版本的上位机协议字节格式可能不同最可靠的方法是把助手自带的例程包里的发送代码找出来对着看。如果上位机打开后不显示图像先发一张固定颜色图片测试数据通路而不是反复去改图像采集代码。4.3 分辨率和波特率的匹配帧率到底能到多少图像数据量、波特率、帧率三者直接相关。串口每发送1字节实际需要10bit1起始位8数据位1停止位所以有效吞吐是波特率除以10。下面是几种组合的理论帧率图像格式单帧大小1152004608009216002000000QVGA灰度76800B0.15fps0.6fps1.2fps2.6fpsVGA灰度307200B0.04fps0.15fps0.3fps0.65fpsQVGA二值化9600B1.2fps4.8fps9.6fps20.8fps这个表格是用“单帧字节数 ÷ 有效波特率”直接算出来的理论值没算协议帧头和校验的开销实际帧率会更低一点。一个很直观的结论QVGA灰度在921600波特率下勉强能到1fps左右基本能“看到画面在动”但谈不上流畅。想要实时显示要么把分辨率降到QQVGA要么把图像二值化后再传数据量直接缩小到原来的八分之一。山外助手的二值化图像显示就是这么用的每1bit代表一个像素一帧9600字节921600下能跑到接近10fps流畅度完全够智能车调试用。5. 联调中最容易踩的坑5.1 读不到摄像头ID先别怀疑传感器坏了读ID失败是这套方案里出现概率最高的问题。我的排查顺序很固定先量供电OV7725内核电压是1.8V如果模块上没有自带稳压强行接5V供电可能直接烧掉再确认SDA上拉电阻这个最容易被忽略接着用逻辑分析仪抓SCCB时序看起始条件、停止条件、ACK位是否正常最后把SCL频率往低压有些模块SDA翻转速度跟不上也会导致读ID失败。另外一个细节STM32的GPIO在读SDA时要从输出模式切到输入模式很多移植的I2C代码在这里处理得不对。用开漏输出外部上拉可以规避这个问题GPIO配置成开漏模式后写0就是拉低写1就是释放总线读取时直接读IDR寄存器不用来回切换模式。5.2 画面花屏、错行、斜条纹三种现象三种原因花屏通常是FIFO写侧没有正常关闭读到一半被新帧覆盖。检查VSYNC中断里的写使能控制逻辑确保“写完一帧再读”的时序严格成立。错行和左右错位多半是窗口配置问题。OV7725的HREF和窗口起始寄存器配错了图像就会整体平移或者在行首出现一条杂色边。这时候用一个纯色物体对准摄像头如果画面里颜色分布整体偏移基本可以确认是窗口配置问题回初始化表里检查0x17、0x18、0x32这些寄存器。斜条纹或规律性噪点则相反问题往往在采集侧。RCLK频率太高DOUT数据还没稳定就被读走了就会出现非常规律的斜纹。把RCLK翻转速度降下来试试或者确认D0~D7接线顺序是不是反了。我之前遇到过模块的D0~D7排针顺序和PCB丝印不一致的情况用万用表逐个确认才解决。5.3 串口丢包与画面撕裂上位机显示画面被撕成两半通常是MCU一边串口发送FIFO里的图像数据一边FIFO又写入了新帧数据两者重叠导致拼接错乱。解决办法是给图像传输加“双缓冲”机制DMA把新一帧图像搬到缓冲B的同时串口正在发送缓冲A的旧数据两边互不干扰。或者更简单一点发送期间暂时关掉FIFO写使能宁可少刷几帧也要保证画面完整。USB转TTL模块的选择也很重要。大数据量连续传输时劣质PL2303会频繁掉包CH340和CP2102表现好很多。另外强烈建议在帧协议里加一个帧序号上位机每次收帧后检查序号是否连续不连续就说明发生了丢包而不是靠肉眼去猜。5.4 给新手的调试顺序这个项目千万不能“一把梭”。我建议按三个独立阶段推进每个阶段都有明确的通过标准第一阶段SCCB读ID成功PID读到0x77。这一步通过说明传感器供电、时钟、通信全正常第二阶段能读到FIFO数据并且数据会随镜头画面变化。这一步可以通过观察数组数值或者串口发送原始数据来验证第三阶段上位机能正确显示图像。这一步通了整条链路就闭环了我自己实测下来第二阶段最容易卡住因为FIFO读时序看不见摸不着只能靠数据特征判断。一个很有效的办法是在镜头前放一张纯白纸再盖住镜头对比两次读出的数据是否有明显差异。如果差异幅度很小说明FIFO读时序可能根本没工作。调试时我习惯用山外助手的波形显示功能同步看一行的灰度值比如第120行的320个像素点配合图像显示窗口一起看。图像窗口看整体波形窗口看细节两个角度互相印证定位问题快很多。最后再分享一个实际体会整套代码跑通以后我给固件打了个“V1.0”的标签——不是因为它多完善而是因为它能稳定复现了。OV7725_FIFO模块做图像采集真正的价值在于帮你理解“传感器时序、数据缓存、传输协议”这条完整链路这些东西搞明白了后面换F4平台、换更高分辨率的传感器都是顺理成章的事。如果后续有空闲我打算把图像处理部分也搬上来比如在F103上做一版跑得动的灰度二值化数据量降下来以后帧率还能再往上提到时候继续和大家分享。本文还有配套的精品资源点击获取
返回列表