HC-SR04超声波测距库:硬件捕获+状态机实现非阻塞高精度测距
1. 项目概述Simple_HC-SR04_Control是一个面向嵌入式微控制器MCU的轻量级、零依赖型超声波测距库专为驱动 HC-SR04 模块而设计。该库不依赖 HAL 库、CMSIS-RTOS 或任何第三方中间件仅使用标准 C99 和 MCU 原生外设资源如通用定时器、GPIO适用于 STM32F0/F1/F3/F4、GD32、NXP KL25Z、ESP32裸机模式、RA4M1 等主流 Cortex-M 系列平台亦可轻松移植至 8051、AVR 或 RISC-V 架构如 GD32VF103。HC-SR04 是工业界最普及的低成本超声波测距模块之一其工作原理基于声波飞行时间Time-of-Flight, ToF通过 TRIG 引脚输入一个 ≥10μs 的高电平脉冲触发发射模块内部电路驱动超声换能器发出 40kHz 方波脉冲随后在 ECHO 引脚输出一个与距离成正比的高电平信号——该高电平持续时间即为声波从发射到接收回波的往返时间t。根据空气中声速约 340 m/s25℃单程距离 d (340 × t) / 2 170 × t单位米其中 t 单位为秒。实际工程中常简化为距离cm 脉宽μs× 0.017因 340 m/s 34000 cm/s单程系数为 34000 / 2 / 10⁶ 0.017 cm/μs。本库的核心价值在于规避传统轮询或阻塞式实现的缺陷❌ 轮询 ECHO 引脚电平变化 → 浪费 CPU 周期无法响应其他中断实时性差❌while(GPIO_ReadInputDataBit(...) SET)→ 在无回波如目标过远、吸音表面时导致无限等待系统挂死❌ 使用软件延时测量脉宽 → 定时精度受编译器优化、中断嵌套影响误差可达数十微秒对应距离误差 0.5 mm❌ 未处理超时与异常状态 → 实际部署中易因接线松动、模块故障、环境干扰导致系统不可靠。Simple_HC-SR04_Control采用“硬件定时器捕获 中断驱动 状态机”三位一体设计确保✅ 单次测量全程非阻塞CPU 可执行其他任务✅ 精确捕获 ECHO 高电平起始与结束时刻分辨率由定时器时钟决定典型值 1 μs✅ 内置 65ms 全局超时对应理论最大测距约 11m防止单次测量失控✅ 支持连续自动测量模式Auto-Trigger适用于液位监控、避障机器人等场景✅ 提供线程安全封装可选 FreeRTOS 集成层支持多任务并发调用✅ 所有 API 接口无动态内存分配栈空间占用恒定 ≤ 32 字节满足 ASIL-B 级别功能安全要求。2. 硬件接口与电气特性2.1 HC-SR04 引脚定义与连接规范引脚类型电压范围功能说明MCU 连接建议VCC电源5.0 V ± 0.5 V模块供电必须接 5V不可接 3.3V内部稳压电路需 5V 启动GND地—电源地与 MCU 共地建议星型接地TRIG输入3.3V/5V TTL 兼容触发信号≥10μs 高电平脉冲推挽输出 GPIO上拉至 VCC防浮空ECHO输出3.3V TTL 电平回波信号高电平持续时间 往返飞行时间必须经电平转换或分压若 MCU I/O 不耐 5V⚠️ 关键警告HC-SR04 的 ECHO 引脚在模块内部由 5V 电源驱动实测输出高电平为 4.8–5.0 V。若直接连接至 STM32F103 等仅支持 3.3V 输入的 MCU将永久损坏 GPIO 引脚。强制要求采取以下任一防护措施方案 A推荐ECHO → 10kΩ 上拉至 3.3V 10kΩ 下拉至 GND → 分压后接入 MCU输出 ≈ 2.4V兼容 3.3V 逻辑方案 BECHO → 1N4148 钳位二极管阳极接 ECHO阴极接 3.3V→ 限幅至 3.3V 0.7V方案 C使用 TXS0108E 等双向电平转换芯片。2.2 MCU 外设资源需求外设数量配置要求用途GPIO2TRIG推挽输出ECHO浮空输入启用内部上拉/下拉依电路而定信号收发通用定时器TIMx116/32 位支持输入捕获IC1/IC2、更新中断UEV、主从模式精确测量 ECHO 脉宽NVIC1TIMx_CC_IRQn捕获中断、TIMx_UP_IRQn更新中断中断服务调度✅ 典型资源映射以 STM32F103C8T6 为例TRIG → PA0GPIOA, Pin 0ECHO → PA1GPIOA, Pin 1定时器 → TIM2APB1, 72MHzTIM2_CH1 → PA0复用为 TRIG 输出❌ 错误TRIG 为纯输出无需复用ECHO 应接 TIM2_CH2PA1用于输入捕获。3. 核心 API 接口详解库提供 6 个核心函数全部声明于hc_sr04.h实现位于hc_sr04.c。所有函数均返回int8_t状态码返回值含义处理建议0成功读取last_distance_cm获取结果-1初始化失败GPIO/TIM 配置错误检查 RCC 使能、引脚重映射、时钟树配置-2测量超时65ms无有效回波检查接线、目标距离、环境噪声、模块供电-3捕获溢出ECHO 高电平 65535 μs降低定时器预分频器PSC或改用 32 位定时器-4状态冲突前次测量未完成即发起新测量增加调用间隔或启用自动模式3.1 初始化函数int8_t HC_SR04_Init(TIM_TypeDef* tim, uint32_t tim_clk_freq, GPIO_TypeDef* trig_port, uint16_t trig_pin, GPIO_TypeDef* echo_port, uint16_t echo_pin, uint8_t echo_ch); // echo_ch: TIM_CHANNEL_1 ~ _4参数说明tim: 定时器基地址如TIM2,TIM3tim_clk_freq: 定时器时钟频率Hz需与 RCC 配置一致如 APB136MHz → TIM2_CLK36MHztrig_port/echo_port: GPIO 端口GPIOA,GPIOB...trig_pin/echo_pin: GPIO 引脚号GPIO_PIN_0,GPIO_PIN_1...echo_ch: ECHO 信号所连的定时器通道决定捕获引脚内部执行流程配置 TRIG 引脚为GPIO_MODE_OUTPUT_PP推挽输出速度GPIO_SPEED_FREQ_HIGH配置 ECHO 引脚为GPIO_MODE_INPUT浮空输入禁用上下拉由外部电路决定初始化定时器计数模式向上计数预分频器PSC设为(tim_clk_freq / 1000000) - 1→ 实现 1μs 计数精度自动重装载值ARR0xFFFF65535覆盖 0–65.535ms通道echo_ch配置为输入捕获模式滤波器采样 4 次抗毛刺使能定时器更新中断TIM_IT_UPDATE和捕获中断TIM_IT_CCx启动定时器HAL_TIM_Base_Start_IT()或寄存器操作。 工程提示若 MCU 无硬件输入捕获如某些低端 8-bit MCU本库提供#define HC_SR04_SOFTWARE_CAPTURE宏开关启用基于 SysTick 的边沿检测软捕获精度降至 ±5μs仍满足 1cm 级应用。3.2 单次触发测量int8_t HC_SR04_Trigger(void);执行逻辑拉高 TRIG 引脚 →HAL_GPIO_WritePin(trig_port, trig_pin, GPIO_PIN_SET)精确延时 15μs使用__NOP()或DWT_CYCCNT微秒级延时拉低 TRIG 引脚 →HAL_GPIO_WritePin(..., GPIO_PIN_RESET)设置内部状态机为HC_SR04_STATE_WAITING_RISE返回0立即返回不等待结果。 源码关键片段hc_sr04.cstatic void hc_sr04_trigger_pulse(void) { HAL_GPIO_WritePin(hc_trig_port, hc_trig_pin, GPIO_PIN_SET); for(volatile uint8_t i 0; i 3; i) __NOP(); // ~15μs 72MHz HAL_GPIO_WritePin(hc_trig_port, hc_trig_pin, GPIO_PIN_RESET); }3.3 获取测量结果int8_t HC_SR04_GetDistance(uint16_t* distance_cm);行为说明若当前处于HC_SR04_STATE_IDLE测量完成则将计算后的距离cm写入*distance_cm返回0若仍在测量中HC_SR04_STATE_WAITING_RISE/_FALL返回-4若超时返回-2且*distance_cm 0该函数线程安全内部使用原子操作保护共享变量。距离计算公式// 捕获值差 ECHO 高电平持续计数值 uint32_t pulse_width_us (cap_fall - cap_rise) * 1000000U / hc_tim_clk_freq; // 转换为厘米四舍五入 *distance_cm (uint16_t)((pulse_width_us * 17U 500U) / 1000U); // 0.017 cm/μs 17/10003.4 自动测量模式控制int8_t HC_SR04_EnableAutoMode(uint16_t interval_ms); // 启用interval_ms ∈ [20, 1000] int8_t HC_SR04_DisableAutoMode(void); // 禁用工作原理启用后库利用定时器更新中断TIM_IT_UPDATE周期性触发测量interval_ms设定两次触发间隔最小 20msHC-SR04 规格书要求中断服务程序ISR中调用hc_sr04_trigger_pulse()并重载 ARR 实现精确周期结果通过回调函数HC_SR04_OnDistanceReady(uint16_t dist_cm)通知用户需用户实现。✅ FreeRTOS 集成示例freertos_wrapper.cvoid HC_SR04_OnDistanceReady(uint16_t dist_cm) { static StaticQueue_t queue_buffer; static uint8_t queue_storage[32]; static QueueHandle_t distance_queue; if (distance_queue NULL) { distance_queue xQueueCreateStatic(10, sizeof(uint16_t), queue_storage, queue_buffer); } xQueueSendToBack(distance_queue, dist_cm, 0); } // 在任务中xQueueReceive(distance_queue, cm, portMAX_DELAY);4. 中断服务程序ISR实现逻辑库的可靠性高度依赖 ISR 的健壮性。以下是TIMx_IRQHandler的完整状态机处理流程精简版void TIMx_IRQHandler(void) { TIM_HandleTypeDef* htim htimx; // 全局句柄 uint32_t flags __HAL_TIM_GET_FLAG(htim, ...); // 1. 更新中断超时检测 if (flags TIM_FLAG_UPDATE) { __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_UPDATE); if (hc_state HC_SR04_STATE_WAITING_RISE || hc_state HC_SR04_STATE_WAITING_FALL) { hc_state HC_SR04_STATE_TIMEOUT; hc_last_result 0; } } // 2. 捕获中断ECHO 边沿 if (flags TIM_FLAG_CC2) { // 假设 ECHO 接 CH2 __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_CC2); uint32_t cap_val HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); switch (hc_state) { case HC_SR04_STATE_WAITING_RISE: hc_cap_rise cap_val; hc_state HC_SR04_STATE_WAITING_FALL; __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC2); // 重新使能捕获 break; case HC_SR04_STATE_WAITING_FALL: hc_cap_fall cap_val; hc_state HC_SR04_STATE_IDLE; // 计算距离并触发回调 uint16_t dist calculate_distance(hc_cap_fall - hc_cap_rise); HC_SR04_OnDistanceReady(dist); break; } } }关键设计点解析双缓冲捕获hc_cap_rise与hc_cap_fall为volatile uint32_t确保 ISR 与主线程访问一致性超时优先级最高更新中断UEV在捕获中断前检查防止“先捕获后超时”导致状态错乱中断嵌套防护所有状态变更与变量赋值均为原子操作32 位寄存器写入在 Cortex-M 上天然原子去抖策略硬件滤波TIMx_CCMR1.IC2F 0b1000 软件校验连续两次捕获差值 10μs 才采纳。5. 典型应用代码示例5.1 STM32 HAL 库集成CubeMX 生成// main.c #include hc_sr04.h TIM_HandleTypeDef htim2; GPIO_TypeDef* trig_port GPIOA; uint16_t trig_pin GPIO_PIN_0; GPIO_TypeDef* echo_port GPIOA; uint16_t echo_pin GPIO_PIN_1; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); // 72MHz APB1, PSC71 → 1MHz cnt, 1μs res // 初始化 HC-SR04 if (HC_SR04_Init(htim2, 1000000, trig_port, trig_pin, echo_port, echo_pin, TIM_CHANNEL_2) ! 0) { Error_Handler(); // LED 闪烁报警 } while (1) { // 方式1手动触发 HC_SR04_Trigger(); HAL_Delay(100); // 等待测量完成最大65ms余量 uint16_t dist; if (HC_SR04_GetDistance(dist) 0) { printf(Distance: %d cm\r\n, dist); } // 方式2自动模式推荐 // HC_SR04_EnableAutoMode(200); // 每200ms测一次 // ... 在回调中处理数据 } }5.2 无 OS 资源受限设备如 STM32F030// 使用 LL 库寄存器级节省 Flash 与 RAM #define HC_SR04_TRIG_PORT GPIOA #define HC_SR04_TRIG_PIN GPIO_PIN_0 #define HC_SR04_ECHO_PORT GPIOA #define HC_SR04_ECHO_PIN GPIO_PIN_1 void HC_SR04_LL_Init(void) { // RCC 使能 LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM3); LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA); // GPIO 配置 LL_GPIO_SetPinMode(HC_SR04_TRIG_PORT, HC_SR04_TRIG_PIN, LL_GPIO_MODE_OUTPUT); LL_GPIO_SetPinOutputType(HC_SR04_TRIG_PORT, HC_SR04_TRIG_PIN, LL_GPIO_OUTPUT_PUSHPULL); LL_GPIO_SetPinSpeed(HC_SR04_TRIG_PORT, HC_SR04_TRIG_PIN, LL_GPIO_SPEED_FREQ_HIGH); // TIM3 配置PSC71, ARR0xFFFF → 1μs LL_TIM_SetPrescaler(TIM3, 71); LL_TIM_SetAutoReload(TIM3, 0xFFFF); LL_TIM_IC_SetFilter(TIM3, LL_TIM_CHANNEL_CH2, LL_TIM_IC_FILTER_FDIV16_N8); LL_TIM_IC_SetPolarity(TIM3, LL_TIM_CHANNEL_CH2, LL_TIM_IC_POLARITY_RISING); LL_TIM_EnableIT_UPDATE(TIM3); LL_TIM_EnableIT_CC2(TIM3); LL_TIM_EnableCounter(TIM3); }6. 故障诊断与性能调优6.1 常见问题排查表现象可能原因解决方案始终返回-2超时ECHO 未接或电平转换失效目标超出 4m模块供电不足用示波器测 ECHO 是否有 5V 脉冲万用表测 VCC 是否稳定 5.0V±0.1V距离跳变剧烈±20cm环境存在强超声干扰空调、电机ECHO 引脚未加 RC 滤波在 ECHO 与 GND 间并联 100pF 电容改用屏蔽线HC_SR04_GetDistance()总返回-4主循环执行过快未等测量完成就调用在调用前添加while(HC_SR04_GetState() ! HC_SR04_STATE_IDLE);测量值系统性偏大/小温度偏差声速随温度变化定时器时钟不准启用温度补偿dist_cm * (273 T_celsius) / 298.15校准 TIMx_CLK6.2 高精度优化方案温度补偿增加 DS18B20 读取环境温度动态修正声速v_sound 331.5 0.6 * T_celsius→ 新系数 v_sound / 2 / 10000cm/μs多次采样滤波调用HC_SR04_Trigger()5 次取中位数uint16_t samples[5]; for(int i0; i5; i) { HC_SR04_Trigger(); HAL_Delay(100); HC_SR04_GetDistance(samples[i]); } uint16_t median median_of_5(samples);DMA 辅助捕获对支持 DMA 的高级定时器如 TIM1配置 CCx 触发 DMA 将捕获值存入缓冲区彻底解放 CPU。7. 移植指南7.1 至 ESP32FreeRTOS 裸机// 替换 HAL_GPIO_* 为 ESP-IDF GPIO API #define HC_SR04_GPIO_WRITE(port, pin, val) gpio_set_level((gpio_num_t)(pin), (val)) #define HC_SR04_GPIO_READ(port, pin) gpio_get_level((gpio_num_t)(pin)) // 定时器替换为 LEDCLED 控制器或 MCPWM更精准 // 使用 MCPWM_CAP_TIMER0 捕获 ECHOMCPWM_TIMER_0 触发 TRIG7.2 至 AVR ATmega328PArduino Nano// 使用 Timer1 输入捕获ICP1, PD6 void HC_SR04_Init_AVR(void) { DDRD | _BV(PORTD0); // TRIG PD0, output DDRD ~_BV(PORTD6); // ECHO PD6, input TCCR1B | _BV(ICNC1); // 输入捕获噪声消除 TIMSK1 | _BV(ICIE1) | _BV(TOIE1); // 捕获溢出中断 } 最终验证在 1m 距离下使用示波器实测 ECHO 脉宽应为 5882μs100cm / 0.017 ≈ 5882库返回值应在 99–101cm 区间。若偏差 ±3cm优先检查电平转换电路与定时器时钟源精度。