1.项目接线

接线示意图和实物图

示意图和接线说明:

  • 舵机控制口P1.1(定时器0中断);
  • 超声波Trig接P1.5 ,Echo接P1.4 ;
  • 蜂鸣器接P2.0 口;
  • 震动传感器接P3.2 口(外部中断0)。

实物图(未封装):

信号传输路线

路线1: 单片机P1^5引脚 ——> 超声波模块Trig引脚 ——> 超声波模块Echo引脚 ——> 单片机P1^4引脚 ——> 单片机P1^1引脚 ——> SG90舵机PWM引线

路线2:振动信号 ——> 振动传感器DO引脚 ——> 单片机P3^2引脚 ——> 单片机P1^1引脚 ——> SG90舵机PWM引线

路线3:单片机P2^0引脚 ——> 蜂鸣器I/O引脚


2.项目实现流程

基本控制逻辑

  1. 业务需求:
  • 功能1:检测靠近时,垃圾桶自动开盖并伴随蜂鸣器滴一声,2秒后关盖
  • 功能2:发生震动时,垃圾桶自动开盖并伴随蜂鸣器滴一声,2秒后关盖
  • 功能3:按下按键时,垃圾桶自动开盖并伴随蜂鸣器滴一声,2秒后关盖
  1. 控制逻辑: 可以看出本项目包含两三种模式。
  • 超声波感应工作模式(配合舵机)

    • 舵机用定时器0
    • 超声波用定时器1
  • 查询的方式添加按键控制(配合舵机)
  • 查询的方式添加震动控制(配合舵机)
    • 使用外部中断0配合震动控制

模式1(超声波控制舵机)——定时器中断

​ 首先对初始化定时器这部分代码进行整合:我们让超声波用定时器1,让舵机用定时器0。根据定时器编程的思路,针对之前的代码修改以下东西,更改完后测试代码:

  1. TMOD寄存器配置定时器0和1的工作模式为16位:回顾51芯片手册第7.1.1小节

    • 定时器0:

      • 让TMOD寄存器的高4位不变,低4位全部“置0”:TMOD &= 0XF0;
      • 让TMOD寄存器的高4位不变,第0位“置1”:TMOD |= 0X01;
    • 定时器1:

      • 让TMOD寄存器的低4位不变,高4位全部“置0”:TMOD &= 0X0F;
      • 让TMOD寄存器的低4位不变,第4位“置1”:TMOD |= 0X10;
  2. 配置定时器0和1的起始数数位置:回顾51芯片手册第7.1.1小节

    • 定时器0:舵机定一个0.5ms出来

      • TL0 = 0X33;
      • TH0 = 0XFE;
    • 定时器1:超声波从0开始数数

      • TL1 = 0;
      • TH1 = 0;
  3. TCON寄存器配置定时器0和1的溢出标志位清零

    • 定时器0:TF0 = 0;
    • 定时器1:TF1 = 0;
  4. IE寄存器配置定时器0和1开启中断:回顾51芯片手册第6.2小节

    • 定时器0:舵机需要定时器中断来软件模拟PWM

      • EA = 1;
      • ET0 = 1;
    • 定时器1:超声波不需要定时器中断,数数就行了
  5. TCON寄存器配置定时器0和1开始数数

    • 定时器0:TR0 = 1;
    • 定时器1:TR1 = 1;

​ 对超声波和舵机测试后的代码进行整合,经过学习,我整理出如下代码(用到的硬件是超声波传感器和舵机),接下来我们对如下代码进行优化,二次开发即可:

  1. 思路:

    全局变量:sfr指令直接找到AUXR寄存器: sfr AUXR = 0X8E;    //因为AUXR没有在reg52.h中声明sbit指令找到P1这个I/O口组的第5位,用作输出口,传递tirg信号给超声波传感器:sbit ultrasonicTrig = P1^5;sbit指令找到P1这个I/O口组的第4位,用作输入口,从超声波传感器接收echo信号:sbit ultrasonicEcho = P1^4;sbit指令找到与D5这个led对应的引脚: sbit ledD5 = P3^7;sbit指令找到与D6这个led对应的引脚: sbit ledD6 = P3^6;sbit指令找到P1这个I/O口组的第1位,用作输出口,传递PWM信号给舵机: sbit sg90_servo = P1^1;//sg90_servo的传递路线是:API6 ———> 定时器0中断(中断1)定义一个在定时器0的中断服务程序中用于计数的全局变量cnt: int cnt = 0;定义一个代表舵机角度的全局变量servoAngle: int servoAngle;   //servoAngle的值需要根据舵机的参数确定,传递路线为:API6 ——> 定时器0中断(中断1)
    
    1. 调用API1. 初始化定时器1,用于超声波传感器计时
    2. 调用API5. 初始化定时器0,用于舵机绘制PWM波形图
    3. 调用API6. 初始化舵机到0度转动位置,让垃圾桶处于关盖状态
    4. while死循环,不断用超声波传感器测出距离,达到临界值后转动舵机,同时用led进行测试4.1 调用API2. 获取超声波传感器与障碍物的距离,保存在变量distance中:distance = get_distance();4.2 判断距离是否小于10cm,判据是distance < 104.2.1 如果是,调用API9. 让舵机带动垃圾桶盖处于打开状态:调用API9: openStatus();4.2.2 否则,调用API10. 让舵机带动垃圾桶盖处于关闭状态:调用API10:closeStatus();
    
    中断1: 封装定时器0的中断服务程序, void Timer0_Routine()   interrupt 1
    //每当定时器0爆表都会由硬件自动调用该函数,目的是用定时器中断软件模拟PWM波形,输出给SG90舵机1.1 修改代表数数次数的循环变量cnt,让其自加1: cnt++;1.2 程序清零:令溢出标志位TF0“置0”: TF0 = 0;1.3 重新初始化寄存器TL0和TH0,重新确定计数起点:TL0 = 0X33;TH0 = 0xFE;1.4 判断是否数到与角度对应的数值,其中舵机0度位置对应数值1,即高电平0.5ms,...,舵机135度位置对应数值4,即高电平2.0ms, 判据是cnt <= servoAngle//内在逻辑:根据设置的转动角度servoAngle,实时绘制PWM波形图//注意事项: 判据到底有没有等于号要根据你在main函数中初始化的PWM波形图进行选择1.4.1 如果是,说明需要把电平拉高: sg90_servo = 1;1.4.2 否则,把电平拉低:  sg90_servo = 0;1.5 判断是否到达20ms,即是否爆表40次,判据是cnt == 40//内在逻辑:利用40次爆表,确定PWM波形的周期是20ms1.5.1 如果是,说明需要重置cnt: cnt = 0;1.5.2 否则,啥也不干
    
    /* 一级函数:f1 f2 f5 f6 f9 f10 */
    f1. 封装初始化定时器1的API: void initT1();
    //用于超声波传感器测距,所以不着急开始数数,也不需要开启中断f1.1 禁用ALE信号,降低单片机时钟对外界的电磁辐射: AUXR &= 0x1F;f1.2 通过TMOD寄存器配置定时器16位工作模式:TMOD &= 0X0F;TMOD |= 0X10;  f1.3 通过TL0和TH0寄存器,初始化定时器,从0开始数数:TL1 = 0;TH1 = 0;
    f2. 封装利用超声波传感器计算距离的API: double get_distance();f2.1 调用API3. 给超声波的trig端口至少10us的高电平: start_ultrasonic();f2.2 echo引脚由低电平跳转到高电平代表开始发送超声波,波发出的那一下开始启动定时器根据超声波时序图,用空循环体,暂时卡死程序: while(ultrasonicEcho == 0);定时器开始计时: TR1 = 1;f2.3 echo引脚由高电平跳转到低电平代表波返回来了,停止定时器计数根据超声波时序图,用空循环体,暂时卡死程序: while(ultrasonicEcho == 1);定时器停止计时: TR1 = 0;f2.4 根据定时器数一次用时1.085us的特点,计算超声波发送和返回经历的时间,保存在变量time中:time = ((TH1<<8) + TL1)*1.085;f2.5 根据公式计算出超声波传感器和障碍物之间的距离,保存在变量distance中:distance = time * 0.034 / 2;    //确定量纲: 340m/s = 34000cm/s = 34cm/ms = 0.034cm/usf2.6 定时器1清零:TL1 = 0;TH1 = 0;f2.7 返回distance
    f5. 封装初始化定时器0的API,在11.0592MHz的晶振频率下定0.5ms出来: void initT0();
    //用于绘制舵机的PWM波形图,0.5ms是控制舵机的最小时间单位f5.1 禁用ALE信号,降低单片机时钟对外界的电磁辐射: AUXR &= 0x1F;f5.2 通过TMOD寄存器配置定时器16位工作模式:TMOD &= 0XF0;TMOD |= 0X01;  f5.3 通过TL0和TH0寄存器,初始化定时器,定一个0.5ms出来:TL0 = 0x33;TH0 = 0xFE;f5.4 让溢出标志位清零: TF0 = 0;f5.5 打开定时器0的中断开关:EA = 1;ET0 = 1;f5.6 通过TCON寄存器的运行控制位TR0,让定时器开始数数: TR0 = 1;
    f6. 封装初始化舵机位置的API: void initSG90_0degree();f6.1 调用API8. 让系统稳定一下,减少软件模拟PWM信号的误差: Delay300ms();f6.2 修改全局变量sg90_servo,让PWM波形从低电平开始: sg90_servo = 0;f6.3 修改全局变量servoAngle,让舵机初始角度为0度:servoAngle = 1;cnt = 0;
    f9. 封装舵机带动垃圾桶盖处于打开状态的API: void openStatus();f9.1 用LED作为测试:ledD5 = 0;ledD6 = 1;f9.2 修改全局变量servoAngle,让舵机转到135度位置,并保持2s时间servoAngle = 4;cnt = 0;调用API7: Delay2000ms();
    f10. 封装舵机带动垃圾桶盖处于关闭状态的API: void closeStatus();f10.1 用LED作为测试:ledD5 = 1;ledD6 = 0;f10.2 修改全局变量servoAngle,让舵机转到0度位置,仅维持0.15s时间servoAngle = 1;cnt = 0;调用API11: Delay150ms();
    
    /* 二级函数: f3 f4 f7 f8 f11 */
    f3. 封装启动超声波传感器的API: void start_ultrasonic();f2.1 修改全局变量ultrasonicTrig,让它一开始处于低电平: ultrasonicTrig = 0;f2.2 修改全局变量ultrasonicTrig,让它处于高电平,并保持10μs:ultrasonicTrig = 1;调用API4: Delay10us();f2.3 修改全局变量ultrasonicTrig,让它回到低电平:
    f4. 封装软件延时10微秒的API,用于给超声波传感器trig引脚10us的高电平: void Delay10us();
    f7. 封装软件延时2s的API,用于打开垃圾桶盖时,即使感应不到物体也能保持开盖: void Delay2000ms();
    f8. 封装软件延时0.3s的API,用于稳定程序,减少软件模拟PWM信号的误差: void Delay300ms();
    f11. 封装软件延时0.15s的API,用于防止物体快速接近传感器时也会转动舵机的情况(有点像消抖): void Delay150ms();
    
  2. 代码:

    #include "reg52.h"
    #include "intrins.h"sfr AUXR            = 0x8E;
    sbit ultrasonicTrig = P1^5;
    sbit ultrasonicEcho = P1^4;
    sbit ledD5          = P3^7;
    sbit ledD6          = P3^6;
    sbit sg90_servo     = P1^1;
    int cnt = 0;
    int servoAngle;/* API1: 定时器T1的初始化@11.0592MHz,我们不关心初值,从0开始计算就行,用于超声波传感器 */
    void initT1();
    /* API2. 超声波计算距离 */
    double get_distance();
    /* API3: 启动超声波传感器 */
    void start_ultrasonic();
    /* API4: 软件延时10微秒,用于给超声波传感器trig引脚10us的高电平 */
    void Delay10us();
    /* API5: 定时器T0的初始化,定0.5ms出来@11.0592MHz,用于舵机 */
    void initT0();
    /* API6. 初始化sg90舵机到0度位置 */
    void initSG90_0degree();
    /* API7: 软件延时2s,用于打开垃圾桶盖时,即使感应不到物体也能保持开盖2s */
    void Delay2000ms();
    /* API8: 软件延时0.3s,用于稳定程序,减少软件模拟PWM信号的误差 */
    void Delay300ms();
    /* API9: 测试,代表舵机带动垃圾桶盖打开状态 */
    void openStatus();
    /* API10: 测试,代表舵机带动垃圾桶盖关闭状态 */
    void closeStatus();
    /* API11. 软件延时0.15s,用于在关盖状态进行延时,防止物体快速接近传感器时也会转动舵机 */
    void Delay150ms();void main(void)
    {double distance;initT1();               //定时器1用于超声波传感器initT0();               //定时器0用于舵机initSG90_0degree();     //初始化舵机,让垃圾桶处于关盖状态while(1){distance = get_distance();if(distance < 10){         openStatus();}else{closeStatus();}}
    }void initT1()
    {AUXR &= 0x1F;       //禁用ALE信号,降低单片机时钟对外界的电磁辐射TMOD &= 0X0F;        TMOD |= 0X10;       //配置定时器1为16位工作模式TL1 = 0;       TH1 = 0;            //初始化定时器,从0开始就行//定时器1不着急开始数数
    }double get_distance()
    {double time;double distance;/* 1.给超声波的trig端口至少10us的高电平 */start_ultrasonic();/* 2.echo引脚由低电平跳转到高电平代表开始发送超声波,波发出的那一下开始启动定时器 */while(ultrasonicEcho == 0); //空循环体,暂时卡死程序TR1 = 1;/* 3.echo引脚由高电平跳转到低电平代表波返回来了,停止定时器计数 */while(ultrasonicEcho == 1); //空循环体,暂时卡死程序TR1 = 0;/* 4.计算超声波发送和返回经历的时间 */time = ((TH1<<8) + TL1)*1.085;  //us为单位/* 5.计算出超声波传感器和障碍物之间的距离,先确定量纲: 340m/s = 34000cm/s = 34cm/ms = 0.034cm/us */distance = time * 0.034 / 2;    //cm为单位/* 6.定时器1清零,重新计时 */TL1 = 0;TH1 = 1;return distance;
    }void start_ultrasonic()
    {ultrasonicTrig = 0;ultrasonicTrig = 1;Delay10us();ultrasonicTrig = 0;
    }void Delay10us()        //@11.0592MHz
    {unsigned char i;i = 2;while (--i);
    }void initT0()
    {AUXR &= 0x1F;       //禁用ALE信号,降低单片机时钟对外界的电磁辐射TMOD &= 0XF0;        TMOD |= 0X01;       //配置定时器0为16位工作模式TL0 = 0x33;    TH0 = 0xFE;         //初始化定时器,定一个0.5ms出来TF0 = 0;            //让溢出标志位清零EA = 1;             //打开总中断EAET0 = 1;            //打开定时器0的中断ET0TR0 = 1;            //定时器0开始数数
    }void initSG90_0degree()
    {Delay300ms();           //让系统稳定一下sg90_servo = 0;         //初始化PWM波形从低电平开始servoAngle = 1;         //初始角度是0度cnt = 0;
    }//定时器0的中断服务程序,目的是用定时器中断软件模拟PWM波形,输出给SG90舵机
    void Timer0_Routine()   interrupt 1
    {cnt ++;TF0 = 0;TL0 = 0x33;TH0 = 0xFE;if(cnt <= servoAngle){    //根据设置的转动角度,实时绘制PWM波形图sg90_servo = 1;}else{sg90_servo = 0;}if(cnt == 40){            //利用40次爆表,确定PWM波形的周期是20mscnt = 0;}
    }void Delay2000ms()      //@11.0592MHz
    {unsigned char i, j, k;_nop_();i = 15;j = 2;k = 235;do{do{while (--k);} while (--j);} while (--i);
    }void Delay300ms()       //@11.0592MHz
    {unsigned char i, j, k;_nop_();i = 3;j = 26;k = 223;do{do{while (--k);} while (--j);} while (--i);
    }void openStatus()
    {ledD5 = 0;ledD6 = 1;servoAngle = 4;     //舵机转到135度位置cnt = 0;Delay2000ms();
    }void closeStatus()
    {ledD5 = 1;ledD6 = 0;servoAngle = 1;     //舵机转到0度位置cnt = 0;Delay150ms();
    }void Delay150ms()       //@11.0592MHz
    {unsigned char i, j, k;_nop_();i = 2;j = 13;k = 237;do{do{while (--k);} while (--j);} while (--i);
    }
    

模式2(查询法按键控制舵机)

  1. 初步思路:

    全局变量 | 增加:
    1. sbit指令找到P2这个I/O口组的第1位,用作输入口,代表开发板上的按键SW1: sbit SW1 = P2^1;
    
    main函数 | 修改第4.2步的逻辑:
    ......
    ......
    4. while死循环,不断用超声波传感器测出距离,达到临界值后转动舵机,同时用led进行测试4.1 调用API2. 获取超声波传感器与障碍物的距离,保存在变量distance中:distance = get_distance();4.2 判断距离是否小于10cm或者是否按下SW1按键,判据是distance < 10 || SW1 == 02.2.1 如果是,调用API9. 让舵机带动垃圾桶盖处于打开状态:调用API9: openStatus();2.2.2 否则,调用API10. 让舵机带动垃圾桶盖处于关闭状态:调用API10: closeStatus();
    
  2. 这个程序中,是否需要给按键额外一个软件消抖?答:不需要,因为distance > 10是大概率事件,所以一般情况下程序总是在调用closeStatus()函数,我们回顾这个函数中实际上有加上一个150ms的延时,所以模式2中不需要再给按键额外的软件消抖了。

模式3(查询法振动控制舵机)——外部中断

  • ==选用P32口作为振动传感器接线口的原因==:一会儿我们要用外部中断实现该模式,而查阅51单片机开发板原理图,P32口是复用引脚,可以作为外部中断0。

  1. 初步思路:

    全局变量 | 增加:
    1. sbit指令找到P3这个I/O口组的第2位,用作输入口,获取振动传感器信号: sbit vibrate = P3^2;
    
    main函数 | 继续修改第4.2步的逻辑:
    ......
    ......
    4. while死循环,不断用超声波传感器测出距离,达到临界值后转动舵机,同时用led进行测试4.1 调用API2. 获取超声波传感器与障碍物的距离,保存在变量distance中:distance = get_distance();4.2 判断距离是否小于10cm,或者是否按下SW1按键,或者受到振动,判据是:distance < 10 || SW1 == 0 || vibrate == 04.2.1 如果是,调用API9. 让舵机带动垃圾桶盖处于打开状态:调用API9: openStatus();4.2.2 否则,调用API10. 让舵机带动垃圾桶盖处于关闭状态:调用API10: closeStatus();
    
  2. 出现的BUG:有时候受到振动了,但是振动传感器上的指示灯短暂亮了一下就又灭掉了,直接表现为舵机没有转动,也就是说在高精度的场合振动传感器工作好像出现了问题。

  3. BUG分析:这种情况和我们第一个项目《电动车报警器》出现的bug很像(在报警状态下强制解除报警模式)

    • 振动传感器的电平信号维持的时间很短,而按键的低电平信号是个持续的信号,也就是说振动信号没有按键信号稳定和持久,这是一方面。
    • 另一方面,因为distance > 10是大概率事件,所以一般情况下程序总是在调用closeStatus()函数,我们回顾这个函数中实际上有加上一150ms的延时,所以振动传感器的信号很有可能在这150ms以内被丢失掉了,没有及时被捕获到。
  4. 解决方法:外部中断

  1. **外部中断0的中断号是什么?**查阅51芯片手册的6.1小节:

解释:

  • 外部中断的中断号是0。
  • 中断开关有两个,分别是EX0和EA。
  • 暂时不用考虑IE0,图中可以看出,它类似于定时器的溢出标志位TF0。

  1. 外部中断0的中断开关是什么?查阅51芯片手册的6.2小节:

解释:

  • 在IE这个寄存器的第0位,也就是EX0,是外部中断0的中断开关,高电平代表中断开启。可以把EX0理解为:enable extern 0

  1. 外部中断0什么时候触发?查阅中断触发行为:

解释:

  • 外部中断有两种触发方式可以配置,一种是下降沿触发,一种是低电平触发。
  • 下降沿触发由TCON寄存器的第0位,也就是IT0 进行配置,高电平代表下降沿触发。
  • 低电平触发也由TCON寄存器的第0位,也就是IT0 进行配置,低电平代表低电平触发。

  1. 最终思路:

    全局变量 | 增加
    1. 代表是否受到振动的状态标志位,默认是0: char vibrate_falg = 0;  //1表示受到振动
    
    main函数 | 继续修改第4步开始的逻辑:
    ......
    ......
    4. 调用API12. 初始化外部中断0,用于振动传感器控制舵机
    5. while死循环,不断用超声波传感器测出距离,达到临界值后转动舵机,同时用led进行测试5.1 调用API2. 获取超声波传感器与障碍物的距离,保存在变量distance中:distance = get_distance();5.2 判断距离是否小于10cm,或者是否按下SW1按键,或者受到振动,判据是:distance < 10 || SW1 == 0 || vibrate_flag == 05.2.1 如果是:调用API9,让舵机带动垃圾桶盖处于打开状态: openStatus();将状态标志位vibrate_flag程序“置0”:vibrate_flag = 0;5.2.2 否则,调用API10. 让舵机带动垃圾桶盖处于关闭状态:调用API10: closeStatus();
    
    中断 | 增加
    中断0: 封装外部中断0的中断服务程序, void INT0_Routine()   interrupt 0
    //每当检测到P3^2引脚出现低电平,都会由硬件自动调用该函数,目的是检测到振动传感器的低电平后,挂起closeStatus()函数,立马记住低电平状态0.1 修改状态标志位vibrate_flag: vibrate_flag = 1;
    
    /* 一级函数:f1 f2 f5 f6 f9 f10 */ | 增加
    f12. 封装初始化外部中断0的API,用于振动传感器控制舵机f12.1 通过TCON寄存器的ITO位,令外部中断0的触发方式为低电平触发,匹配振动传感器: IT0 = 0;f12.2 通过总中断EA和IE寄存器的EX0位,开启外部中断0:EA = 1;EX0 = 1;
    

Debug成功,完事

  • 最后加入蜂鸣器,我将它的I/O口接在了单片机的P2^0口。程序中简单修改openStatus()函数即可,另外,对于超声波传感器一直感应到distance<10时蜂鸣器还在那响,甚至垃圾桶出现了“抽抽”的情况(因为cnt在开盖状态下又被赋值为0了),我们的解决方法是定义一个记录上一次舵机角度的全局变量lastAngle。思路如下:

    全局变量 | 增加
    1. sbit指令找到P2这个I/O口组的第0位,用作输出口,控制蜂鸣器: sbit buzzer = P2^0;
    2. 定义记录上一次舵机角度的全局变量lastAngle: char lastAngle;
    //lastAngle的传递路线为:initSG90_0degree() ——> openStatus(); 或 closeStatus();
    
    /* 一级函数:f1 f2 f5 f6 f9 f10 */ | 修改
    f6. 封装初始化舵机位置的API: void initSG90_0degree();f6.1 调用API8. 让系统稳定一下,减少软件模拟PWM信号的误差: Delay300ms();f6.2 修改全局变量sg90_servo,让PWM波形从低电平开始: sg90_servo = 0;f6.3 修改全局变量servoAngle,让舵机初始角度为0度: servoAngle = 1;cnt = 0;
    f9. 封装舵机带动垃圾桶盖处于打开状态的API: void openStatus();f9.1 修改全局变量servoAngle,让舵机转到135度位置,不着急令cnt=0servoAngle = 4;f9.2 判断这次的角度是否和上次的角度不一样,判据是: servoAngle != lastAnglef9.2.1 如果是,说明需要做出动作f9.2.1.1 程序清零cnt: cnt = 0;f9.2.1.2 用LED作为测试: ledD5 = 0;ledD6 = 1;f9.2.1.3 利用已有的延时函数,让蜂鸣器响0.3s:buzzer = 0;调用API8: Delay300ms();buzzer = 1;f9.2.2 否则,啥也不干f9.3 不管上一次角度是否等于这次角度,总是要更新最后一次角度值,并延时2slastAngle = servoAngle;调用API7: Delay2000ms();
    f10. 封装舵机带动垃圾桶盖处于关闭状态的API: void closeStatus();f10.1 用LED作为测试: ledD5 = 1;ledD6 = 0;f10.2 修改全局变量servoAngle和lasrAngle,让舵机转到0度位置,仅维持0.15s时间servoAngle = 1;cnt = 0;修改全局变量lastAngle: lastAngle = servoAngle;调用API11: Delay150ms();f9.
    

代码心得:

  • 基于之前的代码做二次开发时,我们先将之前的代码烧录到单片机中进行验证:确保接线没问题、确保代码能正常运行来完成所需功能。

可以优化的地方:

  • BUG:超声波传感器没有被供电时,按键和振动传感器是无效的,无法驱动舵机转动。原因:在我们main函数第4.1点中有这么一条语句:distance = get_distance();。再回顾我在get_distance()函数中写的一条暂停程序的语句:while(ultraSonicEcho == 0);。所以,如果程序不给超声波传感器上电,那么get_distance()函数永远会卡死在那里,因为无法发送超声波,也无法接收回超声波。
  1. 总体思路:将上面的三种模式的思路组合在一起就行了,最后确认一全局变量的传递路线

    全局变量:
    1.sfr指令直接找到AUXR寄存器: sfr AUXR = 0X8E;    //因为AUXR没有在reg52.h中声明
    2.sbit指令找到P1这个I/O口组的第5位,用作输出口,传递tirg信号给超声波传感器:sbit ultrasonicTrig = P1^5;
    3.sbit指令找到P1这个I/O口组的第4位,用作输入口,从超声波传感器接收echo信号:sbit ultrasonicEcho = P1^4;
    4.sbit指令找到与D5这个led对应的引脚: sbit ledD5 = P3^7;
    5.sbit指令找到与D6这个led对应的引脚: sbit ledD6 = P3^6;
    6.sbit指令找到P1这个I/O口组的第1位,用作输出口,绘制PWM信号给舵机: sbit sg90_servo = P1^1;
    //sg90_servo的传递路线是:initSG90_0degree() ———> 定时器0中断(中断1)
    7.定义一个在定时器0的中断服务程序中用于计数的全局变量cnt: int cnt = 0;
    8.定义一个代表舵机角度的全局变量servoAngle: int servoAngle;
    //servoAngle的值需要根据舵机的参数确定,传递路线为:
    //路线1. initSG90_0degree() ——> 定时器0中断(中断1)
    //路线2. openStatus() ——> lastAngle ——> 定时器0中断(中断1)
    //路线3. closeStatus ——> lastAngle ——> 定时器0中断(中断1)
    9.sbit指令找到P2这个I/O口组的第1位,用作输入口,代表开发板上的按键SW1: sbit SW1 = P2^1;
    10. 定义代表是否受到振动的状态标志位,默认是0: char vibrate_falg = 0;
    //vibrate_flag的传递路线是:外部中断0(中断0) ——> main函数
    11.sbit指令找到P2这个I/O口组的第0位,用作输出口,控制蜂鸣器: sbit buzzer = P2^0;
    12.定义记录上一次舵机角度的全局变量lastAngle: char lastAngle;
    //lastAngle的传递路线为:initSG90_0degree() ——> openStatus(); 或 closeStatus();
    
    1. 调用API1. 初始化定时器1,用于超声波传感器计时
    2. 调用API5. 初始化定时器0,用于舵机绘制PWM波形图
    3. 调用API6. 初始化舵机到0度转动位置,让垃圾桶处于关盖状态
    4. 调用API12. 初始化外部中断0,用于振动传感器控制舵机
    5. while死循环,不断用超声波传感器测出距离,达到临界值后转动舵机,同时用led进行测试5.1 调用API2. 获取超声波传感器与障碍物的距离,保存在变量distance中:distance = get_distance();5.2 判断距离是否小于10cm,或者是否按下SW1按键,或者受到振动,判据是:distance < 10 || SW1 == 0 || vibrate_flag == 05.2.1 如果是:调用API9,让舵机带动垃圾桶盖处于打开状态: openStatus();将状态标志位vibrate_flag程序“置0”:vibrate_flag = 0;5.2.2 否则,调用API10. 让舵机带动垃圾桶盖处于关闭状态:调用API10: closeStatus();
    
    中断0: 封装外部中断0的中断服务程序, void INT0_Routine()   interrupt 0
    //每当检测到P3^2引脚出现低电平,都会由硬件自动调用该函数,目的是检测到振动传感器的低电平后,挂起closeStatus()函数,立马记住低电平状态0.1 修改状态标志位vibrate_flag: vibrate_flag = 1;
    中断1: 封装定时器0的中断服务程序, void Timer0_Routine()   interrupt 1
    //每当定时器0爆表都会由硬件自动调用该函数,目的是用定时器中断软件模拟PWM波形,输出给SG90舵机1.1 修改代表数数次数的循环变量cnt,让其自加1: cnt++;1.2 程序清零:令溢出标志位TF0“置0”: TF0 = 0;1.3 重新初始化寄存器TL0和TH0,重新确定计数起点:TL0 = 0X33;TH0 = 0xFE;1.4 判断是否数到与角度对应的数值,其中舵机0度位置对应数值1,即高电平0.5ms,...,舵机135度位置对应数值4,即高电平2.0ms, 判据是cnt <= servoAngle//内在逻辑:根据设置的转动角度servoAngle,实时绘制PWM波形图//注意事项: 判据到底有没有等于号要根据你在main函数中初始化的PWM波形图进行选择1.4.1 如果是,说明需要把电平拉高: sg90_servo = 1;1.4.2 否则,把电平拉低:  sg90_servo = 0;1.5 判断是否到达20ms,即是否爆表40次,判据是cnt == 40//内在逻辑:利用40次爆表,确定PWM波形的周期是20ms1.5.1 如果是,说明需要重置cnt: cnt = 0;1.5.2 否则,啥也不干
    
    /* 一级函数:f1 f2 f5 f6 f9 f10 f12*/
    f1. 封装初始化定时器1的API: void initT1();
    //用于超声波传感器,所以不着急开始数数,也不需要开启中断f1.1 禁用ALE信号,降低单片机时钟对外界的电磁辐射: AUXR &= 0x1F;f1.2 通过TMOD寄存器配置定时器16位工作模式:TMOD &= 0X0F;TMOD |= 0X10;  f1.3 通过TL0和TH0寄存器,初始化定时器,从0开始数数:TL1 = 0;TH1 = 0;
    f2. 封装利用超声波传感器计算距离的API: double get_distance();f2.1 调用API3. 给超声波的trig端口至少10us的高电平: start_ultrasonic();f2.2 echo引脚由低电平跳转到高电平代表开始发送超声波,波发出的那一下开始启动定时器根据超声波时序图,用空循环体,暂时卡死程序: while(ultrasonicEcho == 0);定时器开始计时: TR1 = 1;f2.3 echo引脚由高电平跳转到低电平代表波返回来了,停止定时器计数根据超声波时序图,用空循环体,暂时卡死程序: while(ultrasonicEcho == 1);定时器停止计时: TR1 = 0;f2.4 根据定时器数一次用时1.085us的特点,计算超声波发送和返回经历的时间,保存在变量time中:time = ((TH1<<8) + TL1)*1.085;f2.5 根据公式计算出超声波传感器和障碍物之间的距离,保存在变量distance中:distance = time * 0.034 / 2;    //确定量纲: 340m/s = 34000cm/s = 34cm/ms = 0.034cm/usf2.6 定时器1清零:TL1 = 0;TH1 = 0;f2.7 返回distance
    f5. 封装初始化定时器0的API,在11.0592MHz的晶振频率下定0.5ms出来: void initT0();
    //用于绘制舵机的PWM波形图,0.5ms是控制舵机的最小时间单位f5.1 禁用ALE信号,降低单片机时钟对外界的电磁辐射: AUXR &= 0x1F;f5.2 通过TMOD寄存器配置定时器16位工作模式:TMOD &= 0XF0;TMOD |= 0X01;  f5.3 通过TL0和TH0寄存器,初始化定时器,定一个0.5ms出来:TL0 = 0x33;TH0 = 0xFE;f5.4 让溢出标志位清零: TF0 = 0;f5.5 打开定时器0的中断开关:EA = 1;ET0 = 1;f5.6 通过TCON寄存器的运行控制位TR0,让定时器开始数数: TR0 = 1;
    f6. 封装初始化舵机位置的API: void initSG90_0degree();f6.1 调用API8. 让系统稳定一下,减少软件模拟PWM信号的误差: Delay300ms();f6.2 修改全局变量sg90_servo,让PWM波形从低电平开始: sg90_servo = 0;f6.3 修改全局变量servoAngle,让舵机初始角度为0度:servoAngle = 1;cnt = 0;
    f9. 封装舵机带动垃圾桶盖处于打开状态的API: void openStatus();f9.1 用LED作为测试:ledD5 = 0;ledD6 = 1;f9.2 修改全局变量servoAngle,让舵机转到135度位置,并保持2s时间servoAngle = 4;cnt = 0;调用API7: Delay2000ms();
    f10. 封装舵机带动垃圾桶盖处于关闭状态的API: void closeStatus();f10.1 用LED作为测试:ledD5 = 1;ledD6 = 0;f10.2 修改全局变量servoAngle,让舵机转到0度位置,仅维持0.15s时间servoAngle = 1;cnt = 0;调用API11: Delay150ms();
    f12. 封装初始化外部中断0的API,用于振动传感器控制舵机f12.1 通过TCON寄存器的ITO位,令外部中断0的触发方式为低电平触发,匹配振动传感器: IT0 = 0;f12.2 通过总中断EA和IE寄存器的EX0位,开启外部中断0:EA = 1;EX0 = 1;
    
    /* 二级函数: f3 f4 f7 f8 f11 */
    f3. 封装启动超声波传感器的API: void start_ultrasonic();f2.1 修改全局变量ultrasonicTrig,让它一开始处于低电平: ultrasonicTrig = 0;f2.2 修改全局变量ultrasonicTrig,让它处于高电平,并保持10μs:ultrasonicTrig = 1;调用API4: Delay10us();f2.3 修改全局变量ultrasonicTrig,让它回到低电平:
    f4. 封装软件延时10微秒的API,用于给超声波传感器trig引脚10us的高电平: void Delay10us();
    f7. 封装软件延时2s的API,用于打开垃圾桶盖时,即使感应不到物体也能保持开盖: void Delay2000ms();
    f8. 封装软件延时0.3s的API,用于稳定程序,减少软件模拟PWM信号的误差: void Delay300ms();
    f11. 封装软件延时0.15s的API,用于防止物体快速接近传感器时也会转动舵机的情况(有点像消抖): void Delay150ms();
    
  2. 代码:

    #include "reg52.h"
    #include "intrins.h"sfr AUXR            = 0x8E;
    sbit ultrasonicTrig = P1^5;
    sbit ultrasonicEcho = P1^4;
    sbit ledD5          = P3^7;
    sbit ledD6          = P3^6;
    sbit sg90_servo     = P1^1;
    sbit SW1            = P2^1;
    sbit vibrate        = P3^2;    //外部中断0口
    sbit buzzer         = P2^0;
    int cnt = 0;
    char servoAngle;
    char vibrate_flag = 0;
    char lastAngle;/* API1: 定时器T1的初始化@11.0592MHz,我们不关心初值,从0开始计算就行,用于超声波传感器 */
    void initT1();
    /* API2. 超声波计算距离 */
    double get_distance();
    /* API3: 启动超声波传感器 */
    void start_ultrasonic();
    /* API4: 软件延时10微秒,用于给超声波传感器trig引脚10us的高电平 */
    void Delay10us();
    /* API5: 定时器T0的初始化,定0.5ms出来@11.0592MHz,用于舵机 */
    void initT0();
    /* API6. 初始化sg90舵机到0度位置 */
    void initSG90_0degree();
    /* API7: 软件延时2s,用于打开垃圾桶盖时,即使感应不到物体也能保持开盖2s */
    void Delay2000ms();
    /* API8: 软件延时0.3s,用于稳定程序,减少软件模拟PWM信号的误差 */
    void Delay300ms();
    /* API9: 测试,代表舵机带动垃圾桶盖打开状态 */
    void openStatus();
    /* API10: 测试,代表舵机带动垃圾桶盖关闭状态 */
    void closeStatus();
    /* API11. 软件延时0.15s,用于在关盖状态进行延时,防止物体快速接近传感器时也会转动舵机 */
    void Delay150ms();
    /* API12. 外部中断0的初始化,用于振动传感器控制舵机 */
    void initINT0();void main(void)
    {double distance;initT1();               //定时器1用于超声波传感器initT0();               //定时器0用于舵机initINT0();                //外部中断0用于振动传感器initSG90_0degree();     //初始化舵机,让垃圾桶处于关盖状态while(1){distance = get_distance();if(distance < 10 || SW1 == 0 || vibrate_flag == 1){       openStatus();vibrate_flag = 0;}else{closeStatus();}}
    }void initT1()
    {AUXR &= 0x1F;     //禁用ALE信号,降低单片机时钟对外界的电磁辐射TMOD &= 0X0F;        TMOD |= 0X10;        //配置定时器1为16位工作模式TL1 = 0;       TH1 = 0;           //初始化定时器,从0开始就行//定时器1不着急开始数数
    }double get_distance()
    {double time;double distance;/* 1.给超声波的trig端口至少10us的高电平 */start_ultrasonic();/* 2.echo引脚由低电平跳转到高电平代表开始发送超声波,波发出的那一下开始启动定时器 */while(ultrasonicEcho == 0);   //空循环体,暂时卡死程序TR1 = 1; /* 3.echo引脚由高电平跳转到低电平代表波返回来了,停止定时器计数 */while(ultrasonicEcho == 1);   //空循环体,暂时卡死程序TR1 = 0;/* 4.计算超声波发送和返回经历的时间 */time = ((TH1<<8) + TL1)*1.085;  //us为单位/* 5.计算出超声波传感器和障碍物之间的距离,先确定量纲: 340m/s = 34000cm/s = 34cm/ms = 0.034cm/us */distance = time * 0.034 / 2;   //cm为单位/* 6.定时器1清零,重新计时 */TL1 = 0;TH1 = 1;return distance;
    }void start_ultrasonic()
    {ultrasonicTrig = 0;ultrasonicTrig = 1;Delay10us();ultrasonicTrig = 0;
    }void Delay10us()       //@11.0592MHz
    {unsigned char i;i = 2;while (--i);
    }void initT0()
    {AUXR &= 0x1F;     //禁用ALE信号,降低单片机时钟对外界的电磁辐射TMOD &= 0XF0;        TMOD |= 0X01;        //配置定时器0为16位工作模式TL0 = 0x33;        TH0 = 0xFE;            //初始化定时器,定一个0.5ms出来TF0 = 0;         //让溢出标志位清零EA = 1;             //打开总中断EAET0 = 1;            //打开定时器0的中断ET0 TR0 = 1;           //定时器0开始数数
    }void initSG90_0degree()
    {Delay300ms();          //让系统稳定一下sg90_servo = 0;           //初始化PWM波形从低电平开始servoAngle = 1;        //初始角度是0度cnt = 0;lastAngle = 1;
    }//定时器0的中断服务程序,目的是用定时器中断软件模拟PWM波形,输出给SG90舵机
    void Timer0_Routine()    interrupt 1
    {cnt ++;TF0 = 0;TL0 = 0x33;TH0 = 0xFE;if(cnt <= servoAngle){     //根据设置的转动角度,实时绘制PWM波形图sg90_servo = 1;}else{sg90_servo = 0;}if(cnt == 40){            //利用40次爆表,确定PWM波形的周期是20mscnt = 0;}
    }void Delay2000ms()     //@11.0592MHz
    {unsigned char i, j, k;_nop_();i = 15;j = 2;k = 235;do{do{while (--k);} while (--j);} while (--i);
    }void Delay300ms()      //@11.0592MHz
    {unsigned char i, j, k;_nop_();i = 3;j = 26;k = 223;do{do{while (--k);} while (--j);} while (--i);
    }void openStatus()
    {servoAngle = 4;       //舵机转到135度位置if(servoAngle != lastAngle){cnt = 0;ledD5 = 0;ledD6 = 1;buzzer = 0;Delay300ms();buzzer = 1;}lastAngle = servoAngle;  //不管上一次角度是否等于这次角度,总是要更新最后一次角度值Delay2000ms();
    }void closeStatus()
    {ledD5 = 1;ledD6 = 0;servoAngle = 1;     //舵机转到0度位置cnt = 0;lastAngle = servoAngle;Delay150ms();
    }void Delay150ms()      //@11.0592MHz
    {unsigned char i, j, k;_nop_();i = 2;j = 13;k = 237;do{do{while (--k);} while (--j);} while (--i);
    }void initINT0()
    {IT0 = 0;  //外部中断0的触发方式为低电平触发,匹配振动传感器EA = 1;EX0 = 1;  //开启外部中断0
    }//外部中断0的中断服务程序,目的是检测到振动传感器的低电平后,挂起closeStatus()函数,立马记住低电平状态
    void INT0_Routine()    interrupt 0
    {vibrate_flag = 1;
    }
    

感应开关盖垃圾桶项目实现.md相关推荐

  1. 超声波、震动感应开关盖垃圾桶(教程里面提供源码)

    前言: 基于前面学习的知识,这次我们利用一个项目来做一个总结:超声波.震动感应开关盖垃圾桶.话不多说,先来看一下效果展示: 超声波,震动感应,开关垃圾捅 1.项目概述 功能描述 检测靠近时,垃圾桶自动 ...

  2. 智能开盖垃圾桶项目详解

    通过两天时间,终于把这项目弄出来了,所用到硬件是WemosD1开发板,基于Arduino开发板的,软件是Arduino开发环境.及一些简要的环境配置.项目涉及到的模块如下: 超声波模块 超声波传感器模 ...

  3. 【STC89C52RC】感应开关盖垃圾桶

    功能描述 检测靠近时,垃圾桶自动开盖,2秒后关盖 发生震动时,垃圾桶自动开盖,2秒后关盖 按下按键时,垃圾桶自动开盖,2秒后关盖 硬件展示 SG90 舵机,超声波模块,震动传感器 震动传感器模块 舵机 ...

  4. 基于Wemos的智能感应开盖垃圾桶——日记

    参考:基于Wemos的智能感应开盖垃圾桶--日记 作者:9art0 发布时间:2020-09-24 00:40:59 网址:https://blog.csdn.net/GatoWong/article ...

  5. 基于Wemos的感应开盖垃圾桶

    基于Wemos的感应开盖垃圾桶 一.绪论 1.研究背景 2.研究意义 二.系统总体设计方案 1.设计思路 2.硬件平台功能介绍 3.软件开发环境 三.设计思路实现步骤 1.Wemos的IO口研究驱动蜂 ...

  6. 基于STM32F103C8T6的超声波与舵机综合应用的垃圾桶项目

    #一.垃圾桶项目简介 #二.超声波代码 #三.舵机代码 #四.串口代码 #五.联合代码(主函数) 一.垃圾桶项目简介 利用超声波测距原理,感应到有人靠近垃圾桶会使能舵机打开垃圾桶盖. 超声波部分 舵机 ...

  7. 基于Wemos的智能感应开盖垃圾桶

    基于Wemos的智能感应开盖垃圾桶 前言 硬件部分 软件部分 一.舵机控制 二.超声波控制 三.项目整体代码 思考 前言 记录嵌入式学习的第一个小项目吧,基于Wemos的智能感应开盖垃圾桶(上官可编程 ...

  8. 基于Wemos D1的智能感应开盖垃圾桶

    硬件:这个项目我们主要用到的东西有环保型垃圾桶,Wemos D1模块,舵机,超声波模块,串口助手,若干条杜邦线等等. 环境:arduino 思路:首先这个项目又超声波,wemos d1模块,舵机SG9 ...

  9. 基于Wemos D1的感应开盖垃圾桶

    参考:基于Wemos D1的感应开盖垃圾桶(增加自己的代码实现部分) 作者:LEO-max 发布时间:2020-12-29 15:21:26 网址:https://blog.csdn.net/zouc ...

最新文章

  1. CF338D GCD Table(拓展中国剩余定理,细节处理,2900分)
  2. php url映射,php – Laravel:将任意URL解析为相应的Controller / Route?
  3. 一场B站服务端开发面试之旅
  4. .net core 调用c dll_工具:搭建Camp;C,一睹模样
  5. 运营资源很少的时候,怎么运营自己的产品(完结)
  6. C语言中的位域 bit field [转]
  7. 源哥每日一题第十三弹 百练4124:海贼王之伟大航路 状压dp
  8. Mybatis助手之Mybatis-Plus——开始使用
  9. Hadoop核心之MapReduce架构设计
  10. 一个对Winsock完成端口模型封装的类
  11. 在win7在结构cocos2d-x v3.2rc0开发环境(For Android)
  12. Nginx安装的两种方法
  13. 影视链进入区块链应用时代大潮 展现影视新巅峰
  14. html+css+js的生日祝福网页+更改教程
  15. Android跑马灯进度条,跑马灯进度条在Powershell中冻结
  16. 制造工厂中的计件工资管理
  17. Linux下部署worldPress
  18. ai人工智能对话了_对话人工智能模型
  19. Cocostudio学习笔记(2) Button + CheckBox
  20. 固定资产折旧计算的方法[轉帖]

热门文章

  1. 利用计算机系统基础知识分析程序结果,计算机二级公共基础知识笔记
  2. python中color的用法顺序_Python Matplotlib.colors.Normalize用法及代码示例
  3. 高通 camera CTS Verify FOV calibration debug
  4. 企业负面舆情危机公关处理方法技巧与方法
  5. Hal3_2v6模块介绍---普通Photo模式configureStreams及processCaptureRequest流程
  6. 用calibre抓取RSS新闻制作电子书及推送到kindle
  7. 使用conda安装包
  8. 2014年新年新气象
  9. 微信公众号自定义菜单关联小程序
  10. NOI 1.5 45