STM32 HAL库实战:F103C8T6驱动HC-SR04实现高精度超声波测距与状态机编程

📅 2026/7/15 1:51:09
STM32 HAL库实战:F103C8T6驱动HC-SR04实现高精度超声波测距与状态机编程
1. 项目背景与硬件准备第一次接触超声波测距是在大学机器人社团当时用Arduino驱动HC-SR04模块做避障小车结果发现测量结果总是飘忽不定。后来转用STM32配合状态机编程稳定性直接提升了一个量级。这次就用最经典的STM32F103C8T6蓝色药丸开发板带大家实现高精度测距。必备硬件清单STM32F103C8T6核心板某宝20元左右HC-SR04超声波模块注意区分新旧版本ST-Link V2下载器USB-TTL模块推荐CH340G杜邦线若干建议用20cm长度的特别提醒HC-SR04有GPIO/UART/IIC三种模式老版本只支持GPIO模式。我实测发现某宝有些商家混着发货购买时一定要确认版本。模块背面印有RCWL-9206芯片的是新版老版则是裸电路板加几个分立元件。2. HC-SR04工作原理深度解析这个模块的工作原理其实特别有意思——就像蝙蝠回声定位。当Trig引脚收到10μs以上的高电平脉冲后模块会发射8个40kHz的超声波脉冲这个频率人耳听不到同时Echo引脚会拉高。当超声波遇到障碍物返回被接收器检测到Echo引脚就会拉低。关键的时间差测量Echo高电平持续时间就是从发射到接收的往返时间T。声速在25℃空气中约346m/s温度补偿后面会讲所以距离 (T * 346) / 2。实测发现模块在2cm-450cm范围内精度可达±3mm但超过3米后反射信号会明显衰减。硬件连接注意点VCC接5V虽然STM32是3.3V电平但模块内部有电平转换Trig和Echo引脚建议接10KΩ上拉电阻避免将模块安装在金属表面超声波会被反射干扰3. CubeMX工程配置详解打开CubeMX选择STM32F103C8系列具体配置步骤如下时钟树配置外部晶振选择8MHz系统时钟设为72MHzAPB1总线36MHz保证TIM2的时钟是72MHz用于输入捕获GPIO设置Trig引脚配置为GPIO_Output推挽输出无上下拉Echo引脚配置为GPIO_Input上拉输入模式定时器关键配置// TIM2输入捕获参数 Prescaler 71 // 72MHz/(711)1MHz计数频率 Counter Mode Up Period 0xFFFF // 最大计数值 AutoReloadPreload Disable // 通道1配置 ICPolarity Rising Edge ICSelection Direct TI ICPrescaler No divided ICFilter 0串口配置USART1异步模式波特率1152008位数据位无校验位生成代码前记得勾选Generate peripheral initialization as a pair of .c/.h files选项这样外设代码会更清晰。4. 状态机编程实现测距流程传统轮询方式在超声波测距中会有严重缺陷——阻塞式等待Echo信号会导致系统卡死。这里采用状态机将测距过程分解为四个阶段状态转移图IDLE → TRIGGER → WAIT_ECHO → CAPTURE → CALCULATE具体代码实现精简版typedef enum { STATE_IDLE, STATE_TRIGGER, STATE_WAIT_RISING, STATE_WAIT_FALLING, STATE_CALCULATE } SR04_State; void SR04_Handler(void) { static SR04_State state STATE_IDLE; static uint32_t rise_time, fall_time; switch(state) { case STATE_IDLE: if(measure_flag) { HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET); state STATE_TRIGGER; trigger_timer HAL_GetTick(); } break; case STATE_TRIGGER: if(HAL_GetTick() - trigger_timer 10) { // 维持10us高电平 HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); __HAL_TIM_SET_CAPTUREPOLARITY(htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1); state STATE_WAIT_RISING; } break; // 其他状态处理... } }实测发现加入超时判断特别重要。我在回调函数中添加了超时检测if(HAL_GetTick() - echo_start_time 38) { // 38ms超时 HAL_TIM_IC_Stop_IT(htim2, TIM_CHANNEL_1); state STATE_IDLE; error_count; }5. 高精度时间测量技巧定时器输入捕获是精度关键这里有几个优化点时钟分频优化1MHz计数频率每计数1μs最大可测距离 65535μs * 346m/s / 2 ≈ 11.3米捕获中断处理void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { static uint32_t capture_buf[2]; if(htim-Instance TIM2) { switch(capture_state) { case 0: // 上升沿捕获 capture_buf[0] HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING); capture_state 1; break; case 1: // 下降沿捕获 capture_buf[1] HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); HAL_TIM_IC_Stop_IT(htim, TIM_CHANNEL_1); high_time_us capture_buf[1] - capture_buf[0]; capture_state 0; measure_ready 1; break; } } }温度补偿算法float get_compensated_distance(uint32_t time_us, float temp_C) { float speed 331.4f (0.606f * temp_C); // 声速公式 return (time_us * 1e-6f * speed) / 2.0f; }6. 常见问题与调试技巧问题1测量结果跳动大检查电源稳定性示波器看5V纹波添加10μF电解电容并联在模块VCC-GND软件端采用滑动平均滤波#define FILTER_SIZE 5 float distance_buf[FILTER_SIZE]; float filtered_distance 0; void update_distance(float new_val) { static uint8_t index 0; distance_buf[index] new_val; if(index FILTER_SIZE) index 0; float sum 0; for(int i0; iFILTER_SIZE; i) { sum distance_buf[i]; } filtered_distance sum / FILTER_SIZE; }问题2超过3米后数据不准调整模块安装角度建议15°倾斜在接收端添加聚音罩可用3D打印提高发射功率需修改模块硬件问题3偶尔出现70米等异常值添加超时判断前文已提及增加信号校验检查时间差是否合理硬件上在Echo脚加100pF电容滤波7. 进阶优化方向多模块协同// 使用一个定时器驱动多个模块 void multi_SR04_measure(void) { static uint8_t current_module 0; switch(current_module) { case 0: trigger_module1(); break; case 1: trigger_module2(); break; } current_module (current_module 1) % MODULE_COUNT; }DMA传输优化配置定时器触发DMA传输捕获数据减少CPU中断处理开销RTOS集成创建专用测距线程使用消息队列传递测量结果实际项目中我将这个方案用在了智能仓储机器人上配合卡尔曼滤波算法实现了±1mm的测量精度。最远在5米距离仍能稳定检测货架位置整套代码已经稳定运行超过2000小时。