4、超声波传感器实战:典型型号(如HC-SR04)的接口、驱动代码、数据读取与滤波处理

超声波测距是机器人避障系统中成本最低、应用最广泛的感知方案之一。HC-SR04 作为入门级典型型号,其工作原理基于声波在空气中的传播时间(Time of Flight, ToF)测量。本章将围绕 HC-SR04 的硬件接口、时序驱动、数据读取与滤波处理展开实战讲解,帮助学习者建立从传感器原始信号到可用距离数据的完整处理链路。

HC-SR04 超声波测距信号链路与滤波处理架构 MCU 控制单元 GPIO 输出 Trig GPIO 输入 Echo HC-SR04 模块 VCC (5V) Trig (输入) Echo (输出) GND 物理声波传播 发射 40kHz 脉冲 遇障碍物反射 Trig 10µs 脉冲 Echo 高电平时间 声波发射 回波接收 数据处理链路 原始数据 Echo 脉宽 µs 距离换算 距离 = 时间 × 声速 / 2 滤波处理 中值/均值/限幅 避障决策 阈值比较/转向 执行机构 电机/舵机 滑动窗口 N=5~10 | 剔除异常值 | 加权平均 TP1: 原始脉宽 TP2: 滤波后距离

4.1 HC-SR04 硬件接口与电气特性

HC-SR04 是一款集成了超声波发射器、接收器与控制电路的测距模块,工作电压为 5V,静态电流小于 2mA。模块对外提供 4 个引脚:VCC(电源正极)、GND(电源地)、Trig(触发信号输入)和 Echo(回波信号输出)。其测距范围为 2cm~400cm,测量精度可达 3mm,波束角约为 15°。

在接口设计上,Trig 引脚为数字输入,接受来自 MCU 的 TTL 电平脉冲;Echo 引脚为数字输出,输出高电平持续时间与距离成正比。需要注意的是,Echo 输出高电平的电压为 5V,若 MCU 的 GPIO 耐压为 3.3V(如 ESP32、STM32 的部分引脚),必须使用电阻分压或电平转换电路,否则可能损坏 MCU 引脚。

引脚名称 类型 电气特性 连接目标
VCC 电源 5V DC,纹波 < 50mV 5V 电源轨
GND 地 0V 系统地
Trig 输入 TTL 高电平触发,≥10µs 脉冲 MCU GPIO(3.3V/5V 兼容)
Echo 输出 高电平 5V,低电平 0V MCU GPIO(需电平转换)
风险提示:Echo 引脚在无回波时会保持高电平约 200ms 后自动拉低,若 MCU 使用阻塞式等待,将导致主循环卡死。建议使用外部中断或定时器输入捕获功能读取 Echo 脉宽。

4.2 驱动时序与距离计算原理

HC-SR04 的测距时序分为四个阶段:触发、发射、回波、输出。MCU 首先向 Trig 引脚发送一个 ≥10µs 的高电平脉冲,模块内部自动产生 8 个 40kHz 的超声波脉冲并发射。声波遇到障碍物后反射回来,被接收器捕获,模块随即在 Echo 引脚输出一个高电平脉冲,其持续时间即为声波往返的时间。

距离计算公式为:

距离 (cm) = Echo 高电平时间 (µs) / 58
或
距离 (cm) = Echo 高电平时间 (µs) × 0.017

其中 58 的推导过程为:声速在 20°C 空气中约为 343m/s,即 0.0343cm/µs。声波往返距离为 2 倍目标距离,因此单程距离 = 时间 × 声速 / 2 = 时间 × 0.0343 / 2 ≈ 时间 / 58.3。为便于计算,工程上常取 58 或 58.2 作为换算系数。

温度补偿:声速随温度变化约为 0.6m/s/°C。在 0°C 时声速约 331m/s,40°C 时约 355m/s。若在极端温度环境下使用,建议加入温度传感器进行补偿,公式为:声速 = 331.4 + 0.6 × T(°C)。

4.3 驱动代码实现(以 STM32 和 Arduino 为例)

以下分别给出 Arduino 和 STM32 两种主流平台的驱动代码。Arduino 版本适合快速原型验证,STM32 版本适合嵌入式产品开发。

4.3.1 Arduino 驱动代码(阻塞式)

#define TRIG_PIN 9
#define ECHO_PIN 10

void setup() {
  Serial.begin(9600);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
}

void loop() {
  // 发送 10µs 触发脉冲
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  
  // 读取 Echo 高电平持续时间
  long duration = pulseIn(ECHO_PIN, HIGH);
  
  // 计算距离
  float distance = duration / 58.0;
  
  Serial.print("Distance: ");
  Serial.print(distance);
  Serial.println(" cm");
  
  delay(100);  // 测量周期 100ms
}

4.3.2 STM32 驱动代码(中断+定时器输入捕获)

// 使用 TIM2_CH1 (PA0) 作为 Echo 输入捕获
// 使用 PA1 作为 Trig 输出

void HC_SR04_Init(void) {
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  TIM_IC_InitTypeDef TIM_ICInitStruct = {0};
  
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_TIM2_CLK_ENABLE();
  
  // Trig 引脚配置
  GPIO_InitStruct.Pin = GPIO_PIN_1;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  
  // Echo 引脚配置(复用为 TIM2_CH1)
  GPIO_InitStruct.Pin = GPIO_PIN_0;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  
  // 定时器配置:72MHz / 72 = 1MHz,即 1µs 计数
  TIM_Handle.Instance = TIM2;
  TIM_Handle.Init.Period = 0xFFFFFFFF;
  TIM_Handle.Init.Prescaler = 72 - 1;
  TIM_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  TIM_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
  HAL_TIM_IC_Init(&TIM_Handle);
  
  // 输入捕获配置:上升沿触发
  TIM_ICInitStruct.Channel = TIM_CHANNEL_1;
  TIM_ICInitStruct.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
  TIM_ICInitStruct.ICSelection = TIM_ICSELECTION_DIRECTTI;
  TIM_ICInitStruct.ICPrescaler = TIM_ICPSC_DIV1;
  TIM_ICInitStruct.ICFilter = 0;
  HAL_TIM_IC_ConfigChannel(&TIM_Handle, &TIM_ICInitStruct, TIM_CHANNEL_1);
  
  // 启动捕获
  HAL_TIM_IC_Start_IT(&TIM_Handle, TIM_CHANNEL_1);
  
  // 使能中断
  HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0);
  HAL_NVIC_EnableIRQ(TIM2_IRQn);
}

volatile uint32_t echo_start = 0;
volatile uint32_t echo_end = 0;
volatile uint8_t echo_flag = 0;

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
  if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
    if (echo_flag == 0) {
      echo_start = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
      __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
      echo_flag = 1;
    } else {
      echo_end = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
      __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
      echo_flag = 0;
      echo_duration = echo_end - echo_start;  // 单位 µs
    }
  }
}
经验建议:STM32 方案中,定时器输入捕获可以精确记录 Echo 脉宽,误差仅为 1µs(对应 0.017cm),远优于 Arduino 的 pulseIn() 函数(其精度受中断延迟影响,通常有 5~10µs 误差)。

4.4 数据读取与滤波处理

超声波传感器在实际使用中常受到多种噪声干扰,包括:多径反射(声波经墙壁多次反射)、环境噪声(其他超声波源)、温度梯度(声速变化)、机械振动(传感器安装松动)等。因此,原始测量数据必须经过滤波处理才能用于避障决策。

4.4.1 常见滤波算法对比

滤波方法 原理 优点 缺点 适用场景
限幅滤波 相邻两次差值超过阈值则丢弃 简单高效,实时性好 无法处理连续漂移 快速剔除突变值
中值滤波 取 N 次采样中间值 抗脉冲干扰强 延迟较大,需排序 静态或慢速场景
滑动均值 N 次采样取平均 平滑效果好 对突变响应慢 移动机器人避障
加权滑动均值 近期数据权重更高 兼顾平滑与响应 参数需调优 动态避障
卡尔曼滤波 状态估计+噪声协方差 最优估计,自适应 计算量大,调参复杂 高精度定位

4.4.2 工程级滤波代码示例

// 滑动窗口中值+均值组合滤波
#define FILTER_SIZE 7

float distance_buffer[FILTER_SIZE];
uint8_t buffer_index = 0;

// 插入新数据
void filter_insert(float value) {
  distance_buffer[buffer_index] = value;
  buffer_index = (buffer_index + 1) % FILTER_SIZE;
}

// 中值滤波
float median_filter(void) {
  float temp[FILTER_SIZE];
  memcpy(temp, distance_buffer, sizeof(temp));
  
  // 冒泡排序
  for (int i = 0; i < FILTER_SIZE - 1; i++) {
    for (int j = 0; j < FILTER_SIZE - 1 - i; j++) {
      if (temp[j] > temp[j + 1]) {
        float t = temp[j];
        temp[j] = temp[j + 1];
        temp[j + 1] = t;
      }
    }
  }
  return temp[FILTER_SIZE / 2];
}

// 限幅+均值组合滤波
float distance_filtered = 0;
float last_valid = 0;

float limit_average_filter(float new_value) {
  // 限幅:最大变化量 20cm
  if (fabs(new_value - last_valid) > 20.0f) {
    return last_valid;  // 丢弃突变值
  }
  
  // 滑动均值
  filter_insert(new_value);
  float sum = 0;
  for (int i = 0; i < FILTER_SIZE; i++) {
    sum += distance_buffer[i];
  }
  distance_filtered = sum / FILTER_SIZE;
  last_valid = distance_filtered;
  return distance_filtered;
}

4.5 实战案例:移动机器人前向避障系统

下面通过一个完整的案例,展示 HC-SR04 在移动机器人避障中的实际应用。

案例背景:设计一款两轮差速驱动的小型移动机器人,要求在前方 30cm 处检测到障碍物时自动停车并转向。机器人主控为 STM32F103,工作环境为室内平整地面,环境温度 20~25°C。

初始现象与问题:在初步测试中,机器人频繁出现误判——在无障碍物的空旷区域突然停车,或在距离障碍物 50cm 处才触发避障。通过串口日志分析发现,原始距离数据存在大量跳变:

距离(cm): 45.2, 44.8, 12.3, 45.5, 46.1, 120.4, 44.9, 45.0

其中 12.3cm 和 120.4cm 为明显异常值。进一步排查发现,12.3cm 的异常源于机器人前方地面的金属反光板造成的多径反射;120.4cm 的异常则是因为超声波波束边缘扫到了侧方墙壁。

分析与处理过程:

  1. 硬件检查:确认传感器安装牢固,无机械振动;检查电源纹波,发现 5V 电源在电机启动时跌落至 4.6V,导致测量不稳定。解决方案:在传感器电源引脚并联 100µF 电解电容和 0.1µF 陶瓷电容。
  2. 软件滤波:采用"限幅滤波 + 滑动中值滤波"组合方案。限幅阈值设为 30cm(相邻两次测量差值超过 30cm 则丢弃),滑动窗口大小设为 5。
  3. 测量周期优化:将测量周期从 50ms 调整为 100ms,避免与电机 PWM 频率产生谐振干扰。
  4. 多传感器融合:在机器人左右两侧各增加一个 HC-SR04,采用"三传感器投票"策略——当至少两个传感器同时检测到障碍物时才触发避障。

结果对比:

指标 滤波前 滤波后 提升幅度
误触发率(次/分钟) 8~12 0~1 降低 90%+
距离测量标准差 (cm) ±4.2 ±0.8 提升 81%
避障响应延迟 (ms) 150 200 延迟增加 50ms(可接受)

风险与代价:滤波处理引入了约 50ms 的额外延迟,对于高速移动的机器人(>1m/s)可能造成制动距离不足。解决方案是提高传感器采样频率(从 10Hz 提升至 20Hz),同时减小滑动窗口大小至 3,以平衡响应速度与滤波效果。

复盘经验:

  • 超声波传感器的异常值往往具有"物理根源",单纯依靠软件滤波无法根治,需结合硬件改进(电源滤波、安装减震)。
  • 滤波参数的选择应基于实际场景的噪声特性,而非盲目套用经验值。建议先采集 1000 组原始数据,统计分析噪声分布后再确定限幅阈值和窗口大小。
  • 多传感器融合是提升系统鲁棒性的有效手段,但会增加成本和复杂度,需根据项目预算权衡。

4.6 常见误区与总结

在实际工程中,HC-SR04 的使用存在以下常见误区:

  • 误区一:认为 HC-SR04 可以测量 4m 以上的距离。实际上,在 4m 处回波信号极弱,且易受环境噪声干扰,可靠测量范围通常不超过 3m。
  • 误区二:忽略波束角的影响。HC-SR04 的波束角约 15°,在 1m 距离处波束覆盖直径约 26cm,可能检测到侧方障碍物导致误判。
  • 误区三:将 Echo 引脚直接连接 3.3V MCU 的 GPIO。长期运行可能导致 MCU 引脚损坏,应使用分压电阻(如 1kΩ 串联 + 2kΩ 对地)进行电平转换。
  • 误区四:在电机启动时进行测量。电机产生的电磁干扰和电源跌落会严重影响测量精度,应在电机停稳或使用独立电源供电时测量。
安全提醒:超声波传感器不能用于检测透明物体(如玻璃)、吸音材料(如海绵)或倾斜角度大于 45° 的表面。在涉及人身安全的机器人应用中,必须配合红外或视觉传感器进行冗余检测。

本章总结:HC-SR04 作为入门级超声波传感器,其接口简单、驱动容易,但要在实际避障系统中获得稳定可靠的测距数据,必须重视硬件设计(电源滤波、电平转换、安装方式)和软件滤波(限幅、中值、滑动均值)的配合。通过本章的学习,学习者应能够独立完成 HC-SR04 的硬件连接、驱动编写、数据采集与滤波处理,并具备排查常见故障的能力。