51单片机的定时器中断(数码管读秒+LED闪烁)

51单片机两种不同的定时器中断程序实例

关于51单片机的定时器系统:

定时器原理图(其实没必要搞懂)

1:51单片机有两组定时器/计数器,因为既可以定时,又可以计数,故称之为定时器/计数器。
2:定时器/计数器和单片机的CPU是相互独立的。定时器/计数器工作的过程是自动完成的,不需要CPU的参与。
3:51单片机中的定时器/计数器是根据机器内部的时钟或者是外部的脉冲信号对寄存器中的数据加1。
4:有了定时器/计数器之后,可以增加单片机的效率,一些简单的重复加1的工作可以交给定时器/计数器处理。CPU转而处理一些复杂的事情。同时可以实现精确定时作用。
5:定时器各工作方式如下表
定时器工作方式
打开单片机的定时器中断过程十分繁复,在一篇文章中难以完全讲解,故本文只简单的介绍必要步骤,对其中的个中原理略过。

定时器方式一

实现代码
#include <reg52.h>
typedef unsigned int u16;
typedef unsigned char u8;
u16 interrupt_cnt,clock;
u8 i;
u8 str[8]={0,0,0,0,0,0,0,0};
u8 segSel[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};				//数码管段选数据
sbit LSA=P2^2;			//定义38译码器位选口
sbit LSB=P2^3;
sbit LSC=P2^4;
sbit LED0=P2^0;			//定义LED灯
sbit LED1=P2^1;
	
void bitSel(u8 x)	 	//段选函数				
{
	switch(x)
	{
		case(0):
			LSA=1;LSB=1;LSC=1; break;
		case(1):
			LSA=0;LSB=1;LSC=1; break;
		case(2):
			LSA=1;LSB=0;LSC=1; break;
		case(3):	
			LSA=0;LSB=0;LSC=1; break;
		case(4):
			LSA=1;LSB=1;LSC=0; break;
		case(5):
			LSA=0;LSB=1;LSC=0; break;
		case(6):
			LSA=1;LSB=0;LSC=0; break;
		case(7):
			LSA=0;LSB=0;LSC=0; break;	
	}
}
void delay(int t)		//简易延时函数
{
	while(t) t--;			 
}
void display(u16 dig)	//数码管动态扫描函数
{
	u16 tmp=dig;
	i=7;
	while(tmp>0)
	{
		str[i]=tmp%10;
		tmp/=10;
		i--;
	}	
	for(i=7;i>=1;i--)
	{
		bitSel(i);
		P0=segSel[str[i]];
		delay(100);
		P0=0x00;
	}
}
void interrupt_T0()		//中断起始函数
{
	EA=1;
	ET0=1;
	TR0=1;
	TMOD|=0x01;
}
void timer_interrupt() interrupt 1	//定时器中断函数
{
	TH0=0xFC;
	TL0=0x18;
	interrupt_cnt++;
	if(interrupt_cnt==1000)
	{
		clock++;
		interrupt_cnt=0;
		TH0=0xFC;
		TL0=0x18;
		LED1=~LED1;
	}	
}
	

int main()
{
	interrupt_T0();
	TH0=0xD8;
	TL0=0xF0;
	while(1)
		display(clock);	
}
如何打开定时器方式一中断(interrupt_t0函数)

1:打开总中断(EA=1)
2:打开定时器中断(ET0=1)
3:启动定时器1(TR0=1)
4:设置定时器工作方式为方式1(TMOD=0x01)(M1=0,M0=1)

该工作方式原理

定时器中断有一个很重要的概念:溢出。最开始我也搞不懂,郭天祥书上也讲的不明不白。有一个概念叫“T0溢出率”,闻所未闻,他也不说明。。。最后查各种资料才发现,T0溢出率就是一秒内T0溢出的次数(T0由TH0和TL0组成,后文详讲),初值越大溢出率越高,溢出一次时间越短。通过设定T0溢出率,我们就可以精确的表示1秒的长短(就是T0溢出时间的叠加)

下面我们来看看TH1和TL1究竟应该怎么操作
定时器工作方式一是怎样运行的呢?

我们来看看课本上的定义:

定时/计数器实质上是一个加1计数器。它随着计数器的输入脉冲进行自加1,也就是每来一个脉冲,计数器就自动加1,当加到计数器为全1时,再输入一个脉冲就使计数器回零,且计数器的溢出使相应的中断标志位置1,向CPU发出中断请求(定时/计数器中断允许时)。如果定时/计数器工作于定时模式,则表示定时时间已到;如果工作于计数模式,则表示计数值已满。

上面提到了计数器回零,再前面我们提到了“溢出”这个概念,想必大家已经猜到了,计数器回零就是溢出。

那么TH0,TL0又是什么东西呢?
TH0,TL0可以被看作是T0计数器的高八位和低八位,我们在写代码的时候对其分别赋值,但在程序运行的时候他们是被当成一个数对待的。

如果我们要实现一秒的计时该怎么办?
当我们使用12MHZ的晶振时,单片机的机器周期是1us,对于机器周期,我们把它看成单片机里的最小时间单位。1ms/1us=1000,也就是要计数1000个数,故T0初值=65535-1000+1(因为计数器在65536时才算溢出)=64536,转换成16进制就是0xFC18,高八位0xFC,低八位0x18分别赋值。然后,1s=1000ms,只要加一个程序内部的自增变量(interrupt_cnt),每溢出一次自加1,当该变量加到了1000时,就代表经过了一秒钟了。最后我们让clock变量自加一,用数码管显示来表示时间的流逝。至于数码管的操作,改天有空再开一篇新的博文来写。

定时器方式二

实现代码
#include <reg52.h>
typedef unsigned int u16;
typedef unsigned char u8;
u16 interrupt_cnt,clock;
u16 i,a;
u8 str[8]={0,0,0,0,0,0,0,0};
u8 segSel[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
sbit LED0=P2^0;
sbit LED1=P2^1;
sbit KEY3=P3^2;
	
void bitSel(u8 x)	 							   //位选
{
	switch(x)
	{
		case(0):
			LSA=1;LSB=1;LSC=1; break;
		case(1):
			LSA=0;LSB=1;LSC=1; break;
		case(2):
			LSA=1;LSB=0;LSC=1; break;
		case(3):	
			LSA=0;LSB=0;LSC=1; break;
		case(4):
			LSA=1;LSB=1;LSC=0; break;
		case(5):
			LSA=0;LSB=1;LSC=0; break;
		case(6):
			LSA=1;LSB=0;LSC=0; break;
		case(7):
			LSA=0;LSB=0;LSC=0; break;	
	}
}
void delay(int t)								  //延时函数
{
	while(t) t--;			 
}
void display(u16 dig)							  //动态数码管显示函数
{
	u16 tmp=dig;
	i=7;
	while(tmp>0)
	{
		str[i]=tmp%10;
		tmp/=10;
		i--;
	}	
	for(i=7;i>=1;i--)
	{
		bitSel(i);
		P0=segSel[str[i]];
		delay(100);
		P0=0x00;
	}
}
void timer0_init()								  //定时器0初始化
{
	EA=1;
	ET0=1;
	TR0=1;
	TH0=6;
	TL0=6;
	TMOD=0x02;
}
void timer0_interrupt()	interrupt 1				  //设置定时器中断程序
{
	if(a==4000) 
	{
		a=0;
		LED0=~LED0;
		clock++;
	}
	else a++;
}
int main()
{
	timer0_init();
	while(1)
	{
		display(clock);								//在大循环中运行显示函数部分
	}
	return 0;
}
如何打开定时器方式二中断

与前文方式一步骤基本相同,唯一不同的是把TMOD=0x01改成了TMOD=0x02(0001 0010)。

该工作方式原理

前文已解释了定时器中断的基本原理,故不赘述。方式二与方式一最大的区别就是T0装载的初始值是一样的,而且该定时器单次只使用高八位。当高八位溢出后 ,低八位自动把其中的值装载入高八位,故不需要像方式一一样在代码里写装载定时器的函数。我们知道,八位数据最大数据范围是0~256,故它在257时便会溢出申请中断并自动重新装载。我们在程序里面唯一需要做的就是使clock变量自增一。
大家可能对初始TH0,TL0装载的值为6这一点有疑问。为什么呢?我们已经知道一个机器周期是1us,该方式下定时器在257时便会自动重新装载,所以只要我们把初始值设定为6:

256-6=250us,250*4000=10^6us=1s

故当interrupt_cnt=4000时,我们的clock变量便可以自加一,表示一秒已经过去了!

小tip:

使用定时器,该提前做哪些工作:

1.对TMOD赋值,以确定T0和T1的工作方式。
2.计算初值,并将其写入TH0、TL0或TH1、TL1。
3.中断方式时,则对EA赋值,开放定时器中断。
4.使TR0或TR1置位,启动定时/计数器定时或计数。

  • 19
    点赞
  • 95
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
根据提供的引用内容,我们可以使用定时器来实现51单片机数码管秒表的功能。具体步骤如下: 1. 首先需要设置定时器的工作模式和计数值,以控制秒表的计时精度和时间范围。可以使用定时器的模式1或模式3,计数值为65536-定时器初值。 2. 在定时器中断服务函数中,每次计时完成后,将计时器清零,并将计时器的值转换为数码管可以显示的格式,例如BCD码或十进制数。 3. 将转换后的计时器值通过数码管进行显示。 4. 可以通过按键来控制秒表的启动、停止、复位等功能。 下面是一个简单的51单片机数码管秒表的代码示例,其中使用定时器0的模式1进行计时,计时精度为1ms,使用4位共阳数码管进行显示: ```c #include <reg52.h> #define uint unsigned int #define uchar unsigned char sbit D1 = P2^0; // 数码管位选引脚 sbit D2 = P2^1; sbit D3 = P2^2; sbit D4 = P2^3; sbit S1 = P3^0; // 按键引脚 sbit S2 = P3^1; sbit S3 = P3^2; uint cnt = 0; // 计时器计数值 uchar flag = 0; // 秒表状态标志,0表示停止,1表示计时中 void init_timer0() // 初始化定时器0 { TMOD &= 0xF0; // 设置定时器0为模式1 TH0 = 0xFC; // 设置定时器初值,计数器从65536-TH0开始计数,1ms中断一次 TL0 = 0x67; ET0 = 1; // 允许定时器0中断 EA = 1; // 允许总中断 TR0 = 1; // 启动定时器0 } void display(uint num) // 数码管显示函数 { uchar code table[] = { // 数码管段码表 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F }; uchar digit[4]; // 存储转换后的数码管显示值 digit[0] = num % 10; digit[1] = num / 10 % 10; digit[2] = num / 100 % 10; digit[3] = num / 1000 % 10; D1 = 1; P0 = table[digit[0]]; D1 = 0; // 显示个位 D2 = 1; P0 = table[digit[1]]; D2 = 0; // 显示十位 D3 = 1; P0 = table[digit[2]]; D3 = 0; // 显示百位 D4 = 1; P0 = table[digit[3]]; D4 = 0; // 显示千位 } void main() { init_timer0(); // 初始化定时器0 while (1) { if (!S1) { // 按下S1启动/停止秒表 flag = !flag; while (!S1); // 等待按键释放 } if (!S2) { // 按下S2复位秒表 cnt = 0; flag = 0; display(cnt); while (!S2); // 等待按键释放 } if (flag) { // 秒表计时中 display(cnt); } } } void timer0_isr() interrupt 1 // 定时器0中断服务函数 { TH0 = 0xFC; // 重新设置定时器初值 TL0 = 0x67; cnt++; // 计数器加1 } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值