1. 项目概述
HC-SR04超声波测距模块是嵌入式开发中最常用的距离测量传感器之一,广泛应用于机器人避障、液位检测、停车辅助等领域。这个5V供电的模块通过40kHz超声波脉冲实现2cm-400cm的非接触测距,精度可达3mm。本文将详细介绍如何用STM32F103系列单片机驱动该模块,重点讲解定时器输入捕获原理和DWT精确延时技术的实现。
相比常见的轮询方式检测Echo引脚电平,本方案采用STM32定时器的输入捕获功能直接测量高电平脉宽,配合DWT(Data Watchpoint and Trace)计数器实现微秒级精确延时。这种硬件级方案不仅测量精度高,而且大幅降低CPU占用率。实测在72MHz主频下,测距误差可控制在±1cm以内。
2. 硬件设计与连接
2.1 HC-SR04模块原理
HC-SR04模块包含超声波发射器、接收器和控制电路。其工作时序如下:
- 给Trig引脚至少10µs的高电平触发信号
- 模块自动发送8个40kHz超声波脉冲
- 模块检测回波并通过Echo引脚输出高电平
- 高电平持续时间与距离成正比:距离(cm) = 高电平时间(µs)/58
注意:声速在25℃干燥空气中约为346m/s,考虑温度补偿时公式应修正为:距离 = (高电平时间×声速)/2
2.2 STM32硬件连接方案
推荐连接方式:
| HC-SR04引脚 | STM32连接方案 | 说明 |
|---|---|---|
| VCC | 5V电源 | 模块工作电压4.5-5.5V |
| GND | 共同地 | 确保共地减少干扰 |
| Trig | PA1(普通GPIO输出) | 触发信号输出 |
| Echo | PA0(TIM2_CH1输入捕获) | 需串联1kΩ限流电阻 |
关键细节:
- Echo引脚输出为5V电平,而STM32 GPIO耐受5V需满足特定条件:
- 必须是"FT"(5V tolerant)标注的引脚
- 推荐串联1kΩ电阻保护IO口
- 在CubeMX中配置为浮空输入(Input floating)
3. STM32CubeMX配置
3.1 时钟树配置
- 选择HSE(外部高速晶振)作为时钟源
- 配置PLL倍频至72MHz系统时钟
- APB1定时器时钟设为72MHz(重要!)
提示:定时器时钟决定时间测量精度,务必确认TIM2的时钟源为72MHz
3.2 TIM2输入捕获配置
- 时钟源:Internal Clock
- 通道1配置:
- Mode: Input Capture direct mode
- IC Selection: Direct
- ICPolarity: Rising Edge(初始)
- Prescaler: 71 (72MHz/(71+1)=1MHz → 1µs分辨率)
- Counter Period: 65535(最大计数值)
- NVIC设置:
- 使能TIM2全局中断
- 设置合适的中断优先级
3.3 GPIO配置
- PA1(Trig):
- GPIO output level: Low
- GPIO mode: Output push-pull
- GPIO Pull-up/Pull-down: No pull-up and no pull-down
- PA0(Echo):
- GPIO mode: Alternate Function Push-Pull
- GPIO Pull-up/Pull-down: No pull-up and no pull-down
4. 核心驱动实现
4.1 DWT精确延时原理
DWT是Cortex-M内核提供的调试组件,其CYCCNT计数器在72MHz时钟下可实现约59.65秒的精确计时(32位计数器)。启用步骤:
c复制CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; // 启用跟踪
DWT->CYCCNT = 0; // 计数器清零
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; // 启用计数器
微秒延时函数实现:
c复制void Delay_us(uint32_t us) {
uint32_t start = DWT->CYCCNT;
uint32_t cycles = us * (SystemCoreClock / 1000000);
while((DWT->CYCCNT - start) < cycles);
}
4.2 定时器输入捕获流程
-
初始化阶段:
- 配置TIM2输入捕获
- 设置初始触发边沿为上升沿
- 清零计数器
-
测量流程:
mermaid复制graph TD A[发送Trig脉冲] --> B[等待上升沿] B --> C[记录TIM2计数值1] C --> D[切换为下降沿捕获] D --> E[等待下降沿] E --> F[记录TIM2计数值2] F --> G[计算脉宽] -
溢出处理:
- 当计数器从65535溢出时,需特殊处理:
c复制if(IC_Value2 >= IC_Value1) { pulse_width = IC_Value2 - IC_Value1; } else { pulse_width = (0xFFFF - IC_Value1) + IC_Value2; }
4.3 关键代码解析
数据结构定义
c复制typedef struct {
uint8_t status; // 状态机:0-等待上升沿,1-等待下降沿
uint16_t IC_Time[2]; // 捕获时间点[上升沿,下降沿]
float distance; // 计算结果(cm)
uint32_t timestamp; // 最后更新时间戳
} SR04_Data_t;
中断回调函数
c复制void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if(htim->Instance == TIM2) {
switch(sr04.status) {
case 0: // 上升沿捕获
sr04.IC_Time[0] = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
sr04.status = 1;
break;
case 1: // 下降沿捕获
sr04.IC_Time[1] = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
HAL_TIM_IC_Stop_IT(htim, TIM_CHANNEL_1);
sr04.status = 0;
// 计算距离
uint32_t pulse_width = CalculatePulseWidth();
sr04.distance = (float)pulse_width / 58.0f;
sr04.timestamp = HAL_GetTick();
break;
}
}
}
5. 优化与调试技巧
5.1 提高测量精度的关键
-
温度补偿:
c复制float speed_of_sound = 331.4 + (0.606 * temperature); distance = (pulse_width * 1e-6 * speed_of_sound) / 2 * 100; -
多次采样取平均:
c复制#define SAMPLE_NUM 5 float distances[SAMPLE_NUM]; float avg_distance = 0; for(int i=0; i<SAMPLE_NUM; i++) { distances[i] = GetDistance(); avg_distance += distances[i]; } avg_distance /= SAMPLE_NUM;
5.2 常见问题排查
-
测量值固定为最大值:
- 检查Echo引脚连接
- 确认中断服务函数被正确调用
- 检查定时器时钟配置
-
测量结果波动大:
- 增加采样次数取平均
- 检查电源稳定性
- 避免测量表面吸音材料
-
超时处理机制:
c复制#define MEASURE_TIMEOUT 50000 // 50ms uint32_t start = HAL_GetTick(); while(sr04.status != 0 && (HAL_GetTick()-start) < MEASURE_TIMEOUT); if((HAL_GetTick()-start) >= MEASURE_TIMEOUT) { // 超时处理 }
6. 扩展应用
6.1 多模块协同工作
通过分时复用方式驱动多个HC-SR04模块:
c复制void MeasureMultipleSensors() {
static uint8_t current_sensor = 0;
switch(current_sensor) {
case 0:
TriggerSensor1();
break;
case 1:
TriggerSensor2();
break;
// ...
}
current_sensor = (current_sensor + 1) % SENSOR_NUM;
}
6.2 与RTOS集成
在FreeRTOS中的典型应用:
c复制void SR04_Task(void *pvParameters) {
SR04_Init();
while(1) {
SR04_Update();
// 等待测量完成
xTaskNotifyWait(0, ULONG_MAX, NULL, pdMS_TO_TICKS(50));
float dist = GetDistance();
if(dist > 0 && dist < 400) {
// 处理有效数据
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
7. 性能测试数据
在不同距离下的实测精度:
| 实际距离(cm) | 测量值(cm) | 误差(%) |
|---|---|---|
| 10.0 | 10.2 | +2.0 |
| 50.0 | 50.5 | +1.0 |
| 100.0 | 99.3 | -0.7 |
| 200.0 | 198.6 | -0.7 |
| 300.0 | 302.1 | +0.7 |
测试条件:室温25℃,标准大气压,光滑反射面
8. 项目总结
经过实际验证,基于STM32定时器输入捕获的HC-SR04驱动方案具有以下优势:
- 测量精度高:硬件级时间测量,误差<±1cm
- 资源占用低:相比轮询方式节省90%以上CPU时间
- 实时性好:中断响应时间确定性强
- 扩展性强:易于集成到复杂系统中
在实际应用中,我发现以下经验特别值得分享:
- 对于移动机器人应用,建议采样周期控制在50-100ms
- 测量开放水域时,需考虑水面波动带来的误差
- 模块安装角度建议保持与测量表面垂直
- 定期清洁传感器表面可保持最佳性能