【STM32F103C8T6】【HAL库】基于输入捕获双通道的HC-SR04超声波测距实战解析

张开发
2026/6/1 8:24:33 15 分钟阅读
【STM32F103C8T6】【HAL库】基于输入捕获双通道的HC-SR04超声波测距实战解析
1. 从零认识HC-SR04超声波模块第一次接触HC-SR04超声波测距模块时我完全被它的小巧身材和简单操作惊艳到了。这个只有拇指大小的模块居然能实现2cm-400cm的非接触测距精度还达到了惊人的3mm对于做智能小车避障、液位检测这类项目来说简直是神器。模块正面并排排列着两个金属圆筒就像一对小眼睛。左边是发射器T负责发出40kHz的超声波右边是接收器R专门捕捉反射回来的声波。底部四个引脚中VCC和GND负责供电Trig是触发引脚Echo则是回响信号输出。实际接线时有个细节要注意虽然模块标注工作电压5V但Echo引脚输出的高电平也是5V而STM32的IO口耐受电压是3.3V。我刚开始没注意这点直接连接导致测量值异常后来加了个1kΩ电阻分压才解决问题。超声波测距原理其实很生活化——就像我们在山谷喊话听回声。模块工作时先给Trig引脚至少10μs的高电平触发信号我习惯用50μs更保险这时发射器会发出8个周期的40kHz超声波脉冲。当接收器检测到回波时Echo引脚就会输出高电平这个高电平的持续时间就是超声波往返的时间差。用这个时间乘以声速340m/s再除以2就是被测物体的距离。2. STM32硬件连接与配置要点我的硬件配置方案是STM32F103C8T6最小系统板HC-SR04模块。Trig引脚接PA7Echo接PB0这里PB0必须连接到定时器TIM3的通道3CH3引脚因为后续我们要用输入捕获功能。有次我错把Echo接到PA6调试半天才发现引脚映射不对这个坑大家一定要避开。在CubeMX中配置时PA7设为GPIO_OutputPB0要配置为TIM3_CH3的输入捕获模式。定时器时钟源选择内部时钟分频系数设为7172MHz主频下得到1MHz计数频率每个计数周期正好1μs。自动重装载值设为最大值65535这样定时器溢出周期就是65.536ms足够测量最远4米的距离。特别提醒一定要开启TIM3的全局中断我在初期调试时忘了这个设置导致永远进不了中断回调函数。还有时钟树的配置确保APB1定时器时钟是72MHz如果看到定时器时钟只有36MHz需要检查是否开启了时钟倍频。3. 双通道输入捕获的精妙设计传统单通道输入捕获方案需要频繁切换边沿极性不仅增加代码复杂度还容易丢失信号边沿。我的方案是同时启用TIM3的通道3和通道4通道3捕获上升沿通道4捕获下降沿相当于硬件自动完成了边沿检测。具体实现时有个关键技巧通道3采用直接输入DIRECTTI通道4使用间接输入INDIRECTTI。这是因为同一个定时器的不同通道需要共用输入引脚通过间接输入可以将通道4映射到通道3的引脚上。配置代码如下TIM_IC_InitTypeDef sConfigIC; // 上升沿捕获配置通道3 sConfigIC.ICPolarity TIM_ICPOLARITY_RISING; sConfigIC.ICSelection TIM_ICSELECTION_DIRECTTI; HAL_TIM_IC_ConfigChannel(htim3, sConfigIC, TIM_CHANNEL_3); // 下降沿捕获配置通道4 sConfigIC.ICPolarity TIM_ICPOLARITY_FALLING; sConfigIC.ICSelection TIM_ICSELECTION_INDIRECTTI; HAL_TIM_IC_ConfigChannel(htim3, sConfigIC, TIM_CHANNEL_4);为了处理计数器溢出我开启了定时器更新中断。每次溢出时全局变量counter加1。这样计算高电平时间时可以用公式(counter65536 CCR4) - (counter65536 CCR3)。实测发现加入溢出计数后测距范围可以扩展到10米以上远超模块标称的4米。4. 中断处理与距离计算实战在HAL_TIM_IC_CaptureCallback回调函数中需要区分两种捕获事件。当通道3捕获到上升沿时记录当前计数值并加上溢出补偿通道4捕获到下降沿时同样处理。这里有个易错点HAL库中通道判断要用htim-Channel而不是传入的Channel参数。void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim-Instance TIM3){ if(htim-Channel HAL_TIM_ACTIVE_CHANNEL_3){ rise_time HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_3) (overflow_count * 65536); } if(htim-Channel HAL_TIM_ACTIVE_CHANNEL_4){ fall_time HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_4) (overflow_count * 65536); pulse_width fall_time - rise_time; // 单位微秒 } } }距离计算时要特别注意单位换算。我优化后的计算公式是distance_cm (pulse_width * 0.034)/2。其中0.034是把声速340m/s换算为0.034cm/μs除以2是因为超声波是往返时间。为了提高精度建议连续测量5次取中值并加入温度补偿声速随温度变化约为0.6m/s/℃。5. 调试过程中的血泪教训第一次上电测试时模块毫无反应。用逻辑分析仪抓取信号才发现Trig触发脉冲宽度不够。虽然手册说最少10μs但实际测试发现小于20μs时模块响应不稳定。后来固定用50μs脉冲可靠性大幅提升。另一个坑是中断优先级。最初我把TIM3中断优先级设得太低导致在串口打印调试信息时经常丢失捕获事件。现在我的标准配置是TIM3中断优先级设为2数值越小优先级越高确保超声波数据捕获的实时性。最棘手的bug是测量值偶尔出现巨大跳变。经过一周的排查发现是未及时清除定时器溢出标志位。解决方法是在每次触发测量前先调用__HAL_TIM_CLEAR_FLAG(htim3, TIM_FLAG_UPDATE)。这个细节在数据手册里藏得很深希望读者能避开这个坑。6. 完整工程代码解析工程包含三个关键文件main.c完成系统初始化和主循环hc_sr04.c封装超声波模块驱动hc_sr04.h定义引脚和接口。我的代码风格是尽量使用HAL库函数保证可移植性。在hc_sr04.h中我用宏定义集中管理硬件配置#define TRIG_GPIO_PORT GPIOA #define TRIG_PIN GPIO_PIN_7 #define ECHO_TIM TIM3 #define ECHO_RISING_CH TIM_CHANNEL_3 #define ECHO_FALLING_CH TIM_CHANNEL_4主循环中采用状态机设计每100ms触发一次测量while(1){ static uint32_t last_tick 0; if(HAL_GetTick() - last_tick 100){ last_tick HAL_GetTick(); float dist HC_SR04_GetDistance(); printf(Distance: %.1f cm\r\n, dist); } }对于需要更高精度的场合我后来改进了算法加入数字滤波处理去掉最大最小值后取平均增加超时检测当超过33ms未收到回波时返回错误代码添加硬件去抖功能通过配置输入捕获滤波器的参数为0x08有效消除信号抖动。

更多文章