学习STC51单片机13(芯片为STC89C52RC)

发布于:2025-05-24 ⋅ 阅读:(12) ⋅ 点赞:(0)

我去,兄弟们我们今天来学习一个牛逼 的硬件,它叫超声波测距模块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 始终为 1timeout 从 65535 递减至 0(耗时约 65ms)。
  • ​结果​​:timeout == 0 → 进入 else 分支,双灯亮报错。
​场景3:干扰导致 Echo 异常变低​
  • Echo 因干扰短暂变低,但 timeout 未减到 0(如剩余 10000)。
  • ​结果​​:timeout != 0 → 仍进入 if 分支,但计算的距离可能无效(需软件滤波处理)。


网站公告

今日签到

点亮在社区的每一天
去签到