1. 超声波测距原理与硬件连接超声波测距模块的工作原理其实特别像蝙蝠探路。模块先发射一束超声波声波遇到障碍物反射回来模块再接收这个回波。我们只要测量从发射到接收的时间差就能算出距离。这里有个生活小常识常温下声速约340m/s所以距离高电平时间×340/2。为什么要除以2因为声波走了个来回嘛常用的HC-SR04模块有四个引脚VCC接5V电源Trig脚接单片机任意GPIO用来触发测距Echo脚必须接支持输入捕获的定时器引脚用来测量高电平时间GND接地这里有个新手容易踩的坑Echo脚绝对不能随便接普通IO口我刚开始玩超声波时用普通GPIO配合外部中断来测脉宽结果误差大到能测出1米的桌子变成1.5米。后来才发现定时器的输入捕获功能有硬件级的时间戳记录精度能达到微秒级。2. CubeMX定时器配置详解打开CubeMX新建工程时建议先做三件事在Pinout视图里把调试用的串口打开比如USART1在Clock Configuration里把主频调到最大F4系列建议168MHz找到适合的定时器我常用TIM2或TIM5因为它们是32位计数器具体到输入捕获配置选择TIMx的某个通道比如TIM5_CH1模式选择Input Capture direct mode分频系数(Prescaler)设为84-1假设主频84MHz这样计数器每1MHz计数自动重装载值(ARR)设为0xFFFFFFFF32位最大值捕获极性选Rising Edge别忘了开启NVIC中断有个实用技巧在Parameter Settings标签页把auto-reload preload设为Enable。这样修改ARR值时不会立即生效能避免计数异常。3. HAL库中断处理实战输入捕获的精髓就在中断处理先看这个结构体uint8_t capture_status 0; // 状态寄存器 uint32_t capture_value; // 捕获值我用status的bit7表示捕获完成bit6表示当前是上升沿还是下降沿bit5~0记录溢出次数。关键的中断回调函数有两个// 定时器溢出中断用来处理计数器溢出 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if((capture_status 0x80) 0) { // 未完成捕获 if(capture_status 0x40) { // 正在捕获高电平 if((capture_status 0x3F) 0x3F) { // 溢出太多直接标记完成 capture_status | 0x80; } else { capture_status; // 溢出次数1 } } } } // 捕获中断处理边沿变化 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(capture_status 0x40) { // 之前是上升沿现在是下降沿 capture_status | 0x80; // 标记完成 capture_value HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); } else { // 首次捕获上升沿 capture_status 0x40; // 标记上升沿 __HAL_TIM_SET_COUNTER(htim, 0); // 计数器清零 __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING); } }4. 距离计算与误差优化拿到高电平时间后距离计算看似简单distance (capture_value * 340) / 2 / 10000; // 单位cm但实际上要考虑三个优化点温度补偿声速随温度变化可用DS18B20测温后修正speed 331.4 0.6 * temperature; // m/s溢出补偿当高电平时间很长时要加上溢出时间total_time (capture_status 0x3F) * 0xFFFFFFFF capture_value;数字滤波连续采样5次取中值int compare(const void *a, const void *b) { return (*(uint32_t*)a - *(uint32_t*)b); } qsort(values, 5, sizeof(uint32_t), compare); median values[2];实测发现在2米范围内误差可以控制在±1cm内。超过3米后建议改用TOF传感器超声波受环境影响会变大。5. 串口调试技巧分享调试时我习惯用printf输出多维数据printf(Cnt:%lu | St:0x%02X | Dist:%.2fcm\r\n, capture_value, capture_status, distance);这样能同时看到原始数据和计算结果。如果发现数据跳动严重可以检查Trig信号宽度建议10us以上在Echo脚加100nF电容滤波避开电机、继电器等干扰源有个血的教训曾经因为没在电源脚加0.1uF去耦电容测距结果随机跳变折腾了一整天6. 进阶应用——多探头协同单个超声波模块有探测盲区我做过一个四探头方案用TIM1~TIM4分别接四个Echo脚在CubeMX里配置全局同步HAL_TIMEx_MasterConfigSynchronization(htim1, sMasterConfig);采用分时触发策略避免相互干扰这样实现的360°测距系统用在智能小车上效果非常好。关键是要在代码里做好状态机管理避免多个探头的中断互相抢占。最后提醒超声波对玻璃、海绵等材质的物体检测效果较差这是物理特性决定的。在实际项目中最好结合红外传感器做数据融合。
第六节:STM32输入捕获实战——超声波测距应用(基于CubeMX与HAL库)
1. 超声波测距原理与硬件连接超声波测距模块的工作原理其实特别像蝙蝠探路。模块先发射一束超声波声波遇到障碍物反射回来模块再接收这个回波。我们只要测量从发射到接收的时间差就能算出距离。这里有个生活小常识常温下声速约340m/s所以距离高电平时间×340/2。为什么要除以2因为声波走了个来回嘛常用的HC-SR04模块有四个引脚VCC接5V电源Trig脚接单片机任意GPIO用来触发测距Echo脚必须接支持输入捕获的定时器引脚用来测量高电平时间GND接地这里有个新手容易踩的坑Echo脚绝对不能随便接普通IO口我刚开始玩超声波时用普通GPIO配合外部中断来测脉宽结果误差大到能测出1米的桌子变成1.5米。后来才发现定时器的输入捕获功能有硬件级的时间戳记录精度能达到微秒级。2. CubeMX定时器配置详解打开CubeMX新建工程时建议先做三件事在Pinout视图里把调试用的串口打开比如USART1在Clock Configuration里把主频调到最大F4系列建议168MHz找到适合的定时器我常用TIM2或TIM5因为它们是32位计数器具体到输入捕获配置选择TIMx的某个通道比如TIM5_CH1模式选择Input Capture direct mode分频系数(Prescaler)设为84-1假设主频84MHz这样计数器每1MHz计数自动重装载值(ARR)设为0xFFFFFFFF32位最大值捕获极性选Rising Edge别忘了开启NVIC中断有个实用技巧在Parameter Settings标签页把auto-reload preload设为Enable。这样修改ARR值时不会立即生效能避免计数异常。3. HAL库中断处理实战输入捕获的精髓就在中断处理先看这个结构体uint8_t capture_status 0; // 状态寄存器 uint32_t capture_value; // 捕获值我用status的bit7表示捕获完成bit6表示当前是上升沿还是下降沿bit5~0记录溢出次数。关键的中断回调函数有两个// 定时器溢出中断用来处理计数器溢出 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if((capture_status 0x80) 0) { // 未完成捕获 if(capture_status 0x40) { // 正在捕获高电平 if((capture_status 0x3F) 0x3F) { // 溢出太多直接标记完成 capture_status | 0x80; } else { capture_status; // 溢出次数1 } } } } // 捕获中断处理边沿变化 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(capture_status 0x40) { // 之前是上升沿现在是下降沿 capture_status | 0x80; // 标记完成 capture_value HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); } else { // 首次捕获上升沿 capture_status 0x40; // 标记上升沿 __HAL_TIM_SET_COUNTER(htim, 0); // 计数器清零 __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING); } }4. 距离计算与误差优化拿到高电平时间后距离计算看似简单distance (capture_value * 340) / 2 / 10000; // 单位cm但实际上要考虑三个优化点温度补偿声速随温度变化可用DS18B20测温后修正speed 331.4 0.6 * temperature; // m/s溢出补偿当高电平时间很长时要加上溢出时间total_time (capture_status 0x3F) * 0xFFFFFFFF capture_value;数字滤波连续采样5次取中值int compare(const void *a, const void *b) { return (*(uint32_t*)a - *(uint32_t*)b); } qsort(values, 5, sizeof(uint32_t), compare); median values[2];实测发现在2米范围内误差可以控制在±1cm内。超过3米后建议改用TOF传感器超声波受环境影响会变大。5. 串口调试技巧分享调试时我习惯用printf输出多维数据printf(Cnt:%lu | St:0x%02X | Dist:%.2fcm\r\n, capture_value, capture_status, distance);这样能同时看到原始数据和计算结果。如果发现数据跳动严重可以检查Trig信号宽度建议10us以上在Echo脚加100nF电容滤波避开电机、继电器等干扰源有个血的教训曾经因为没在电源脚加0.1uF去耦电容测距结果随机跳变折腾了一整天6. 进阶应用——多探头协同单个超声波模块有探测盲区我做过一个四探头方案用TIM1~TIM4分别接四个Echo脚在CubeMX里配置全局同步HAL_TIMEx_MasterConfigSynchronization(htim1, sMasterConfig);采用分时触发策略避免相互干扰这样实现的360°测距系统用在智能小车上效果非常好。关键是要在代码里做好状态机管理避免多个探头的中断互相抢占。最后提醒超声波对玻璃、海绵等材质的物体检测效果较差这是物理特性决定的。在实际项目中最好结合红外传感器做数据融合。