STM32+HC-SR04+OV7670实现超声波触发拍照系统
2026/9/9 0:14:23 网站建设 项目流程

简介:一套基于STM32的超声波监视器完整工程源码,面向嵌入式开发与STM32学习者,适用于智能安防、实验室监控等场景。项目将HC-SR04测距与OV7670图像采集结合,当检测到物体靠近时自动触发拍照,并利用FATFS文件系统将图像数据写入存储卡,从而实现具备入侵记录能力的实用监视系统。资源包共312个文件,其中以C源文件、头文件及工程配置文件为主,包含uvprojx工程、链接脚本、编译中间文件与少量图片;压缩包仅8.25MB,结构清晰,适合对照学习或在此基础上二次开发。目前已有1429人学习下载,覆盖传感器驱动、图像采集、文件系统移植等关键环节。读者可重点分析HC-SR04距离触发逻辑、OV7670数据读取时序、FATFS底层接口对接以及主循环任务调度,配套的txt说明与Keil工程配置也能帮助快速编译调试,避免常见坑点。 这个项目标题看着挺唬人,其实就是把三种常见外设拼成一个“声控拍照”的小系统:用 HC-SR04 超声波传感器测距离,STM32 当大脑,检测到物体进入设定范围后,触发 OV7670 摄像头拍下一帧图像。做安防监控、实验平台、毕设过渡都很合适,尤其适合手里已经有三件套但不知道能怎么组合的新手。

文章会把整个系统拆开,从硬件选型、引脚接线、核心寄存器配置,到软件触发逻辑、调试踩坑,一条线走完。不堆晦涩理论,只讲能落地的东西,每个关键参数都会解释为什么这么定,让你看完不仅能复现,还能根据自己需求改出花样来。

1. 整体设计方案与核心思路拆解

1.1 为什么选这三个核心模块

先说为什么是这个组合。OV7670 是一颗经典的 30 万像素 CMOS 摄像头,输出格式支持 RGB565、YUV422 等,数据接口是 DVP 并行口。它的优势是便宜、资料多、配置流程成熟,各大论坛一搜一大把参考代码;缺点也明显,没有内置 JPEG 压缩,需要额外 FIFO(如 AL422B)或用 STM32 的 DCMI 接口直采。STM32 这边正好大多带 DCMI 外设,所以选带 DCMI 的型号(如 F407、F429)可以省掉 FIFO 芯片,直接并行抓取数据。HC-SR04 就更常规了,一个 GPIO 发触发脉冲,一个 GPIO 读回波高电平宽度,就能算出距离,成本几块钱,探测范围 2cm 到 4m,完全够用。

三个模块的组合逻辑是典型的“事件驱动”结构:超声波负责“感知”,STM32 负责“决策”,摄像头负责“记录”。系统平时处于低功耗等待状态,只有超声波检测到目标进入设定距离才启动摄像头抓拍,拍完继续休眠。这种设计比起让摄像头连续运行再靠算法判断,功耗低得多,数据处理量也小得多。在实际项目里,很多低功耗监测设备都是这个思路。

1.2 系统工作流程和各模块职责

把整个工作过程拆开看,大概是这样一个状态流转:

  1. STM32 上电,完成时钟配置、GPIO 初始化、DCMI 配置、OV7670 寄存器初始化,进入待机。
  2. 主循环里周期性触发 HC-SR04,每次触发后通过输入捕获或外部中断测量回波高电平时间,换算成距离。
  3. 距离值经过滤波判断,若连续多次小于设定阈值(比如 50cm),判定为“有目标靠近”。
  4. 判定成立后,STM32 配置 DCMI 开始接收 OV7670 输出的一帧 RGB565 数据,存入内部 SRAM 或外部 SRAM。
  5. 一帧图像采集完成后,可以将原始数据通过串口发送给上位机,或写入 SD 卡,也可直接做简单的颜色识别或车牌定位。

这套流程里有个容易忽略的关键点:OV7670 和 HC-SR04 的供电电压不同。HC-SR04 一般 5V 供电,OV7670 模块通常 3.3V(有些模块自带稳压),而 STM32 的 GPIO 容忍电平有限,接线时必须处理好电平转换,否则轻则数据错乱,重则烧坏引脚。我习惯在 HC-SR04 的 Echo 输出脚串联一个 1kΩ 电阻再进 STM32,Trig 用 STM32 输出 3.3V,实验证明 HC-SR04 能稳定识别这个高电平。

2. 硬件选型与连接细节

2.1 器件清单与注意事项

动手之前先把料备齐,这里列一份我常用的清单,都是比较耐操的选择:

器件推荐型号/规格说明
主控STM32F407VET6 或 STM32F429带 DCMI,性能足够,F429 可外扩 SDRAM
摄像头OV7670 模块(带 24MHz 有源晶振版本)尽量选带稳压和晶振的成品模块,踩坑少
超声波HC-SR04(5V 供电版本)最普及,别买那种山寨焊工极差的
显示屏2.4 寸 SPI 屏幕(可选)实时显示采集结果用
存储MicroSD 卡模块(SPI)存原始图像
电平转换2 路双向电平转换模块或 1k 电阻关键,防止烧 GPIO
稳压AMS1117-3.3给 STM32 和摄像头供电,独立一点更好

选择模块时有几个注意点。OV7670 一定要确认模块上的晶振是否是 24MHz,有些廉价模块把晶振省了,需要 STM32 的 MCO 引脚直接输出时钟,这种配置很折腾,建议直接买带晶振版本。HC-SR04 模块的型号版本很多,有些新款带温补或者改成了 3.3V 供电,买之前看商家说明,老版 5V 供电在淘宝最主流,用来入门完全没问题。

2.2 引脚分配与接线参考

引脚分配上,我采用的是最直接的 DCMI 直连方式,接线表如下:

STM32F407 引脚外设引脚说明
PE4OV7670 D7数据线
PE5OV7670 D6数据线
PE6OV7670 D5数据线
PE7OV7670 D4数据线
PE8OV7670 D3数据线
PE9OV7670 D2数据线
PE10OV7670 D1数据线
PE11OV7670 D0数据线
PA4OV7670 PCLK像素时钟输入
PA6OV7670 VSYNC帧同步
PA8OV7670 HREF行同步
PB6OV7670 SCLSCCB 时钟
PB7OV7670 SDASCCB 数据
PC1HC-SR04 Echo回波输入,串 1k 电阻
PC2HC-SR04 Trig触发输出
PE2摄像头复位复位控制

注意,DCMI 数据引脚不是随便配的,每个芯片的 DCMI 数据线对应固定引脚组,不是任意 GPIO 都能用。建议打开芯片数据手册的 DCMI 引脚映射表确认,不要靠猜。我第一次做的时候想当然把数据线接到了 PB 口一堆脚上,结果 DCMI 寄存器怎么配都不出数据,查了半天才发现引脚映射不对。

供电部分也要分开处理。STM32F407 用 3.3V 供电,OV7670 模块若自带稳压可直接供 3.3V 或 5V(看模块说明),HC-SR04 则用 5V。共用电源时,STM32 和 HC-SR04 要共地,否则回波信号参考电位不一致,测距必然乱跳。建议用一个 USB 转串口模块的 5V 作为总输入,经 AMS1117 再分出 3.3V。

2.3 时钟和复位电路的设计考量

STM32 外部晶振我用的 8MHz 无源晶振,配置 PLL 倍频到 168MHz,这是 F407 的常规操作。晶振的两个负载电容,网上计算工具很多,实际 18pF 到 22pF 都能跑得动,但注意要选用 C0G 材质电容,温度稳定性好,不会出现冬天能启动夏天不启动的诡异问题。复位电路用经典的 10k 上拉加 100nF 电容到地即可。

OV7670 模块的复位引脚,在初始化之前要拉低至少 1ms 再拉高,保证摄像头内部寄存器回到上电默认状态。SCCB 时序本质上与 I2C 非常接近,用 STM32 的硬件 I2C 也能跑,但我实测下来软件模拟 I2C 更省心,可以随便换引脚,还能避开硬件 I2C 的 bug 坑。延迟函数尤其重要,OV7670 上电后要等几毫秒,等内部晶振稳定再发配置命令。

3. 软件核心实现:从初始化到图像采集

3.1 代码结构规划

软件层面我拆成四个模块:

  • ov7670.c:OV7670 寄存器初始化、SCCB 读写函数、配置命令表。
  • hc_sr04.c:超声波测距驱动,采用输入捕获方式测量回波高电平时间。
  • dcmi_capture.c:DCMI 外设配置、DMA 传输、帧完成中断回调。
  • main.c:主循环状态机、距离判断、触发采集、串口输出。

代码分模块的好处是后面想换传感器(比如换成红外测距)或者换摄像头(换成 OV2640),不用重写整个工程,只替换对应模块就行。这也是为什么推荐用库函数或 HAL 库,而不是直接操作寄存器——可读性好,项目后期维护省心。

3.2 OV7670 关键寄存器配置

OV7670 上电后内部寄存器默认输出是 YUV422 格式,不是我们想要的 RGB565,所以第一步必须通过 SCCB 写入寄存器,切换输出格式和数据时序。配置表和网上流传版本大体相同,但有几个关键寄存器值得单独说:

寄存器地址写入值含义
0x120x80重置整个芯片
0x120x06使用 RGB 输出,且 QVGA 尺寸
0x400x10RGB565 格式,RGB 顺序
0x110x03内部 PCLK 分频,配合输入时钟调整帧率
0x3d0x83输出时序相关,PCLK 极性设置
0x170x13HSTART 等,窗口位置
0x180x01HSTOP
0x320xB6HREF 相关
0x190x02VSTART
0x1A0x7AVSTOP
0x030x04VREF 等
0x0C0x00关闭自动增益控制调整个数
0x3E0x00关闭 PLL 倍频,直接用外部时钟

这里最容易出问题的是 0x3d 寄存器的 PCLK 极性,不同模块(不同批次)实际输出的沿可能不同。如果采集到的图像出现错位、颜色花屏,先试着把这个寄存器的值改成 0x83 或 0x82 翻转极性,多数情况下能解决。另一个重点是 0x40 寄存器,只有设置为 0x10 才能输出 RGB565,否则输出的是 RGB444 或其他格式,图像颜色会完全不对。

窗口配置这块,QVGA 分辨率对应 320x240,实际有效区域要从 OV7670 的感光区中间截取。寄存器 0x17、0x18、0x19、0x1A 的组合决定窗口起始和结束位置,如果看到的图像有黑边或偏移,就是这几个寄存器没配好。网上推荐值多,但不同模块仍有差异,调试时可以通过串口回传整帧数据后用上位机软件查看效果,逐步微调。

3.3 HC-SR04 测距驱动的实现思路

HC-SR04 的使用非常简单:Trig 引脚拉高 10us 以上,模块自动发出 8 个 40kHz 脉冲,然后把 Echo 拉高,高电平持续时长就是超声波往返时间。距离计算公式是:

距离(cm) = Echo高电平时间(us) / 58

这个 58 是怎么来的?声速在空气中约 340m/s,换算成 cm/us 是 0.034cm/us,超声波来回走了两倍距离,所以单程时间需要除以 2,因此距离 = 时间(us) * 0.034 / 2 ≈ 时间(us) / 58.8。所以如果测得高电平时间是 3000us,距离约等于 51.7cm。

我用的是 STM32 定时器输入捕获功能。初始化 TIM2 通道 1,捕获上升沿和下降沿,记录两次捕获值之差就是高电平时间。相比用外部中断加 Delay 轮询的死等方式,输入捕获不占用 CPU,测量精度还高得多。每次测量前,Trig 输出一段 15us 高电平,然后等待捕获完成标志,超时则返回一个超出量程的值(比如 999cm)。

3.4 DCMI 采集 RGB565 图像帧

STM32 的 DCMI 外设可以接收外部摄像头并行数据,工作在硬件同步模式下,由 VSYNC 和 HREF 信号标记帧与行的边界。配置时,几组关键的参数:

  • 选择 8 位数据宽度,对应 OV7670 的 D0-D7。
  • 设置 VSYNC 和 HREF 极性,这要与 OV7670 输出匹配,通常 VSYNC 低有效,HREF 高有效。
  • 嵌入同步关闭,使用独立同步模式。
  • 连续模式单帧采集,即收到一帧 VSYNC 后自动产生帧结束事件。

为了不让 CPU 在接收图像时被打断太多,我用 DMA 把 DCMI 数据直接搬运到内存缓冲区。缓冲区大小按一帧算:320x240x2 字节 = 153600 字节,大约 150KB。F407 的内部 SRAM 是 192KB,勉强够放一帧,但如果同时还想跑系统、放栈、放协议缓冲区,就会非常紧张。更稳妥的做法是分配在外部 SRAM 或 SDRAM 里。F429 带 SDRAM 控制器,外扩一块 8MB 的 SDRAM 就舒服多了,能缓存多帧数据,方便做连续抓拍。

DCMI 初始化代码片段(HAL 库风格):

DCMI_HandleTypeDef hdcmi; hdcmi.Instance = DCMI; hdcmi.Init.SynchroMode = DCMI_SYNCHRO_HARDWARE; // 硬件同步 hdcmi.Init.PCKPolarity = DCMI_PCKPOLARITY_RISING; // 像素时钟上升沿采样,如果花屏改下降沿 hdcmi.Init.VSPolarity = DCMI_VSPOLARITY_LOW; // VSYNC低有效 hdcmi.Init.HSPolarity = DCMI_HSPOLARITY_HIGH; // HREF高有效 hdcmi.Init.CaptureRate = DCMI_CR_ALL_FRAME; // 捕获全部帧 hdcmi.Init.ExtendedDataMode = DCMI_EXTEND_DATA_8B; // 8位数据线 hdcmi.Init.JPEGMode = DCMI_MODE_SNAPSHOT; // 快照模式,抓单帧 hdcmi.Init.ByteSelectMode = DCMI_BSM_ALL; hdcmi.Init.LineSelectMode = DCMI_LSM_ALL; hdcmi.Init.LineNumber = 240; // 一帧240行

配置完 DCMI 后调用HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_SNAPSHOT, (uint32_t)frame_buffer, 320/2),注意最后一个参数是像素个数除以 2,因为 DMA 传输的是 32 位字,每个字包含两个像素(两个 16 位 RGB565)。这个细节如果搞错,缓冲区会溢出或者只收到一半数据。

4. 开发环境搭建与调试实战

4.1 STM32 开发环境选择

这里想结合目前比较常用的几种开发方式说下我的体验。官方主推的 STM32CubeIDE 集成了 CubeMX 配置工具、编译器和调试器,对新手最友好,生成工程时选 HAL 库就行。使用它的STM32CubeMX点点选选就能把时钟、GPIO、DCMI、定时器都配置好,省去查寄存器的大量时间。但要注意,CubeMX 生成的 DCMI 相关代码在某些芯片版本上有小坑,比如 HREF 极性配置和实际时序不一致,需要手动调整hdcmi.Init.HSPolarity

很多人装好 STM32CubeIDE 后,下载程序时遇到“Error: no STM32 target found!”或者提示调试认证问题,这通常有几种原因:一是 ST-Link 驱动没装好,Windows 设备管理器里能看到未知设备;二是目标板供电不正常,VDD 没接好导致 SWD 无法握手;三是连接线太长或者杜邦线接触不良。解决办法是先在 STM32CubeProgrammer 里测试连接,确认能读到芯片 ID 再回 IDE 调试。还有一个很隐蔽的问题,如果代码里把 SWDIO/SWCLK 引脚复用成了普通 GPIO,下载一次后第二次就连接不上了,这时需要用 BOOT0 拉高进入系统存储器模式,用串口或 ST-Link 的“Under reset”方式擦除 Flash。

VSCode 配 EIDE 插件或者 PlatformIO 也是一种方式,适合喜欢 Vim、Remote 开发的人。EIDE 插件可以支持 SCONS 或 CMake 构建,也能调用 ST-Link 命令行工具烧录。我个人觉得如果你主要是单文件调试,CubeIDE 就够用;如果项目有大量外部库、要做版本管理,VSCode 的工作区体验更好些。

4.2 晶振电容计算与时钟树配置

系统时钟对摄像头采集稳定性的影响比很多人想象的大。OV7670 的 PCLK 输出是它与 STM32 DCMI 之间的同步信号,如果 STM32 系统时钟抖动太大或者 PCLK 采样点选得不对,图像就会出现横条纹。

晶振负载电容的计算公式是Cl = (C1 * C2) / (C1 + C2) + Cstray,其中 Cstray 是 PCB 走线和引脚寄生电容,一般在 3pF 到 7pF。假如选用 8MHz 晶振要求负载电容 18pF,C1=C2=20pF,则 (20*20)/(20+20)+4=14pF,偏低;改成 22pF 时计算得 15pF,也是偏低;实际配 27pF 才能接近 18pF。所以网上很多人直接套 20pF 实际是启动慢的隐患。如果你首件样品能正常启动但低温下起振失败,多半就是负载电容偏小或者 C0G 材质没选对。

在 CubeMX 里,时钟树我按照外部 8MHz、PLL 倍频至 168MHz 来配置。APB2 总线上的定时器时钟通常为 84MHz,DCMI 挂在 AHB1 上,时钟 168MHz。这没问题,关键是要保证 DCMI 的像素时钟 PCLK 不超过 DCMI 最大工作频率。OV7670 在 QVGA RGB565 输出时,PCLK 大约在 12MHz 到 24MHz,明显低于 DCMI 上限,所以时间裕量充足。

4.3 从初始化到采集的完整流程

这里给一个我在 main 函数里实现的状态机伪代码:

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DCMI_Init(); MX_TIM2_Init(); // 定时器输入捕获 USART1_UART_Init(); // 串口调试 OV7670_Init(); // SCCB 写入寄存器 while (1) { uint16_t distance = HC_SR04_Measure(); if (distance < TRIGGER_DISTANCE && distance > 0) { DCMI_Snapshot(frame_buffer); // 简单滤波防止误触发 if (HC_SR04_Measure() < TRIGGER_DISTANCE) { USART_SendImage(frame_buffer, 320 * 240 * 2); HAL_Delay(2000); // 抓拍间隔,避免连续触发 } } HAL_Delay(100); } }

有人可能会问为什么要二次确认测距结果。超声波模块偶尔会收到杂散回波,比如旁边墙壁反射、模块自身抖动,单次测量可能误判。连续两次确认能过滤掉大多数随机错误,代价是多花 60ms 左右,完全可接受。这个思路还可以扩展成一连 N 次都低于阈值再触发,适合要求更严的场景。

5. 常见问题与排查技巧实录

5.1 图像黑屏或全花屏

这是 OV7670 项目里最常见的现象,没有之一。排查顺序我建议这样来:

先确认 SCCB 是不是真的写进去了。用示波器探 SDA/SCL,或者写个读 ID 的函数,读 0x1A 寄存器(厂商 ID),正确应返回 0x76。如果读出来是 0xFF 或 0x00,说明 SCCB 时序或引脚接线有问题。很多模块的 SDA/SCL 需要上拉电阻,如果模块内部没有,要在外部加 4.7k 到 3.3V。

再检查 DCMI 的时序配置。黑屏通常意味着 DMA 没有收到数据或者收到了全 0,跳线、DCMI 使能、复位顺序都要看。花屏则多半是 PCLK 极性反了,或者数据线顺序接错。最好用逻辑分析仪抓一下 VSYNC、HREF、PCLK 三根线的波形,看帧同步和行同步是否正常。第一次调 DCMI 时,我直接跳过 HREF、VSYNC 引脚确认这一步,结果花了整整两天找数据线问题,实际上就是一组线序搞反了。

调试技巧上,可以初始配置后抓取单行数据输出,用串口传到上位机,检查一行数据的 RGB565 值是否符合预期。如果一行数据看起来有规律但颜色不对,大概率是 RGB 顺序反了,修改 0x40 寄存器里 RGB 顺序的位即可。

5.2 测距异常跳动

HC-SR04 测量值跳动的原因,八成是供电不稳或者 Trig 脉宽时间不够。模块内部发射电路需要瞬间电流,如果电源线细或者 5V 被其他外设拉低,回波幅度会波动,测出的距离自然不稳。建议用一个独立的 100uF 电解电容在 HC-SR04 的 VCC 和 GND 之间去耦,效果立竿见影。我的板子上这个电容是必焊的。

软件层面也要加滤波。简单的中值滤波或者滑动平均都可以,取 5 次测量排序取中间值,基本能滤掉单点毛刺。如果仍跳得厉害,检查一下 Echo 引脚是否和某个电机或继电器靠得太近,电磁干扰也会造成捕获时间异常。测量超时时返回一个特定错误码而不是 0,否则主循环会误认为目标近在咫尺,触发拍照。

5.3 连接 ST-Link 时提示“No STM32 Target Found”

好多人拿到板子第一件事就是下载,结果就卡在这一步。除了前面说的驱动问题之外,还有几种容易踩坑的场景:目标板由 ST-Link 供电时,如果板上有大电容或者其他耗电单元,ST-Link 的 3.3V 输出能力不足,握手失败,这时改用外部供电并共地;板子上电顺序不对时,也可能是 ST-Link 先识别到了非调试状态,可以在 STM32CubeProgrammer 的连接设置里改成“Connect under reset”,用硬件复位保证 CPU 停在起始位置再连接。

如果你的开发板用的是自制的最小系统板,SWD 接口特别要注意 SWDIO 和 SWCLK 上各接一个 10k 上拉电阻。很多人画的板子这俩脚直接连 ST-Link,省略了上拉,结果导致 ST-Link 在高速通信时电平不稳定,时好时坏。还有一个常见操作,程序里用了GPIO_PIN_6GPIO_PIN_7做别的功能,却不小心把 SWDIO/SWCLK 复用了,这种问题只能通过 BOOT0 拉高进系统引导去擦除。

5.4 摄像头帧率上不去或采集卡顿

OV7670 在 QVGA RGB565 输出下,理论上帧率可以达到 30fps,但 STM32 通过 DCMI+DMA 抓一帧 150KB 数据再通过串口发送,速度会严重拖后腿。如果只是做“超声波触发拍照”,慢一点无所谓;但如果想做连续预览, 115200 波特率的串口肯定不够,至少用 921600 或者 USB 虚拟串口。

帧率不足还有一个隐藏原因:DCMI 的快照模式下,如果上一帧还没有传输完成,下一帧的 VSYNC 不会被响应,导致丢帧。解决办法是在帧完成中断里快速处理数据并重新启动新一轮 DMA。中断优先级要设置得比其他外设高,避免被串口中断阻塞错过 VSYNC。另外,DMA 传输完成回调里不要再做耗时操作,比如把数据转存到 SD 卡,要放到主循环里异步处理,否则中断持续时间太长,会影响后续帧采集。

6. 进阶改进思路

这个项目做完其实只是开始,往上还有不少可以玩的方向。比如把图像输出到 SD 卡做成一个真正的“触发式监控记录仪”;或者在上位机对 RGB565 数据进行简单处理,识别图像中特定颜色的物体位置;再比如给 STM32 挂一个 ESP8266,把超声波触发拍照的结果直接推到局域网,搞一个低成本的智能猫眼原型。

有人还问过能不能直接识别车牌,OV7670 的 30 万像素在光线好、距离合适(30cm 到 1m)的情况下确实能拍到车牌轮廓,但受限于分辨率和没有 ISP 处理,识别率不如专用摄像头。可以在 STM32 端对图像做二值化和边缘提取,减少上位机压力,再配合模板匹配,简单数字识别可以跑通。如果想追求更高的识别率,建议换 OV2640 或 OV5640,像素更高,但寄存器配置难度也上了一个台阶,后续想深入的朋友可以先从 OV7670 练手,把 DCMI 和图像缓存流程跑通,再迁移到更高分辨率模组上会轻松很多。

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

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

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

立即咨询