☰
STM32+OV7670+HC-SR04:低成本超声波触发式图像采集系统设计与实现
2026/11/1 8:32:46 网站建设 项目流程

简介:面向嵌入式学习者的超声波监视器综合项目资源,基于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太小——一张QVGA(320×240)的RGB565图像数据大约需要150KB,内存根本存不下。所以实际工程里,要么用带FIFO缓冲的OV7670模块,要么用DMA直接往串口或SD卡搬运数据。我后面详细讲我用的方案。

HC-SR04没什么悬念,2cm到400cm的测量范围、3mm标称精度、十几块钱的价格,室内环境绰绰有余。唯一需要上心的是回波时间的精确测量,这个后面专门讲。

2. 硬件连接与初始化配置

2.1 硬件清单与接线方案

我实际用的物料清单:

  • STM32F103C8T6最小系统板
  • OV7670摄像头模块(带AL422B FIFO版本)
  • HC-SR04超声波模块
  • 3.3V稳压模块(AMS1117)
  • SD卡模块(用于存储抓拍图片)
  • 若干杜邦线、面包板或洞洞板

引脚分配是我的一个参考方案,你可以按自己板子调整:

外设信号STM32引脚
OV7670SIOCPB10
OV7670SIODPB11
OV7670VSYNCPB0
OV7670HREFPB1
OV7670PCLKPB2
OV7670XCLKPA8(TIM1_CH1输出)
OV7670D0~D7PC0~PC7
HC-SR04TrigPA0
HC-SR04EchoPA1(TIM2_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都读不出来,后面全是白搭。

我整理了一份基础配置流程:

  1. 发送软件复位,操作寄存器COM7(0x12)写入0x80
  2. 延时30ms以上,等传感器稳定
  3. 配置输出格式:COM7写入0x04,选择RGB565输出
  4. 配置COM15(0x40)为RGB565,并设置数据范围
  5. 设置QVGA分辨率相关寄存器,开启缩放
  6. 配置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官方例程,修改了曝光值”,这样后期调试或换分辨率时能快速定位需要改的地方。

我个人操作后最大的体会是:这类“多传感器融合”项目真正难的不是单个模块的驱动,而是模块之间的节奏协调和稳定性容错。超声波检测距离只需要几十毫秒,但图像采集和存储可能要几百毫秒,如果不在状态机层面做合理的调度,要么测距被阻塞导致漏触发,要么图像抓了一半就被打断。建议你也先从“状态机+超时保护”的框架入手,再逐步加功能,这个思路相比上来就硬凑代码会有用得多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询