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

1.项目接线

接线示意图和实物图

示意图和接线说明:

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

实物图(未封装):

image-20230424155200026

信号传输路线

路线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小节

    image-20230423131507959
    • 定时器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小节

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

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

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

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

    image-20230420220024273
    • 定时器0:舵机需要定时器中断来软件模拟PWM
      • EA = 1;
      • ET0 = 1;
    • 定时器1:超声波不需要定时器中断,数数就行了
  5. TCON寄存器配置定时器0和1开始数数

    image-20230418230722881
    • 定时器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 < 10
            4.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波形的周期是20ms
            1.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/us
        f2.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;             //打开总中断EA
        ET0 = 1;            //打开定时器0的中断ET0
        TR0 = 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波形的周期是20ms
            cnt = 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 == 0
            2.2.1 如果是,调用API9. 让舵机带动垃圾桶盖处于打开状态:
                    调用API9: openStatus();
            2.2.2 否则,调用API10. 让舵机带动垃圾桶盖处于关闭状态:
                    调用API10: closeStatus();
    
  2. 这个程序中,是否需要给按键额外一个软件消抖?答:不需要,因为distance > 10是大概率事件,所以一般情况下程序总是在调用closeStatus()函数,我们回顾这个函数中实际上有加上一个150ms的延时,所以模式2中不需要再给按键额外的软件消抖了。

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

  • ==选用P32口作为振动传感器接线口的原因==:一会儿我们要用外部中断实现该模式,而查阅51单片机开发板原理图,P32口是复用引脚,可以作为外部中断0。
image-20230423225652220
  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 == 0
            4.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小节:
image-20230424100057755

解释

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

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

解释

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

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

解释

  • 外部中断有两种触发方式可以配置,一种是下降沿触发,一种是低电平触发。
  • 下降沿触发由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 == 0
            5.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=0
        	servoAngle = 4;
    	f9.2 判断这次的角度是否和上次的角度不一样,判据是: servoAngle != lastAngle
            f9.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 不管上一次角度是否等于这次角度,总是要更新最后一次角度值,并延时2s
    		lastAngle = 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 == 0
            5.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波形的周期是20ms
            1.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/us
        f2.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;             //打开总中断EA
        ET0 = 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波形的周期是20ms
            cnt = 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;
    }
    
### STM32 HAL库实现感应开关垃圾桶功能的嵌入式软件项目案例 #### 1. 系统概述 该项目旨在通过STM32微控制器及其HAL库实现一个智能感应开关垃圾桶的功能。系统能够检测到人体接近信号后自动打开垃圾箱,并伴有提示音效,在设定时间后关闭子[^1]。 #### 2. 主要硬件配置 - **核心处理器**: STM32系列单片机。 - **驱动模块**: SG90微型舵机用于控制垃圾桶的开启与闭合动作。 - **传感器组件**: HC-SR501红外热释电传感器负责感知人体活动。 - **电源管理**: 提供稳定的电压供给各部件正常工作,其中舵机供电需特别注意其电流需求[^2]。 #### 3. 软件设计要点 ##### 初始化部分 在`main.c`文件中完成必要的初始化操作,包括但不限于GPIO端口、定时器以及中断服务函数等资源分配和参数设定。对于SG90舵机的具体操控,则封装成独立头文件形式以便调用: ```c #ifndef __SG90_H__ #define __SG90_H__ #include "sys.h" void sg90_init(void); void sg90_angle_set(uint16_t angle); #endif /* __SG90_H__ */ ``` 上述代码片段展示了如何定义了一个简单的接口来处理舵机角度设置命令[^3]。 ##### 功能逻辑编写 以下是关于如何利用PWM技术调节伺服电机位置的一个实例演示: ```c // 定义变量 uint16_t pwmVal = 0; // 调整PWM占空比 uint8_t dir = 1; // 设置改变方向。1表示增加;0减少 // 启动Timer4第3通道PWM输出 HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_3); while (1) { HAL_Delay(1); if (dir) { pwmVal++; if (pwmVal >= 499) { dir = 0; } } else { pwmVal--; if (pwmVal == 0) { dir = 1; } } // 修改比较值,从而修改占空比 __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, pwmVal); } ``` 此段伪代码描述了一种渐变式的LED亮度变化过程(即所谓的“呼吸灯”效果),但它同样适用于精确控制舵机转动的角度范围内的任意一点位置[^4]。 另外值得注意的是CCR寄存器数值计算原理:假设目标周期T等于20毫秒而期望高电平持续时间为0.5毫秒的话那么就有如下关系成立\(CRRx=\frac{T_{high}}{T}\times ARR\)因此当ARR设为200时对应的CCR应该取值约为5才能达到预期的效果[^5]。 #### 4. 实现细节说明 整个项目的具体实施流程大致可以分为以下几个方面展开讨论: - 当前状态监测: 使用HC-SR501或其他类型的运动探测装置获取环境中的动态信息作为触发条件之一; - 控制策略制定: 结合实际应用场景确定合理的响应机制比如延迟多久再执行下一步骤等等因素考虑进去形成完整的算法框架结构图解出来更直观易懂些; - 数据传输协议选择: 如果涉及到无线通信则要考虑采用何种标准格式来进行数据交换确保双方都能正确解析收到的消息内容. 综上所述我们已经了解到了有关于基于stm32hal库开发出来的这样一个具备智能化特性的家用电器产品设计方案思路了希望对你有所帮助! ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值