1. 超声波测距模块原理与硬件选型
1.1 超声波测距物理原理
超声波测距基于声波反射原理,工作时模块发射40kHz的超声波脉冲,遇到障碍物后反射回接收器。通过测量发射与接收之间的时间差Δt,利用声速v(常温下约340m/s)即可计算距离:距离 = (v × Δt)/2。除以2是因为声波经历了往返路程。
HC-SR04模块的典型参数:
- 工作电压:DC 5V
- 静态电流:<2mA
- 探测角度:<15°
- 探测距离:2cm-400cm
- 精度:约3mm
注意:实际测量中,声速会随温度变化而改变,每升高1℃声速增加0.6m/s。高精度应用需加入温度补偿。
1.2 硬件连接方案
模块与STM32的典型连接方式:
- VCC接5V电源
- GND共地
- TRIG接PA15(任意GPIO输出引脚)
- ECHO接PB3(TIM2_CH2输入捕获引脚)
电路设计注意事项:
- 电源需加100nF去耦电容
- ECHO信号线建议串联100Ω电阻保护IO口
- 长距离传输时考虑加入74HC14施密特触发器整形
2. STM32定时器输入捕获配置
2.1 定时器基础配置
使用TIM2的基本参数计算:
- 系统时钟72MHz
- 预分频值Prescaler=71 → 计数频率=72MHz/(71+1)=1MHz
- 自动重装载值ARR=0xFFFF(最大65535)
- 计数周期=65536/1MHz=65.536ms > 33ms(满足模块要求)
配置要点:
c复制htim2.Instance = TIM2;
htim2.Init.Prescaler = 71;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 0xFFFF;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
2.2 双通道捕获配置技巧
创新使用通道1间接捕获下降沿+通道2直接捕获上升沿的方案:
-
通道2配置:
- Input Capture Direct Mode
- IC Selection = TIM_ICSELECTION_DIRECTTI
- ICPolarity = TIM_ICPOLARITY_RISING
-
通道1配置:
- Input Capture Indirect Mode
- IC Selection = TIM_ICSELECTION_INDIRECTTI
- ICPolarity = TIM_ICPOLARITY_FALLING
关键寄存器操作:
c复制// 通道2捕获上升沿配置
TIM_ICInitTypeDef sConfigIC;
sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_2);
// 通道1捕获下降沿配置
sConfigIC.ICPolarity = TIM_ICPOLARITY_FALLING;
sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI;
HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);
3. 核心代码实现与优化
3.1 触发信号生成优化
原10μs触发信号的软件延时存在不稳定性,改进方案:
- 硬件PWM方案(推荐):
c复制// 配置TIM3_CH1输出单脉冲
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 10); // 10us脉冲
HAL_TIM_GenerateEvent(&htim3, TIM_EVENTSOURCE_UPDATE);
- 精确软件延时方案:
c复制void delay_us(uint16_t us)
{
uint32_t ticks = us * (SystemCoreClock / 1000000) / 8;
while(ticks--);
}
3.2 捕获处理流程详解
完整测量流程代码分析:
c复制// 1. 复位定时器计数器
__HAL_TIM_SET_COUNTER(&htim2, 0);
// 2. 启动双通道捕获
HAL_TIM_IC_Start(&htim2, TIM_CHANNEL_1);
HAL_TIM_IC_Start(&htim2, TIM_CHANNEL_2);
// 3. 发送触发脉冲
CS100A_TRIG_Start();
// 4. 等待上升沿捕获
while(__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_CC2) == RESET);
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_CC2);
start_time = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_2);
// 5. 等待下降沿捕获
while(__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_CC1) == RESET);
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_CC1);
end_time = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
// 6. 停止捕获
HAL_TIM_IC_Stop(&htim2, TIM_CHANNEL_1);
HAL_TIM_IC_Stop(&htim2, TIM_CHANNEL_2);
3.3 距离计算与滤波处理
改进的距离计算算法:
c复制#define SOUND_SPEED 340.0f // m/s
// 移动平均滤波
static float distance_buf[5] = {0};
static uint8_t index = 0;
float get_distance(uint32_t pulse_cnt)
{
// 基础计算
float temp = pulse_cnt * 1e-6f * SOUND_SPEED / 2.0f;
// 滑动滤波
distance_buf[index++] = temp;
if(index >= 5) index = 0;
// 计算平均值
float sum = 0;
for(int i=0; i<5; i++) {
sum += distance_buf[i];
}
return sum / 5.0f;
}
4. 实战调试经验与问题排查
4.1 常见问题速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 距离值固定为0 | 1. 触发信号未发出 2. 捕获通道配置错误 |
1. 检查TRIG引脚波形 2. 确认TIM通道极性设置 |
| 测量值跳动大 | 1. 电源噪声 2. 物体表面吸收声波 |
1. 加强电源滤波 2. 对准反射良好的物体 |
| 最大距离受限 | 1. ARR值设置过小 2. 模块功率不足 |
1. 增大ARR值 2. 检查VCC电压 |
| 测量值偏小 | 1. 声速参数不准 2. 温度影响 |
1. 加入温度补偿 2. 校准声速参数 |
4.2 调试技巧实录
-
示波器诊断法:
- 同时观察TRIG和ECHO信号
- 确认触发脉冲宽度≥10μs
- 检查回波信号是否干净无毛刺
-
定时器计数验证:
c复制// 在main循环中添加测试代码
__HAL_TIM_SET_COUNTER(&htim2, 0);
HAL_Delay(1);
uint32_t cnt = __HAL_TIM_GET_COUNTER(&htim2);
printf("1ms计数值=%lu\n", cnt); // 应为1000左右
- 边界情况处理:
c复制// 添加超时检测
uint32_t timeout = HAL_GetTick();
while(__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_CC2) == RESET) {
if(HAL_GetTick() - timeout > 100) {
printf("Echo timeout!\n");
break;
}
}
4.3 性能优化建议
-
中断驱动方案:
- 配置捕获中断替代轮询
- 减少CPU占用率
-
DMA传输:
- 使用DMA将捕获值直接传输到内存
- 适合高速连续采样
-
温度补偿实现:
c复制float get_speed(float temp_c)
{
return 331.4f + 0.6f * temp_c; // 声速温度公式
}
- 多模块协同:
- 分时复用触发信号
- 通过IO扩展器连接多个模块
在完成基础功能后,我通常会进行以下验证步骤:
- 用尺子实测多个距离点,记录模块输出
- 绘制误差曲线,计算线性度
- 在不同环境温度下测试稳定性
- 长时间运行测试可靠性
实际项目中,超声波测距最关键的三个细节:
- 触发信号必须干净准确
- 定时器配置要确保不溢出
- 必须对回波信号进行有效滤波
最后分享一个实用技巧:当测量近距离物体时,可以在代码中添加死区过滤,避免模块自身振动导致的误检测。例如:
c复制if(distance < 0.02f) { // 2cm以下视为无效
distance = 0;
}