拓冰建站拓冰建站
首页 / 资讯中心 / 正文

单片机超声波测距实战:从HC-SR04到STM32代码与误差校正

简介这是一份基于STC89C52单片机的超声波测距系统设计资料适合电子信息、自动化等专业学生用于课程设计或毕业设计参考。内容围绕超声波传感器的工作原理与特性、发射接收电路、液晶显示模块、报警模块以及温度补偿机制展开完整呈现了从方案论证、硬件电路设计到软件算法与误差修正的设计思路。压缩包内仅含1个docx文档大小约389KB属于可直接打开阅读的设计报告格式便于按章节查阅系统总体方案、单片机控制流程、测距计算过程以及报警范围设定等内容。目前已有一百人学习下载。对于正在做单片机测距类课题、需要论文框架或实现细节的读者这份资料可作为低成本高精度测距系统设计与撰写的参考能够帮助快速理解超声波测距原理并借鉴其硬件电路和软件设计方法。1. 基于单片机的超声波测距从 HC-SR04 到实物量产的完整链路如果只把 HC-SR04 模块的四根线接到单片机最小系统上串口助手能蹦出距离值这个“单片机超声波测距”的课程设计其实只完成了一半。定时器分频选错了回波脉宽会量不准Trig 脉宽卡在临界值模块会偶尔不响应Echo 悬空时程序干脆死在等待语句里——这些全是实物调试一定会遇到的边界问题。下面按“基于单片机的超声波测距设计与实现”这条路先讲物理原理和器件选型再给 C51 与 STM32 两套能直接烧录的代码最后用温度补偿和实物标定把误差压到毫米级适合正在做课程设计、备赛蓝桥杯单片机或者想把手头开发板变成测距工具的读者。2. 超声波测距原理与器件选型从声速公式到定时器量程超声波测距的逻辑非常朴素发射端发出 40kHz 的脉冲串声波遇到目标反射回来接收端记录从发射到收到回波的时间距离就是声速乘时间的一半。单片机在这条链路里的任务只有两个触发发射、测量回波高电平的宽度。难点不在于触发而在于把时间量准。2.1 回波时间与声速公式÷2 是最容易漏掉的一步先写结论在 20℃ 空气中声速约为 343m/s合 0.343mm/us。如果回波脉宽是 t单位是 us那么目标距离的计算方式如下// 单程距离 声速 × 时间 / 2 float dist_mm 0.343f * echo_ticks / 2.0f;echo_ticks是定时器测量到的 Echo 高电平持续时间单位是 us。之所以要除以 2是因为声波走了去程加回程两倍距离HC-SR04 的 Echo 脚恰好把这两段合并成一个高电平。C51 里做整数运算时常见写法是时间乘 17 再除 100得到的单位是毫米本质就是 0.343 / 2 约等于 0.1715 的整数近似。这个写法没问题但要注意 16 位乘法溢出尤其是回波时间超过 38000us 时。依赖固定声速 343m/s 有一个隐含前提空气成分基本不变、温度 20℃、湿度适中。实际环境里温度每升 1℃声速约增加 0.6m/s这会在 2m 距离上带来约 3.5mm 的偏差。量程很短时无所谓量程超过 1m 后就应该把温度补偿作为正式功能纳入设计而不是可选优化。具体公式和标定流程放在第 5 章。2.2 HC-SR04 与单片机的选型边界从课程设计到工程现场课程设计和竞赛中最常见的组合是 51 单片机加 HC-SR04STC 系和 STC32 这类增强型 51 内核单片机也能直接驱动 HC-SR04。HC-SR04 的核心参数是量程 2cm 到 400cm、工作电压 5V、Trig 和 Echo 为 TTL 电平。JSN-SR04T 是防水探头版本量程更长适合液位检测US-016 输出模拟电压适合不需要回波时序的场景。下表是几种常见模块的对比。模块量程供电接口方式典型场景HC-SR042cm400cm5VTrig/Echo电平宽度课程设计、避障小车US-0162cm300cm5V/3.3V模拟电压/串口对时序敏感度低的应用JSN-SR04T20cm600cm5VTrig/Echo探头与电路分体水箱液位、户外探测选型时我一般先确认工作电压。STM32F103 的 IO 是 3.3V 逻辑虽然能驱动 HC-SR04 的 Trig但 Echo 返回的高电平是 5V直接接在 3.3V 单片机上存在烧引脚风险必须加电平处理。51 单片机在这一点上有天然优势5V IO 直接对接这也是大量超声波课设选 51 的原因之一。超声波雷达在倒车系统中用的也是同一套回波测距逻辑HC-SR04 把收发端做成一体模块单片机要做的动作被简化到只剩触发和计时。2.3 定时器计数频率决定毫米级精度量化误差不是噪声测距的核心是把 Echo 高电平宽度量准。常见做法是把定时器配置成固定频率的计数器例如 12MHz 晶振的 51 单片机在 12 分频下计数器每 1us 加 1主频 72MHz 的 STM32 把定时器预分频设为 71同样得到 1us 计数周期。不同配置对应的分辨率如下表。单片机/晶振定时器分频计数周期距离分辨率单次最大量程(16位)51 12MHz12 分频(默认)1us约 0.1715mm约 11.2m51 11.0592MHz12 分频(默认)约 1.085us约 0.186mm约 10.4mSTM32 72MHz72 分频1us约 0.1715mm约 11.2mSTM32 72MHz36 分频0.5us约 0.086mm约 5.6m这里有两个容易被忽略的结论。第一1us 计数量化带来的误差是 0.17mm 级别远小于 HC-SR04 本身的测量抖动所以从 12MHz 51 换成 72MHz STM32并不会让距离数据变干净。第二16 位定时器的最大计数是 65535在 1us 周期下对应约 65ms按 343m/s 算能覆盖到约 11m比 HC-SR04 的 4m 标称量程大但如果把分频压到 0.5us可测距离会缩到约 5.6m这是实际使用中需要权衡的。代码实现上要注意不要在 while 循环里裸等while (Echo 1);而要在循环里检查定时器溢出标志。不然目标超出量程时 Echo 一直为高程序会死锁具体写法在第 4 章。3. 硬件电路设计最小系统、电平匹配与干扰处理从模块插上下载线能用到画一块能复现的板子中间隔着三个问题电平能不能安全对接、引脚分配会不会影响下载调试、供电纹波会不会让距离值周期性漂移。硬件设计的目标是把这三个问题在画板阶段解决掉而不是等实物出了问题再飞线。3.1 3.3V 单片机与 5V Echo 的电平转换分压是最稳的入门方案HC-SR04 手册没有统一标准但市面上大部分版本的 Echo 输出为 5V TTL。51 单片机 IO 能容忍 5V直连即可STM32 这类 3.3V 器件直连长时间处于高电平就可能超过引脚绝对最大额定值。最稳妥且最简单的做法是电阻分压。以 PA0 接 Echo 为例Echo 输出串一个 1kΩ 电阻到 PA0再从 PA0 对地接一个 2kΩ 电阻得到的分压约为 5 × 2 / (1 2) ≈ 3.33V正好落在 3.3V 逻辑高电平范围内。电阻选 1k/2k 而不是更大的阻值是为了保证分压节点在高阻输入下仍有足够驱动能力不至于因为引脚偏置电流把电压拉低。不要只串一个限流电阻而不接地那样得到的不是固定电平效果取决于单片机引脚内部结构。// 分压网络选型示意Echo(5V) --[1kΩ]-- PA0 --[2kΩ]-- GND // 分压约 3.33V可直接进入 3.3V 单片机 IO如果追求更低的输出阻抗也可以用 NPN 三极管做电平转换Echo 接基极、集电极上拉到 3.3V。但测距这种低速信号分压电阻方案已经足够没必要引入额外器件。3.2 引脚分配Trig 与 Echo 放同一组端口避开串口下载脚硬件设计的第二个坑是引脚分配。51 单片机的 P3.0/RXD 和 P3.1/TXD 同时承担串口下载功能如果把 Echo 接到 P3.0外部干扰可能拉高引脚导致下载失败或者程序启动异常。常见做法是把 Trig 和 Echo 放到 P2 口或者 P1 口的相邻引脚便于 PCB 布线也方便程序里用同一个端口的位操作。信号51 常用引脚STM32 常用引脚作用TrigP2.0PA1输入单片机给至少 10us 高电平脉冲EchoP2.1PA0需分压输出高电平宽度等于声波往返时间VCC5V5V 供电模块供电GNDGNDGND必须与单片机共地在线调试还有一个细节如果调试器用的是 SWD而 SWDIO/SWCLK 恰好被传感器占用也会导致无法连接。尽量把 Trig/Echo 安排在定时器通道和 SWD 都不冲突的引脚。下面把端口和位号抽成宏换平台时只改这一处#define TRIG_PORT P2 #define TRIG_PIN 0 #define ECHO_PORT P2 #define ECHO_PIN 1把端口、位号抽成独立宏代码其他地方不再出现硬编码引脚号这在从 C51 迁移到 STM32 时能少改很多处。要注意 Trig 和 Echo 不要共用一个位操作Trig 始终是输出Echo 是输入测距中 Echo 会被模块从低拉高再拉低共用一个变量会互相覆盖。3.3 电源去耦与共地供电纹波会让距离值周期性漂移超声波模块发射瞬间电流会突然拉高如果电源线又细又长模块供电电压会瞬间跌落导致 Echo 波形宽度抖动。典型现象是距离值每隔几百毫秒整体跳变一两个厘米且与测量目标无关。解决手段在模块 VCC 与 GND 之间并一个 100uF 电解电容和一个 100nF 陶瓷电容电解电容负责吸收瞬态电流陶瓷电容负责滤高频模块地线尽量单独走线回到电源地不要和单片机大电流回路共用一段细走线。更隐蔽的问题是 Echo 信号线上的干扰。HC-SR04 的 Echo 输出阻抗不低靠近电机或继电器会耦合噪声。如果实测发现 Echo 上升沿抖动可以在 Echo 引脚对地并联一个 100pF 电容做简单滤波但电容值不要选太大否则会对回波边沿产生积分延迟影响精度。这个 100pF 只能针对高频毛刺不能解决共地不良根因还是要处理地线。4. 软件实现C51 与 STM32 双平台的测距时序与滤波硬件把信号链路打通后软件要回答的问题只有一个怎么把一个变化的高电平宽度变成稳定可信的距离。这一章给出 C51 和 STM32 两套实现第一套用定时器轮询 Echo第二套用输入捕获中断分别对应课程设计和稍高一点的工程要求。4.1 HC-SR04 的触发时序手册上的 10us 不是等宽脉冲手册对 Trig 的典型描述是给一个大于 10us 的高电平但实际触发逻辑是检测上升沿高电平持续时间不是精确定时关键。常见错误是延时函数恰好写成 10us而函数调用和入栈出栈开销就可能让实际脉宽不足。我一般直接用 15us 到 20us留出余量模块会自行完成 8 个 40kHz 脉冲的发射。// 触发一次超声波发射Trig 脉冲宽度约 15us Trig 0; delay_us(5); Trig 1; delay_us(15); // 略大于 10us 即可不要纠结精确值 Trig 0;delay_us在 12MHz 51 下用空循环实现偏差不会影响触发结果。关键是Trig 0之后要等待一小段时间再拉高确保模块内部状态机复位避免连续触发时模块还没准备好。提示在 Proteus 仿真里HC-SR04 不会自己产生 Echo 回波需要手动加一个脉冲信号模拟。仿真只能验证触发时序和测距计算不能验证真实回波质量实物调试前别只依赖仿真结果。4.2 C51 完整测距代码定时器 0 计时 Echo 脉宽串口输出#include reg52.h #include stdio.h sbit Trig P2^0; sbit Echo P2^1; void delay_us(unsigned int n) { while (--n); } void delay_ms(unsigned int ms) { unsigned int i, j; for (i 0; i ms; i) for (j 0; j 120; j); } char putchar(char c) { SBUF c; while (TI 0); TI 0; return c; } unsigned int measure_once(void) { unsigned int cnt 0; Trig 0; delay_us(5); Trig 1; delay_us(15); Trig 0; // 等待 Echo 拉高带超时保护 while (Echo 0) { if (cnt 60000) return 0; // 约 60ms 无回波按超时处理 } TH0 0; TL0 0; TR0 1; // 测量高电平宽度溢出即时退出避免死锁 while (Echo 1) { if (TF0 1) { TR0 0; TF0 0; return 0xFFFF; // 超量程 } } TR0 0; return (TH0 8) | TL0; } void main(void) { unsigned int ticks; unsigned long dist_mm; TMOD 0x21; // T0 模式1(16位)T1 模式2(波特率) SCON 0x50; // 串口模式1 TH1 0xFD; // 9600bps 11.0592MHz TL1 0xFD; TR1 1; TI 1; while (1) { ticks measure_once(); if (ticks 0) printf(no echo\r\n); else if (ticks 0xFFFF) printf(out of range\r\n); else { // 17/100 等价于 0.343/2 换算到毫米 dist_mm (unsigned long)ticks * 17UL / 100UL; printf(dist%lumm\r\n, dist_mm); } delay_ms(200); } }代码里有三个值得说明的地方。第一TMOD 0x21一次性配置好 T0 为 16 位手动重装模式、T1 为 8 位自动重装模式避免分散初始化时覆盖对方。第二等待 Echo 拉高用cnt做超时保护目标是太近或者没有目标时程序不会卡死。第三(unsigned long)ticks * 17UL / 100UL必须先乘后除ticks 最大 65535乘以 17 后约 111 万超过 16 位范围所以必须强转成unsigned long。这个类型转换是初学者最容易漏的地方漏掉后除法结果会完全不对。串口部分用putchar重定向Keil C51 工程里不需要额外库配置printf就能直接往串口发数据。波特率 9600定时器 1 初值 0xFD 对应 11.0592MHz 晶振换晶振时要同步改。4.3 STM32 输入捕获与溢出处理1us 计数溢出即退出STM32 上更推荐用定时器输入捕获测距过程不占用 CPU 轮询。核心思路是配置 TIM2 通道 1捕获 Echo 的上升沿和下降沿用两次捕获值之差得到高电平宽度。void TIM2_IC_Init(void) { GPIO_InitTypeDef gpio; TIM_ICInitTypeDef ic; TIM_TimeBaseInitTypeDef tb; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); gpio.GPIO_Pin GPIO_Pin_0; // PA0 接 Echo gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, gpio); tb.TIM_Prescaler 72 - 1; // 72MHz 分频到 1MHz tb.TIM_CounterMode TIM_CounterMode_Up; tb.TIM_Period 0xFFFF; tb.TIM_ClockDivision 0; TIM_TimeBaseInit(TIM2, tb); ic.TIM_Channel TIM_Channel_1; ic.TIM_ICPolarity TIM_ICPolarity_Rising; // 先捕获上升沿 ic.TIM_ICSelection TIM_ICSelection_DirectTI; ic.TIM_ICPrescaler 0; ic.TIM_ICFilter 0x0F; TIM_ICInit(TIM2, ic); // 每次上升沿清零计数器只保留单次高电平宽度 TIM_SelectInputTrigger(TIM2, TIM_TS_TI1FP1); TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset); TIM_SelectMasterSlaveMode(TIM2, TIM_MasterSlaveMode_Enable); TIM_Cmd(TIM2, ENABLE); TIM_ClearFlag(TIM2, TIM_FLAG_CC1); TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE); }这里的TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset)是关键每次上升沿到来计数器自动清零于是捕获到的 CCR1 值本身就是 Echo 高电平的持续时间单位是 us。中断里只需把 CCR1 读出来转成距离。量程处理要对应第 2 章的分辨率表格。65535us 在 1us 计数下对应约 11mHC-SR04 标称 4m所以中断里判断CCR1 12000约 4m直接丢弃避免模块在弱回波时输出异常长的高电平。4.4 中值滤波与滑动平均把厘米级跳动降下来实测中 HC-SR04 的表现不是噪声大而是偶尔冒出明显异常值例如 50cm 目标连续测几次偶尔会跳成 55cm。这种情况用滑动平均效果不好因为均值会被野值拉偏中值滤波更合适。常见做法是每轮取 5 次测量排序后取中间值。#define FILTER_N 5 unsigned int median_filter(unsigned int *buf) { unsigned char i, j; unsigned int tmp; for (i 0; i FILTER_N - 1; i) for (j i 1; j FILTER_N; j) if (buf[i] buf[j]) { tmp buf[i]; buf[i] buf[j]; buf[j] tmp; } return buf[FILTER_N / 2]; }要点是滤波在原始ticks上进行而不是在毫米换算之后因为换算后的毫米值已经是整数会丢掉亚毫米信息。排序 5 个数在 12MHz 51 上开销也很小但要注意每次测距之间保留几十毫秒间隔不能连续触发 HC-SR04否则模块尚未结束上一次回波处理测量值会整体偏移。5. 温度补偿与实物标定把误差压到毫米级5.1 温度每升 1℃声速增加 0.6m/s少算的距离有多明显第 2 章提过固定声速 343m/s 的误差来源。标准计算公式是 v 331.4 0.6TT 是摄氏温度。20℃ 时声速约 343.4m/s和代码里的 343 几乎一致但环境是 5℃ 的户外时声速变成 334.4m/s同样回波时间下用 343 计算距离会少算约 2.6%。在 2m 距离上就是 52mm 的偏差这已经远超 HC-SR04 本身毫米级的标称分辨率。所以只要设备可能在非空调房工作温度补偿就是必要功能。5.2 单点标定法用卷尺反推补偿系数温度补偿公式处理的是物理规律带来的偏差但模块自身还有零偏和安装偏差比如传感器没有完全垂直于目标平面。实际工程里更可靠的做法是把两者合并做一次单点标定。标定方法目标放在已知距离处例如卷尺量出 1000mm连续测 10 次取平均值用实际距离除以测量距离得到修正系数然后写进代码。下面是一组标定示例。卷尺距离(mm)测量均值(mm)修正前误差(mm)修正后误差(mm)500493-701000998-212000200440标定的测量距离要覆盖实际使用范围不能只在 500mm 处标定然后用于 2m 场景单点标定只能修正乘性误差和固定偏移叠加的结果。代码里加一个比例因子即可float calibration_k 1.0f; // 单点标定系数默认不修正 float speed 331.4f 0.6f * temp_c; // temp_c 来自 DS18B20 或 BME280 float dist_mm_raw speed * echo_ticks / 2000.0f; float dist_mm dist_mm_raw * calibration_k;calibration_k就是 1000 / 998 这类比值第一次运行时打印出来之后作为常量写死。如果装了温度传感器temp_c直接参与计算没装就把temp_c固定为当前室温标定系数仍然能吸收一部分固定偏差。把这一行替换进第 4 章的距离计算部分比在 printf 里修修改改干净得多。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门