定时器/计数器的应用

前言

对近期学习定时器进行简单的记录

参考链接

LED数码管的静态显示与动态显示(Keil+Proteus)-CSDN博客

外中断的应用-CSDN博客

【mcuclub】定时器/计数器_定时器/计数器的内部结构和工作方式-CSDN博客

5.图解定时器/计数器 - 知乎 (zhihu.com)

51单片机——计数器与定时器的区别_定时器和计数器的区别-CSDN博客

什么是方波_方波的主要参数_方波计算 - 与非网 (eefocus.com)

什么是方波、矩形波、修正正弦波、纯正弦波? (baidu.com)

proteus的示波器怎么用-百度经验 (baidu.com)

51单片机 Proteus仿真有源蜂鸣器始终不响原因及解决方法_程序员_IT虾米网 (itxm.cn)

共阳/共阴数码管的段码详解(图文并茂)-物联沃-IOTWORD物联网

proteus设计教程-数码管使用方法_proteus数码管_zd845101500的博客-CSDN博客

字符型液晶显示器LCD 1602的显示控制(Keil+Proteus)-CSDN博客

0x30的全方位解读_笔记大全_设计学院 (python100.com)

定时时间=(65536-X)*12/晶体振荡器频率(定时时间单位um,晶体振荡器频率单位MHz,X为计时器初值。)

定时器/计数器工作方式寄存器TMOD(89H)

D7 D6 D5 D4 D3 D2 D1 D0
GATE C/~T M1 M0 GATE C/~T M1 M0
T1方式单元格 T0方式字段

P1口控制8只LED

实验要求:在AT89C51单片机的P1口上接有8只LED,采用定时器T0的方式1的定时中断方式,是P1口外接的8只LED每0.5s闪亮一次。

Keil

相信点亮LED灯的程序大家都知道了,前面也有提到,这里采用的是共阳极的接法,所以当P1口的值为低电平的时候,LED灯会被点亮。

之前是通过主函数进行切换,现在是在中断里面进行切换,5ms为5000us,根据前面的公式可以知道:5000=(65536-X)*12/11.0592,解得X=60928.(0xee00),所以下面的定时器初值赋值为ee00,因为实验要求是采用定时器T0的方式1的定时中断,故而TMOD的高4位都是0,低4位的M1,M0为的值,分别是0和1,C/!T,计数器模式和定时器模式选择位的值为0,所以表示程序采用的是定时器工作模式,对系统时钟12分频后的内部脉冲进行计数。

#include<reg51.h>
char i=100;
void main(){
	TMOD=0x01;//定时器T0为方式1
	TH0=0xee;//设置定时器初值	
	TL0=0x00;		
	P1=0x00;//P1口8个LED点亮
	EA=1;//总中断允许
	ET0=1;//允许定时器T0中断
	TR0=1;//启动定时器T0
	while(1);//循环等待
}

void timer0()interrupt 1{
	TH0=0xee;//重新赋初值
	TL0=0x00;
	i--;
	if(i<=0){//循环次数减1
		P1=~P1;//P1口按位取反
		i=100;//重置循环次数
	}
}

Proteus

这个原理图前面都有提到了,相信大家已经得心应手了,这里就不再赘述。

 计数器的应用

实验要求:定时器T1采用计数模式,方式1中断,计数输入引脚T1上外接按钮开关,作为计数信号输入。按下4次按钮开关后,P1口的8只LED闪烁不停。

keil

这个要注意的是按下4次开关之后才有现象的变化,以往的中断服务程序都是直接进行响应,如今是第4次才进行响应,应该是采用的是计数模式,而且是方式1中断,所以TMOD的低4位为0,高4位中的M1M0的值为01,模式选择位的值为1,根据4次可以确定计数器的初值为35536-4=65532(0xFFFC),通过这个设置TH1和TH1的初值。

#include<reg51.h>
#define uint unsigned int
	
void Delay(uint i){//延时函数
	uint j;
	for(;i>0;i--)
	for(j=0;j<125;j++);
}

void main(){
	TMOD=0x50;//设置定时器T1为方式1计数
	TH1=0xFF;//向TH1写入初值的高8位
	TL1=0xFC;//向TL1写入初值的低8位
	EA=1;//总中断允许
	ET1=1;//定时器T1中断允许
	TR1=1;//启动定时器T1
	while(1);//循环
}
//T1中断函数
void timer1(void) interrupt 3{
	while(1){
		P1=0xFF;//全灭
		Delay(500);//延时
		P1=0;//全亮
		Delay(500);//延时
	}
}

Proteus

原理图就是在前面的基础上面添加一个按钮用于中断计数,当前面按下3次之后LED灯都不会有闪烁,当第4次按下之后,LED灯的状态发生变化。

 按下4次按钮之后

控制P1.0产生周期为2ms的方波

实验要求:假设系统时钟为12MHz,设计电路并编写程序实现从P1.0引脚上输出一个周期为2ms的方波。

 Keil

由于系统的时钟频率为12MHz,周期为2ms,所以定时器应该产生1ms的定时中断,定时的时间到则在中断服务程序中对P1.0求反即可。通过前面的公式(65536-X)*12/12=1000,可以求出X=64536(0xFC18)

#include<reg51.h>
sbit P1_0=P1^0;//定义特殊功能寄存器P1的位变量P1_0

void main(void){
	TMOD=0x01;//设置T0为方式1
	TR0=1;//接通T0
	while(1){
		TH0=0xFC;//置T0高8位初值
		TL0=0x18;//置T0低8位初值
		while(!TF0);//判断TF0是否为1,为1表示T0溢出,往下面执行,负责原地循环
		P1_0=!P1_0;//P1的0号口状态求反
		TF0=0;//TF0标志清零
	}
}

Proteus

这里就需要添加一个之前没有出现的期间,其他的连接就很简单了,这里接的是A通道。

运行结果

利用T1控制发出1kHz的音频信号

实验要求:利用定时器T1的中断控制P1.7引脚输出频率为1kHz的方波音频信号,驱动蜂鸣器发声。系统时钟为12MHz。方波音频信号的周期为1ms,因此T1的定时中断时间为0.5ms,进入中断服务程序后,对P1.7取反。

Keil

由公式可知,65536-X=500,X=65036(0xFE0C)所以TH1的初值就为(65536-X)/256,TH2的初值就为(65536-X)%256。

#include<reg51.h>
sbit sound=P1^7;//将sound定义为P1.7引脚
#define F1(a) (63336-a)/256//定义装入定时器高8位的时间常数
#define F2(a) (63336-a)%256//定时装入定时器低8位的时间常数
#define uint unsigned int
uint i=500;
uint j=0;

void main(void){
	EA=1;//开总中断
	ET1=1;//允许定时器T1中断
	TMOD=0x10;//使用T1的方式1计时
	TH1=F1(i);//给定时器T1的高8位赋初值
	TL1=F2(i);//给定时器T1的低8位赋初值
	TR1=1;//启动定时器T1
	while(1){
		i=500;
		while(j<2000);
		j=0;
	}
}

void Timer1(void) interrupt 3{
	TR1=0;//关闭定时器T1
	sound=~sound;//P1.7输出求反
	TH1=F1(i);//定时器T1的高8位重新赋初值
	TL1=F2(i);//定时器T1的低8位重新赋初值
	j++;
	TR1=1;//启动定时器T1
}

Proteus

这里又添加了一个新的器件,蜂鸣器SPEAKER

运行结果:示波器有现象且蜂鸣器会有声音发出,有点像电流声音又有点像蜜蜂扇动翅膀的声音。

LED数码管秒表的制作

实验要求:制作一个LED数码管显示的秒表,用2位数码管显示计时时间,最小计时单位为“百毫秒”,计时范围为0.1~9.9s。当第1次按一下计时功能键时,秒表开始计时并显示;第2次按一下计时功能键时,停止计时,将计时的时间值送到数码管显示;如果计时到9.9s,将重新开始从0计时;第3次按一下计时功能键,秒表清零。再次按一下计时功能键,则重复上述计时过程。

Keil

要制作秒表的话,首先要考虑的就是时间的准确性,从前面的实验我们可以知道定时时间为5ms的情况,而本次实验计时的最小单位是0.1s,也就是20个5ms,这就可以保证时间的准确,此时的晶体振荡器的频率为11.0592MHz,然后需要解决的是按钮功能的实现,可以用switch也可以用if来判断,我这里采用的是switch。

当按钮第一次按下,就会根据定时器的中断(每5ms一次)开始每20次一次更新数码管上面的数值,当到达9.9的时候会停止计时,同时让他再按下一次按钮就清零,因为此时不需要暂停了,根据实验的要求也是计时到9.9s,将重新开始从0计时。

当按钮第二次按下,因为数码管状态更新的地方是在中断服务函数里面,所以只需要将定时器关闭,就可以让数码管达到暂停的状态。

当按钮第三次按下,就将记录的次数清零,同时让数码管显示0,0即可。

这个要注意的是,为了防止程序的延时以及用户一直按下导致按键次数一直再增加的情况,这里是用一个循环来解决的,如果按钮一直按下,程序就不会进行到下一步的操作。

按钮是否按下,这里采用的是查询的方式来判断。

#include<reg51.h>
#define uchar unsigned char
//数码管显示0~9的段码表,带小数点(共阴极)
uchar code discode1[]={0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF};
//数码管显示0~9的段码管,不带小数点(共阴极)
uchar code discode2[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};

uchar timer=0;//记录中断次数
uchar second;//储存秒
uchar key=0;//记录按键次数

void main(void){
	TMOD=0x01;//定时器T0方式1定时模式
	ET0=1;//允许定时器T0中断
	EA=1;//总中断允许
	second=0;//设置秒的初值
	P0=discode1[second/10];//显示秒位,初始0
	P2=discode2[second%10];//显示0.1s秒位,初始0
	while(1){
		if((P1&0x80)==0x00){
			key++;
			switch(key){
				case 1://第一次按下为启动秒表计时
					TH0=0xee;//向TH0写入初值的高8位
					TL0=0x00;//向TL0写入初值的低8位,定时5ms
					TR0=1;//启动定时器T0
					break;
				case 2://第二次按下为停止计时
					TR0=0;//关闭计时器T0
					break;
				case 3://第三次按下为秒表清零
					key=0;//按键次数清零
					second=0;//秒表清零
					P0=discode1[second/10];//显示秒位
					P2=discode2[second%10];//显示0.1秒位
					break;
			}
			while((P1&0x80)==0x00);//如果按键时间过长就在这里循环(就是避免一直按下导致重复记录按键次数)
		}
	}
}
//定时器T0中断函数
void timer0(void) interrupt 1{
	TR0=0;//停止计时,避免带来误差
	TH0=0xee;//设置定时器的初值,让中断时间为5ms
	TL0=0x00;
	timer++;
	if(timer==20){//中断20次,就是20*5ms=0.1s,让程序0.1秒,数码管更新一次
		timer=0;//中断清零(为下一个0.1秒重新累计)
		second++;//加0.1s
		P0=discode1[second/10];//根据计时时间,即时显示秒位
		P2=discode2[second%10];//根据计时时间,计时显示0.1s为
	}
	if(second==99){//当计时到9.9s时
		TR0=0;//停止计时
		second=0;//秒数清零
		key=2;//按键数置2,当再次按下按键时,key++,即key=3,秒表清零复原(计时到9.9的时候数码管就一直保持9.9显示,需要再次按下按钮才行)
	}else{//计时不到9.9s时
		TR0=1;//启动定时器继续计时
	}
}

Proteus 

这里数码管采用的是7SEG-MPX1-CC,采用的是共阴极接法。

因为P0口没有上拉电阻,所以需要外接电阻,我这里是接了一个排阻。

初始值

 计时达到9.9时

LCD时钟的设计

实验要求:使用定时器/计时器来实现一个LCD显示的时钟。液晶显示器采用LCD 1602。

Keil

这个题目主要还是程序的编写,由前面我们已经知道让程序0.1s更新一次显示的方法,现在我们是1s显示一次,中断时间还是保持不变。

在显示方面其实其他的地方都是固定的显示,需要变换的就是时间的输出,所以每次就只需要更改固定位置上面的数值即可。因为一行是16个字符,这其实就可以确定输出时分秒对应的地址。(第二行的首地址是0xC0,所以可以得到他们需要输出的地址)

0 1 2 3 4 5 6 7 8 9 A B C D E F
T I M E h h : m m : s s

因为计算是数值,但是输出是字符,所以输出的时候需要加上0x30(十进制数48,字符'0')

#include<reg51.h>
#define uchar unsigned char
#define uint unsigned int
sbit RS=P2^0;
sbit RW=P2^1;
sbit E=P2^2;

uchar int_time;//定义中断次数变量
uchar second;//秒计数变量
uchar minute;//分钟计数变量
uchar hour;//小时计数变量
uchar code date[]="	 H.I.T. CHINA ";//LCD 第1行显示的内容
uchar code time[]=" TIME  23:59:55 ";//第2行显示的内容
uchar second=55,minute=59,hour=23;//秒,分钟,小时
void delay(uint i);//延时函数
void write_com(uchar com);//写入命令数据到LCD
void write_data(uchar date);//写入字符显示数据到LCD
void init1602();//LCD1602初始化设定
void write_sfm(uchar add,uchar date);//向指定地址写入数据
void clock_init();//对时钟进行初始化
void clock_write(uint s,uint m,uint h);//写数据

void main(){
	int_time=0;//中断次数、秒、分钟、小时变量进行清零
	second=55;
	minute=59;
	hour=23;
	init1602();//lcd的初始化
	clock_init();//时钟的初始化
	TMOD=0x01;//设置定时器T0为方式1定时
	EA=1;//总中断开
	ET0=1;//允许T0中断
	TH0=0xEE;//对T0进行初始化
	TH1=0x00;
	TR0=1;
	while(1){
		clock_write(second,minute,hour);
	}
}

void delay(uint j){//延时函数
	uchar i=110;
	for(;j>0;j--){
		while(i--);
		i=110;
	}
}


void write_com(uchar com){//写入命令数据到LCD
	RW=0;
	RS=0;
	P0=com;
	delay(5);
	E=1;
	delay(5);
	E=0;
}

void write_data(uchar date){//写入字符显示数据到LCD
	RW=0;
	RS=1;
	P0=date;
	delay(5);
	E=1;
	delay(5);
	E=0;
}

void init1602(){//LCD1602初始化设定
	RW=0;
	E=0;
	write_com(0x3C);
	write_com(0x0C);//开整体显示,光标关,无闪烁
	write_com(0x06);//光标右移,写入一个字符后地址指针加1
	write_com(0x01);//清屏
	write_com(0x80);//字符输入地址,字符的第一位
}


void write_sfm(uchar add,uchar date){//向指定地址写入数据
	uchar shi,ge;
	shi=date/10;//十位
	ge=date%10;//个位
	write_com(add);
	write_data(0x30+shi);//0x30表示48,让数字变成字符
	write_data(0x30+ge);
}

void clock_init(){//对时钟进行初始化
	uchar i,j;
	//写第一行
	write_com(0x80);
	for(i=0;i<16;i++){
		write_data(date[i]);
	}
	//写第二行
	write_com(0xC0);
	for(j=0;j<16;j++){
		write_data(time[j]);
	}
}

void clock_write(uint s,uint m,uint h){//向指定位置写数据
	//写小时0111
	write_sfm(0xC7,h);
	//写分钟1010
	write_sfm(0xCa,m);
	//写秒1101
	write_sfm(0xCd,s);
}
//中断服务函数
void timer0(void) interrupt 1{
	TR0=0;//停止计时,避免给计时造成误差(需要有这个,不然就一直在中断,导致时间不准确)
	TH0=0xEE;//对T0重新赋初值
	TH1=0x00;
	int_time++;//记录中断次数
	if(int_time==200){//中断次数满200次5ms*200=1s
		int_time=0;//中断次数变量清零
		second++;//加1秒
	}
	if(second==60){//60秒为一分钟
		second=0;
		minute++;
	}
	if(minute==60){//60分钟为一小时
		minute=0;
		hour++;
	}
	if(hour==24){//一天是24小时
		hour=0;
	}
	TR0=1;
}

Proteus

没有什么特殊的元器件,都是之前有出现过的。

 运行结果

 总结

写的还有有点崩溃的,最后时钟的设计他最后就是不是到00:00:00,个位上面出现3和9,这个我感觉是因为初始显示的时候是23,59这种个位上面是3,9,然后卡了还是什么的,因为他不对之后又会回到正常的计时时间。而且错误的时候一直会提示一个警告“controller received data whilst busy”,我以为是正常的,结果正确运行发现并没有这个错误。

总之还不知道原因,但是已经避免了他的发生,也算是不幸中的万幸了,也没有完全按照书上面的来操作,毕竟书上面是一个错误的,弄的我改了一下午,索性是弄出来结果了。

猜你喜欢

转载自blog.csdn.net/weixin_64066303/article/details/134601236