STM32 HAL库驱动HC-SR04:从阻塞轮询到中断捕获的工程实践

张开发
2026/4/17 22:53:22 15 分钟阅读

分享文章

STM32 HAL库驱动HC-SR04:从阻塞轮询到中断捕获的工程实践
1. HC-SR04超声波模块基础认知HC-SR04作为嵌入式领域最常用的超声波测距模块其工作原理简单却暗藏玄机。模块正面并排的两个金属圆柱体一个是发射器T一个是接收器R工作时就像蝙蝠的声波系统。当Trig引脚接收到10μs以上的高电平脉冲后模块会自动发射8个40kHz的超声波脉冲同时Echo引脚会拉高电平直到接收到回波后才会拉低。这个高电平持续时间就是超声波往返时间通过公式距离(高电平时间×声速)/2就能算出实际距离。市面上常见的有三种工作模式GPIO模式最基础的触发-回响方式需要手动控制Trig和测量Echo高电平时间UART模式模块内部集成处理芯片直接输出距离数据帧I2C模式通过地址寻址读取距离寄存器我在实际项目中发现GPIO模式虽然接线简单只需VCC、GND、Trig、Echo四线但不同厂家的模块性能差异很大。曾遇到过某批次模块在3.3V下测距不稳定后来改用支持宽电压3.3V-5V的改良版才解决问题。模块背面那些0603封装的电阻就是模式选择的关键焊接不同的组合可以切换通信方式。2. 阻塞式轮询方案剖析2.1 硬件连接与CubeMX配置使用STM32F103C8T6开发板时典型接线如下VCC → 3.3V注意模块电压范围GND → 共地Trig → PB10任意GPIOEcho → PA3需连接定时器通道在CubeMX中的关键配置步骤时钟树配置HCLK为72MHz最大化定时器精度配置Trig引脚为GPIO_Output选择TIM2通道1对应PA3为输入捕获模式定时器预分频设为7172MHz/(711)1MHz即1μs计数自动重装载值设为6553516位最大值// 生成的部分初始化代码 GPIO_InitStruct.Pin GPIO_PIN_10; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); htim2.Instance TIM2; htim2.Init.Prescaler 71; htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 65535; HAL_TIM_IC_Init(htim2);2.2 阻塞式代码实现传统轮询法的核心代码如下这种实现简单粗暴但问题明显void GetDistance(void) { HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET); delay_us(12); // 实测10us可能不够稳定 HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); while(HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_RESET); uint32_t start HAL_GetTick(); while(HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_SET); uint32_t duration HAL_GetTick() - start; float distance duration * 0.034 / 2; // 声速340m/s→0.034cm/us }我在智能小车项目初期就采用这种方案很快发现了三大致命缺陷CPU资源浪费两个while循环会独占CPU实测在4米最大测距时可能阻塞30ms多任务冲突在RTOS环境中会阻塞其他任务运行精度受限依赖HAL_GetTick()的1ms分辨率近距离测量误差大3. 中断捕获方案进阶3.1 定时器输入捕获原理输入捕获是定时器的杀手锏功能其工作原理就像精准的电子秒表配置定时器通道为输入捕获模式上升沿触发时硬件自动记录当前计数器值CCR1下降沿触发时再次捕获计数器值CCR2两次捕获值之差即为高电平时间CubeMX中需要额外开启两项配置NVIC中使能TIM2全局中断定时器设置中勾选Input Capture direct mode3.2 中断驱动代码框架创建hcsr04.h头文件定义数据结构typedef struct { uint8_t edge_state; // 0-等待上升沿 1-等待下降沿 uint16_t overflow_cnt; uint32_t rise_time; uint32_t fall_time; float distance_cm; } HCSR04_TypeDef;核心中断处理逻辑void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim-Instance TIM2) { uint32_t cnt HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); if(hcsr04.edge_state 0) { // 上升沿 hcsr04.rise_time cnt; hcsr04.overflow_cnt 0; __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING); hcsr04.edge_state 1; } else { // 下降沿 hcsr04.fall_time cnt; uint32_t total (hcsr04.overflow_cnt 16) hcsr04.fall_time - hcsr04.rise_time; hcsr04.distance_cm total * 0.034 / 2; __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); hcsr04.edge_state 0; } } } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim-Instance TIM2) { hcsr04.overflow_cnt; } }3.3 性能对比实测在72MHz主频的STM32F103上实测数据指标阻塞轮询法中断捕获法CPU占用率最高100%1%最大响应延迟30ms微秒级测距精度±1cm±0.3cm多任务兼容性极差优秀特别在RTOS环境中中断方案的优势更加明显。我在FreeRTOS项目中测试即使创建5个任务并行运行测距模块依然能稳定工作而阻塞方案会导致系统明显卡顿。4. 工程优化实践4.1 软件滤波处理超声波易受环境干扰需要添加滤波算法。推荐组合方案中值滤波连续采样5次取中间值滑动平均保留最近10次记录求平均野值剔除超过±15%突变视为无效#define FILTER_SIZE 10 float distance_buf[FILTER_SIZE]; float Filter_Distance(float raw) { static uint8_t index 0; distance_buf[index] raw; if(index FILTER_SIZE) index 0; float sum 0; for(uint8_t i0; iFILTER_SIZE; i) { sum distance_buf[i]; } return sum / FILTER_SIZE; }4.2 低功耗优化技巧对于电池供电设备可采取以下措施动态调整采样率近距离时100ms采样远距离时500ms采样模块电源管理通过MOS管控制VCC供电测量前才上电定时器自动关闭连续5次无回波自动休眠void Power_Save_Mode(void) { HAL_GPIO_WritePin(PWR_CTRL_GPIO_Port, PWR_CTRL_Pin, GPIO_PIN_RESET); HAL_TIM_Base_Stop_IT(htim2); } void Wake_Up(void) { HAL_GPIO_WritePin(PWR_CTRL_GPIO_Port, PWR_CTRL_Pin, GPIO_PIN_SET); HAL_Delay(50); // 等待模块稳定 HAL_TIM_Base_Start_IT(htim2); }4.3 多模块协同方案当需要多个HC-SR04时如360°避障推荐两种方案分时复用每个模块间隔20ms触发共用同一个Echo引脚独立定时器为每个模块分配独立定时器如TIM2TIM3TIM4我曾用方案1实现四路超声波雷达关键代码如下void Multi_Measurement(void) { static uint8_t current_module 0; switch(current_module) { case 0: HAL_GPIO_WritePin(TRIG1_GPIO_Port, TRIG1_Pin, GPIO_PIN_SET); delay_us(12); HAL_GPIO_WritePin(TRIG1_GPIO_Port, TRIG1_Pin, GPIO_PIN_RESET); break; // 其他模块同理... } current_module (current_module 1) % 4; }在调试多模块时发现一个重要细节必须确保前一个模块的回波完全结束后再触发下一个否则会出现信号干扰。实测建议间隔至少15ms以上。

更多文章