收发一体超声波测距内部源程序:时序、余振与精度优化
简介这是一套面向嵌入式初学者与电子爱好者的超声波测距完整方案采用收发一体超声波探头配合51单片机完成收发处理可直接用于倒车雷达等测距场景的复现与二次开发。压缩包共21个文件约54KB包含C源程序、汇编文件、STARTUP.A51启动代码、HEX烧写文件以及uvproj、uvopt等Keil工程配置和编译生成的obj、lst、m51等中间文件工程结构完整便于直接打开编译与烧录验证。目前已有1236人学习下载说明该方案在同类教程中具有较好的参考价值。读者可从中获取收发一体探头的驱动逻辑、定时器测距算法、中断处理与串口输出等关键实现思路并对照原理图理解硬件与软件的配合方式适合作为课程设计、毕业设计或倒车雷达入门项目的参考素材。1. 收发一体超声波探头做测距为什么比模块方案更值得折腾市面上几块钱的 HC-SR04 模块用起来确实省心Trig 一发、Echo 一收pulseIn读个高电平时间距离就出来了。但只要你拆过它就知道那上面是收发分体的两个探头中间隔着一道塑料筋。一旦你想把探头塞进更小的结构里或者想自己控制发射波形、自己算回波到达时刻模块就变成了黑匣子——你只能用它给你的结果改不了它内部的任何一步。收发一体探头常见型号如 TCT40-16T/R 的一体封装、MA40S4S 这类把发射和接收压电陶瓷做在同一个壳里体积小、成本低但代价是发射时它自己在振收完余振才能听回波。这个「余振拖尾」就是整个方案里最玄学的地方也是内部源程序真正要解决的核心问题。标题里的「内部源程序」说白了就是一套跑在单片机上的完整固件驱动发射、切换收发、采样回波、算时间、出距离。这套东西适合谁适合已经会用 STM32 或 51 点个灯、想真正搞懂超声波测距底层时序的人适合做倒车雷达、液位计、机器人避障但嫌模块精度不够的人也适合被「超声波换能器等效电路图」绕晕、想从代码反推硬件行为的人。它不适合只想三行代码出个距离的纯应用层开发者——那用模块更划算。2. 收发一体探头的时序与等效电路先搞懂余振再写代码2.1 从换能器等效电路看「收完才能听」超声波换能器在谐振频率附近电气上可以等效成一个 RLC 串并联网络静态电容 C0 并联在端口上动态支路由 Lm、Cm、Rm 串联组成谐振时 Lm 和 Cm 相互抵消只剩 Rm 决定阻抗。这个等效电路图不是学术摆设它直接解释了两件事。第一为什么驱动要用方波而不是正弦。方波里含有丰富的谐波只要基频落在探头的谐振点常见 40kHz换能器自己会把非谐振分量滤掉机械振动幅度反而比纯正弦更大电路还简单。第二为什么发射完不能立刻接收。发射瞬间 C0 和动态支路都被激励压电片在机械上还在自由振荡这个余振衰减到能被回波淹没通常需要几百微秒到一两毫秒取决于探头的 Q 值和阻尼设计。我一般把这段余振叫「盲区时间」。40kHz 探头在空气中盲区大概 15~25cm想测更近就得靠阻尼电路或者软件上做余振抵消但那是另一个话题了。2.2 收发切换的硬件与软件配合收发一体意味着同一个端口既要输出驱动又要输入回波。常见做法是用两个 IO一个推挽输出接探头一端另一端通过限流电阻接地接收时把输出 IO 切成高阻或经模拟开关断开回波信号经放大比较后送进 MCU 的输入捕获或外部中断。下面这段是 STM32 上典型的发射与切换骨架用 TIM 输出 40kHz 方波发完立刻切输入捕获// 发射TIM3_CH1 输出 40kHz 方波持续 8 个周期 void Ultrasonic_Transmit(void) { // 探头驱动脚设为复用推挽 GPIO_InitStruct.Mode GPIO_MODE_AF_PP; HAL_GPIO_Init(TRIG_PORT, GPIO_InitStruct); __HAL_TIM_SET_AUTORELOAD(htim3, 899); // 72MHz/900 80kHz? 见下方说明 __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 450); // 50% 占空比 HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); // 用更新中断计数发满 8 个周期就停 burst_count 0; __HAL_TIM_ENABLE_IT(htim3, TIM_IT_UPDATE); } // 更新中断里数周期够了就关 PWM 并切接收 void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) { if (burst_count 8) { HAL_TIM_PWM_Stop(htim3, TIM_CHANNEL_1); __HAL_TIM_DISABLE_IT(htim3, TIM_IT_UPDATE); Ultrasonic_SwitchToRx(); // 切高阻 开输入捕获 } }逻辑说明先让定时器输出 PWM 驱动探头用更新中断数够 8 个周期后停发。参数上40kHz 对应周期 25us若定时器时钟 72MHz则 ARR 应设为 72e6/40e3 - 1 1799上面写 899 是 80kHz 的笔误实际要按你的时钟树算。占空比 50% 是常见选择也有用窄脉冲提高发射能量的做法但会牺牲波形对称性。发完立刻切接收切换延迟越短盲区越小但太短会把余振当成回波这就是后面避坑章要讲的。2.3 回波到达时刻怎么算才准接收端有两种主流做法输入捕获测脉宽或者 ADC 采样做包络检波。输入捕获简单但需要外部比较器把回波整形成方波ADC 方案灵活能自己设阈值但占 CPU。我一般用「输入捕获 可调比较阈值」的组合回波经两级放大后送比较器比较器阈值由 DAC 或 PWM 滤波给出这样能动态适应不同距离的回波幅度。捕获到上升沿记下计数值减去发射结束的时刻就是回波飞行时间 t。距离 t × 声速 / 2。声速别用 340 死值温度补偿公式 v 331.4 0.607 × T(℃)室内温差 10℃ 就能带来 1.8% 的误差3 米外差 5cm 以上。// 输入捕获回调记录回波上升沿 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Channel HAL_TIM_ACTIVE_CHANNEL_2) { echo_tick HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); // 处理定时器溢出拼出完整计数值 echo_tick overflow_cnt * 65536; flight_time_us (echo_tick - tx_end_tick) / 72; // 72MHz 计数 float temp Read_Temperature(); float speed 331.4f 0.607f * temp; distance_cm flight_time_us * 1e-6f * speed * 100.0f / 2.0f; } }参数说明overflow_cnt是定时器溢出次数长距离必须处理否则 65536 计数溢出后距离会跳变。tx_end_tick是发射最后一个周期结束时的计数值不是发射开始的时刻差一个 burst 时长会引入固定偏差。温度读取可以用 DS18B20 或 MCU 内部传感器后者精度差但够用。3. 内部源程序的模块拆解从驱动到距离输出的完整链路3.1 工程目录与各文件职责拿到一份「内部源程序」别急着全编译。先看目录结构通常这类工程会分成驱动层、算法层、应用层。我见过的典型布局是这样文件/目录职责改动频率bsp_gpio.c探头驱动脚、切换脚初始化低bsp_tim.cPWM 发射、输入捕获配置中ultrasonic.c发射、接收、计时、距离换算高filter.c中值、均值、滑动滤波中main.c主循环、串口输出高驱动层别乱动尤其是定时器配置改错一个预分频整个时序就崩。算法层是你要重点读的距离换算和滤波都在这里。应用层最自由接 OLED、串口、蓝牙都行。3.2 发射驱动与接收放大的参数怎么定发射部分的核心参数是 burst 周期数和驱动电压。周期数太少发射能量不够回波弱太多余振拖尾长盲区大。40kHz 探头在 3~5 米量程下我一般用 8~12 个周期。驱动电压直接决定发射声压3.3V 直驱只能测一两米想上 5 米得用升压或桥式驱动到 10V 以上。接收放大链的增益要算清楚。回波信号在 1 米处大概几毫伏5 米处衰减到几十微伏。如果比较器阈值固定近处会误触发、远处收不到。所以要么用可变增益放大器VGA要么用 MCU 的 DAC 动态调阈值。下面是一个按距离分段设阈值的思路// 根据发射后经过的时间动态调整比较器阈值 // 近处余振大阈值高远处回波弱阈值低 uint16_t Get_Threshold(uint32_t elapsed_us) { if (elapsed_us 2000) return 2000; // 2ms 内压制余振 else if (elapsed_us 8000) return 1200; // 对应 1.4m 内 else if (elapsed_us 20000) return 600; // 对应 3.4m 内 else return 300; // 更远尽量灵敏 }逻辑说明阈值随时间递减本质是用时间换灵敏度。参数不是拍脑袋要拿示波器看实际回波幅度再定。注意阈值切换的时刻要避开回波到达的瞬间否则会把真实回波切掉。3.3 距离换算与温度补偿的代码落点距离换算看着简单坑都在细节里。除了前面说的声速温度补偿还有两个点一是计时起点到底取发射开始还是发射结束二是单位换算里的整数溢出。// 完整距离计算含温度补偿和溢出保护 float Calc_Distance(uint32_t flight_us, float temp_c) { float speed 331.4f 0.607f * temp_c; // m/s float dist_m flight_us * 1e-6f * speed / 2.0f; if (dist_m 6.0f) dist_m 6.0f; // 超量程钳位 return dist_m * 100.0f; // 转 cm }参数说明flight_us用 32 位无符号最大约 4294 秒不会溢出。temp_c来自温度传感器没有的话用 25.0 默认值误差约 1%。钳位是为了防止无回波时输出乱跳实际产品里还会加「连续 N 次无回波判定为超量程」。4. 避坑与排查收发一体方案最容易翻车的五个地方4.1 余振被当成回波近距离永远测不准现象距离小于 20cm 时读数乱跳或者固定显示某个值。原因发射结束后探头还在振比较器把余振的尾巴当成回波触发了捕获。解决软件上在发射后加一段「消隐时间」比如 1.5ms 内不响应捕获硬件上给探头并联阻尼电阻加快余振衰减。消隐时间要实测太长会吃掉真实近距回波。4.2 定时器溢出没处理远距离读数突然变小现象测 2 米以内正常超过某个距离读数突然变成几十厘米。原因输入捕获用的是 16 位定时器72MHz 下 65536 计数只有 0.91ms对应约 15cm 单程早就溢出了。没累加溢出次数计数值回绕。解决在定时器更新中断里累加overflow_cnt捕获时拼成 32 位。或者把预分频调大牺牲分辨率换量程。4.3 声速用固定值温度一变精度就崩现象早上校准好的中午读数偏大或偏小。原因声速随温度变化代码里写死 340m/s。解决加温度传感器按331.4 0.607T实时算。没有传感器就用 NTC 分压进 ADC成本几毛钱。别信「室内温度稳定」这种话空调房和室外差 15℃ 很常见。4.4 收发切换延迟盲区比理论值大一圈现象理论盲区 15cm实际 30cm 才稳定。原因软件切换 GPIO 模式有延迟加上比较器恢复时间实际消隐窗口比设定值长。解决用模拟开关做硬件切换MCU 只管控制信号或者把切换代码放到中断里减少主循环延迟。实测切换延迟可以用示波器看驱动脚和捕获脚的时序差。4.5 多探头互相干扰串扰当成回波现象单个探头工作正常装两个以上就互相串数据。原因A 探头发的波被 B 探头收到B 以为是自己发的回波。解决分时复用同一时刻只允许一个探头发射或者给不同探头用不同频率40kHz 和 38kHz接收端加带通滤波。分时复用最简单代价是刷新率下降。5. 把精度再压一档多脉冲相干平均与阈值自适应单次测量的抖动主要来自回波前沿的判断误差和噪声。想把精度从厘米级压到毫米级我一般做两件事多脉冲相干平均和阈值自适应。多脉冲相干平均不是简单地把 N 次距离求平均那样对随机噪声有效但对固定偏差没用。做法是连续发 N 个 burst每个 burst 的回波都做 ADC 采样把 N 段波形按时间对齐后累加。真实回波因为相位一致会增强随机噪声因为不相关会抵消信噪比提升约 √N。N 取 8~16 比较划算再多就影响刷新率了。#define COHERENT_N 8 uint32_t coherent_sum[COHERENT_N][SAMPLES]; // 每个 burst 采一段存进对应行 // 全部采完后按列累加找峰值位置 uint32_t Find_Peak(void) { uint32_t max_val 0, max_idx 0; for (int i 0; i SAMPLES; i) { uint32_t sum 0; for (int j 0; j COHERENT_N; j) sum coherent_sum[j][i]; if (sum max_val) { max_val sum; max_idx i; } } return max_idx; // 峰值对应的采样点换算成时间 }参数说明SAMPLES是一次采样的点数取决于 ADC 速率和量程比如 1MHz 采样、6 米量程需要约 350 个点。COHERENT_N越大信噪比越好但内存占用线性增长STM32F103 这种 20KB RAM 的片子要算着用。阈值自适应则是根据历史回波幅度动态调比较器阈值。维护一个回波峰值的历史均值当前阈值设为均值的 60%~70%。这样环境变化比如探头积灰、温度漂移时不用重新校准。我习惯在开机时做一次自校准对着已知距离的挡板测 10 次记下峰值之后按这个基准跑。最后说个我自己的习惯每次改完时序参数一定拿示波器同时看驱动脚和捕获脚确认消隐窗口和回波位置对得上再去看串口数据。光看数字容易被滤波后的平滑值骗过去波形不会骗人。这套收发一体的方案折腾下来精度做到 ±3mm 是现实的但前提是每一步时序都自己心里有数别指望一份源程序原样烧进去就万事大吉。希望帮到你。本文还有配套的精品资源点击获取