定时与中断
今天查有关51单片机的定时与中断部分资料,本来并不复杂甚至可以说是挺容易的东西,整整查了一个下午才算弄懂。一份好的学习材料实在是难找,大部分资料要么不说人话要么太过于杂乱没有重点,更别说有些资料还有误导性。
我把我学到的知识概括如下,先给出最直接的编程方法,再说明相关原理。希望可以写得足够易懂,如果能够帮助到别人我就更开心了。
定时器的初始化方法
极简版:
-
给TMOD赋初值,一般直接赋值 T M O D = 0 x 01 TMOD=0x01 TMOD=0x01,表示使用十六位(可计数至 2 16 = 65536 2^{16}= 65536 216=65536)的0号定时器。
-
设置计数初值。可以直接赋
TH0 = (65536 - 45872) / 256;
TL0 = (65536 - 45872) % 256;
这样设置,表示经过50ms计数完成。
- 开放单片机的中断使能,既:
EA = 1; // 开总中断
ET0 = 1; // 开定时器0中断
- 开启计数
TR0 = 1;
以上便完成了对0号计数器的初始化工作,如果要使用1号计数器,把上述的TH0、TL0、ET0、TR0改成TH1、TL1、ET1、TR1即可
示例代码如下:
#include<reg51.h>
#include<intrins.h>
#define uchar unsigned char
#define uint unsigned int
sbit led1 = P0^0;
uchar num = 0;
//主程序
void main()
{
TMOD = 0x01;
TH0 = (65536 - 45872) / 256;
TL0 = (65536 - 45872) % 256;
EA = 1;
ET0 = 1;
TR0 = 1;
while(1)
{
if (num == 20) //50ms * 20 = 1s
{
num = 0;
led1 = ~led1;
}
}
}
void T0_time() interrupt 1
{
TH0 = (65536 - 45872) / 256;
TL0 = (65536 - 45872) % 256;
num++;
}
接下来说明有关原理
打开<reg51.h>,可以看到51单片机中的寄存器分为两类,一类是位寄存器(BIT Register),在程序中用sbit
(special bit)修饰;一类是字节寄存器(Byte Register),用sfr
修饰(special function register)–之所以说它特殊,是因为这些sfr
有特殊的名字,并且有特殊的功能,在我们所需要用到的寄存器中:
TMOD、TCON、TH0/TH1、TL0/TL1都是sfr,因此都有八位
- TMOD(Timer Mode)指定了两个计时器的工作模式,主要指定了计时器的工作位数
- TCON(Timer Control)控制着计时器的工作,主要需要利用其中的位
TR0
(Timer Run)与TR1
来控制计时器的开始工作。 TH0
(Timer High)与TL0
(Timer Low),这两个sfr合起来一共16位,构成timer的计数初值,每过一个时钟周期,这个合起来的16位数就+1,直到 2 16 = 65536 2^{16}=65536 216=65536溢出。此时由于产生了溢出,因此MCU会启动中断程序(interrupt 1),而这个中断程序的内容可由我们随便写。
以下两个都是sfr IE中的sbit
- EA(enable all)= 1,ET(enable timer)= 1,看英文含义知道,这两个当然应该置1。
有关TMOD、TCON、IE中各位的含义,TH0、TL1的设置,资料非常多可随时查阅,比如这一篇参考连接总结得非常好。
其他总结
- 在单片机中,如果“使能”一词未加修饰,一般都指的是中断使能,比如EA(enable all)
可以看这个引脚缩写说明 - 仔细观察<reg51.h>,可以发现sfr与sbit的地址范围有重叠之处,但这不会引起寻址冲突,因为
位寻址
与字节寻址
是不同的,对应到汇编语言中,则是位操作
与字节操作
不同 - 在中断函数中一般都需要重新初始化TH与TL。
计数器与多线程
在主函数中,常可以在结束前写一个while(1)使得程序在结束前死循环,但由于计时器仍然一直在工作,因此中断程序在周期性地每隔一个定时间隔(一般都在1s以内)执行一次。而由于中断程序一直在循环执行,因此好像多了一个线程一样,和多线程程序有异曲同工之妙
例如在这个例子中,由于计数器的使用,仿佛有一个监听器
一直在监听我是否在改变滑动变阻器的电阻大小一样,达到了多线程程序的效果
基于同样的原理,可以用多个计数器,来达到更多个线程的效果