一、HC-SR04工作原理与关键时序

HC-SR04采用IO触发-回波测量方式,内部集成超声波发射器、接收器和控制电路。其测距本质是测量超声波从发射到遇障碍物返回的时间差,再乘以声速除以2得到距离。

核心时序要求:

  • 触发信号:至少10μs的高电平脉冲(建议20μs确保稳定)
  • 回波信号:高电平持续时间与距离成正比
  • 测量范围:2cm~400cm,精度理论可达3mm
  • 声速:常温(20℃)下约343m/s,实际受温度影响显著

距离计算公式:

distance_cm = (echo_time_us * 0.0343) / 2;
// 简化:distance_cm = echo_time_us / 58.0;

二、驱动电路与引脚连接

HC-SR04工作电压5V,但回波输出为5V电平,若MCU为3.3V系统(如STM32),需进行电平转换或分压,避免损坏IO口。

典型连接(以STM32F103为例):

  • VCC → 5V(注意电流约15mA)
  • GND → GND
  • Trig → PA0(推挽输出)
  • Echo → PA1(输入捕获或外部中断)

电平转换方案:

  • 使用电阻分压(如1kΩ+2kΩ)将Echo降至3.3V
  • 或使用电平转换芯片(如TXS0108)

三、驱动代码实现(STM32 HAL库)

1. 使用输入捕获模式测量脉宽

// 定时器配置:TIM2 CH1(PA0)输入捕获,上升沿+下降沿捕获
void MX_TIM2_Init(void) {
    TIM_IC_InitTypeDef sConfigIC = {0};
    
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 72-1; // 1MHz计数频率,1us分辨率
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 0xFFFF; // 最大65535us,约1.1m
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_IC_Init(&htim2);
    
    sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
    sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
    sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
    sConfigIC.ICFilter = 0; // 不滤波
    HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);
    
    HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
    __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_CC1);
}

// 中断回调中测量脉宽
volatile uint32_t echo_start, echo_end, echo_width;
volatile uint8_t echo_captured = 0;

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM2) {
        if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
            if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC1)) {
                if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_SET) {
                    // 上升沿
                    echo_start = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
                    __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
                } else {
                    // 下降沿
                    echo_end = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
                    echo_width = echo_end > echo_start ? (echo_end - echo_start) : (0xFFFF - echo_start + echo_end);
                    echo_captured = 1;
                    __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
                }
            }
        }
    }
}

// 触发一次测量
void HCSR04_Trigger(void) {
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
    delay_us(20);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);
}

// 获取距离(单位cm)
uint16_t HCSR04_GetDistance(void) {
    if (echo_captured) {
        echo_captured = 0;
        return (uint16_t)(echo_width / 58.0);
    }
    return 0xFFFF; // 超时或未捕获
}

2. 使用外部中断+定时器计时(替代方案)

// 外部中断配置:Echo引脚上升沿触发,开启定时器;下降沿触发,读取定时器值
void EXTI1_IRQHandler(void) {
    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_1)) {
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_1);
        if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1)) {
            __HAL_TIM_SET_COUNTER(&htim3, 0);
            HAL_TIM_Base_Start(&htim3);
        } else {
            HAL_TIM_Base_Stop(&htim3);
            echo_width = __HAL_TIM_GET_COUNTER(&htim3);
            echo_captured = 1;
        }
    }
}

四、精度优化策略

1. 温度补偿

声速随温度变化明显:v = 331.4 + 0.6 * T(T为摄氏度)。若环境温度波动大,应使用温度传感器(如DS18B20)实时修正。

float speed_of_sound(float temp_c) {
    return 331.4f + 0.6f * temp_c; // m/s
}

uint16_t HCSR04_GetDistanceCompensated(float temp_c) {
    if (echo_captured) {
        echo_captured = 0;
        float dist_m = (echo_width / 1e6f) * speed_of_sound(temp_c) / 2.0f;
        return (uint16_t)(dist_m * 100); // cm
    }
    return 0xFFFF;
}

2. 多次测量与滤波

单次测量易受噪声干扰,建议连续测量5~10次,去除最大值和最小值后取平均。

#define SAMPLE_COUNT 5
uint16_t HCSR04_GetDistanceFiltered(void) {
    uint16_t samples[SAMPLE_COUNT];
    for (int i = 0; i < SAMPLE_COUNT; i++) {
        HCSR04_Trigger();
        while (!echo_captured); // 等待捕获
        samples[i] = HCSR04_GetDistance();
        delay_ms(50); // 间隔至少60ms避免余振
    }
    // 冒泡排序去极值
    for (int i = 0; i < SAMPLE_COUNT-1; i++) {
        for (int j = 0; j < SAMPLE_COUNT-i-1; j++) {
            if (samples[j] > samples[j+1]) {
                uint16_t tmp = samples[j]; samples[j] = samples[j+1]; samples[j+1] = tmp;
            }
        }
    }
    uint32_t sum = 0;
    for (int i = 1; i < SAMPLE_COUNT-1; i++) sum += samples[i];
    return (uint16_t)(sum / (SAMPLE_COUNT-2));
}

3. 触发间隔控制

超声波模块需要等待余波消散,两次触发间隔应大于60ms(对应最大量程4m的往返时间约23ms,留足余量)。若测量距离较近,可适当缩短,但建议不低于30ms。

4. 测量角度与障碍物特性

  • 超声波波束角约15°,障碍物表面应尽量垂直于波束方向,否则回波衰减严重。
  • 软质材料(如海绵、布料)吸收超声波,导致测距失败,需考虑替代方案。
  • 避免在强气流或风扇附近使用,气流会干扰声波传播。

五、常见问题与注意事项

  • Echo悬空问题:若未检测到回波,Echo引脚会保持低电平,但模块内部可能输出高电平持续一段时间,需设置超时机制(如200ms)避免程序卡死。
  • 电源稳定性:HC-SR04对电源纹波敏感,建议在VCC和GND之间加100μF电解电容和0.1μF陶瓷电容。
  • 多模块干扰:多个超声波模块同时工作会互相干扰,可采用分时触发或不同频率(但HC-SR04固定40kHz,只能分时)。
  • 测量盲区:2cm以内无法可靠测量,设计时需考虑安装位置。
  • 定时器溢出:若使用16位定时器且分频后计数频率为1MHz,最大测量时间65535μs,对应距离约1.1m,超过则需使用32位定时器或扩展计数。

六、总结

HC-SR04虽然廉价易用,但要做到高精度测量,需从硬件电路、驱动时序、软件滤波和温度补偿等多方面综合优化。本文提供的代码框架可直接应用于STM32等主流MCU,通过输入捕获或外部中断实现精确脉宽测量。实际项目中,建议根据具体场景调整采样策略和滤波算法,并充分测试不同温度、角度下的表现。掌握这些要点,你就能在机器人避障、液位检测等应用中发挥HC-SR04的最大潜力。