STM32与HC-SR04超声波测距实战指南
1. 项目概述:超声波测距的硬件搭档
在嵌入式开发领域,STM32和HC-SR04的组合堪称经典CP。这个搭配之所以被广泛采用,是因为它完美平衡了性能和成本——STM32系列微控制器提供丰富的外设接口和可靠的定时器功能,而HC-SR04模块则以不到20元的价格实现了厘米级精度的距离测量。我经手过的智能小车、自动泊车系统、工业料位检测等项目中,有七成以上都采用了这个方案。
超声波测距的基本原理其实很直观:模块发射40kHz的超声波脉冲,遇到障碍物反射后被接收器捕获,通过计算发射和接收的时间差(Time of Flight, TOF),结合声速(常温下约343m/s)就能算出距离。公式简单到令人感动:距离 = (时间差 × 声速)/2。这个"除以2"很关键,因为声波走了往返路程。
注意:实际应用中要考虑温度补偿,声速会随温度变化。每升高1℃,声速增加约0.6m/s。如果对精度要求高,建议增加DS18B20温度传感器进行动态校准。
2. 硬件搭建:避开那些新手必踩的坑
2.1 硬件选型要点
HC-SR04模块有新旧版本之分,老版需要5V供电,新版3.3V也能工作。我强烈建议用万用表实测模块的VCC引脚电压需求,曾经有个项目因为用了新版模块却按老版接5V,导致STM32的GPIO被反向灌电流烧毁。安全接法是在STM32的GPIO和HC-SR04的Echo引脚间串接1kΩ电阻,既保证信号传输又限流。
STM32型号选择上,F1系列的C8T6就够用,但如果你需要同时处理其他复杂任务(比如OLED显示或无线传输),建议选F4系列,它的硬件浮点单元能加速距离计算。以下是典型接线方案:
| HC-SR04引脚 | STM32连接 | 备注 |
|---|---|---|
| VCC | 3.3V/5V | 根据模块版本选择 |
| Trig | PA0(任意GPIO) | 输出模式 |
| Echo | PA1(带定时器通道) | 输入捕获模式 |
| GND | GND | 共地至关重要 |
2.2 电源管理的隐藏技巧
很多教程不会告诉你:HC-SR04在发射瞬间电流可达15mA,如果电源质量不好会导致电压跌落,引发STM32复位。我的解决方案是在模块VCC和GND之间并联一个100μF电解电容+0.1μF陶瓷电容组合,实测可将电压波动控制在3%以内。如果使用电池供电,建议增加TPS61090这样的升压稳压芯片,确保电压稳定。
3. 软件设计:从阻塞式到中断驱动的进化
3.1 基础阻塞式实现
新手通常会这样写代码——先拉高Trig引脚10μs触发发射,然后死等Echo变高,再用while循环等待变低,期间用定时器计数。这种写法简单粗暴,但会完全占用CPU资源:
void HC_SR04_GetDistance(void) { HAL_GPIO_WritePin(Trig_GPIO_Port, Trig_Pin, GPIO_PIN_SET); delay_us(10); HAL_GPIO_WritePin(Trig_GPIO_Port, Trig_Pin, GPIO_PIN_RESET); while(!HAL_GPIO_ReadPin(Echo_GPIO_Port, Echo_Pin)); // 死等上升沿 __HAL_TIM_SET_COUNTER(&htim2, 0); while(HAL_GPIO_ReadPin(Echo_GPIO_Port, Echo_Pin)); // 死等下降沿 uint32_t pulse = __HAL_TIM_GET_COUNTER(&htim2); distance = pulse * 0.0343 / 2; // 单位:cm }3.2 中断+定时器高级方案
实际项目中我推荐使用输入捕获中断方案,解放CPU资源。配置步骤:
- 初始化TIM2为1MHz计数频率(每微秒计数1次)
- 配置Echo引脚对应的定时器通道为输入捕获模式
- 开启上升沿/下降沿中断
关键代码结构:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { if(isFirstCapture) { // 上升沿 __HAL_TIM_SET_COUNTER(htim, 0); isFirstCapture = 0; } else { // 下降沿 pulse_width = __HAL_TIM_GET_COUNTER(htim); distance = pulse_width * 0.01715; // 合并除2的计算 isFirstCapture = 1; } } }实测技巧:在TIM2初始化时加入以下配置可提高精度:
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.RepetitionCounter = 0; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
4. 误差分析与补偿策略
4.1 主要误差来源
根据我的项目经验,误差主要来自三个方面:
- 温度影响:-20℃时声速约319m/s,+50℃时约360m/s,温差70℃会导致约6%的误差
- 多径反射:超声波遇到光滑表面可能产生多次反射,导致检测到非直接回波
- 定时器分辨率:1MHz时钟时理论最小分辨率1cm,对于高精度应用需提升时钟频率
4.2 软件滤波算法
我常用的五样本加权滤波算法,能有效抑制突发干扰:
#define SAMPLE_NUM 5 float distance_filter(void) { static float buf[SAMPLE_NUM] = {0}; static uint8_t index = 0; float sum = 0; buf[index++] = get_raw_distance(); if(index >= SAMPLE_NUM) index = 0; // 加权系数:最近样本权重最高 for(uint8_t i=0; i<SAMPLE_NUM; i++) { sum += buf[i] * (i+1)/(SAMPLE_NUM*(SAMPLE_NUM+1)/2.0); } return sum; }对于动态测量场景(如小车避障),可以增加运动补偿算法。我曾在四旋翼无人机项目中使用卡尔曼滤波,将测距抖动从±3cm降低到±0.5cm。
5. 进阶应用:多模块组网与三维定位
5.1 时分复用方案
当需要同时使用多个HC-SR04时(比如三维空间定位),直接并联会导致信号冲突。我的解决方案是采用时分复用:
- 为每个模块分配独立的Trig控制线
- Echo信号线通过74HC125三态缓冲器共享同一定时器通道
- 按固定时序轮询触发各模块,间隔建议大于50ms
void multi_HC_SR04_scan(void) { static uint8_t current_sensor = 0; // 禁用所有Echo输出 HAL_GPIO_WritePin(EN1_GPIO_Port, EN1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(EN2_GPIO_Port, EN2_Pin, GPIO_PIN_SET); // 触发当前传感器 HAL_GPIO_WritePin(Trig_GPIO_Port[current_sensor], Trig_Pin[current_sensor], GPIO_PIN_SET); delay_us(10); HAL_GPIO_WritePin(Trig_GPIO_Port[current_sensor], Trig_Pin[current_sensor], GPIO_PIN_RESET); // 启用当前Echo HAL_GPIO_WritePin(EN_GPIO_Port[current_sensor], EN_Pin[current_sensor], GPIO_PIN_RESET); if(++current_sensor >= SENSOR_NUM) current_sensor = 0; }5.2 三维坐标解算
使用三个以上模块组成定位阵列时,可以通过解超定方程组计算目标物坐标。以三个模块为例:
- 设模块坐标分别为 (x₁,y₁,z₁), (x₂,y₂,z₂), (x₃,y₃,z₃)
- 测得距离为 d₁, d₂, d₃
- 建立方程组: (x-x₁)² + (y-y₁)² + (z-z₁)² = d₁² (x-x₂)² + (y-y₂)² + (z-z₂)² = d₂² (x-x₃)² + (y-y₃)² + (z-z₃)² = d₃²
实际实现时我推荐使用最小二乘法求解,STM32F4的硬件FPU能在10ms内完成解算。有个坑要注意:当模块共面时方程组会出现病态解,最佳实践是让至少一个模块的z坐标与其他不同。
6. 性能优化与特殊场景处理
6.1 低功耗设计
对于电池供电设备,我总结的省电技巧:
- 将测量间隔从100ms调整为500ms,功耗直降80%
- 在间隔期间关闭HC-SR04电源(需增加MOSFET控制电路)
- 使用STM32的STOP模式,通过RTC定时唤醒
典型电路改进:
VCC ----[MOSFET]---- HC-SR04 | GPIO_PA46.2 极限距离测量
HC-SR04标称最大测距4米,但通过以下改造可达6米:
- 在接收端并联100pF电容增强信号灵敏度
- 将发射脉冲宽度从10μs延长到20μs
- 在代码中增加动态增益控制:
void adjust_sensitivity(uint32_t last_distance) { if(last_distance > 300) { // 单位:cm HC_SR04_PulseWidth = 20; // 延长发射脉冲 ADC_Threshold = 150; // 降低接收阈值 } else { HC_SR04_PulseWidth = 10; ADC_Threshold = 200; } }6.3 抗干扰方案
在工业现场遇到强电磁干扰时,这些措施很有效:
- 使用屏蔽双绞线连接模块,屏蔽层单点接地
- 在Echo信号线上增加TVS二极管(如SMBJ3.3A)
- 软件上增加脉冲宽度校验,丢弃异常短(<100μs)或过长(>25ms)的结果
最后分享一个真实案例:某AGV项目初期频繁误检测,后来发现是变频器干扰。解决方案是在HC-SR04的电源端增加π型滤波器(10Ω电阻+100μF电容+0.1μF电容),成本不到2元却彻底解决了问题。这提醒我们:硬件设计永远要比软件补丁更可靠。