
简介面向51单片机学习者、Proteus仿真爱好者和电子设计初学者的超声波测距完整方案基于HC-SR04传感器经测试可完美运行。资源包共38个文件、81KB涵盖Keil项目.uvproj、Proteus仿真工程.pdsprj、C语言源码.c/.h及编译生成的HEX烧录文件代码按主函数、传感器驱动、按键、数码管显示、定时器等功能模块拆分便于快速定位逻辑已有7854人学习下载。通过该项目可掌握HC-SR04触发与回波时序、51单片机定时器计数测距、数码管动态显示以及Proteus联合仿真调试等关键知识点。无需额外硬件直接用仿真工程即可观察测距结果若配合实物HEX文件可直接烧录验证。整体结构清晰、体量轻巧适合课程设计、毕业设计或入门练手也是一份便于二次开发的单片机项目模板可帮助读者厘清从传感器数据采集到显示输出的完整流程。 做单片机的人大概率都碰过超声波测距项目不管是课程设计、毕业设计还是纯粹想把手上的51开发板玩出点新花样。我自己的第一块开发板就是STC89C52点完流水灯之后干的第一件正事就是用HC-SR04超声波模块在Proteus里搭了一个测距仿真。当时踩了不少坑比如Echo引脚一直拉不高、数码管乱跳、距离数据完全不靠谱后来才一点点理顺。这篇就把整个项目从头到尾拆开讲清楚包括方案选型、HC-SR04时序、C语言程序怎么写以及Proteus仿真里那些容易卡住的地方适合刚学完51基础、想拿一个完整小项目练手的读者。1. 项目整体设计与方案选型1.1 这个项目到底在做什么一句话概括用51单片机控制超声波模块发射超声波接收回波后计算时间差根据声速算出障碍物距离最后把距离显示在数码管或LCD1602上。整个过程全程在Proteus软件里仿真完成不需要实物硬件也不需要真的握着超声波探头对着墙测。这个项目虽然看着小但能串起来的东西非常多定时器、中断、延时、信号时序、C语言状态机思维、外部电路连线全用上了。对初学者来说它是一个很理想的“毕业项目”练兵场。对有一定基础的人来说通过仿真快速验证逻辑也可以避免反复焊板子的麻烦。1.2 为什么是“51 HC-SR04 Proteus”这套组合先说51单片机。现在STM32、ESP32满天飞但51在学习和教学场景里依旧不可替代原因就一个简单到透明。8位内核、标准GPIO、定时器、串口、中断寄存器就那么几个不会像ARM那样动不动就要配时钟树和复用功能。对于学原理、学时序、学底层逻辑来说51反而比高端芯片更容易讲清楚。超声波测距对CPU主频和算力要求极低51完全扛得住。再说HC-SR04。它是市面上最普及的超声波测距模块成本低、资料多、时序简单不需要复杂的信号处理芯片。模块一共4个引脚VCC、GND、Trig、Echo工作逻辑非常直白给Trig一个大于10微秒的高电平脉冲模块自动发8个40kHz的超声波脉冲然后拉高Echo引脚Echo高电平的时间就是从发射到收到回波的时间。你只需要测出Echo高电平持续多久就能算出距离。最后是Proteus仿真。它的价值不在于“省一块开发板的钱”而在于把调试成本降到几乎为零。改程序、重新编译、重新仿真几秒钟的事。实物调试要动烙铁、接示波器、查接触不良仿真完全没有这些烦恼。而且Proteus的虚拟示波器可以直观看到Trig和Echo的波形时序这对理解模块工作原理帮助极大。注意Proteus仿真里选的“HC-SR04”是理想模型它不会像实物那样因为声波反射角度、环境噪声、供电不稳而出现信号抖动。所以仿真跑通了只能证明你的“逻辑”是对的不代表实物一定一焊就成后面我会专门说这个差异。2. 超声波测距的核心原理与关键参数2.1 HC-SR04工作时序与测距公式超声波测距的原理和生活里喊山听回声一模一样。你朝山谷喊一声记下从发声到听到回声的时间用这个时间乘以声速再除以2就是山和你的距离。HC-SR04就是做了这么件事只不过把“喊声”换成了人耳听不到的40kHz超声波。具体时序分四步单片机给Trig引脚一个大于10微秒的高电平脉冲模块被触发。模块自动发送8个周期的40kHz方波同时内部把Echo引脚拉高。超声波遇到障碍物后产生回波模块收到回波后把Echo引脚拉低。单片机测量Echo引脚高电平持续的时间t然后计算距离。距离计算公式距离 (时间 t × 声速 v) / 2为什么要除以2因为t是超声波“往返”的总时间从模块到障碍物再反射回来实际单程距离要减半。在标准环境温度20℃下声速大约是343m/s折合每微秒走0.0343cm。如果你用微秒做时间单位公式可以写成距离(cm) t(us) × 0.0343 / 2 ≈ t(us) / 58.3所以很多人的程序代码里会直接写成distance time / 58这个58就是这么来的。有人喜欢用time / 58.0保留小数有人用time * 0.017效果一样。用整数除法time / 58会舍去小数部分做厘米级显示没问题但如果你想精确到毫米就得用浮点运算或者定点数处理。2.2 温度对声速的影响值不值得做补偿声速不是恒定不变的。标准干燥空气里声速和温度的关系近似为v 331.4 0.607 × TT为摄氏温度v的单位m/s也就是说温度每升高1℃声速约增加0.607m/s。在20℃时声速约343.5m/s30℃时约349.6m/s差了约6m/s折算到1米距离上误差大约有17mm。如果你的项目只是做个测距显示或者避障小车这种误差完全不影响功能。但如果你做的是测量类仪器比如水位计、料位计那就得考虑加个DS18B20温度传感器做补偿了。我做这个项目的建议是仿真阶段先用固定声速343m/s也就是distance time * 17 / 1000这种写法。先把主体流程跑通再考虑补偿的问题。不要一上来就想着做到实验室级精度超声波测距本身就不是高精度传感器它的定位是“非接触、低成本、中等精度”量程内误差在3mm左右已经算优秀了。2.3 定时器计时的选型与配置测Echo高电平时间最常见的做法是用定时器计时。51单片机里定时器最常用的配置是T0工作在方式1也就是16位定时器模式。为什么用方式1因为方式0是13位最大计数8191方式2是8位自动重装最大计数256。如果系统时钟用12MHz机器周期是1微秒方式0和方式2能计的时间上限都不够用。方式1的16位计数上限是65535也就是最长能测65535微秒。65535微秒够不够用按照声速343m/s计算最大可测距离大约是65535 × 0.0343 / 2 ≈ 1124cm超过11米就超出量程了。而HC-SR04本身标称量程是2cm到400cm实际上在超出4米以后回波很微弱稳定性已经明显下降。所以16位定时器完全够用不用考虑级联扩展。程序里需要分两步给Trig引脚发送一个10微秒以上的高电平脉冲。等待Echo引脚变高变高瞬间启动定时器T0。等待Echo引脚变低变低瞬间停止T0并读取计数。如果Echo一直不变低超过某个阈值比如50毫秒就认为本次测量超时直接清零重来。3. Keil端C语言程序设计3.1 程序整体架构这个项目的程序不算复杂但写成“初始化主循环中断处理计算显示”这种清晰的结构对你后面做大项目非常有帮助。我建议至少拆成五个文件main.c 主函数负责初始化和循环调用 ultrasonic.c 超声波触发与回波时间测量 ultrasonic.h 超声波模块头文件 lcd1602.c LCD屏幕驱动 lcd1602.h LCD驱动头文件有的教程会教你用软件延时方式测Echo也就是死等Echo变高然后数空循环次数。这种写法少用因为空循环的延时和编译器优化强相关换个优化等级结果可能完全不同。定时器的方式更可靠也更能锻炼你对单片机的理解。我用的是T0定时器查询方式测Echo。3.2 LCD1602还是数码管显示方案我推荐LCD1602原因非常实际它能直接显示“Distance: 12.5cm”这种带单位的完整信息调试阶段你一眼就能判断程序逻辑是否正确。数码管的优势是便宜、亮度高、显示直观但显示距离值需要自己处理小数点和单位代码量反而更多。Proteus仿真的LCD1602模型非常成熟接线也方便这里直接用LCD1602。3.3 核心代码实现下面是我整理好的完整程序结构关键部分都加了注释。主函数main.c#include REGX52.H #include lcd1602.h #include ultrasonic.h void main(void) { unsigned int distance_cm; LCD_Init(); // LCD1602初始化 Ultrasonic_Init(); // 超声波引脚初始化 LCD_ShowString(1, 1, Distance:); while(1) { // 触发一次超声波测量返回距离值单位cm distance_cm Ultrasonic_GetDistance(); // 把距离值显示到LCD第二行 LCD_ShowNum(2, 1, distance_cm, 4); LCD_ShowString(2, 5, cm); } }超声波模块驱动ultrasonic.c#include REGX52.H #include ultrasonic.h sbit Trig P1^0; sbit Echo P1^1; void Ultrasonic_Init(void) { Trig 0; Echo 0; TMOD 0x00; // 清零T0相关位 TMOD | 0x01; // 定时器0方式116位定时器 ET0 1; // 允许T0中断 EA 1; // 开启总中断 } // 发送至少10us高电平脉冲触发模块测距 void Ultrasonic_SendTrig(void) { Trig 1; // 12MHz晶振下空指令延时约10微秒 _delay_us(20); Trig 0; } // 测量Echo高电平持续时间返回微秒数 unsigned int Ultrasonic_GetDistance(void) { unsigned int count 0; unsigned int distance 0; // 先发Trig脉冲触发测量 Ultrasonic_SendTrig(); // 等待Echo变高 while(Echo 0); // Echo拉高清零并启动定时器T0 TH0 0; TL0 0; TR0 1; // 等待Echo变低如果超时则处理 while((Echo 1) (count 60000)) { count; } // 停止定时器 TR0 0; // 读取计数值12MHz晶振下每个计数单位为1微秒 count (TH0 8) | TL0; // 计算距离cm us / 58 // 这里除以58是因为声速343m/s时往返时间与距离的关系近似为58us/cm distance count / 58; return distance; }代码里有一个隐藏细节值得说明。Ultrasonic_GetDistance函数里我没有在Echo变高之前判断模块状态直接用了一个while(Echo 0);死等。仿真环境里模块是理想模型不存在问题。但实物使用中如果模块没接好或者前方没有回波Echo可能一直为低程序就会卡死在这个死循环里。所以我建议在实物项目中加一个超时判断比如等待Echo变高超过50毫秒就放弃本次测量返回上一次的有效距离或者一个错误标志。3.4 自定义延时函数实现上面代码里用到的_delay_us有两种实现思路。如果你使用Keil C51可以直接调用intrins.h头文件里的_nop_()函数每个_nop_()消耗一个机器周期。12MHz晶振下一个机器周期是1微秒所以10微秒延时写10个_nop_()就够了不够优雅但绝对准确。更稳妥的方式是写一个带参数的空循环延时函数void _delay_us(unsigned int us) { while(us--) { _nop_(); } }需要注意这种方式受编译器优化影响_nop_()是内嵌汇编指令不会被优化掉所以可靠性比较高。如果你的晶振是11.0592MHz机器周期是约1.085微秒10个_nop_()就是10.85微秒依然满足“大于10微秒”的触发要求问题不大。HC-SR04的Trig触发对脉宽的精度要求并不苛刻网上说的“必须精确10微秒”其实是个误解只要大于10微秒即可20微秒、30微秒都没问题。3.5 主循环频率的设定问题主循环里我让Ultrasonic_GetDistance每轮都执行一次。这样会导致超声波模块以非常高的频率反复触发模块自己可能反应不过来LCD也会出现明显的闪烁感。标准做法是在主循环里加一个延时控制测量频率。HC-SR04模块内部测量周期大约要几十毫秒所以你每隔50毫秒到100毫秒测一次比较合理。我一般写成while(1) { distance_cm Ultrasonic_GetDistance(); LCD_ShowNum(2, 1, distance_cm, 4); LCD_ShowString(2, 5, cm); Delay(20); // 20ms延时控制刷新频率 }刷新太频繁不但显示闪烁还会增加功耗和模块误触发的概率。在某些要求高的场景里你可能还想加滑动平均滤波把最近几次的数据取平均减少偶尔的毛刺值。这些优化不急先把基础功能跑通最重要。4. Proteus仿真搭建与调试过程4.1 元件清单与电路连线打开Proteus ISIS新建工程之后按以下元件清单添加元件名称库中关键字说明STC89C52单片机AT89C52Proteus自带模型与51内核兼容HC-SR04超声波模块HC-SR04Proteus 8.6以上版本自带该模型LCD1602液晶屏LM016L常见的16x2字符液晶10k电阻POT-HG也可以直接接个固定电阻排阻RESPACK-8用于LCD数据端口上拉晶振和电容CRYSTAL、CAP12MHz晶振33pF电容连接关系和要点如下单片机P1.0接TrigP1.1接Echo这两个引脚直接和模块相连。LCD1602的RS、RW、EN分别接P2.0、P2.1、P2.2。LCD1602数据口D0到D7接到P0口P0是开漏输出必须接10k排阻上拉到VCC否则LCD数据口电平不稳白屏或者乱码。复位电路用标准的10uF电容10k电阻接RST引脚。晶振电路用12MHz配合两个33pF电容接地给单片机提供时钟。提示Proteus里如果不放晶振电路直接让单片机上电默认会以1MHz频率运行。很多人仿真时发现程序能跑但时序完全不对定时器算出来的距离差得离谱一查发现晶振忘画了。这个坑非常经典先检查晶振再怀疑代码。4.2 用虚拟示波器观察时序波形Proteus仿真的最大优势就是可以拖一个虚拟示波器出来看波形。这是实物调试里需要真示波器才能做到的事。把示波器的A通道接到Trig引脚B通道接到Echo引脚然后运行仿真。你会看到这样的现象每发一次Trig脉冲紧接着Echo会拉高持续一段时间后变低。Echo高电平的宽度换算成微秒再除以58就是你测到的厘米距离。如果Echo一直是低电平说明模块根本没有收到回波或者Trig信号没发出来。另一个好用的工具是虚拟终端。Proteus里有个VIRTUAL TERMINAL元件可以把单片机的串口数据直接显示在电脑上。你可以加一段串口发送代码把每次测得的距离打印出来数据曲线变化规律一眼就能看出来不用盯着LCD一个数字一个数字地看。4.3 仿真调试过程实录我模拟一次实际调试过程给你看。第一次运行仿真LCD第一行正确显示了“Distance:”但第二行没有数字。我第一反应是测量函数卡住了。检查代码发现while(Echo 0);这一步确实在死等。用示波器看Echo引脚发现它始终保持低电平根本没有被拉高。继续查发现我把Trig接到了P1.0但程序里定义的sbit Trig P3^0;地址对不上程序发送的触发脉冲根本没送到模块上。这是我当时复制别人代码时没改引脚定义非常低级的错误。改回来之后LCD显示出了一个巨大的数值比如“1922cm”。这个数字明显超出量程典型的超时错误。我从示波器上看到Echo拉高后一直没有拉低正确做法是检查Echo等待变低那段逻辑里的超时机制发现我没有加程序一直在等Echo变低。加上超时时间之后问题才解决。第三个问题出在显示刷新。数据本身是对的但LCD上数字跳得厉害肉眼根本读不清楚。把主循环里的延时加上测量间隔控制在50毫秒左右显示马上稳定了。4.4 仿真和实物的几个显著差异Proteus仿真跑通很容易给人“我已经做完了”的错觉。但仿真和实物至少有四点不一样仿真里的HC-SR04是理想模型不考虑噪声、供电波动、障碍物反射率这些因素。实物里如果障碍物表面是海绵或者布料超声波会被吸收大量能量回波很弱测距就不稳定。实物中HC-SR04模块供电必须稳定5V电源如果纹波大模块工作会异常。仿真里不存在这个问题。实物中Echo引脚是5V电平如果你的51单片机工作电压是3.3V需要加电平转换电路。仿真里不用考虑。实物中超声波模块的Trig和Echo引脚不能接反模块上没有防反接保护。所以我的建议是仿真用来验证逻辑和时序实物验证做好充足的心理准备预留调试时间。5. 常见问题与避坑实录5.1 高频问题速查表故障现象可能原因排查思路LCD白屏或显示乱码P0口没加上拉电阻接10k排阻检查RS、EN接线Echo引脚始终为低Trig引脚没接对、程序没发触发脉冲示波器看Trig是否有脉冲显示距离超大Echo超时未处理、前方没有障碍物加超时判断检查回波信号测量数值跳动大主循环刷新太快加20ms以上延时测量间隔控制定时器计数值混乱晶振未接入、TMOD配置错误用示波器看Echo脉宽是否符合预期程序烧录后无反应复位电路接错、晶振电容过大仿真先跑通再检查硬件5.2 关于Proteus元件库的几个坑Proteus里搜索“AT89C52”能搜到经典8051芯片。有些版本还带“STC89C52RC”模型如果你用的是STC版本库模型是旧版或者需要自己加库推荐统一用AT89C52引脚和STC89C52完全兼容。区别只在内部Flash大小、ISP下载这些细节跟你的程序没直接关系。再就是LCD1602。很多人在Proteus里搜“LCD1602”搜不到正确关键字是“LM016L”。这是Proteus用来建模的编号。你用LM016L搜出来的元件就是标准的16x2 LCD屏。HC-SR04在Proteus里的模型有的版本在“Ultrasonic”或“HC-SR04”关键字下搜索如果找不到可以确认一下Proteus版本。有些旧版本需要手动加载第三方元件库可以去单片机论坛找一下下载后放到Proteus的Library目录下面。新版本一般自带因为这已经是教学仿真的标配元件了。5.3 距离显示乱跳的处理心得仿真里没有噪音数字乱跳基本都是逻辑问题。我遇到最典型的一个是Echo引脚信号太短定时器来不及启动。排查方式很简单在代码里加一个计数器变量每次进入中断时自增再通过串口发出来看看中断到底有没有进、进了多少次。这个方法在所有单片机调试里都适用与其猜哪里不对不如打点看执行流程。跑一遍所有猜测都变事实。5.4 最后的扩展建议仿真跑通了你可以尝试三个方向的扩展难度递进加温度补偿用DS18B20测环境温度按公式实时计算声速距离精度能提升不少。改成报警器加上蜂鸣器和LED当距离低于设定阈值时蜂鸣报警变成一个简单的倒车雷达。做避障小车把测距逻辑集成到两轮小车主控程序里按距离控制电机方向实现自动避障。这三个扩展里任何一个做出来都可以直接放进简历作品集里当项目经验。写在最后我个人做这个项目最大的感受是超声波测距看起来简单但真正的价值在于它迫使你去理解时序——什么时候发脉冲、什么时候计时、什么时候读取结果每一步都环环相扣。仿真通过只是第一步如果手头有开发板强烈建议把代码烧进去试一把。第一次听到模块发出“啾啾”声看到LCD上跟着手移动变化的数字那种成就感不是仿真能给到的。最后再分享一个习惯调试时多用串口和示波器不要只盯着显示器上的数字。把中间变量打出来把关键波形录下来排查问题的效率会高很多。希望这篇能帮你少踩几个坑。本文还有配套的精品资源点击获取