项目二 感应开关垃圾桶项目+定时器

定时器

C51中的定时器和计数器是是同一个硬件电路支持的,通过寄存器配置的不同,就可以将他当作定时器或者计数器来使用。

确切的说,定时器和计数器区别是致使他们背后的计数寄存器加1的信号不同。
当适配为计时器模式时,每经过一个机械周期,计数存储器的值就加1。
而适配为计数器的时候,每来一个负跳变(下降沿信号)(信号从P3.4或者P3.5输入),就加1,从而达到计数的目的。

标准的C51中有两个定时器:T0和T1。C52相比C51多了一个T2.

概念解读

  • 定时器和计数器,电路一样。
  • 定时器和计数器本质就是让单片机某个零件计数。
  • 当定时器用的时候,靠内部振荡电路数数。
  • 当计数器用的时候,数外面的信号,读取针脚的数据。

定时器怎么计时的

定时器的本质原理:每经过一个机器周期就加1。

什么是晶振

晶振(晶体振荡器),又称数字电路的心脏,是各种电子产品里边必不可缺的一部分,数字电路的所有工作都离不开时钟,晶振的好坏、晶振电路的设计,会影响到整个系统的稳定性

什么是时钟周期----->晶振的倒数也就是11.0598Mhz的倒数

时钟周期也称为振荡周期,定义为时钟频率的倒数,时钟周期也是计算机中最基本的,最小的时间单位,在一个时钟周期内,CPU仅仅能完成一个最基本的动作,时钟周期也是一个时间的量,更小的时钟周期就意味这更高的工作频率

什么是机器周期

机器周期也称是CPU周期,在计算机中,为了便于管理,常常把一条指令的执行过程划分为若干个阶段,每一个阶段完成一个基本的操作。完成一个基本操作所需要的时间是机器周期,一般来说,一个机器周期由若干个时钟周期(12个或者6个)组成。

加1经过了多少时间

.当晶振频率是11.0592Mhz的时候
.一个时钟周期就是:1/11059200 秒
.一个机器周期等于6个或者12个时钟周期,拿12个时钟周期来说:12/11059200 = 1.085微秒
.如果要定时20ms需要加多少下?
	20000/1.085 = 18433下
.在哪里加1
	在TH和TL寄存器中去加,默认是从0开始计数,最多可以加65536下,最长累计计时是71ms

配置定时器所需要的寄存器

image-20221230173200509

image-20221230173335989

image-20221230173414304

.当T0定时器计数报表的时候,TCON寄存器中的TF0会变成1.
.由TL0和TH0可以设置计数的初值,因为有两个寄存器分别放计数数据的第八位和高八位,所以最大的计数范围是:0-71ms
.例如 要定时10ms
	需要加 10 000 / 1.085 = 9216下
	初值应该设置为:65536-9216 = 56320
	转换为16进制是:0xDC00
	所以寄存器TL0 = 0x00
	寄存器TH0 = 0XDC
.如何知道爆表
	TCON寄存器的bit5能表示爆表,当爆表的时候,硬件会将bit5位上的数据改成1.如果不用中断,需要软件置0
.怎么开始计数
	TCON寄存器的bit4,通过编程让这个位为1的时候,开始计时。
.定时器使用是有很多的模式的
	需要配置TMOD(定时器模式寄存器),来选择寄存器模式,低4位为定时器0的模式选择,高4位为定时器1的模式选择。

代码:用定时器实现LED灯亮一秒灭一秒

#include <reg52.h>
sbit led = P3^7;
void main()
{
	int cum = 0;
	TMOD = 0x01; // 0000 0001 配置定时器0的工作模式,后两位为0 1为16位计数模式
	TL0 = 0x00; //给一个初值,定时10ms
	TH0 = 0xDC;
	TF0 = 0; // 定时器0的溢出标志位,如果TL0和TH0爆表了,硬件置为1。
	TR0 = 1; //开始计时	
	while(1)
	{
		if(TF0 == 1) //说明此时已经计时10ms
		{
			TF0 = 0; //因为没有使用中断,所以需要软件清0.
			TL0 = 0x00; //重新给初值,定时10ms
			TH0 = 0xDC;
			cum++;  
			if(cum == 100) //说明已经定时了1ms
			{
				cum = 0;
				led = !led; // led状态反转.
			}
		}
	}
}

关于按位操作

配寄存器推荐用按位操作,清零的时候,需要清零的位与上0

置1的时候需要置1的部分或上1.

例如:

void Timer0Init(void)		//100微秒@11.0592MHz
{
	AUXR |= 0x80;		//定时器时钟1T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0xAE;		//设置定时初值
	TH0 = 0xFB;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
}

TMOD &= 0xF0;		//设置定时器模式
	&= 是按位与运算 例如:TMOD的状态为1010 0010
	此时与0xF0按位与的结果是 : 1010 0010
							1111 0000 = 1010 0000
							也就是TMOD的高四位不变,将低四位清零
	|= 是按位或运算 例如:TMOD的经过上一步的状态为1010 0000
	此时与0x01按位或的结果是: 1010 0000
						   0000 0001 = 1010 0001
						   也就是将TMOD的高四位不变,低四位置1
	这样做的好处就是,在即使不知道定时器1的模式选择的情况下,不影响定时器1的模式选择,来配置定时器0的模式选择

中断系统

   当中央处理机CPU正在处理某件事情的时候外界发生了紧急的事件请求,要求CPU暂停当前的工作,转而去处理这个紧急事件,处理完之后,再回到原来被中断的地方,继续原来的工作,这样的过程被称为时中断,规定每一个中断源有一个优先级别。CPU总是先响应优先级别最高的中断请求。
   
中断的嵌套:就是当CPU正在处理一个中断请求的时候,发生了请另外一个优先级比它还要高的中断请求,这是CPU会转去处理优先级高的请求。

打个比方:你的女朋友正在看电视,你来打过来一个电话,你的女朋友会先暂停电视和你通话(响应低级中断),这时候有人来给你女朋友送快递(响应高级中断),你女朋友会暂停和你的通话去拿快递,拿完之后,回来继续通话,你挂断电话,你的女朋友继续看电视。

中断查询次序

image-20221231145431204

中断查询次序号

image-20221231145516377

定时器中断

​ 中断寄存器

image-20221231150014433

​ CPU能响应定时器0中断的条件是:需要配置IE寄存器 bit1 ET0 和 bit7 EA

​ 1.ET0中断允许要置一 ET0 = 1

​ 2.EA总中断要置一 EA = 1

硬件内部设计逻辑如下

image-20221231150320763

代码实现

#include <reg52.h>
sbit led = P3^7;
int cum = 0;

void initTime0()
{
	// 1.0000 0001 配置定时器0的工作模式,后两位为0 1为16位计数模式
	TMOD = 0x01; 
	//2.给一个初值,定时10ms
	TL0 = 0x00; 
	TH0 = 0xDC;
	// 3.定时器0的溢出标志位,如果TL0和TH0爆表了,如果没有使用定时器中断,需要软件手动置为0,硬件置为1。
	TF0 = 0;
    //4.开始计时
	TR0 = 1; 
	//5.打开定时器0的中断
	ET0 = 1;
	//6.打开中断总开关
	EA = 1;
}
void main()
{
	initTime0();
	while(1)
	{
		//空循环
	}
}

void Time0Fun() interrupt 1 //设置定时器0中断函数,1为定时器0的中断号
{
	cum++;
	TL0 = 0x00; //重新给初值,定时10ms
	TH0 = 0xDC;
	if(cum == 100)
	{
		led =! led; //逻辑取反.
		cum = 0;
	}

}

PWM开发SG90舵机

PWM是脉冲宽度调制,通过占空比来传输信号

占空比:一个周期内,高电平所占的时间的比值

如何实现PWM波

1.通过芯片内部的PWM模块输出:由于本单片机采用STC89C系列的单片机,所以不具备硬件输出PWM的功能,stc89w系列可以实现
2.通过软件模拟

怎么控制舵机

向黄色信号线“灌入”PWM 信号
PWM波的频率不能太高,大概50hz,一个周期也就是20ms
数据:
	0.5ms ----- 0度;
	1.0ms ----- 45度;
	1.5ms ----- 90度;
	2.0ms ----- 135度;
	2.5ms ----- 180度;

代码实现

#include <reg52.h>
sbit sg90_pwm = P1^1;

int cnt = 0;
int jd;
void Delay2000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	i = 15;
	j = 2;
	k = 235;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
void initTime0()
{
	// 0000 0001 配置定时器0的工作模式,后两位为0 1为16位计数模式
	TMOD = 0x01; 
	//给一个初值,定时0.5ms
	TL0 = 0x33; 
	TH0 = 0xFE;
	// 定时器0的溢出标志位,如果TL0和TH0爆表了,硬件置为1。
	TF0 = 0; 
	//开始计时
	TR0 = 1; 
	//打开定时器0的中断
	ET0 = 1;
	//打开中断总开关
	EA = 1;
}
void main()
{
	initTime0(); //初始化定时器

	while(1)
	{
		jd = 4; //高点平的时间是2ms,旋转135度
		cnt = 0;
		sg90_pwm = 1;
		Delay2000ms();
		jd = 1;
		cnt = 0;
		sg90_pwm = 1;
		Delay2000ms();
	}
}
void Time0Fun() interrupt 1 //设置定时器0中断函数,1为定时器0的中断号
{
	cnt++; //每次爆表,就自加
	TL0 = 0x33; //重装一次初值 
	TH0 = 0xFE;
	if(cnt < jd)
	{
		sg90_pwm = 1;
	}
	else
	{
		sg90_pwm = 0;
	}
	if(cnt == 40) //40*0.5 = 20ms
	{
		cnt = 0;
		sg90_pwm = 1; //保证每个周期的最开始都是高电平
	}

}

超声波模块 HC-SR04

时序图

image-20221231194608854

1.首先给trig引脚一个10us的高电平,表示触发信号,此时超声波模块发送波出去
2.当Echo中的信号由低变高,则表示发送波出去
3.当Echo中的信号由高变低,表示波回来了
4.高电平的时间就是声波发送到回来的时间

代码实现

#include <reg52.h>
/*
	距离小于10cm,D5亮,D6灭,反之相反
	Trig 1.5
	Echo 1.6
*/
sbit D5 = P3^7;
sbit D6 = P3^6;
sbit Trig = P1^5;
sbit Echo = P1^6;

double Data;
double dis;

void Delay10us()		//@11.0592MHz
{
	unsigned char i;

	i = 2;
	while (--i);
}
void Delay500ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	i = 4;
	j = 129;
	k = 119;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
void startHC()  //给Trig口一个10us的高电平,表示触发信号
{
	Trig = 0;
	Trig = 1;
	Delay10us();
	Trig = 0;
}
void initTime0()
{
	TMOD &= 0xF0;		//设置定时器模式 0000 0001
	TMOD |= 0x01;		//将定时器0 配置为16位定时模式
	//将TH和TL寄存器都配置成0
	TL0 = 0;      
	TH0 = 0;
	//先不要开始计数,等超声波模块有回响(即 Echo = 1)再开始计数。
}
void main()
{
	initTime0();
	while(1)
	{
		//触发信号
		startHC();
		//等待Echo发送声波,发送声波后Echo的电平由低变为高
		while(Echo == 0); 
		//计时器开始计时
		TR0 = 1;
		//等待Echo收回声波,收回声波后Echo的电平由高变成低
		while(Echo == 1); 
		//停止计数
		TR0 = 0;
		//读取TH0和TL0两个寄存器中 所计的数
		//将TH0的数据前移动8位 + TL0的数据就是总共所用的时间
		//如何将TH0的数据前移动8位?
		//十进制的2向左移动一位是20   即 2*10  (2*10的一次方)
		//十进制的2向左移动两位是200  即 2*100 (2*10的二次方)
		//二进制的1向左移动一位是10   即 1*2		(1*2的一次方)
		//二进制的1向左移动两位是100  即 1*4		(1*2的二次方)	
		//所以TH里面的数据是二进制,向左移动8就是 TH*2的8次方 = TH * 256
		Data = (TH0*256 + TL0)*1.085; //单位是us
		//距离的计算:声速*时间 = 340m/s * Data
		//340m/s = 34000cm/s = 34cm/ms = 0.034cm/us
		dis = 0.017*Data;
		//根据距离做出判断
		if(dis < 10)
		{
			D5 = 0;
			D6 = 1;
		}
		else
		{
			D6 = 0;
			D5 = 1;
		}
		//将TH0和TL0清空,准备下一次计数
		TH0 = 0;
		TL0 = 0;
		//延时一小会
		Delay500ms();
	}
}

外部中断的使用

image-20230101150833954

image-20230101150847562

使用外部中断要先将 IE 寄存器中的 EX0 位(外部中断开关 )置为 1 ,再将中断总开关 EA 置为 1 .

然后在 TCON 寄存器中的第零位配置外部中断触发方式

外部中断 0 使用的引脚为 INT0 P3^2 口

外部中断在项目中的体现

在项目中外部中断用于检测震动传感器的状态
震动传感器的D0口链接外部中断P3^2口
触发模式为低电平触发模式
当外部有震动的时候,震动传感器的D0口输出低电平
当单片机的P3^2口检测到低电平,则进入中断函数

外部中断的配置

void initINT0()
{
	//开启外部中断 EX0
	EX0 = 1;
	//开启中断总开关 EA
	EA = 1;
	//配置TCON寄存器中的 IT0
	IT0 = 0; //表示低电平触发 IT0 = 1为下降沿触发
}

项目代码

#include <reg52.h>
/*
	智能垃圾桶项目实现
	Trig      1.5
	Echo      1.6
	server    1.1
	key1      2.1
	vibration 3.2 
	beep      1.2
*/

sbit Trig      = P1^5;
sbit Echo      = P1^6;
sbit Server    = P1^1; //舵机控制引脚
sbit key1      = P2^1;
sbit vibration = P3^2; //使用外部中断
sbit beep      = P1^2; //开盖滴滴声
int cut = 0;
int jd = 1;
int vibMark;
double Data;
double dis;

void Delay10us()		//@11.0592MHz
{
	unsigned char i;

	i = 2;
	while (--i);
}
void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
void Delay150ms()		//@11.0592MHz
{
	unsigned char i, j, k;
	i = 2;
	j = 13;
	k = 237;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void startHC()  //给Trig口一个10us的高电平,表示触发信号
{
	Trig = 0;
	Trig = 1;
	Delay10us();
	Trig = 0;
}
void initTime0()
{
	//设置定时器模式
	TMOD &= 0xF0;		
	TMOD |= 0x01;		
	//设置定时初值 定时0.5ms
	TL0 = 0x33;		
	TH0 = 0xFE;
	//清除TF0标志	
	TF0 = 0;
	//定时器0开始计时
	TR0 = 1;		
	//开启定时0中断开关
	ET0 = 1;
	//开启中断的总开关
	EA = 1;
}
void initTime1()
{
	TMOD &= 0x0F;		
	//配置定时器1的工作模式
	TMOD |= 0x10;		
	//将TH和TL寄存器都配置成0
	TL1 = 0;      
	TH1 = 0;
	//先不要开始计数,等超声波模块有回响(即 Echo = 1)再开始计数。
}
//超声波测距离函数
void getDis()
{
		//将TH1和TL1清空,准备下一次计数
		TH1 = 0;
		TL1 = 0;
	  //触发信号
		startHC();
		//等待Echo发送声波,发送声波后Echo的电平由低变为高
		while(Echo == 0); 
		//计时器开始计时
		TR1 = 1;
		//等待Echo收回声波,收回声波后Echo的电平由高变成低
		while(Echo == 1); 
		//停止计数
		TR1 = 0;
		//读取TH1和TL1两个寄存器中 所计的数
		//将TH1的数据前移动8位 + TL1的数据就是总共所用的时间
		//如何将TH1的数据前移动8位?
		//十进制的2向左移动一位是20   即 2*10  (2*10的一次方)
		//十进制的2向左移动两位是200  即 2*100 (2*10的二次方)
		//二进制的1向左移动一位是10   即 1*2		(1*2的一次方)
		//二进制的1向左移动两位是100  即 1*4		(1*2的二次方)	
		//所以TH里面的数据是二进制,向左移动8就是 TH*2的8次方 = TH * 256
		Data = (TH1*256 + TL1)*1.085; //单位是us
		//距离的计算:声速*时间 = 340m/s * Data
		//340m/s = 34000cm/s = 34cm/ms = 0.034cm/us
		dis = 0.017*Data;
		//根据距离做出判断
}
//初始化外部中断0
void initINT0()
{
	//开启外部中断 EX0
	EX0 = 1;
	//开启中断总开关 EA
	EA = 1;
	//配置TCON寄存器中的 IT0
	IT0 = 0; //表示低电平触发
}
void BeepDiTwo()
{
	beep = 0;
	Delay150ms();
	beep = 1;
	Delay150ms();
	beep = 0;
	Delay150ms();
	beep = 1;
}
void main()
{
	initTime1();
	initTime0();
	initINT0();
	while(1)
	{
		getDis();
		//判断距离 dis距离小于10 开盖子 或者 按键一被按下
		if(dis < 10 || key1 == 0 || vibMark == 1)
		{
			if(jd != 3) //如果一直开盖,jb则一直等于3 就不需要同步
			{
					cut = 0;
					//开盖滴滴声
					BeepDiTwo();
			}
			//jd = 3 让舵机开盖
			jd = 3;
			//清空震动标志位
			vibMark = 0;		
			//给舵机一个反应的时间
			Delay1000ms();
			Delay1000ms();
		}
		else
		{
			//jd = 1 让舵机关盖
			jd = 1;
			cut = 0;
			//给舵机一个反应的时间
			Delay150ms();
		}
	}
}
//定时器0中断函数 每隔0.5ms进来一次
void Time0Fun() interrupt 1
{
	//记录进来的次数
	cut++;
	//重新给寄存器赋值
	TL0 = 0x33;		
	TH0 = 0xFE;
	if(cut < jd)
	{
		Server = 1;
	}
	else
	{
		Server = 0;
	}
	//cut = 40相当于进来了40次 表示经历了一个周期
	if(cut == 40)
	{
		cut = 0;
		Server = 1;
	}

}
void Int0Fun() interrupt 0
{
	vibMark = 1;
}
posted @ 2023-09-01 14:02  徐博正  阅读(54)  评论(0)    收藏  举报