
我去,兄弟们我们今天来学习一个牛逼 的硬件,它叫超声波测距模块HC---SR04
硬件:HC---SR04
哎,想当初最想要玩的就是这个模块,科技感十足,那现在就让我们玩玩吧

超声波测距传感器
原理就是说需要给Trig 10us的高电平,相当于是一个信号,到模块内部,模块内部有脉冲模块一直向着外部发送超声波,此时echo由低电平变成了高电平,然后如果有障碍物了,那么返回到echo里面会由高电平转换为低电平,计算的就是echo接收的第一个波到最后一个波,由高电平到最后一个波结束变成低电平的时间
因为你可以拿 0001(1) 0010(2) 0100(3) 1000(4) 这个几个二进制数从1转换到4,你一个个转换会发现就是有这个规律 :二进制左移一位相当于x2的一次方 左移8位相当于左移2的8次方
超声波的时序图:(这也是我们第一次接触时序图)

这个是我们的最终代码,其中有很多的优化细节 都是为了测试硬件和提高稳定性的,所以有些代码不是功能的实现,而是细节的提升
#include "reg52.h"
#include <intrins.h> // 包含_nop_()函数
sbit D5 = P3^7; // 黄灯(距离<10cm时亮)
sbit D6 = P3^6; // 蓝灯(距离≥10cm时亮)
sbit Trig = P1^5; // 超声波触发引脚
sbit Echo = P1^6; // 超声波接收引脚
// 精确延时函数
void Delay10us() //@11.0592MHz
{
nop(); nop(); nop(); nop(); nop();
nop(); nop(); nop(); nop(); nop();
}
void Delay50ms() //@11.0592MHz
{
unsigned char i, j;
i = 90;
j = 163;
do
{
while (--j);
} while (--i);
}
void Delay100ms() //@11.0592MHz
{
unsigned char i, j;
i = 183;
j = 105;
do
{
while (--j);
} while (--i);
}
// 定时器0初始化(16位模式)
void Time0_Init()
{
TMOD &= 0xF0; // 清零T0模式位
TMOD |= 0x01; // 设置T0为模式1(16位定时器)
TH0 = 0; // 初始化计数寄存器
TL0 = 0;
TR0 = 0; // 初始不启动定时器
}
// 触发超声波测距
void Trigger_HC_SR04()
{
Trig = 0;
nop();
Trig = 1; // 发送至少10μs的高电平触发信号
Delay10us();
Trig = 0;
}
// 主函数
void main()
{
unsigned int Time; // 超声波飞行时间(μs)
unsigned int Distance; // 距离(cm)
unsigned int timeout; // 超时计数器
unsigned char i; // 定义循环变量(解决编译错误)
Time0_Init(); // 初始化定时器
// 上电自检:LED交替闪烁3次
for(i=0; i<3; i++) { // 修正后的for循环
D5 = 0; D6 = 1; // 黄灯亮
Delay100ms();
D5 = 1; D6 = 0; // 蓝灯亮
Delay100ms();
}
// 默认状态:蓝灯亮(表示距离≥10cm)
D5 = 1;
D6 = 0;
Delay100ms(); // 等待传感器稳定
while(1)
{
// 确保Echo初始为低电平(否则可能是传感器异常)
if(Echo == 1) {
D5 = 1; D6 = 1; // 双灯亮表示异常
Delay100ms();
continue;
}
// 触发测距
Trigger_HC_SR04();
// 等待Echo变高(添加超时:约10ms)
timeout = 10000;
while(Echo == 0 && --timeout);
if(timeout == 0) {
D5 = 1; D6 = 1; // 超时错误:双灯亮
Delay50ms();
continue;
}
// 启动定时器
TH0 = 0;
TL0 = 0;
TR0 = 1;
// 等待Echo变低
timeout = 65535;
while(Echo == 1 && --timeout);
TR0 = 0;
// 计算距离(仅在未超时的情况下)
if(timeout != 0) {
// 计算时间(晶振补偿:11.0592MHz需要乘以1.085)
Time = (TH0 * 256 + TL0) * 1.085;
// 计算距离(单位:cm)
Distance = Time * 17 / 1000; // 等价于Time * 0.017
// 根据距离控制LED
if(Distance < 10) { //如果 timeout 没有减到 0(即 Echo 信号在超时之前成功从高电平变回低电平)
D5 = 0; D6 = 1; // 蓝灯亮:距离大于小于10cm
} else {
D5 = 1; D6 = 0; // 黄灯亮:距离大于10cm
}
} else {
D5 = 1; D6 = 1; // 超时错误:双灯亮
}
Delay50ms(); // 测量间隔,避免频繁触发 手册规定两次间隔至少60ms(哎我这边写50也无所谓的)
}
}
当然我这个是优化过了的代码,因为之前的代码不稳定,容易实现不了功能你们可以拿去试试水
旧代码



这个代码有几点 需要重要解释的就是计算
首先时间的计算,本质就是计算echo的高电平到低电平的时间
那么我们定时器的TH0 和TL0 是发挥作用的,因为他们记录了次数,每次是1.085us,因为晶振的频率是11.059Mhz,我们定时器的模式又是16位,所以我们又要知道TH0和TL0是8位寄存器,高八位给TH0,低八位给TL0,记录的都是次数,我们将低八位让给TL0 其实就是这个效果啦
10101011 要变成1010101100000000(这个是一个二进制格式),那么我们该怎么办呢其实就是用到了这个方法
二进制左移一位相当于x2的一次方 左移8位相当于左移2的8次方

你看这个图就非常明显了,DEC是十进制这个数为65535是2的16次方,也就是16位2进制能有多少种搭配个数,那我们这个 前面8位数TH0的位置 后面8位数TL0的位置,所以我们可以根据这个信息来获取次数,我们通过二进制的数据来转换成10进制的次数,那么这个次数就可以与每次1.085us进行相乘得出总的时间
还有一个要注意的是距离的计算,有个公式叫做距离=速度 x 时间 但是由于我们这个超声波的话是这样的,时间多付出一倍,所以距离要/2的,一来一回

声音的速度为340m/s
随后根据公式就可以算出距离了。

最后我们来一些Tips:
对于第一个timeout设置为10ms是为什么?????
10ms 就是一个"兼顾大家"的折中值 ,既不过短(避免误判),也不过长(避免卡死),属于经验性参数 。它的合理性可以从这几个角度理解
1. "大家"是谁?
- 硬件层面 :超声波模块(如 HC-SR04)的典型响应时间在 微秒级到几毫秒 (例如,测距 1 米时,Echo 变高延迟约 5.8ms,但实际传感器内部电路响应可能更快)。
- 软件层面 :嵌入式系统通常需要 快速失败 (避免因硬件故障导致整个系统僵死)。
- 用户体验 :10ms 对人类来说几乎无感(LED 报错时用户不会觉得"卡顿")。
就是Trig高电平进去之后,我们这个就要进行判断了,因为:超声波模块(如 HC-SR04)的典型响应时间在 微秒级到几毫秒
所以
- 只要 Echo 仍然为 0(低电平)并且 timeout 自减后仍不为 0,就继续循环。
- 如果 Echo 变为 1(高电平)或者 timeout 减到 0,循环终止。
Echo 高电平的持续时间(从第一个有效回波触发变高,到最后一个回波处理完毕变低)直接对应超声波往返的时间 ,而这个时间可以用来计算距离
这段代码有点意思:
timeout = 65535;
while(Echo == 1 && --timeout);
TR0 = 0;
- 如果 Echo 正常变低(测距完成),循环退出。
- 如果 Echo 因硬件故障一直保持高电平,timeout 会递减至 0,强制退出循环(避免死等)。
4. 为什么 timeout 初始值设为 65535?
- 65535(0xFFFF)是 16 位无符号整数的最大值 ,确保超时时间足够长(约 71ms @11.095MHz 51单片机)。
- 覆盖最大测距 :
- HC-SR04 最大测距约 5 米,对应 Echo 高电平时间 ≈ 29ms(580µs/cm × 500cm)。
- 设置 timeout = 65535 可确保即使测最远距离也不会误触发超时。
5米最大测距 = 硬件限制(Echo 最大 29ms) + 声波衰减。
3. 实例验证
场景1:正常测距( Echo 按预期变低)
- Echo 从 1 → 0(耗时 5ms),timeout 从 65535 减到 60000(未到 0)。
- 结果 :timeout != 0 → 进入 if 分支,计算距离。
场景2:超时异常( Echo 保持高电平)
- Echo 始终为 1,timeout 从 65535 递减至 0(耗时约 65ms)。
- 结果 :timeout == 0 → 进入 else 分支,双灯亮报错。
场景3:干扰导致 Echo 异常变低
- Echo 因干扰短暂变低,但 timeout 未减到 0(如剩余 10000)。
- 结果 :timeout != 0 → 仍进入 if 分支,但计算的距离可能无效(需软件滤波处理)。
