超声波测距系统在工业自动化、智能家居和机器人导航等领域有着广泛应用。这个基于STM32和HC-SR04模块的高精度测距方案,通过定时器输入捕获技术实现了毫米级的距离测量精度。相比市面上常见的轮询方式,这种方法不仅测量精度更高,还能显著降低CPU占用率。
我在实际项目中多次使用这种方案,发现它特别适合需要同时处理多个任务的嵌入式系统。比如在自动避障机器人上,采用输入捕获方式可以让主循环有更多资源处理传感器融合和路径规划。
HC-SR04是市面上最常见的低成本超声波模块,工作电压4.5-5.5V,测量范围2cm-400cm。它包含超声波发射器、接收器和控制电路三部分。
模块工作时序如下:
注意:模块的测量角度为15度锥形区域,实际使用时要考虑被测物体的反射特性。光滑表面比粗糙表面更容易获得稳定回波。
STM32的定时器输入捕获功能可以精确记录外部信号边沿的时间戳。我们主要利用以下特性:
以STM32F103为例,其通用定时器(TIM2-TIM5)的时钟源可以配置为内部时钟,通过预分频器获得所需的计数频率。例如:
推荐以下连接方式:
code复制HC-SR04 STM32
VCC → 5V
GND → GND
Trig → GPIO输出
Echo → 定时器通道输入引脚(如TIM2_CH1)
重要提示:Echo信号是5V电平,而STM32 GPIO通常耐受3.3V。建议:
- 使用电平转换芯片(如TXS0108E)
- 简单的电阻分压电路(1kΩ+2kΩ)
- 选择5V容忍的STM32型号(如F103系列)
c复制// 定时器基础配置
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_TimeBaseStruct.TIM_Prescaler = 71; // 72MHz/(71+1)=1MHz
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStruct.TIM_Period = 0xFFFF;
TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
// 输入捕获配置
TIM_ICInitTypeDef TIM_ICInitStruct;
TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStruct.TIM_ICFilter = 0x0F; // 适当滤波
TIM_ICInit(TIM2, &TIM_ICInitStruct);
// 中断配置
TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);
NVIC_EnableIRQ(TIM2_IRQn);
c复制HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET);
delay_us(15); // 保持10μs以上
HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET);
c复制void TIM2_IRQHandler(void) {
if(TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) {
static uint32_t risingTime = 0;
if(TIM_GetCapture1(TIM2) & 0x80) { // 下降沿
uint32_t fallingTime = TIM_GetCapture1(TIM2) & 0x7F;
uint32_t pulseWidth = fallingTime - risingTime;
float distance = pulseWidth * 0.0343 / 2; // cm
// 处理距离数据...
} else { // 上升沿
risingTime = TIM_GetCapture1(TIM2);
// 切换为下降沿捕获
TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Falling);
}
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
}
}
c复制float speed_of_sound = 331.4 + 0.6 * temperature;
distance = pulseWidth * 1e-6 * speed_of_sound / 2 * 100; // cm
多次测量取中值:进行5-7次测量,去掉最大最小值后取平均。
定时器溢出处理:当测量距离超过65cm时(1MHz计数):
c复制// 在TIM2全局中断中添加
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
overflowCount++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
测试环境:室温25℃,标准反射板
| 距离(cm) | 测量值(cm) | 误差(mm) |
|---|---|---|
| 10.0 | 10.2 | +2 |
| 50.0 | 50.1 | +1 |
| 100.0 | 99.8 | -2 |
| 200.0 | 200.3 | +3 |
问题1:测量结果不稳定
问题2:最大测量距离不足
问题3:短距离测量不准
多模块阵列:使用多个HC-SR04组成测距阵列,通过IO扩展器(如74HC595)控制Trig信号,共用同一个定时器捕获不同通道。
三维定位:配合舵机旋转模块,实现空间坐标测量:
c复制// 示例数据结构
typedef struct {
float distance;
uint16_t angle; // 当前舵机角度
uint32_t timestamp;
} SonarPoint;
c复制#define HISTORY_SIZE 5
float history[HISTORY_SIZE];
float adaptiveFilter(float newVal) {
static uint8_t index = 0;
history[index++] = newVal;
if(index >= HISTORY_SIZE) index = 0;
// 简单移动平均
float sum = 0;
for(uint8_t i=0; i<HISTORY_SIZE; i++) {
sum += history[i];
}
return sum / HISTORY_SIZE;
}
在实际部署中,我发现将模块倾斜10-15度安装可以减少地面反射干扰。另外,对于需要防水防尘的场合,可以用硅胶密封模块的超声波收发面,但会略微减小最大测量距离。