定时与中断

今天查有关51单片机的定时与中断部分资料,本来并不复杂甚至可以说是挺容易的东西,整整查了一个下午才算弄懂。一份好的学习材料实在是难找,大部分资料要么不说人话要么太过于杂乱没有重点,更别说有些资料还有误导性。

我把我学到的知识概括如下,先给出最直接的编程方法,再说明相关原理。希望可以写得足够易懂,如果能够帮助到别人我就更开心了。

定时器的初始化方法

极简版:

  1. 给TMOD赋初值,一般直接赋值 T M O D = 0 x 01 TMOD=0x01 TMOD=0x01,表示使用十六位(可计数至 2 16 = 65536 2^{16}= 65536 216=65536)的0号定时器。

  2. 设置计数初值。可以直接赋

TH0 = (65536 - 45872) / 256;   
TL0 = (65536 - 45872) % 256;

这样设置,表示经过50ms计数完成。

  1. 开放单片机的中断使能,既:
EA = 1;                  // 开总中断
ET0 = 1;                 // 开定时器0中断
  1. 开启计数
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以内)执行一次。而由于中断程序一直在循环执行,因此好像多了一个线程一样,和多线程程序有异曲同工之妙
例如在这个例子中,由于计数器的使用,仿佛有一个监听器一直在监听我是否在改变滑动变阻器的电阻大小一样,达到了多线程程序的效果
基于同样的原理,可以用多个计数器,来达到更多个线程的效果

以下是几份很好的参考资料

引脚缩写说明
TMOD、TCON、IE、计时器初始化方法
定时器及其应用