ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于51单片机的超声波测距仪设计:从HC-SR04时序到报警系统

基于51单片机的超声波测距仪设计:从HC-SR04时序到报警系统 简介这份压缩包为基于51单片机的超声波测距仪参考源程序面向单片机初学者与电子竞赛备赛者用于学习超声波测距、定时器中断和报警功能的综合设计。压缩包内共14个文件以Keil工程文件uvproj、C源程序、hex烧录文件为主另含备份与编译过程文件整体仅54KB轻量且便于快速移植。目前已有2966人浏览学习适合作为入门阶段的参考代码。程序覆盖了初始化、超声波脉冲发射、回波时间差测量、距离换算、数码管显示和距离阈值报警等完整流程核心逻辑围绕定时器捕获与GPIO控制展开。代码逻辑简洁可直接在开发板或Proteus仿真中验证也可迁移至智能小车避障、倒车雷达等实际测距场景是理解51单片机与传感器协作的不错范例。1. 超声波测距仪不只是一块HC-SR04关键在51单片机怎么卡住那150微秒拿到这份“超声波测距仪参考源程序”时大多数人第一反应是找HC-SR04驱动代码。但真正决定测距精度和稳定性的是51单片机对回波脉冲宽度的测量方式——用定时器轮询还是外部中断计数溢出了怎么办晶振频率误差怎么修正。这套基于51单片机超声波测距报警系统的源工程包含完整的Keil工程文件、可烧录的HEX文件以及调试产生的LST/M51文件适合正在做课程设计、倒车雷达原型或准备参加电子设计竞赛的人。我会沿着信号时序、定时器配置、距离换算、数码管显示和报警逻辑这条线把代码里的关键分支拆开讲并给出可直接照抄的修改参数。超声波测距仪的核心矛盾从来不是发射脉冲而是如何准确测量往返时间51单片机资源有限每一微秒都得花在刀刃上。2. HC-SR04与51单片机的硬件连接和时序匹配2.1 超声波模块引脚与单片机的IO分配HC-SR04是常见的四脚超声波传感器VCC、Trig触发、Echo回波、GND。它内部自带超声波发射和接收电路对外只需要两根信号线。51单片机负责拉高Trig引脚然后等Echo引脚从低电平变高电平再记录高电平持续的时间。这个高电平宽度就是超声波往返的耗时。代码工程里默认用的是P1.0接Trig、P1.1接Echo实际接线建议参考源程序开头或者硬件原理图里的引脚宏定义。由于HC-SR04功耗较低VCC可以直接接5VGND与单片机共地。Echo引脚输出的是5V电平信号51单片机IO口能直接识别不需要电平转换。模块引脚51单片机连接作用电平说明VCC5V供电5V直流电流约15mAGNDGND共地与单片机电源地相连TrigP1.0触发测距输入拉高至少10usEchoP1.1回波输出输出高电平宽度代表往返时间这里有个容易忽略的点Echo引脚在未接收回波时是低电平一旦收到反射信号会输出高电平高电平持续时间和距离成正比。如果探头前方没有障碍物或者障碍物斜度过大Echo会一直保持低电平程序需要设置超时判断否则会卡死在等待循环里。源程序里通常用一个计时变量做超时保护后面会详细说。2.2 触发信号的时序要求与代码实现HC-SR04的触发时序很简单Trig端口拉高至少10微秒然后拉低模块内部会自动发出8个40kHz的超声波脉冲并等待回波。问题在于51单片机主频通常是12MHz或11.0592MHz一条NOP或者简单赋值语句的耗时不同写延时函数时不能凭感觉。常见做法是用以下代码生成触发脉冲void trigger_ultrasonic() { TRIG 0; // 先拉低确保干净 _nop_(); // 稍作延时等待电平稳定 TRIG 1; // 拉高触发 _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); // 5个空操作12M晶振下约5us _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); TRIG 0; // 拉低完成触发 }这串NOP在12MHz晶振下每个机器周期恰好1微秒10个NOP就是10微秒。如果换成11.0592MHz晶振机器周期约1.085us10个NOP就是10.85us仍然满足HC-SR04要求的10us以上。如果主频变化大比如使用STC的6T模式或者1T模式机器周期大幅缩短就需要改用循环延时或用定时器精确延时。源工程默认配置的是传统12T模式直接使用NOP即可。触发之后立即开始监听Echo引脚。这里有一个关键选择轮询等待Echo拉高还是用外部中断捕捉51单片机只有两个外部中断INT0和INT1通常要留给更有价值的信号。测距项目里Echo高电平持续时间最长约20ms对应4米量程如果一直轮询等待单片机做不了其他事。工程里一般用轮询加定时器计数轮询是在等待Echo从低变高然后再启动定时器测量高电平宽度。3. 定时器测量回波宽度——精度从哪里来3.1 定时器工作模式选择与初值计算52系列单片机有定时器0和定时器1每个都是16位计数器。超声波测距需要测量一个持续时间为数百微秒到数十毫秒的高电平用定时器1工作在模式116位不自动重装比较合适。模式2是8位自动重装最大只能计数256量程不够。模式0是13位没必要用。定时器初值取决于想测的最大距离。HC-SR04标称量程4米对应往返时间是4×2/340≈23.5ms。16位定时器在12MHz晶振下每微秒计数一次最多计数65535us65.5ms足够覆盖4米量程。所以初值可以直接设为0让定时器从0开始走。// 定时器1初始化 void timer1_init() { TMOD 0x0F; // 只修改T1部分保留T0设置 TMOD | 0x10; // T1模式116位定时器 ET1 0; // 不用定时器1中断用查询 TR1 0; // 先关掉 }这里特意关闭定时器1中断原因是Echo高电平期间我们只需要测量宽度不希望中断把正在测量的状态打断。测距过程是单线程的轮询方式是等待Echo下降沿时读取TH1和TL1。如果使用中断则应该考虑在中断里记录累加溢出次数否则超过65ms会溢出。3.2 测量Echo高电平宽度的完整状态机Echo信号在Trig触发后会经历三个阶段低电平等待发射、高电平等待回波、低电平结束。测量程序要准确捕捉高电平的起点和终点。起点是Echo从0变1的时刻终点是Echo从1变0的时刻。unsigned int get_distance_cm() { unsigned int time_us 0; unsigned int distance 0; // 发送触发脉冲 trigger_ultrasonic(); // 等待Echo拉高超时约5ms unsigned int timeout 0; while (ECHO 0) { if (timeout 5000) return 0; // 超时返回0 delay_1us(); } // 启动定时器1 TH1 0; TL1 0; TR1 1; // 等待Echo拉低超时约30ms timeout 0; while (ECHO 1) { if (timeout 30000) break; // 超过量程退出 delay_1us(); } TR1 0; time_us (TH1 8) | TL1; // 读取计数值 // 距离 时间(us) * 0.017cm/us 往返时间的一半乘以声速 distance (unsigned int)(time_us * 0.017); return distance; }这段代码用了两次超时保护。第一次等待Echo拉高如果5ms内没反应可能是Trig没发出去或者模块故障。第二次等待Echo拉低正常情况下Echo高电平时间不会超过30ms超过则视为超量程强制退出。超时原因有两种超声波被大角度反射或者障碍物超过4米这两种情况都返回0由上层程序决定是否报警。time_us的计算依赖TH1和TL1的拼接。注意定时器在运行期间如果Echo一直保持高电平且超过65.5msTH1和TL1会溢出回0测量结果错误。这里的30ms超时已经限制住了所以不会发生溢出。另外用delay_1us()等待Echo拉高会浪费CPU时间更高效的做法是直接把Echo接到外部中断引脚用上升沿中断启动定时器下降沿中断停止定时器。但考虑到51单片机中断资源紧张且这个工程里没有其他高优先级任务轮询方案更简单。如果你用的是STC8或者其它增强型51有PCA/CCP捕获模块可以完全硬件测量误差更小。3.3 距离换算公式的误差校正声速在空气中约340m/s但温度每变化1℃声速大约增加0.6m/s。一个简单的补偿公式是声速331.40.6×TT为摄氏度。如果要求不高直接用0.017这个换算系数距离cm 时间us × 340m/s ÷ 2 ÷ 10000 时间us × 0.017。0.017的精度在25℃时误差很小但冬天和夏天差距能达到3%-4%。工程代码里通常固定用常量建议改成可变的温度补偿参数从DS18B20读取温度后计算。下面给出一个带温度补偿的距离计算float get_distance_temperature_compensated(unsigned int time_us, unsigned char temp_c) { float speed_of_sound 331.4f 0.6f * temp_c; // 单位m/s float distance_m speed_of_sound * time_us / 1000000.0f / 2.0f; return distance_m * 100.0f; // 转成厘米 }注意浮点运算在51单片机上比较慢Keil C51对float支持但会消耗较多代码空间和运行时间。如果不需要温度补偿建议直接用整数运算distance_cm time_us × 17 ÷ 1000这样只需要乘法和除法运行速度远快于浮点。源程序如果直接乘0.017编译器也会自动转换为定点运算但可读性差。我一般写整数乘除效果一样。4. 数码管动态显示与报警阈值——状态机的实际分工4.1 四位一体数码管的扫描方案测量得到的距离需要实时显示。最常见的做法是使用两个或四个共阴数码管通过动态扫描方式点亮。所谓动态扫描就是每次只点亮一位轮流点亮每一位利用人眼视觉暂留形成连续显示。51单片机IO口有限通常用两个锁存器或三极管位选源工程里多半直接P0口驱动段码P2口做位选。unsigned char code seg_code[] {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90}; // 共阳段码 0-9 void display_distance(unsigned int cm) { unsigned char thousands cm / 1000; // 千位一般不会超过4 unsigned char hundreds (cm / 100) % 10; unsigned char tens (cm / 10) % 10; unsigned char ones cm % 10; // 位选选择第1位 P2 0x01; P0 seg_code[thousands]; delay_scan(); P2 0x02; P0 seg_code[hundreds]; delay_scan(); P2 0x04; P0 seg_code[tens]; delay_scan(); P2 0x08; P0 seg_code[ones]; delay_scan(); }段码表使用共阳数码管对应的编码如果你手里的板子是共阴需要改用另一组段码数组。这里的delay_scan()一般延时1-2ms四个位轮流一遍大约5ms刷新率200Hz足够稳定。需要注意的是在切换位选的瞬间P0口会先输出新的段码而此刻位选可能还没切换会造成短暂重影。解决方法先关闭所有位选再送段码最后打开目标位选。上面的代码如果没有消隐在高亮度下能看到残影。改进后的写法是每个位都先P20x00再P0段码再P2对应位。4.2 报警阈值和蜂鸣器动作逻辑报警系统的核心是设置一个安全距离阈值当测量距离小于阈值时触发蜂鸣器。阈值可以通过按键调整固定在程序里也可。源工程中通常定义一个宏或者变量比如ALARM_DISTANCE30单位厘米。当distance不为0且小于等于30时蜂鸣器响。这里要考虑到超声波模块盲区通常2cm以内的距离测量不准确应当忽略或显示特殊值。#define ALARM_THRESHOLD_CM 30 void check_alarm(unsigned int dist) { if (dist 0 dist ALARM_THRESHOLD_CM) { BEEP 0; // 低电平驱动蜂鸣器根据硬件调整 } else { BEEP 1; } }蜂鸣器驱动方式很关键。有源蜂鸣器只需要给高低电平就能发声无源蜂鸣器需要PWM方波驱动。51单片机IO口驱动能力弱一般通过PNP三极管或ULN2003驱动。如果直接接IO口可能推不动。源工程里如果是BEEP0响说明使用了低电平有效的有源蜂鸣器。如果需要动态报警节奏可以在定时器中断里翻转BEEP例如每100ms翻转一次形成滴滴声。这需要把测距放在主循环里定时器0用于产生系统时基定时器1用于测距这样就不会互相干扰。4.3 主循环里的任务调度把测距、显示、报警放在一个循环里最简单但要注意时序拖累。每次测距触发后等待Echo完成大约需要几十微秒到20毫秒这期间单片机浪费在循环等待上。如果显示扫描也放在主循环会导致显示亮度不均匀测距等待时间越长显示刷新越慢。更好的做法是把测距放在一个状态机里或者干脆在等待Echo期间同时进行数码管扫描。因为动态扫描的延迟函数本身就是空耗时间可以利用这段时间来做测距等待。但工程代码为了可读性通常顺序执行。对于距离频繁变化的场景顺序执行没问题。如果追求稳定显示可以把刷新间隔压缩到1ms并保持循环频率在50Hz以上。这里有一个常见的坑每次测距返回后需要等待一段时间才能进行下一次触发。HC-SR04要求两次触发间隔至少60ms否则上一次回波可能还没传播完测量会不准确。所以主循环里的测距周期不能太短控制到100ms左右比较合理。可以通过一个软件计数器或者延时函数保证周期。5. 在Keil中编译源码时的工程文件陷阱与HEX烧录验证5.1 工程文件差异uvproj、uvopt、plg和M51分别是什么压缩包里“基于51单片机超声波测距报警系统.uvproj”是Keil μVision4/5的工程文件双击即可打开。uvopt文件保存的是编译器选项、调试器设置和窗口布局不同电脑打开可能因为路径问题报错。plg是编译过程的日志文件M51是链接后的内存分配文件。一旦你移动了工程目录Keil找不到原始路径可能会提示“missing uvopt”这时打开uvproj重新配置一下输出路径即可。第一次编译时建议先执行一次“Rebuild All target files”因为旧的LST文件、OBJ文件可能和当前源码版本不一致。OBJ目录里存放着编译中间产物比如目标文件的绝对路径记录在lnp文件中。如果你把工程放在中文路径下个别旧版本Keil可能编译失败解决办法是把工程目录改成全英文。5.2 生成HEX文件并烧录到51单片机源工程已经包含HEX文件如果想修改源代码后重新生成需要在Keil里配置Output选项卡。勾选Create HEX File后编译成功会生成新的HEX。默认生成在工程根目录或OBJ目录里取决于Output选项卡的Select Folder for Objects。烧录工具根据你的单片机型号选择AT89C52用ESP烧录器STC系列用STC-ISP。注意烧录前要选择正确的单片机型号并设置波特率。如果使用STC89C52还需要在STC-ISP里选择“双倍速”或“12T”模式必须和代码里的循环延时匹配。常见错误是烧录后数码管显示乱码多半是单片机的时钟模式选错导致延时不对。12MHz晶振对应12T模式如果误选6T延时变成原来的一半触发脉冲宽度不足10us导致测距无反应。5.3 实机调试时的波形测量方法没有示波器很难判断Echo信号是否正常。最简单的办法是写一个测试程序触发后如果Echo无响应点亮一个LED如果Echo响应则熄灭。这样可以快速判断模块好坏。用万用表测量Echo引脚电压在触发后如果保持0V说明模块没有正常工作可能Trig脉冲宽度不够或者模块损坏。如果想更精确地确认定时器测量结果可以把time_us通过串口发送到电脑串口助手。在代码里添加UART初始化函数然后把time_us拆成高8位低8位发送。串口调试时将障碍物放在10cm处观察串口输出应为588us左右。如果偏差超过10us检查晶振实际频率和定时器配置。我一般会用逻辑分析仪抓Echo引脚直接读取高电平宽度和单片机计算结果比对这样能定位是软件测量问题还是模块信号问题。最后分享一个调参技巧报警阈值不要太死板固定30cm在电路上测试地面反射可能导致一些误报。建议把阈值定义成变量通过两个按键调整存储在EEPROM里如果单片机支持这样实机调试时不用反复烧录。整个工程跑顺之后你会发现51单片机虽然老但把IO、定时器、中断这些资源安排得明明白白反而比用STM32更锻炼电路理解力。本文还有配套的精品资源点击获取
返回列表