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:检测靠近时,垃圾桶自动开盖并伴随蜂鸣器滴一声,2秒后关盖
- 功能2:发生震动时,垃圾桶自动开盖并伴随蜂鸣器滴一声,2秒后关盖
- 功能3:按下按键时,垃圾桶自动开盖并伴随蜂鸣器滴一声,2秒后关盖
- 控制逻辑: 可以看出本项目包含两三种模式。
- 超声波感应工作模式(配合舵机)
- 舵机用定时器0
- 超声波用定时器1
- 查询的方式添加按键控制(配合舵机)
- 查询的方式添加震动控制(配合舵机)
- 使用外部中断0配合震动控制
模式1(超声波控制舵机)——定时器中断
首先对初始化定时器这部分代码进行整合:我们让超声波用定时器1,让舵机用定时器0。根据定时器编程的思路,针对之前的代码修改以下东西,更改完后测试代码:
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;
配置定时器0和1的起始数数位置:回顾51芯片手册第7.1.1小节
定时器0:舵机定一个0.5ms出来
TL0 = 0X33;
TH0 = 0XFE;
定时器1:超声波从0开始数数
TL1 = 0;
TH1 = 0;
TCON寄存器配置定时器0和1的溢出标志位清零:
- 定时器0:
TF0 = 0;
- 定时器1:
TF1 = 0;
IE寄存器配置定时器0和1开启中断:回顾51芯片手册第6.2小节
- 定时器0:舵机需要定时器中断来软件模拟PWM
EA = 1;
ET0 = 1;
- 定时器1:超声波不需要定时器中断,数数就行了
TCON寄存器配置定时器0和1开始数数:
- 定时器0:
TR0 = 1;
- 定时器1:
TR1 = 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();
-
代码:
#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. 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();
-
这个程序中,是否需要给按键额外一个软件消抖?答:不需要,因为distance > 10是大概率事件,所以一般情况下程序总是在调用closeStatus()函数,我们回顾这个函数中实际上有加上一个150ms的延时,所以模式2中不需要再给按键额外的软件消抖了。
模式3(查询法振动控制舵机)——外部中断
- ==选用P32口作为振动传感器接线口的原因==:一会儿我们要用外部中断实现该模式,而查阅51单片机开发板原理图,P32口是复用引脚,可以作为外部中断0。
-
初步思路:
全局变量 | 增加: 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();
-
出现的BUG:有时候受到振动了,但是振动传感器上的指示灯短暂亮了一下就又灭掉了,直接表现为舵机没有转动,也就是说在高精度的场合振动传感器工作好像出现了问题。
-
BUG分析:这种情况和我们第一个项目《电动车报警器》出现的bug很像(在报警状态下强制解除报警模式)
- 振动传感器的电平信号维持的时间很短,而按键的低电平信号是个持续的信号,也就是说振动信号没有按键信号稳定和持久,这是一方面。
- 另一方面,因为distance > 10是大概率事件,所以一般情况下程序总是在调用closeStatus()函数,我们回顾这个函数中实际上有加上一150ms的延时,所以振动传感器的信号很有可能在这150ms以内被丢失掉了,没有及时被捕获到。
-
解决方法:外部中断。
- **外部中断0的中断号是什么?**查阅51芯片手册的6.1小节:
解释:
- 外部中断的中断号是0。
- 中断开关有两个,分别是EX0和EA。
- 暂时不用考虑IE0,图中可以看出,它类似于定时器的溢出标志位TF0。
- 外部中断0的中断开关是什么?查阅51芯片手册的6.2小节:
解释:
- 在IE这个寄存器的第0位,也就是EX0,是外部中断0的中断开关,高电平代表中断开启。可以把EX0理解为:enable extern 0
- 外部中断0什么时候触发?查阅中断触发行为:
解释:
- 外部中断有两种触发方式可以配置,一种是下降沿触发,一种是低电平触发。
- 下降沿触发由TCON寄存器的第0位,也就是IT0 进行配置,高电平代表下降沿触发。
- 低电平触发也由TCON寄存器的第0位,也就是IT0 进行配置,低电平代表低电平触发。
-
最终思路:
全局变量 | 增加 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.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();
-
代码:
#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; }