Harbin Institute of Technology Amateur Radio Club

单片机第8课——定时器中断

<p align="right"> 作者:米米糊</p>

<p align="right"> 归档人:HITLYT</p>

前言

由于一些小失误,51单片机教程的定时器中断部分有两个版本,童鞋们不妨把两个版本都看一看,互相参考进行学习。

上集回顾~~

数码管

如何偷懒(封装一个数码管驱动)

再探C语言

C语言子函数

本集预览~~

中断

晶振为我们的单片机提供了一个固定的时钟信号。时钟信号在单片机内部就是一个个的方波信号。

所谓分频,就是让时钟的频率减慢。

假设单片机输入的时钟是24MHz

答案:2MHz

延时

回忆一下之前的延时

得到的结果需要用9位(bit)二进制数才能保存,但是a这个变量只有8位的存储空间,所以,丢掉最高位,只要低8位。所以,最后得到的结果是0。

unsigned int a;
a=0;
a=a-2;//减完后a的值是?

再来给难点的

unsigned char a;
//注意,这次a是有符号的
//提示:8为有符号数的范围是-128~+127
a=127;
a= a + 1;

做加法之前,a的符号位是0,表明这是一个正数~~

可是,加完之后,由于进位了,a的符号位成了1,竟然变成了负数……

那么这个负数究竟是多少呢?以后会学到,负数是用补码表示的~~用“取反加一”的口诀获得它对应的原码,应该是:

1000 000

对应的十进制数是128,由于这是一个负数,所以最终答案是-128

以上这些折腾人的东西就叫做

溢出

绝对的双刃剑!!

把溢出用好,可以在一定程度上简化程序,并且在硬件中很多功能都是基于溢出来实现的。但是,用不好的话,程序会出大问题,很多软件的安全漏洞(比如windows操作系统的)都是由于溢出引起的……

寄存器

两只水桶

为了揭开定时器的真面目,先认识两个水桶~~

TH0和TL0

TH0和TL0都是8位的寄存器(理解为变量就好),两个8位的定时器共同组成了一个16位的定时器

当TL0计满0xff后,再加1会导致低位溢出,产生的进位将进入到TH0,即TL0每溢出一次,TH0就加 1

当TH0和TL0都是0xff之后再加1,会导致低位的TL0向高位的TH0溢出,但由于高位也已经满了,故高位会再向前溢出,结果,导致TH0和TL0同时回到0x00

当TH0溢出或者TL0溢出的时候,便可以让它们通知单片机产生一个中断。那么,究竟是在TH0溢出时产生中断还是在TL0溢出时产生中断呢?这是可以通过编程来设置的~~

如果我们可以让TH0和TL0以固定的速度加1,那么就可以控制TH0和TL0的溢出速率了哦~~换而言之,就是可以控制多长时间让定时器产生一个中断。

还记得刚才讲的分频吗?

TH0和TL0每过12个时钟周期便加1,就是说,定时器的计数频率是晶振频率的十二分频。

之前计算过,24MHz的频率经过12分频是2MHz,对应的周期是0.5μs

如果想定时10ms,应该怎么办?

我们希望定时器每计数20000次便产生一次溢出

所以,不能从(TH0,TL0)=0x0000开始计时,而要从某一个初值开始计时

故它的高八位应该是0xB1,低八位应该是0xE0

TH0=0xB1;
TL0=0xE0;

其实不用算的,我们有小工具

TMOD寄存器

第一道门:每个中断各自的门

第二道门:所有中断的总控制门

下面继续讨论如何实现一个10ms的定时程序。

另外,传统的51单片机定时器输入时钟是晶振的12分频,但是我们这一单片机款也可以设置成不分频,有兴趣自学~~

现在,让我们通过中断来实现每隔1秒,让不同位数码管亮起的效果。

在这里新添加一个变量t,同时可以删除Delay500ms这个函数,本程序中不使用该函数。

并且删除掉原来while循环中的其他语句,只保留while(1);

(注意while(1);后面有分号)

unsigned int t=0,i=0;
void main() 
{
	timer0_init(); 
	while(1);
}

添加初始化定时器的有关代码,并且删除掉原来while循环中的其他语句,只保留while(1);

void timer0_init()//定时器0的初始化,函数名随便起
{
	EA=1;//开启总中断
	TH0=0xb1;//初值的高位
	TL0=0xe0;//初值的低位
	TMOD=0x01;//16位计时器
	ET0=1;//开启定时器中断
	TR0=1;//启动定时器
}

下面要写中断服务函数啦,定时器0对应的是interrupt1

中断查询号 中断类型
0 外部中断0
1 定时器0
2 外部中断1
3 定时器1
4 串口

中断查询号在这里查找哦~~

添加定时器0的中断服务程序

void T0_Service() interrupt 1
{
	t++;
	if(t==100)
	{
		t=0;
		P0=P0>>1;
		i++;
		if(i==4)
		{
			i=0;
			P0=0x08;
		}
	}
}

注:由于定时器的TH0和TL0计满也不能到1000ms,所以我们另外增加一个计数器t,每隔10ms则t加1,t加满100则认为计满了1000ms。

因此就是这样一个结构

unsigned int t=0,i=0;
void main()
{
	void timer0_init();
	while(1);
}

void T0_Service() interrupt 1
{
}

运行程序,观察效果

在中断服务程序中添加重装语句

重装一般写在中断处理程序的开头。如果把重装写在中断处理函数的结尾,那么要等到整个中断处理函数执行完之后才进行重装,执行中断处理函数的时间将被累加到计时的时间中,造成误差。

16位自动重装的精确性讨论

(以下内容有个了解即可,有兴趣可以深入学习~~)

实际上,从中断发生到开始执行中断处理程序的第一行语句之间发生了很多事情,如果查看C语言编译后生成的汇编代码,你会发现这中间发生了包括保护现场压栈长跳转等等操作,只是这些工作编译器自动帮我们完成了,我们不必去了解它是怎么实现的。

但是所有这些操作都是会占用时间的,也就是说,从定时器溢出开始算,到执行我们的第一行代码【 TH0=0xB1; 】,中间一定会差上几微秒的时间。所以,这种定时方式不适合要求精确定时的场合。

8位自动重装模式

修改程序为8位重装模式

将TMOD修改为0x02,这样就设置了定时器工作在8位自动重装模式下

回忆TMOD寄存器:

由于此时计数最大只能计到255,我们不妨让定时器每计250个数便产生一次溢出,也就是每隔

250×0.5μs=125μs

产生一次溢出。在这个设定下,计满1秒钟需要溢出8000次。

定时器初值应该设置为:

255-250+1=6

在这里修改初值为0x06

由于现在要累计满8000次,故t需要改变为int型。

这里,我们依然可以用工具

在中断处理中,删除有关重装的两行程序,同时修改t的计数值为8000。

下载并执行,观察效果~~

把定时器变成计数器

从前面的介绍可以看出,定时器之所以能够定时,是因为它可以按照固定的速度来计数,所以从本质来说,定时器本身是一个计数器。

那么,如果把TH0和TL0的计时脉冲输入从晶振时钟信号改为某个引脚,是不是就可以对这个引脚上的高低电平变化计数了呢?

没错!就是这样!

这里的T0和T1分别表示这两根引脚可以作为计数器的输入引脚~~他们对应的是开发板上的这两个按键。

计数器的使用和定时器的使用方法完全一样,只是定时器的+1信号来自于晶振时钟,而计数器的+1信号来自于某个引脚的输入。

有用到定时器的话可以继续自学一下,这里就不给出例子了哦~~

自学方法还是一样的~~

datasheet、百度、google、QQ群……

刚才定时器说了那么多……其实可以用一 张图来表示,下面给大家一些图,是从 datasheet里中摘取出来的……大家试试~如果 能看懂这些图的话~~那就太棒了!

课堂练习

运用数据手册第47页,关于中断寄存器的表格,写一个程序,使:

中断服务程序的格式

void 函数名() interrupt 中断查询号
{
	//这里写程序
} 

|中断查询号|中断类型| |:–:|:–:| |0|外部中断0| |1|定时器0| |2|外部中断1| |3|定时器1| |4|串口|

中断查询号在这里查找哦~~

挑战任务

如果你对自己有信心……

如果大家实现数码管的电子表功能,会有奖励的哟~~

小“作业”

——The End


版权声明:

文章均由哈尔滨工业大学业余无线电俱乐部,技术部原创,转载请联系BY2HIT技术部 zhaoyuhao@by2hit.net