4、超声波传感器原理与驱动:学习超声波测距原理,编写STM32驱动代码实现距离测量
超声波测距是避障机器人中最经典、成本最低廉的感知方案之一。与ToF(飞行时间)激光传感器相比,超声波传感器虽然精度略低、响应速度较慢,但其在近距离(2cm~400cm)探测、透明物体检测、以及恶劣光照环境下的稳定性方面具有独特优势。本章将深入剖析超声波测距的物理原理,并以HC-SR04这一普及率最高的模块为载体,带领你从零编写STM32的驱动代码,最终实现一个可实际运行的距离测量功能。我们将重点解决“如何精确测量回波时间”“如何处理超时与噪声”“如何将时间转换为距离”这三个核心工程问题。
4.1 超声波测距核心原理:从物理声波到电信号
超声波是指频率高于20kHz的机械波,人耳无法直接听到。在避障机器人中,我们通常使用40kHz的超声波作为工作频率,这是因为该频率在空气中的衰减适中,且40kHz的压电陶瓷换能器技术成熟、成本低廉。HC-SR04模块内部集成了发射探头、接收探头以及信号调理电路,对外仅暴露四个引脚:VCC(5V)、GND、Trig(触发)、Echo(回波输出)。
测距的物理基础是声波在均匀介质中的直线传播与反射。当声波遇到尺寸远大于其波长的障碍物时,会产生明显的反射回波。在标准大气压、20℃的空气中,声速约为343m/s,即0.0343cm/µs。由于声波需要完成“发射→碰到障碍物→返回”的往返旅程,因此实际距离为声速与单程时间的乘积。
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,导致模块工作异常。
实际操作步骤:
- 硬件改进:在Echo引脚对地并联一个100pF的陶瓷电容,构成低通滤波器,抑制高频噪声。同时将Trig引脚的上拉电阻从10kΩ改为4.7kΩ,增强驱动能力。
- 软件优化:在输入捕获配置中,将ICFilter从0x0F改为0x0F(保留最大滤波深度),该滤波器可有效滤除小于8个时钟周期的毛刺。
- 增加超时保护:在定时器更新中断中设置一个标志,如果超过50ms未捕获到下降沿,则强制结束本次测距,返回超时错误。
- 数据平滑:在应用层采用中值滤波算法,连续采集5次数据,取中间值作为有效距离,消除随机噪声。
结果对比:经过上述优化后,在电机全速运转的情况下,超声波数据稳定在±1cm的误差范围内,异常跳变率从原来的约15%降低到0.5%以下。机器人能够可靠地在距离障碍物25cm时触发避障动作。
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传感器进行互补。
本章总结复盘:超声波测距的核心在于精确测量声波往返时间。STM32驱动开发的关键技术点包括:GPIO的快速翻转、定时器输入捕获的边沿切换、中断服务程序的低延迟处理、以及超时保护机制。在实际工程中,还需要关注电磁干扰、温度补偿、多传感器协同等问题。掌握这些内容后,你已经具备了在避障机器人中集成超声波传感器的完整能力。下一阶段,我们将在此基础上引入ToF传感器,对比两种方案在不同场景下的优劣,并构建风险分级决策模型。