超声波测距实战:从HC-SR04到STM32滤波与避障应用

发布时间:2026/8/29 22:40:12
超声波测距实战:从HC-SR04到STM32滤波与避障应用 1. 项目概述从“听”到“测”超声波测距的工程实践超声波测距听起来像是个实验室里的经典实验但它的身影其实无处不在。从你倒车时雷达发出的“滴滴”声到工厂里机械臂精准抓取物料的距离判断再到扫地机器人灵活避开桌腿背后都是这个原理在默默工作。这个实验之所以经典是因为它完美地串联了物理原理、电子电路和程序逻辑是踏入传感器世界一个绝佳的起点。我自己在带学生和做项目时也常常用它来讲解如何将一个物理现象通过传感器转化为单片机可以理解的数字信号再通过算法处理成可靠的距离数据。整个过程就是一个微缩版的嵌入式系统开发流程。对于初学者来说它硬件接线简单通常就四根线代码逻辑清晰实验结果直观距离值直接显示成就感来得很快。而对于有经验的开发者深入下去你会接触到信号处理、误差补偿、滤波算法等更深的领域比如如何让测距在复杂环境中更稳定如何提高响应速度。这次我们就抛开教科书上简化的模型以一个实际工程应用的视角来重新拆解这个“超声波测距”实验我会把那些数据手册里不会写、但实际调试中一定会遇到的“坑”和技巧都摊开来聊聊。2. 核心原理与系统选型为什么是超声波2.1 声波测距的物理基础超声波测距的本质是“回声定位”。模块发射一束频率高于人耳听觉上限20kHz的声波声波在空气中传播遇到障碍物后反射回来被接收器捕获。我们只要测量从发射到接收回波的时间差再乘以声音在空气中的速度就能计算出距离。公式很简单距离 (声速 × 时间差) / 2。那个“除以2”是因为声音走了一个来回。这里第一个关键点就来了声速不是常量。教科书上常说的340m/s是在15℃干燥空气中的近似值。实际上声速v与温度T摄氏度的关系约为v 331.4 0.607 * Tm/s。这意味着温度每变化1℃声速变化约0.6m/s。对于测量1米距离、时间差约6ms的场景温度从10℃升到30℃声速从约337m/s增加到约349m/s计算出的距离会有近3.5%的误差约3.5厘米。对于精度要求高的场合如精密定位温度补偿是必须的。一种简单的做法是增加一个温度传感器如DS18B20实时修正声速值。2.2 传感器模块选型HC-SR04的统治与局限说到超声波模块HC-SR04几乎是 Arduino 和单片机入门项目的“标配”。它价格低廉十元左右、接口简单VCC, GND, Trig, Echo、操作方便测距范围标称2cm-400cm精度可达3mm。它的工作时序需要特别注意单片机给 Trig 引脚一个至少10us的高电平脉冲触发测距模块会自动发射8个40kHz的超声波脉冲。然后Echo 引脚会输出一个高电平其持续时间与超声波往返时间成正比。我们只需要用单片机测量这个高电平的宽度即可。注意很多新手会忽略时序图中的“模块内部信号输出”时间。在触发后模块需要一点时间数据手册标明约200us进行内部处理和发射这段时间Echo引脚会保持低电平。因此在触发后程序需要等待一小段时间例如用delayMicroseconds(200)再去检测Echo上升沿否则可能错过起始点。然而HC-SR04在复杂场景下表现如何呢我实测过在空旷、表面平整的障碍物前它很可靠。但一旦遇到以下情况问题就来了柔软或粗糙表面如窗帘、泡沫声波散射严重回波信号弱可能导致测不到或距离跳动。小角度斜面声波反射方向偏离接收器同样导致接收失败。多障碍物干扰在狭窄空间可能接收到非目标障碍物的多次反射回波。环境噪声其他同频率的超声波源如另一个HC-SR04会造成干扰。因此在项目选型时你需要问自己我的应用环境是怎样的如果环境复杂可能需要考虑其他方案比如收发分离式模块发射头和接收头分开可以调整角度对抗小角度反射有一定改善。模拟量输出型超声波传感器输出与距离成比例的电压信号省去了单片机测脉宽的操作但需要ADC读取且线性度需要校准。工业级超声波传感器如邦纳、基恩士的部分产品内置了更强的信号处理算法抗干扰能力和可靠性更高当然价格也贵得多。网上常有人问“邦纳和基恩士哪个好”这就像问“宝马和奔驰哪个好”完全取决于具体型号、应用场景和预算。对于实验室和一般DIYHC-SR04足够了对于要求高可靠性的工业自动化就需要评估这些专业品牌的产品线。2.3 主控与外围电路设计主控选择很灵活51单片机、STM32、Arduino、ESP32甚至树莓派都可以。核心任务就两个产生精准的触发脉冲以及高精度地测量Echo高电平的持续时间。对于Arduino利用pulseIn()函数可以非常方便地测量高电平脉宽但要注意这个函数是阻塞的在等待回波时程序会卡住。对于需要同时处理其他任务的系统比如机器人这会成为瓶颈。对于STM32等高级MCU强烈推荐使用定时器的输入捕获功能。将Echo引脚连接到定时器的输入捕获通道设置为上升沿和下降沿触发。在中断服务函数中记录时间戳其差值就是高电平时间。这种方式是非阻塞的精度可以达到微秒甚至纳秒级能极大释放CPU资源。供电方面HC-SR04工作电压是5V但很多现代MCU如STM32F103、ESP32的IO口是3.3V电平。虽然模块在3.3V供电下有时也能工作但性能不稳定尤其测距范围会缩短。最稳妥的方案是MCU用3.3V但通过一个电平转换电路如TXS0108E芯片或简单的电阻分压三极管电路来驱动Trig和读取Echo。如果只是简单实验且MCU的IO口耐压5V如很多STM32的IO口兼容5V也可以直接连接但最好查阅MCU数据手册确认。3. 核心代码实现与信号处理3.1 基础驱动与距离计算我们以STM32的HAL库和定时器输入捕获为例展示一个更工程化的实现片段。首先初始化一个定时器预分频设置使其计数频率为1MHz即每个计数代表1微秒。// 定时器初始化代码片段 (以STM32CubeIDE为例) TIM_HandleTypeDef htim2; TIM_IC_InitTypeDef sConfigIC; htim2.Instance TIM2; htim2.Init.Prescaler 71; // 假设系统时钟72MHz72/(711)1MHz htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 0xFFFFFFFF; // 最大周期 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); // 启动输入捕获中断触发和计算逻辑float GetDistance_cm(void) { uint32_t start_time 0, end_time 0; float distance_cm; // 1. 发送触发脉冲 HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET); delay_us(12); // 发送至少10us的高脉冲这里给12us留有余量 HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); // 2. 等待并测量回波 (通过中断服务程序设置 start_time 和 end_time) // 假设在输入捕获中断中已经记录了上升沿时间戳到 start_time下降沿时间戳到 end_time // 这里需要一个标志位来确保一次测量完成例如 measurement_done_flag while(measurement_done_flag 0) { // 等待测量完成可以加入超时机制 if(timeout_occurred) { return -1.0f; // 返回错误值表示超时未收到回波 } } // 3. 计算时间差微秒 uint32_t time_diff_us end_time - start_time; if(time_diff_us 0) { return -2.0f; // 无效测量 } // 4. 计算距离 (假设声速为340m/s即0.034cm/us) // 距离 (时间差 * 声速) / 2 distance_cm (time_diff_us * 0.034) / 2.0; // 5. 可选加入温度补偿 // float temperature Read_Temperature(); // 从温度传感器读取 // float speed_of_sound_cm_per_us (331.4 0.607 * temperature) / 10000.0; // 转换为 cm/us // distance_cm (time_diff_us * speed_of_sound_cm_per_us) / 2.0; measurement_done_flag 0; // 清除标志准备下一次测量 return distance_cm; }3.2 提升稳定性的软件滤波算法直接从传感器读出的数据往往是跳动的尤其是目标物较远或表面不规整时。这时就需要软件滤波。这里介绍几种简单有效的方法中值滤波连续采样N次比如5次或7次将这组数据排序取中间值作为最终结果。这种方法能有效滤除偶然的突发性干扰比如飞虫掠过传感器前方造成的异常值。#define SAMPLE_SIZE 5 float MedianFilter(float new_sample) { static float buffer[SAMPLE_SIZE] {0}; static int index 0; float temp_buffer[SAMPLE_SIZE]; buffer[index] new_sample; index (index 1) % SAMPLE_SIZE; // 复制到临时数组进行排序 for(int i0; iSAMPLE_SIZE; i) temp_buffer[i] buffer[i]; // 使用简单的冒泡排序对于小数组足够 for(int i0; iSAMPLE_SIZE-1; i) { for(int j0; jSAMPLE_SIZE-1-i; j) { if(temp_buffer[j] temp_buffer[j1]) { float temp temp_buffer[j]; temp_buffer[j] temp_buffer[j1]; temp_buffer[j1] temp; } } } return temp_buffer[SAMPLE_SIZE/2]; // 返回中值 }滑动平均滤波维护一个固定长度的队列每次新数据进来就剔除最老的数据计算队列中所有数据的平均值。这种方法能让输出更平滑但对突发异常值的抑制不如中值滤波。#define WINDOW_SIZE 10 float MovingAverageFilter(float new_sample) { static float buffer[WINDOW_SIZE] {0}; static float sum 0; static int index 0; sum - buffer[index]; // 减去即将被覆盖的旧值 buffer[index] new_sample; // 存入新值 sum new_sample; // 加上新值 index (index 1) % WINDOW_SIZE; return sum / WINDOW_SIZE; }复合滤波在实际项目中我常常将两者结合先进行中值滤波剔除野值再进行滑动平均让曲线平滑。例如可以定义两个不同的窗口大小MedianFilter(5)-MovingAverageFilter(10)。3.3 超时与错误处理机制一个健壮的程序必须考虑各种异常情况。对于超声波测距最常见的异常就是“没有回波”。可能的原因有障碍物太远超过最大量程、障碍物表面吸声、角度太偏、传感器故障等。在代码中必须加入超时判断。HC-SR04的最大测距对应的高电平时间大约为(400cm * 2) / (34000cm/s) ≈ 23.5ms。为了保险可以将超时时间设置为30-40ms。在等待Echo上升沿或高电平结束的循环中需要用一个定时器或系统滴答计时器来检查是否超时。一旦超时立即退出等待循环返回一个特定的错误值如-1.0或0.0并在上层逻辑中处理这个错误例如忽略本次测量、重试、或报告“无物体”状态。4. 系统集成与高级应用场景4.1 多传感器融合与避障策略单个超声波传感器只能提供一维的距离信息。在机器人领域为了感知周围环境通常需要多个传感器。例如在机器人前方左、中、右各安装一个超声波传感器。这时你需要处理几个问题分时复用多个HC-SR04如果同时工作它们的声波会互相干扰。必须让它们分时工作即触发一个传感器等待其测量完成并处理数据后再触发下一个。可以在代码中用一个状态机来管理多个传感器的测量序列。数据融合将三个距离数据融合可以粗略绘制出机器人前方的障碍物轮廓。简单的策略可以是“取最小值”作为前进方向的最近障碍物距离。更复杂的可以结合机器人宽度判断障碍物在左侧、右侧还是正前方从而决定转向策略左转、右转或后退。与其它传感器结合超声波对玻璃等透明物体检测效果差而红外测距可能受黑色物体影响。可以考虑将超声波与红外、碰撞开关甚至低成本的单目摄像头进行简单的单目测距结合取长补短。例如用红外做近距离10cm内高精度检测用超声波做中远距离检测。4.2 从实验到项目智能小车避障实例让我们构想一个简单的智能小车避障项目。系统需要感知层三个朝前的HC-SR04超声波模块。控制层一个STM32作为主控负责读取传感器、处理数据、做出决策、控制电机。执行层L298N或TB6612电机驱动模块驱动两个直流电机。核心避障算法状态机可以这样设计typedef enum { STATE_FORWARD, STATE_OBSTACLE_NEAR, STATE_TURN_LEFT, STATE_TURN_RIGHT, STATE_BACK_AND_TURN } RobotState_t; void ObstacleAvoidanceTask(void) { float dist_left, dist_center, dist_right; static RobotState_t state STATE_FORWARD; // 1. 分时读取三个传感器距离 dist_left GetDistance_Sensor(LEFT); dist_center GetDistance_Sensor(CENTER); dist_right GetDistance_Sensor(RIGHT); // 2. 应用滤波 dist_center Filter(dist_center); // 3. 状态机决策 switch(state) { case STATE_FORWARD: Motor_Forward(SPEED_NORMAL); if(dist_center SAFE_DISTANCE) { state STATE_OBSTACLE_NEAR; } break; case STATE_OBSTACLE_NEAR: Motor_Stop(); // 判断左右哪边更空旷 if(dist_left dist_right dist_left SAFE_DISTANCE) { state STATE_TURN_LEFT; } else if(dist_right SAFE_DISTANCE) { state STATE_TURN_RIGHT; } else { state STATE_BACK_AND_TURN; // 两边都不行先后退再转向 } break; case STATE_TURN_LEFT: Motor_TurnLeft(SPEED_TURN); delay_ms(TURN_TIME_MS); // 转向一个固定时间 if(dist_center SAFE_DISTANCE * 1.5) { state STATE_FORWARD; // 前方开阔了继续前进 } else { state STATE_OBSTACLE_NEAR; // 转完发现还有障碍重新评估 } break; // ... 其他状态类似 } }这个例子中SAFE_DISTANCE安全距离和TURN_TIME_MS转向时间是两个关键参数需要在实际场地中反复测试调整。安全距离设置太小容易撞上设置太大小车会过于“胆小”。转向时间决定了转弯的角度需要根据小车轮距和速度来校准。4.3 更复杂的场景与ROS2集成如果你的项目更复杂比如基于ROS2的移动机器人那么超声波传感器可以作为其中一个数据源发布到ROS2的话题中。你可以创建一个ROS2节点这个节点内部运行我们上面写的STM32驱动代码或通过串口与STM32通信获取数据然后将处理后的距离信息封装成标准的sensor_msgs/msg/Range消息类型发布出去。其他节点如导航节点nav2可以订阅这个话题将超声波数据与其他传感器如激光雷达、深度相机D435i的数据融合用于构建代价地图或进行局部避障。虽然超声波在ROS的SLAM同步定位与建图中通常不作为主要传感器因其数据稀疏、噪声大但在近距离、低矮障碍物如玻璃桌腿、黑色踢脚线这些可能被激光雷达或RGB-D相机遗漏检测方面仍有其不可替代的补充价值。5. 调试技巧与常见问题排查即使原理和代码都清楚了实际调试中还是会遇到各种问题。下面这个表格总结了我遇到过的典型问题及解决方法现象可能原因排查步骤与解决方案读数始终为0或极小值1. 接线错误VCC/GND接反或接触不良。2. Echo引脚未正确连接或配置应配置为上拉输入或浮空输入并启用中断。3. 触发脉冲宽度不够小于10us。1. 用万用表检查电源电压是否为稳定的5V检查地线连通性。2. 用逻辑分析仪或示波器观察Trig和Echo引脚波形。先确保Trig引脚有清晰的10us脉冲。3. 如果Trig正常但Echo无反应检查Echo引脚连接和单片机配置。尝试给Echo引脚加一个上拉电阻如4.7kΩ到VCC。读数波动巨大跳变1. 电源噪声。HC-SR04工作时瞬时电流较大可能引起电源电压跌落。2. 环境干扰多个超声波源、强气流、复杂反射面。3. 测量代码精度不够或中断被干扰。1. 在模块的VCC和GND引脚就近并联一个100uF的电解电容和一个0.1uF的瓷片电容这是解决电源噪声最有效的方法之一。2. 尝试更换测量环境或让传感器对准一个平整、坚硬的表面如墙壁。3. 在代码中增加软件滤波如前面介绍的中值滤波滑动平均。确保测量高电平时间的时钟源稳定并关闭不必要的全局中断。测量距离明显偏大或偏小1. 声速参数设置错误。2. 温度影响未补偿。3. 定时器时钟配置错误导致时间基准不准。1. 用卷尺实际测量一个固定距离如50.0cm与传感器读数对比计算出一个校准系数在代码中乘以这个系数。2. 增加温度传感器实现动态声速补偿。3. 检查单片机定时器的预分频和重载值配置用示波器或已知频率的信号验证定时器计数是否准确。最大测距远小于标称的4米1. 供电电压不足低于5V。2. 障碍物表面吸声如布料、泡沫。3. 传感器模块本身质量或性能问题。1. 确保供电电压在5V-5.5V之间且电源能提供足够电流峰值15mA。使用独立的5V稳压模块为传感器供电与单片机电源分开。2. 更换为硬质、平整的障碍物测试。3. 尝试更换另一个同型号模块对比测试。偶尔出现一次极远的错误读数1. 接收到非目标物体的多次反射回波。2. 电磁干扰或程序跑飞。1. 在代码中设置一个合理的最大距离阈值如450cm超过此值的读数直接丢弃。2. 除了软件滤波还可以在硬件上尝试在VCC和GND间加磁珠或为信号线加小电容滤波如22pF。检查单片机复位电路和看门狗是否启用。几个关键的调试工具逻辑分析仪几十块钱的8通道逻辑分析仪是调试数字时序的神器。你可以同时抓取Trig和Echo的波形精确测量脉冲宽度一目了然地看到触发和回波的关系这是排查时序问题最快的方法。示波器观察电源引脚上的噪声比万用表更直观。串口调试助手将计算出的原始时间差微秒数和最终距离值都打印出来可以帮助你判断问题是出在硬件测量阶段还是软件计算阶段。最后分享一个我自己的实操心得在为一个室内机器人部署超声波阵列时发现其中一个传感器在特定角落总是失灵。用逻辑分析仪看发现Echo引脚偶尔会收到一个极短的尖峰脉冲导致测量出错。最后发现是机器人的金属外壳与墙壁构成了一个谐振腔反射产生了干扰。解决方法不是改代码而是在该传感器的收发面上贴了一小圈海绵稍微改变了其声学特性问题就解决了。很多时候硬件问题需要硬件方法来解决软件滤波不是万能的。

相关新闻