4、超声波传感器原理与驱动:学习超声波测距原理,编写STM32驱动代码实现距离测量

超声波测距是避障机器人中最经典、成本最低廉的感知方案之一。与ToF(飞行时间)激光传感器相比,超声波传感器虽然精度略低、响应速度较慢,但其在近距离(2cm~400cm)探测、透明物体检测、以及恶劣光照环境下的稳定性方面具有独特优势。本章将深入剖析超声波测距的物理原理,并以HC-SR04这一普及率最高的模块为载体,带领你从零编写STM32的驱动代码,最终实现一个可实际运行的距离测量功能。我们将重点解决“如何精确测量回波时间”“如何处理超时与噪声”“如何将时间转换为距离”这三个核心工程问题。

STM32 主控侧 HC-SR04 模块 物理环境 GPIO 输出 输入捕获 定时器 TIM 计算距离 d = t * 0.034 / 2 Trig 触发 Echo 回波 40kHz 超声波发射 回波接收 放大+比较器 障碍物 反射 Trig (10µs脉冲) Echo (高电平时间) 发射波 回波 测距时序:Trig 触发 → 发射8个40kHz脉冲 → 等待Echo高电平 → 高电平持续时间 = 声波往返时间 距离 d (cm) = 高电平时间 t (µs) / 58 或 d (cm) = t (µs) × 0.0343 (cm/µs) ÷ 2

4.1 超声波测距核心原理:从物理声波到电信号

超声波是指频率高于20kHz的机械波,人耳无法直接听到。在避障机器人中,我们通常使用40kHz的超声波作为工作频率,这是因为该频率在空气中的衰减适中,且40kHz的压电陶瓷换能器技术成熟、成本低廉。HC-SR04模块内部集成了发射探头、接收探头以及信号调理电路,对外仅暴露四个引脚:VCC(5V)、GND、Trig(触发)、Echo(回波输出)。

测距的物理基础是声波在均匀介质中的直线传播与反射。当声波遇到尺寸远大于其波长的障碍物时,会产生明显的反射回波。在标准大气压、20℃的空气中,声速约为343m/s,即0.0343cm/µs。由于声波需要完成“发射→碰到障碍物→返回”的往返旅程,因此实际距离为声速与单程时间的乘积。

关键公式: 距离 d = (声速 v × 时间 t) / 2。其中 t 为声波从发射到接收的总时间。在工程实践中,为了简化计算,常使用近似公式 d(cm) = t(µs) / 58,该系数已综合考虑了声速和往返因子。

HC-SR04的工作时序非常清晰:主控向Trig引脚发送一个至少10µs的高电平脉冲,模块内部自动发射8个周期的40kHz方波,并等待回波。当接收到有效回波后,Echo引脚输出一个高电平脉冲,该高电平的持续时间即为声波往返时间。因此,STM32驱动代码的核心任务就是:精确测量Echo引脚高电平的持续时间。

4.2 STM32驱动设计:GPIO与定时器输入捕获的配合

测量Echo高电平时间有两种主流方案:阻塞式延时查询和定时器输入捕获。前者代码简单但会占用CPU,且精度受中断响应时间影响;后者利用定时器硬件捕获边沿,精度高且不阻塞主循环。对于避障机器人这类需要实时响应多传感器的场景,推荐使用输入捕获方案。

我们以STM32F103系列为例,使用TIM2的通道1(PA0)作为输入捕获引脚,同时使用一个普通GPIO(如PA1)作为Trig输出。以下是完整的驱动代码框架:

// 超声波传感器驱动 - 基于TIM2输入捕获
// 硬件连接:Trig -> PA1, Echo -> PA0 (TIM2_CH1)

#include "stm32f1xx_hal.h"

// 全局变量
static TIM_HandleTypeDef htim2;
static uint32_t ic_start = 0;   // 上升沿时间戳
static uint32_t ic_end = 0;     // 下降沿时间戳
static uint8_t  capture_done = 0;

// 初始化GPIO和定时器
void Ultrasonic_Init(void) {
    // 1. 使能时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_TIM2_CLK_ENABLE();
    
    // 2. 配置Trig引脚为推挽输出
    GPIO_InitTypeDef gpio = {0};
    gpio.Pin = GPIO_PIN_1;
    gpio.Mode = GPIO_MODE_OUTPUT_PP;
    gpio.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &gpio);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
    
    // 3. 配置Echo引脚为输入捕获模式
    gpio.Pin = GPIO_PIN_0;
    gpio.Mode = GPIO_MODE_AF_INPUT;  // 复用功能输入
    gpio.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &gpio);
    
    // 4. 配置TIM2输入捕获
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 72 - 1;      // 72MHz/72 = 1MHz,即1µs计数一次
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 0xFFFFFFFF;     // 32位计数器,最大溢出时间约71分钟
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_IC_Init(&htim2);
    
    // 5. 配置输入捕获通道
    TIM_IC_InitTypeDef ic_config = {0};
    ic_config.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;  // 先捕获上升沿
    ic_config.ICSelection = TIM_ICSELECTION_DIRECTTI;
    ic_config.ICPrescaler = TIM_ICPSC_DIV1;
    ic_config.ICFilter = 0x0F;  // 启用输入滤波,抑制毛刺
    HAL_TIM_IC_ConfigChannel(&htim2, &ic_config, TIM_CHANNEL_1);
    
    // 6. 启动捕获并使能中断
    HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);
    HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0);
}

// 触发一次测距
void Ultrasonic_Trigger(void) {
    capture_done = 0;
    ic_start = 0;
    ic_end = 0;
    
    // 发送10µs以上的高电平脉冲
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
    delay_us(15);  // 建议15µs,确保模块可靠触发
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
}

// 获取距离(单位:cm),返回-1表示超时或无效
float Ultrasonic_GetDistance(void) {
    if (!capture_done) {
        return -1.0f;
    }
    
    uint32_t time_us = ic_end - ic_start;  // 高电平持续时间(µs)
    float distance_cm = (float)time_us / 58.0f;
    
    // 超出量程范围则视为无效
    if (distance_cm < 2.0f || distance_cm > 400.0f) {
        return -1.0f;
    }
    
    return distance_cm;
}

// 定时器中断回调
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM2) {
        if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
            if (ic_start == 0) {
                // 第一次捕获:上升沿,记录起始时间
                ic_start = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
                // 切换为捕获下降沿
                TIM_RESET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1);
                TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
            } else {
                // 第二次捕获:下降沿,记录结束时间
                ic_end = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
                capture_done = 1;
                // 恢复为捕获上升沿,准备下一次测距
                TIM_RESET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1);
                TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
            }
        }
    }
}

上述代码中,定时器预分频设置为72-1,使得计数器以1MHz频率递增,即每个计数单位代表1µs。输入捕获的边沿切换是关键技巧:第一次捕获上升沿记录开始时间,然后立即切换为下降沿捕获,第二次捕获时记录结束时间。两次时间戳之差即为Echo高电平的持续时间。

4.3 实战案例:避障机器人中的超声波测距模块集成

假设我们正在设计一款基于STM32F103C8T6的差速驱动避障机器人,需要在车头前方安装一个HC-SR04,用于检测前方障碍物。机器人的主循环以50Hz的频率调用测距函数,当距离小于30cm时执行避障动作。

初始现象与目标:在实验室测试中,我们发现超声波数据偶尔会出现跳变——明明前方没有障碍物,却突然报出20cm的距离;或者前方有障碍物时,数据在正确值和错误值之间来回跳动。

分析判断过程:通过串口打印原始时间戳数据,发现异常数据对应的高电平时间约为1160µs(对应20cm),但实际障碍物距离超过1m。进一步检查发现,问题出在两个方面:一是Echo引脚未加滤波,电机驱动产生的电磁干扰导致误触发;二是Trig脉冲宽度不稳定,有时只有8µs,导致模块工作异常。

实际操作步骤:

  1. 硬件改进:在Echo引脚对地并联一个100pF的陶瓷电容,构成低通滤波器,抑制高频噪声。同时将Trig引脚的上拉电阻从10kΩ改为4.7kΩ,增强驱动能力。
  2. 软件优化:在输入捕获配置中,将ICFilter从0x0F改为0x0F(保留最大滤波深度),该滤波器可有效滤除小于8个时钟周期的毛刺。
  3. 增加超时保护:在定时器更新中断中设置一个标志,如果超过50ms未捕获到下降沿,则强制结束本次测距,返回超时错误。
  4. 数据平滑:在应用层采用中值滤波算法,连续采集5次数据,取中间值作为有效距离,消除随机噪声。

结果对比:经过上述优化后,在电机全速运转的情况下,超声波数据稳定在±1cm的误差范围内,异常跳变率从原来的约15%降低到0.5%以下。机器人能够可靠地在距离障碍物25cm时触发避障动作。

经验总结:超声波传感器看似简单,但在实际机器人应用中,电磁兼容性和软件鲁棒性同样重要。建议在PCB布局时,将超声波模块远离电机驱动电路至少5cm以上;在软件层面,务必实现超时保护机制,防止传感器无响应时程序卡死。

4.4 常见误区与工程复盘

在超声波传感器的驱动开发中,初学者常犯以下错误,需要特别注意规避:

误区类型 错误表现 正确做法
Trig脉冲宽度不足 模块不响应或响应不稳定 确保高电平持续至少10µs,建议15µs
未处理超时情况 程序卡死在等待Echo下降沿 使用定时器更新中断或看门狗实现超时退出
忽略声速温度修正 温度变化时测距误差增大 使用公式 v = 331.4 + 0.6×T(℃) 进行补偿
测量盲区处理不当 近距离(<2cm)数据错误 软件中过滤掉小于2cm的数据,或改用ToF传感器
多传感器串扰 多个超声波模块互相干扰 采用分时触发策略,或使用不同频率的模块

关于声速修正:标准声速343m/s是在20℃下的值。当环境温度从0℃变化到40℃时,声速变化范围约为325~355m/s,对应测距误差可达8%。对于高精度应用,建议在代码中加入温度传感器(如DS18B20),实时计算声速。

关于测量盲区:HC-SR04的最小测量距离约为2cm。这是因为发射探头在发出超声波后,由于压电陶瓷的余振效应,接收探头在短时间内无法分辨回波。如果机器人需要检测极近距离的障碍物,建议结合红外或ToF传感器进行互补。

风险提示:超声波传感器对软性材料(如海绵、布料)的反射效果较差,可能导致漏检。在避障机器人中,建议将超声波与红外或ToF传感器融合使用,形成多模态感知,提高避障的可靠性。

本章总结复盘:超声波测距的核心在于精确测量声波往返时间。STM32驱动开发的关键技术点包括:GPIO的快速翻转、定时器输入捕获的边沿切换、中断服务程序的低延迟处理、以及超时保护机制。在实际工程中,还需要关注电磁干扰、温度补偿、多传感器协同等问题。掌握这些内容后,你已经具备了在避障机器人中集成超声波传感器的完整能力。下一阶段,我们将在此基础上引入ToF传感器,对比两种方案在不同场景下的优劣,并构建风险分级决策模型。