STM32+OV7670+HC-SR04:低成本超声波触发式图像采集系统设计与实现 📅 发布时间:2026/9/8 22:45:44 👁 浏览次数: 简介面向嵌入式学习者的超声波监视器综合项目资源基于STM32平台整合HC-SR04超声测距与OV7670摄像头图像采集实现“物体靠近自动触发拍照、FATFS文件系统存储与查看”的小型监控功能。资源共312个文件压缩包8.25MB主要包含大量C/H源码、Keil工程文件uvprojx/uvoptx、编译生成的axf/hex烧录文件、map/lst调试信息以及说明文档和图片素材结构完整可支撑从工程编译、下载调试到实物运行的全流程。包内既有stm32f10x标准库与FATFS源码也有cc936等编码转换模块代码层次分明适合有一定单片机基础、希望动手实践图像采集与超声联动方案的读者参考。从超声触发、图像采集到文件存储形成完整链路可作为课程设计、毕业设计或简易安防装置的参考原型。目前已有1429人学习下载对于想快速搭建类似监测装置或学习OV7670初始化、FATFS移植及多模块协同的开发者这是一份相当实用的实例资料。1. 项目整体设计与思路拆解1.1 为什么是视觉加超声波的组合这个项目最初的灵感其实很朴素我想做一个能“看见”异常、还能主动记录的嵌入式监控设备但真用STM32去跑连续视频流分析性能完全扛不住。OV7670虽然能输出图像数据可 “看懂图像”这件事本身是重计算任务车牌识别、人脸检测这类算法在单片机上直接硬算基本不现实。HC-SR04在这儿充当的角色是“触发开关”。超声波模块负责持续测量前方距离一旦有物体闯入设定阈值范围STM32立刻让OV7670完成一次抓拍把图像通过串口或SD卡保存下来。这个思路的核心价值在于把“一直盯着画面看”变成“有人来了才看一眼”——既省算力又省功耗还大幅降低了图像处理和传输的压力。这种“低成本传感触发视觉确认”的组合在工程上其实很常见。你可以把它理解为超声波是门卫负责判断“有人来了吗”摄像头是值班室里的屏幕只在门卫按铃时才亮起来。相比纯视觉方案动辄需要树莓派或NPU这套系统用几十块钱的硬件就能实现对异常靠近事件的记录非常适合做智能门禁、车位监控、走廊安防等场景的雏形。1.2 方案选型背后的考量先说说为什么选OV7670而不是OV2640或者其他更高像素的摄像头。核心原因有三个便宜、资料多、驱动开发难度可控。OV7670是30万像素VGA分辨率对“有人靠近时抓拍一张清晰照片”这种需求完全够用关键是它的寄存器手册和参考代码在中文社区里非常多遇到问题基本都能搜到这对新手来说比多几十万像素实在得多。主控选择STM32F103C8T6看重的是性价比和外设配置。72MHz主频、20KB RAM、丰富定时器和DMA通道可问题在于RAM太小——一张QVGA320×240的RGB565图像数据大约需要150KB内存根本存不下。所以实际工程里要么用带FIFO缓冲的OV7670模块要么用DMA直接往串口或SD卡搬运数据。我后面详细讲我用的方案。HC-SR04没什么悬念2cm到400cm的测量范围、3mm标称精度、十几块钱的价格室内环境绰绰有余。唯一需要上心的是回波时间的精确测量这个后面专门讲。2. 硬件连接与初始化配置2.1 硬件清单与接线方案我实际用的物料清单STM32F103C8T6最小系统板OV7670摄像头模块带AL422B FIFO版本HC-SR04超声波模块3.3V稳压模块AMS1117SD卡模块用于存储抓拍图片若干杜邦线、面包板或洞洞板引脚分配是我的一个参考方案你可以按自己板子调整外设信号STM32引脚OV7670SIOCPB10OV7670SIODPB11OV7670VSYNCPB0OV7670HREFPB1OV7670PCLKPB2OV7670XCLKPA8TIM1_CH1输出OV7670D0~D7PC0~PC7HC-SR04TrigPA0HC-SR04EchoPA1TIM2_CH2输入捕获这里特别提醒两件事一是XCLK不能随便接普通IO口OV7670需要8~24MHz的时钟输入最容易的方式是用STM32定时器的PWM输出给它提供时钟我用的TIM1产生12MHz二是HC-SR04的Echo引脚输出的是5V电平最好加一个分压电阻再进STM32的PA1直接怼上去长期用有烧引脚的风险。2.2 OV7670的SCCB配置与关键寄存器很多人卡在摄像头出不了图上十有八九是SCCB初始化时序不对。SCCB协议本质上是I2C协议的变体但OV7670对时序比较敏感。我的调试建议是先把读ID的函数写出来如果IO模拟没问题能从0x42地址读出0x7673或0x7677说明通信链路通了再继续配置寄存器。如果ID都读不出来后面全是白搭。我整理了一份基础配置流程发送软件复位操作寄存器COM70x12写入0x80延时30ms以上等传感器稳定配置输出格式COM7写入0x04选择RGB565输出配置COM150x40为RGB565并设置数据范围设置QVGA分辨率相关寄存器开启缩放配置PCLK分频、HREF/VSYNC极性等时序参数注意OV7670的寄存器配置非常挑剔很多寄存器之间存在相互依赖。比如缩放相关的寄存器没配对输出图像就会出现花屏或错位。建议先跑通一套网上成熟寄存配置网上很多工程都带一份完整寄存器数组再按需微调不要上来就自己改。2.3 HC-SR04的时序与中断捕获HC-SR04的测距原理相当直接Trig脚拉高至少10μs模块内部发射8个40kHz超声波脉冲然后Echo脚输出一个高电平高电平持续时间就是声波往返的时间。距离 时间 × 声速 ÷ 2声速按340m/s算简化后公式是距离(cm)时间(μs)/58。实际工程里用定时器输入捕获中断测Echo高电平时间比轮询GPIO要可靠得多。轮询容易漏掉短脉冲尤其在主循环被图像处理阻塞的时候。我用的方案是TIM2_CH2配输入捕获同时开启捕获中断和更新中断捕获中断记录上升沿和下降沿的计数差更新中断用来做超时保护防止长时间没回波导致程序卡死。实测下来HC-SR04的测量结果会有±2cm左右的抖动这个精度做“是否有人靠近”的判定完全够用但如果你要做精确测距建议连续测量5次取中值滤波而不是简单平均——平均值会被极端值拉偏中值更稳。3. 核心代码实现与逻辑融合3.1 系统框架与状态机设计整个系统的逻辑设计成状态机会比裸奔主循环清晰得多。我大致分了四个状态空闲监测、超声触发、图像采集、数据处理。状态流转是这样的默认停在空闲监测每200ms触发一次超声波测距如果连续3次测距都小于设定阈值比如50cm系统进入触发状态再去驱动OV7670抓拍。为什么非要连续3次才触发这是为了去抖——超声波模块偶尔会有零星误报单次触发就抓拍一天能存几百张废图。图像采集完成后系统把数据写入SD卡然后回到空闲状态。整个流程里超声波测距用的中断回调只负责更新距离标志位不直接在中断里做复杂逻辑这样能最大程度保证系统稳定。3.2 超声波测距与去抖逻辑的代码实现初始化部分我用的是标准库操作代码很直观// TIM2输入捕获初始化 void HC_SR04_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_ICInitTypeDef TIM_ICInitStructure; // 时钟使能 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // PA1配置为浮空输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); // TIM2_CH2输入捕获上升沿触发 TIM_ICInitStructure.TIM_Channel TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 0x0F; // 输入滤波防毛刺 TIM_ICInit(TIM2, TIM_ICInitStructure); TIM_ITConfig(TIM2, TIM_IT_CC2 | TIM_IT_Update, ENABLE); TIM_Cmd(TIM2, ENABLE); }测距主函数配合中值滤波和去抖计数uint16_t distance_cm 0; uint8_t trigger_count 0; void Distance_TriggerCheck(void) { static uint16_t dist_samples[5]; static uint8_t sample_index 0; uint16_t median_dist; // 触发一次测距 GPIO_SetBits(GPIOA, GPIO_Pin_0); delay_us(15); GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 等待中断回调更新 distance_raw if (distance_raw ! 0) { dist_samples[sample_index % 5] distance_raw; distance_raw 0; } // 5个样本后取中值 if (sample_index 5) { median_dist get_median(dist_samples, 5); distance_cm median_dist / 58; // 时间转距离 sample_index 0; // 连续三次低于阈值才触发 if (distance_cm TRIGGER_DISTANCE_CM) { trigger_count; if (trigger_count 3) { trigger_count 0; Capture_And_Save(); // 执行抓拍 } } else { trigger_count 0; } } }3.3 图像采集与联动逻辑OV7670带FIFO的模块读取逻辑会简单很多。摄像头模块自己把一帧图像写入AL422B缓冲STM32在收到VSYNC中断后再从FIFO里搬运数据。关键代码如下// VSYNC下降沿中断表示一帧开始 void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) ! RESET) { frame_ready 1; // 置帧标志主循环里处理 EXTI_ClearITPendingBit(EXTI_Line0); } } void Capture_And_Save(void) { if (frame_ready) { FIFO_Set_Read_Pointer(); // 用DMA从FIFO读取QVGA图像数据到SD卡缓冲区 for (int i 0; i 320 * 240 * 2; i DMA_BUF_SIZE) { DMA_Read_FIFO(sd_buffer, DMA_BUF_SIZE); SD_Write_Block(sd_buffer, sector, 1); } frame_ready 0; } }这里有个非常关键的经验DMA读取FIFO的速度不能太快。OV7670的PCLK输出是像素时钟直接从FIFO读数据是并行8位而DMA搬运到SD卡还要过SPI或SDIO如果DMA速度超过FIFO写入速度会读到空数据图像就会出条纹。我的做法是每读完一块数据就让DMA暂停几个时钟周期实测下来能稳定出图。图像保存我直接写的是BMP格式头部加个54字节的BMP文件头把RGB565转成RGB888写入这样就可以在电脑上直接打开查看。如果你需要后续做车牌识别或人脸检测这个BMP文件就是输入源。4. 踩坑实录与排查技巧4.1 典型问题速查表现象可能原因解决方法SCCB读不到摄像头ID上拉电阻缺失、时序不标准、地址错误检查SIOC/SIOD上拉4.7kΩ用IO模拟时序确认地址是0x42图像全黑或全白曝光寄存器异常、HREF/Vsync极性反了检查COM7、COM15配置用示波器看PCLK信号图像有条纹/错位PCLK分频不对、DMA读取速度过快降低XCLK到12MHz调整DMA暂停时序超声波读数跳变剧烈Echo引脚干扰、声波多次反射确认电压匹配用中值滤波调整传感器朝向避免斜射程序卡死无响应等待VSYNC或Echo高电平超时给所有等待循环加超时退出机制SD卡写入失败SPI速率过快、供电不足降SPI到2MHz以下加220μF电容稳定供电4.2 调试心得与避坑技巧讲几个我觉得特别值得分享的经验。第一先单独调通每个模块再整合系统。这句话听起来像废话但实际执行不到位会非常痛苦。我做这个项目时先把OV7670单独接到屏上验证出图像再单独写超声波测距程序用串口打印数据最后才合并功能。如果你一上来就全接好出了问题根本不知道是摄像头的问题还是超声波的问题。第二调试信息一定要打全。在早期阶段我用串口把每次超声波测量的原始时间、滤波后距离、触发状态全部打印出来在调试阶段帮了大忙。尤其当系统在“无明显规律的时间点”触发抓拍时通过串口日志能很快定位是哪一个环节的判断逻辑出了问题。第三电源是整个项目最容易忽略的隐藏杀手。STM32、OV7670、HC-SR04、SD卡同时工作时电流峰值可以到200mA以上。我用USB供电时图像偶尔会出现横条纹排查了很久才发现是电源纹波太大后来在电源入口加了一个220μF电解电容加一个0.1μF陶瓷电容问题立刻消失。嵌入式项目里很多“玄学bug”最后都指向供电。第四学习江科大的风格把寄存器配置封装成带注释的数组。OV7670整套寄存器配下来有上百行如果不加注释过两个月回来看代码跟看天书没区别。我的做法是每段寄存器配置都写明作用和出处比如“来自XVGA_RGB565官方例程修改了曝光值”这样后期调试或换分辨率时能快速定位需要改的地方。我个人操作后最大的体会是这类“多传感器融合”项目真正难的不是单个模块的驱动而是模块之间的节奏协调和稳定性容错。超声波检测距离只需要几十毫秒但图像采集和存储可能要几百毫秒如果不在状态机层面做合理的调度要么测距被阻塞导致漏触发要么图像抓了一半就被打断。建议你也先从“状态机超时保护”的框架入手再逐步加功能这个思路相比上来就硬凑代码会有用得多。本文还有配套的精品资源点击获取