1. HC-SR04超声波测距模块基础
HC-SR04是市面上最常见的低成本超声波测距模块,价格通常在10元以内,但测距效果却相当可靠。我第一次用这个模块是在大学做智能小车项目,当时就被它简单的四线接口和稳定的性能惊艳到了。
模块正面有两个金属圆筒,一个是发射器(T),一个是接收器(R)。工作时,发射器会发出40kHz的超声波脉冲,遇到障碍物反射后被接收器检测到。模块背面有四个引脚:
- VCC:接3.3V或5V电源(实测5V时测距更远)
- Trig:触发信号输入,给10us以上高电平启动测距
- Echo:回响信号输出,高电平持续时间对应距离
- GND:接地
工作时序特别简单:给Trig脚一个10us以上的高电平脉冲,模块就会自动发射8个40kHz的超声波脉冲,然后Echo脚会输出一个高电平,这个高电平的持续时间就是从发射到接收的时间差t。距离计算公式为:距离 = (t × 声速)/2。这里的除以2是因为声波是往返时间。
2. STM32硬件连接与CubeMX配置
我用的是STM32F103C8T6最小系统板,成本不到20元,完全够用。连接方式如下:
- VCC → 5V
- Trig → PA1(任意GPIO均可)
- Echo → PA0(必须连接支持输入捕获的定时器通道)
- GND → GND
在CubeMX中的配置步骤:
- 启用TIM2定时器,选择内部时钟源
- 配置通道1为输入捕获模式
- 设置预分频器(PSC)为71,ARR为65535(72MHz主频下得到1MHz计数频率,1us分辨率)
- 开启TIM2全局中断和捕获中断
- 配置PA1为GPIO输出模式
- 启用USART1用于调试输出
关键点在于定时器配置。我选择TIM2是因为它是最基础的通用定时器,所有STM32型号都有。输入捕获模式可以精确测量Echo高电平的持续时间,1us的分辨率对于超声波测距完全够用。
3. HAL库输入捕获实现
HAL库的输入捕获流程比标准库更抽象,但用熟练后反而更简单。主要逻辑都在回调函数中处理:
// 定义测量结构体 typedef struct { uint16_t start_val; uint16_t end_val; float distance; uint8_t capture_flag; } SR04_TypeDef; SR04_TypeDef hcsr04; // 输入捕获回调函数 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM2) { if(hcsr04.capture_flag == 0) { // 捕获上升沿 hcsr04.start_val = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING); hcsr04.capture_flag = 1; } else { // 捕获下降沿 hcsr04.end_val = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); hcsr04.capture_flag = 0; // 计算距离 uint16_t pulse_width = (hcsr04.end_val > hcsr04.start_val) ? (hcsr04.end_val - hcsr04.start_val) : (65535 - hcsr04.start_val + hcsr04.end_val); hcsr04.distance = pulse_width * 0.0343 / 2; // 单位cm } } }这个实现有几个关键点:
- 使用结构体保存测量状态,避免全局变量混乱
- 在上升沿捕获时立即切换为下降沿捕获,确保能测量完整脉冲宽度
- 处理定时器计数器溢出的情况(end_val < start_val时)
- 计算距离时使用0.0343cm/us的声速(25℃标准值)
4. 温度补偿算法实现
标准声速是25℃下的343m/s,但实际环境中温度变化很大。温度每升高1℃,声速增加约0.6m/s。我在项目中增加了DS18B20温度传感器,接线如下:
- VCC → 3.3V
- DQ → PB0(需接4.7K上拉电阻)
- GND → GND
温度补偿代码:
float Get_Speed_By_Temp(float temp) { return 331.5 + 0.6 * temp; // 声速与温度关系公式 } void Update_Distance_With_Temp(float temp) { float speed = Get_Speed_By_Temp(temp); uint16_t pulse_width = (hcsr04.end_val > hcsr04.start_val) ? (hcsr04.end_val - hcsr04.start_val) : (65535 - hcsr04.start_val + hcsr04.end_val); hcsr04.distance = pulse_width * (speed / 10000) / 2; // 转换为cm单位 }实测发现,在冬季(约5℃)和夏季(约35℃)时,补偿前后的测距误差可以从±3cm降低到±1cm以内。对于需要精确测距的场景(如自动泊车),这个改进非常关键。
5. 完整工作流程与优化技巧
主程序的工作流程如下:
int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); MX_USART1_UART_Init(); // 初始化HC-SR04 HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); while (1) { // 触发测距 HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET); delay_us(20); HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); // 读取温度并更新距离 float temp = DS18B20_GetTemp(); Update_Distance_With_Temp(temp); // 串口输出 printf("距离: %.1fcm 温度: %.1f℃\r\n", hcsr04.distance, temp); HAL_Delay(100); // 两次测量间隔至少60ms } }几个优化点值得注意:
- 触发脉冲严格控制在20us,既满足模块要求又不会过长
- 测量间隔大于60ms,避免上次测量的回波干扰
- 使用printf重定向输出调试信息,方便观察数据
- 加入超时判断,防止因物体超出量程导致程序卡死
6. 常见问题与解决方法
在实际调试中遇到过几个典型问题:
问题1:测量值跳动大解决方法:
- 在软件中加入滑动平均滤波(如取最近5次测量的平均值)
- 确保电源稳定,我在VCC脚加了100uF电容后明显改善
- 检查物体表面是否平整,粗糙表面会导致回波散射
问题2:短距离测量不准原因:HC-SR04有2cm左右的盲区 解决方法:
- 程序中对小于3cm的结果直接过滤
- 改用盲区更小的US-015模块(约0.5cm)
问题3:高温环境下异常原因:温度超过DS18B20量程(-55~125℃) 解决方法:
- 加入温度范围检查,超限时使用默认声速
- 改用防水型的DS18B20探头
7. 进阶应用示例
基于这个基础框架,可以扩展很多实用功能。比如我在智能小车上实现的自动避障:
void Auto_Avoidance(void) { Get_Distance(); // 获取前方距离 if(hcsr04.distance < 20.0) { // 20cm内障碍物 Stop_Car(); HAL_Delay(500); Turn_Right(90); // 右转90度 Get_Distance(); // 再次测距 if(hcsr04.distance < 30.0) { // 右侧也有障碍 Turn_Left(180); // 掉头 } } else { Move_Forward(); // 安全距离,继续前进 } }这个简单的逻辑配合超声波模块,就能实现基本的避障功能。如果需要更复杂的应用,可以结合多路超声波模块实现环境建模。