HC-SR04与STM32在货运无人机低空测距中的工程实践与投递联动

HC-SR04与STM32在货运无人机低空测距中的工程实践与投递联动 超声波传感器在货运无人机上做离地测距看起来是个很小的功能点但真正把它调稳、调准再联动到投递动作上中间其实隔着不少工程问题。我最早接触这个组合是在做一款小型物流无人机的投放系统时——飞机要在悬停状态下把货物降到地面不能靠飞手肉眼估高度更不能靠气压计那东西在低空完全没法看最后选来选去方案定成了“HC-SR04超声波传感器 STM32主控”。整套系统做完之后我的感受是原理一句话能讲完但要把每一个环节都做到可靠真得一步一步踩坑踩过来。这篇文章就把整个过程拆开讲包括传感器选型理由、测距原理、STM32的驱动写法、滤波策略、投递触发逻辑以及我在实测中遇到的那些破事。1. 整体设计思路为什么货运无人机非要精确测距1.1 看似简单的问题实际牵一发动全身货运无人机执行投递任务时最怕的不是飞得不准而是“不知道离地面还有多高”。你可能会说飞控里不是有GPS和气压计吗确实有但这两个东西在低空段都有硬伤GPS的垂直精度在普通模式下动辄±3米以上用来确定巡航高度勉强够用但要在离地1米内判断“能不能放货”这误差直接能把货物摔在地上气压计更离谱旋翼下洗气流会在地面附近形成明显的压力变化高度读数能跳好几米。那用视觉传感器或者激光雷达行不行行但成本和技术门槛都上去了。视觉需要标定、需要光照条件好、还需要一定的算力跑算法单线激光雷达精度高可一颗几百上千块钱用在中小型货运无人机上有点奢侈。相比之下超声波传感器的优势非常突出成本低HC-SR04这种工业级入门型号十几块钱、原理简单、响应速度快、近距精度能做到厘米级正好覆盖无人机“最后一米”到“最后一厘米”的测量区间。1.2 系统架构拆解从探头到舵机的完整链路我做的这套系统整体链路是这样的传感器层HC-SR04超声波探头负责发射和接收40kHz超声波输出脉冲宽度信号。主控层STM32F103系列单片机负责发送触发脉冲、捕获回波时间、计算距离、执行滤波算法、输出高度数据。飞控交互层STM32通过串口将高度数据发送给飞控我调试时用的Pixhawk同时接收飞控的“投递请求”指令。执行层接收到合法的指令且高度判稳后STM32驱动舵机打开货舱舱门或者触发脱扣器完成投递。这样的分工方式核心思路是把“测距”这个任务从飞控里独立出来。原因很简单飞控本身的任务调度已经很满中断多、优先级复杂如果在飞控上直接轮询超声波传感器时序抖动会直接影响测距精度。STM32作为专用测距协处理器能够用定时器精确测量回波脉宽保证测距的稳定性和实时性同时它只向上层飞控汇报“距地高度”和“投递允许”两个逻辑信号接口非常干净。1.3 方案取舍陷阱抄作业最容易忽略的几个点很多人在论坛里看到别人用HC-SR04做了个测距小车就想着原封不动搬到无人机上。这个思路我劝你打住。车载测距和无人机低空测距环境完全不一样旋翼带来的气流扰动、电机产生的电磁噪声、机体本身的振动都会对超声波测距产生致命影响。网上那些“十行代码测距”的教程只适合桌面实验上了真机大概率是一堆乱跳的数值。另外HC-SR04这个传感器本身有测量盲区一般为2cm左右对于固定翼或者高悬停的无人机问题不大但如果你的无人机有快速下降的动作极有可能一下子扎进盲区里导致高度突然丢失。这些都得在设计初期的滤波策略和投递判定逻辑里提前考虑而不是等到试飞炸机了再回去改。2. 核心细节解析超声波测距为什么能测准、怎么测准2.1 HC-SR04的工作过程脉冲怎么变成距离HC-SR04的工作原理说起来非常简单主控给TRIG引脚一个10微秒以上的高电平脉冲传感器内部的超声波发射探头就会发出8个40kHz的超声波脉冲声波遇到物体反射回来被接收探头捕获传感器在ECHO引脚上输出一个高电平脉冲。这个高电平的持续时间就是声波从发射到返回的总时间t。距离计算公式是d v × t / 2因为要考虑声波往返所以要走两倍距离除以2才是单程距离。常温15℃下声速 v 约为 340m/s如果测得回波时间为580微秒那么距离就是d 340 × 0.000580 / 2 0.0986m约等于10cm。HC-SR04默认采用5V供电而STM32的GPIO大部分是3.3V逻辑所以接法上要注意TRIG可以由3.3V直接驱动部分型号可能不稳定建议加电平转换或三极管驱动但ECHO回波输出的高电平是5VSTC的3.3V引脚直接接上去会有烧毁风险——这也是很多新手第一次烧芯片的原因。2.2 精度瓶颈声速不是固定的HC-SR04手册上通常标注“2cm-400cm精度3mm”这是理想条件下的数据。实际使用中最大的不确定因素不是传感器本身而是声速。声速v随气温变化很大经验公式是v 331.4 0.6 × TT是环境温度单位是摄氏度。假如你的无人机在冬天0℃和夏天30℃执行任务声速分别是331.4m/s和349.4m/s相差了约5%。如果在夏天没有做温度补偿用冬天的声速去算距离10米量程下误差能达到50cm低空阶段虽然没这么夸张但30cm的高度上错5%也有1.5cm的误差用来作为是否接触地面的判据还是太危险。我最后做的是在STM32里接了一个DS18B20温度传感器每次测距时先读一次温度然后实时计算声速。这样温度变化引起的误差基本可以忽略。如果你不想额外加传感器也可以直接把声速按343m/s写死但你要清楚这会在多大高度上带来多大的误差好心里有数。2.3 为什么不用多次测量求平均滤波策略讲透很多人一上来就搞“连续测10次取平均”表面上数值稳了实际上了无人机就会发现下洗气流导致的地面“软反射”会让超声波回波幅度忽大忽小偶尔出现完全丢失回波的情况这时如果你还傻傻地做算术平均一个0cm的异常值就会把整组数据拉飞。比如测到“正常值50cm、50cm、50cm、0cm”平均值直接变成37.5cm高度瞬间“下降”了12.5cm飞控可能就误判触地了。我用了三级滤波策略。第一级是限幅滤波设定相邻两次测量值之间的最大步进值。比如无人机处于悬停状态单次测量差超过10cm就直接丢弃这次数据保留上一帧值。这么做的依据是正常工况下悬停时高度变化是连续平缓的但丢波或者多径反射导致的异常值往往是突变性的。第二级是中值滤波每轮连续测5次剔除最大和最小值对剩下3个值取平均。中值滤波对脉冲噪声就是那种突然冒出来的0或者几百的抑制效果非常好而且不会像滑动平均那样在快速下降时产生延迟。第三级才是滑动平均把经过前两级处理的结果存到一个长度为5的环形缓冲区里做加权移动平均权重分配是最近的数据点权重最大。这样做既能保证平滑又能尽量减小滞后。用这套策略之后实际效果对比非常明显裸数据在悬停时抖动幅度将近±8cm滤波之后能控制在±1.5cm以内。在下降过程中因为权重偏向最新数据高度滞后也只有一两帧算下来大概延迟30-40毫秒对投递判定来说完全可接受。2.4 多探头并发问题超声波会“串门”如果你打算在无人机上装多个超声波探头比如前后左右各一个用来避障那就要特别注意多探头之间的串扰问题。超声波在空气中的传播速度慢40kHz的脉冲发出去之后要过一段时间才消散。如果两个探头同时收发A探头发射的声波会被B探头接收到导致B测出一个根本不存在的障碍物距离。这种情况下最简单的办法是分时触发给每个探头分配不同的时间窗口错开工作周期。比如4个探头可以每10ms一个轮询周期每个探头在属于自己的2.5ms窗口内完成测距。代价是更新率降低了——原来单探头每秒能测40帧4个探头轮流测每个探头就只有10帧了。但对于悬停投递场景10帧完全够用。你还可以用不同频率的超声波探头来进一步降低串扰风险不过HC-SR04是固定40kHz没法调频率只能用分时方案。3. 实操过程STM32驱动HC-SR04从0到13.1 硬件连接不只是“接三根线”HC-SR04一共4个引脚VCC、GND、TRIG、ECHO。我调试时的接线是这样的VCC接5V如果STM32板子是3.3V供电的系统需要给传感器单独供5V否则发射功率不足测距距离大打折扣。GND接STM32板子的GND注意要和飞控、电调的GND共地否则信号参考电平不一致脉冲宽度会被干扰。TRIG接STM32的一个普通GPIO配置为推挽输出。我用的是PA0。ECHO不能直接接3.3V引脚我用了两个电阻做分压5V降到3.3V接在PA1上。更稳妥的做法是用BAT54S这类双二极管做电平钳位也可以用电压比较器。还有一点务必注意HC-SR04的VCC和GND之间一定要加一个100uF的电解电容和0.1uF的瓷片电容做电源去耦。无人机上电机的瞬间电流抽动很厉害如果传感器电源不够干净测距数据会直接飘起来很多“玄学问题”其实都是电源不干净导致的。3.2 关键代码STM32的精确定时测量STM32驱动HC-SR04的核心是精确测量ECHO高电平持续时间。我采用的是定时器输入捕获方式而不是简单的GPIO轮询。GPIO轮询的问题在于while循环里等脉冲高低跳变的过程中一旦有中断插入计时就不准了测距精度就崩了。输入捕获是硬件自动记录引脚电平跳变时的定时器计数值精度到微秒级而且不占用CPU。下面是我调试时用的核心代码片段使用STM32标准库定时器TIM2的通道1做输入捕获通道2做TRIG的普通延时触发// 初始化PA0作为TRIG输出PA1作为ECHO输入捕获 void Ultrasonic_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // PA0 - TRIG 推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // PA1 - ECHO 输入捕获浮空输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPD; GPIO_Init(GPIOA, GPIO_InitStructure); // TIM2_CH1 映射到PA1工作在输入捕获模式 TIM_ICInitStructure.TIM_Channel TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; // 先捕获上升沿 TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 0x0F; // 输入滤波防止高频毛刺 TIM_ICInit(TIM2, TIM_ICInitStructure); TIM_Cmd(TIM2, ENABLE); }测量流程我在主循环里通过状态机实现先触发一次TRIG拉高10us以上我习惯拉高20us保证可靠然后使能输入捕获中断在中断里记录上升沿和下降沿的定时器计数值差值就是回波时间。下面这段是中断服务函数里的核心处理逻辑我只贴关键部分// 全局变量捕获边沿标志和计数寄存 volatile uint16_t capture_value 0; volatile uint8_t capture_index 0; volatile uint32_t echo_time_us 0; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_CC1) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); // 捕获到上升沿切换为下降沿捕获记录当前计数值 if (capture_index 0) { capture_value TIM_GetCapture1(TIM2); TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Falling); capture_index 1; } // 捕获到下降沿计算脉宽 else { uint16_t now_value TIM_GetCapture1(TIM2); if (now_value capture_value) { echo_time_us now_value - capture_value; } else { // 处理定时器溢出回绕的问题 echo_time_us now_value (0xFFFF - capture_value); } TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Rising); capture_index 0; echo_time_ready 1; // 置位测量完成标志 } } }定时器配置为72MHz分频到1MHz这样每个计数值正好对应1微秒。echo_time_us就是超声波往返的总时间。测距函数里拿到这个时间后先读温度再计算修正后的声速最后算出距离。实话实说输入捕获这套代码本身不难但特别容易在细节上翻车。我前后调了两天才发现溢出回绕处理的bug当捕获的上升沿接近65535、下降沿在0附近时如果直接相减会得到一个极大的错误值距离瞬间变成几十米。加了溢出判断之后才真正稳定下来。3.3 与飞控通信MAVLink还是直接串口STM32算出了高度之后下一步就是把这个数据告诉飞控让飞控决定是否执行投递。我用的方案是串口直连STM32以50Hz的频率发送一个固定格式的数据帧大致长这样// 帧格式帧头 高度数据(单位cm) 状态位 校验和 uint8_t frame[6]; frame[0] 0xAA; frame[1] 0x55; frame[2] (uint8_t)(distance_cm 8); frame[3] (uint8_t)(distance_cm 0xFF); frame[4] status; // 0正常, 1数据无效, 2盲区 frame[5] frame[0] frame[1] frame[2] frame[3] frame[4]; // 简单校验飞控端通过串口接收校验通过之后把高度数据传到飞控的特定参数通道里参与投递逻辑的判断。如果你用的是Pixhawk可以写在APM或者PX4的脚本里监听串口数据然后映射成自定义消息如果你用的是自研飞控那就更灵活了直接内部订阅这个高度话题就行。有人可能会问为什么不直接让飞控读取超声波传感器省掉STM32这层答案还是回到中断响应和时序控制。飞控的任务调度优先级很复杂运行超声波测距需要精确的微秒级延时触发万一被更高优先级的任务抢占触发脉冲宽度不够传感器直接不工作。STM32独立处理测距之后飞控那边只要接收数据逻辑清爽故障隔离也方便。3.4 投递判定逻辑不是“高度到了就放货”投递动作的判定比很多人想象的要复杂。一开始我想得很简单飞控判断无人机高度低于20cm直接开舱。结果第一次测试就出问题了。因为下洗气流会把地面附近的落叶、碎纸屑吹起来超声波测到的是这些飘浮物的距离而不是真实地面。落叶飘过探头正下方的时候高度读数从45cm瞬间掉到3cm触发开舱货物直接掉在还有30cm高的空中。后来我把投递条件改成了“三重确认”高度确认滤波器输出的高度连续1秒保持在20cm以下不要求瞬时值避免垃圾数据触发。下降速率确认由飞控的垂直速度数据判断无人机垂向速度小于0.1m/s也就是基本稳定悬停。地面一致性确认超声波在1秒内的多次测量值波动不超过3cm。如果波动大大概率是有东西在探头下方晃比如气流吹起的杂物。只有这三个条件同时满足STM32才向舵机发送开舱指令。这样做的原因很简单——无人机物流是“最后一公里”的关键环节你宁可晚投递1秒也不要提前0.1秒把货丢在半空中。4. 系统化测试从桌面实验到真机悬停4.1 桌面初期测试不能跳过的一步正式上无人机之前我花了一整周做桌面测试。测试方法不复杂把超声波模块固定在一个可升降的台架上对着不同材质的地面测距记录数据和实际距离对比。这里有个非常关键的发现不同地面材质对超声波回波的影响天差地别。硬质平整地面水泥地、地板回波干净测距很准草地、泥土这类软地面会吸收大量声波回波变弱偶尔会丢波石子路面则是多径反射严重数据跳动大。我拿到的实测数据大致是这样的地面材质实际距离(cm)测距均值(cm)抖动范围(cm)备注硬质水泥地3030.2±1.0最理想草地3031.5±3.2回波偏弱偶尔丢帧泥土地3029.0±2.8回波弱容易测深碎石地面3033.4±5.6多径干扰明显这个表给我提了个醒投递判定逻辑中高度阈值不能定得太死。如果你要求高度必须在“20cm整”才放货那在碎石地面上很可能永远满足不了。更合理的做法是在高度判断中加一个范围容忍度比如“15cm到25cm之间”同时结合其他条件而不是死磕一个精确值。4.2 真机悬停测试第一次上机的惊喜与惊吓桌面测试通过之后我把这套系统装到四轴无人机上进行室外悬停测试。第一次测试用的条件是无风天气、水泥地面、悬停高度80cm然后手动控制缓慢下降。说实话第一次上机的数据让我挺意外的悬停80cm时超声波测距值还算平稳稳在81cm左右但当我推动油门让无人机缓慢下降时高度在30cm左右时开始出现明显的跳动偶尔会出现0cm的丢波数据。排查下来问题出在电机和电调的电磁干扰上。STM32和超声波模块之间的连接线成了天线把电机PWM的跳变噪声耦合了进来导致ECHO信号出现毛刺输入捕获误触发。解决过程也给大家参考首先把STM32板子和超声波模块之间的排线换成了双绞屏蔽线屏蔽层单端接地其次在STM32的输入端加了RC低通滤波截止频率设在5kHz左右用来滤掉高频电磁噪声最后把ECHO输入捕获的输入滤波器参数调大了一些。这三板斧下去干扰问题基本消失悬停时高度抖动降到了±2cm以内。4.3 快速下降场景的极限测试为什么会“怼地”悬停测试做得差不多了我想测一个更极端的情况无人机从1米高度快速下降。理论上这不太会出现因为正常投递流程应该是缓慢下降但我想看看系统在极限工况下的表现。结果发现一个非常危险的现象快速下降时超声波高度数据明显滞后于真实高度。原因是HC-SR04测量时间本身就有一个周期我配置的测量周期是20ms一帧也就是50Hz再加上滤波算法里的滑动窗口延迟总共滞后了大概50-80ms。如果无人机下降速度是2m/s这80ms的滞后就意味着16cm的实际高度偏差。换句话说飞控看到的高度是34cm真实高度其实已经是18cm了再晚一步就撞地了。解决这个问题我用了两个办法。第一个办法是在飞控端引入IMU加速度计数据进行“高度预测”补偿用无人机自身的垂直加速度推算短时间内的真实高度和超声波数据进行融合相当于用IMU填补超声波测量的时间空隙。第二更简单的办法是限制无人机在投递阶段的下降速度不超过0.5m/s从源头上控制滞后造成的误差。实际执行投递任务时我更倾向于后者——安全大于一切。5. 常见问题与故障排查我踩过的坑你直接避掉5.1 传感器“间歇性失明”丢波和数据跳变丢波就是在测量周期内完全没有回波信号ECHO一直保持低电平测距结果为0。我在测试中遇到丢波的主要原因有两个。第一是传感器探头上有灰尘或水雾。有一次我放在仓库里调试刚好仓库在做喷淋降尘传感器探头上落了一层细水雾结果整个下午数据都不对。后来我查了下超声波探头表面有异物会严重影响声波的发射和接收效率尤其是液体覆盖时声波能量大部分被反射掉了。解决办法也很土在探头外面加了一个3D打印的防尘罩罩子底部开口朝向斜下方既能防尘又不会阻挡声波路径。第二个原因是测量距离超过量程。HC-SR04标称4米量程但实际在户外强光、有风、有气流扰动的环境下有效量程可能缩水到2.5米左右。如果你的无人机要做高高度投递比如5米、或者需要在空中悬停等待时持续监测地面单个HC-SR04是不够的需要换用更大功率的超声波传感器或者与其他测距传感器做融合。5.2 丢波之后高度归零怎么办安全策略不能少前面说过丢波时测距值可能是0这是非常危险的。如果飞控不做任何处理它会把0cm当作“接触地面”的信号直接触发降落结果就是在5米高空直接往下怼。我的安全策略是这样STM32端如果连续3次测量都没有有效回波就把高度状态位标记为“数据无效”这时候飞控端的投递操作应该被强制锁定同时切换到备份测距方案比如激光测距模块或者下视摄像头或者直接取消投递、触发返航。核心原则是数据不可靠的时候系统行为必须是“不做动作”而不是“做错误动作”。5.3 温度补偿到底要不要做看你的使用场景如果你只是在室内飞着玩温度补偿可以不做误差在可接受范围内。但如果你是在做正经的货运无人机项目需要在不同季节、不同地域执行任务温度补偿还是强烈建议加上。加温度补偿的成本非常低一个DS18B20温度传感器大概两三块钱STM32的1-Wire驱动代码网上也有现成的。加上之后你只需要在计算距离时把声速改成动态计算值改动不超过10行代码。在我看来这笔投入换来的精度提升性价比是极高的。还有一个细节超声波的声速其实还受空气湿度影响但湿度的修正系数远小于温度工程上基本忽略不计。5.4 快速自检清单上机前的最后一道防线我总结了一份上机前自检清单每次试飞都会过一遍分享出来给大家参考[ ] 传感器电源电压是否稳定示波器看VCC纹波峰峰值不超过100mV。[ ] ECHO信号是否经过电平转换直接接3.3V GPIO必烧。[ ] 传感器探头表面是否清洁无灰尘、无油污、无遮挡。[ ] 温度补偿是否已启用声速参数是固定值还是动态值[ ] 滤波参数是否匹配当前场景悬停和降落模式是否用了不同策略[ ] 丢波安全逻辑是否生效连续丢波时飞控会不会锁死投递[ ] 与飞控的通信是否正常串口波特率一致校验逻辑无冲突这份清单看起来琐碎但每一条都是我实际踩过坑之后总结出来的。上机前花十分钟过一遍能省下后面至少一小时的排查时间。6. 经验沉淀这套方案还能怎么玩未来能派上什么用场做完整套系统我最大的感受是超声波测距这个“小技术”放到货运无人机这个“大场景”里其实被赋予了新的要求和挑战。它不再是桌面上的玩具而是一个需要跟飞控、执行机构、环境感知体系紧密耦合的可靠性部件。如果你有兴趣继续扩展有几个方向我认为非常值得尝试。第一个方向是多传感器融合。超声波负责近距精测激光或视觉负责中远距离粗测加上IMU的短时预测把三者用卡尔曼滤波融合起来能够实现从高空巡航到低空投递的全过程连续测距。这样就不用在临近地面时才切换到超声波整个下降过程都能保持平滑和连续。第二个方向是用超声波阵列做地面识别。既然不同材质的地面对超声波回波的强度、稳定性有不同反应那就可以用多个探头加上回波幅值分析让无人机判断当前下方是水面、草地还是硬地路面进而自动调整投递策略。比如判断是水面时直接取消投递返航。第三个方向是结合边缘计算做智能决策。STM32作为前级处理把测距结果和原始回波特征传给上位机比如树莓派或者Jetson Nano由视觉和超声波共同决策投递时机。这需要更快的通信链路和更高的算力但从系统能力上讲是把“测距”升级成了“环境理解”是从“能投”走向“会投”的关键一步。就目前而言我这套基于HC-SR04和STM32的方案已经能够满足日常货运无人机在低空投递场景下的精准测距需求。它的核心价值不在于某个单项指标特别突出而在于整套逻辑——测距、滤波、判定、执行、安全保护——环环相扣构成了一套哪怕出问题也不会出大问题的系统。最后分享一个小经验做这类嵌入式测距项目别急着上真机先把测试环境搭好把不同场景下的数据录下来离线回放分析。很多问题在数据回放阶段就能看出来省下来的真机调试时间远比你在电脑前多敲几行代码的时间值钱。