从单片机基础到程序框架(全集 2019pdf版).pdf - 第364页
【85.5 定时器 0 的“自身原配开关”TR0。 】 TR0: 定时 器的“ 自身 原配开 关” 。宽度 是 1 位的位 变量。 很多 初学 者会把 EA,ET0 ,TR 0 三者搞 不清 。 定时器可以 工作在 “查询 标志位” 和“中断” 这两种状态,也就是说在没有 中断的情况下 定时器也 可以单独 使用的。TR 0 是定时器 0 自身的发动 引擎,要不 要把这 个发动引擎 所产生的能 量传输到中断 的渠道,则 取决 于中 断开关…

【85.3 决定时间长度的寄存器 TH0 与 TL0。】
TH0 与 TL0,T 代表定时器英文单词 TIME 的 T,H 代表高位,L 代表低位,0 代表定时器 0。
TH0 是一个 8 位宽度的寄存器,TL0 也是一个 8 位宽度的寄存器,两者合并起来成为一个整体,实际上
就是一个 16 位宽度的寄存器,TH0 是高 8 位,TL0 是低 8 位,它们合并后的数值范围是:0 到 65535。该 16
位寄存器取值越大,定时中断一次的时间反倒越小,为什么?TH0 与 TL0 的初始值,就像一个水桶里装的水。
如果这个桶是空桶(取值为 0),“雨水”想把这个桶“滴满溢出”所需要的时间就很大。如果里面已经装了
大半的水(取值为大于 32767),“雨水”想把这个桶“滴满溢出”所需要的时间就很小。这里的关键词“滴
满溢出”的“滴”与“满溢出”,“滴”的速度是由单片机晶振决定的,而“满溢出”一次就代表产生一次中
断,执行完中断函数在即将返回主函数之前,我们重新装入特定容量的水(重装初值),为下一次的“滴满
溢出”做准备,依次循环,从而连续不断地产生间歇的定时中断。
配置中断时间的大小是需要经验的,因为,每次定时中断的时间太长,就意味着时间的可分度太粗,而
如果每次定时中断的时间太短,则会产生很频繁的中断,势必会影响主函数 main()的执行效率,而且累记中
断次数的时间误差也会增大。因此,配置中断时间是需要经验的,根据经验,定时中断取 1ms 一次,是几乎
所有单片机项目的最佳选择,按我的理解,“1ms 定时中断一次”已经是单片机界公认的一种“标配”。
要配置 1ms 定时中断,TH0 与 TL0 如何取值?刚才提到一个形象的例子“桶,滴,满溢出”。TH0 与 TL0
的最大取值范围是 65535,可以理解成为最大 65535“滴”,如果超过 65535“滴”(比如加 1“滴”后变成 65536
“滴”)就会“满溢出”,从而产生一次中断(65536 是中断发生的临界值)。而“滴一次的时间”就刚好是单
片机执行“一次单指令的时间”,“一次单指令的时间”等于 12 个晶振周期,比如 12MHz 的晶振,晶振周期
是(1/12000000)秒,而“一次单指令的时间”就等于 12 乘以(1/12000000)秒,等于 0.000001 秒,也就
是 1us。1us“滴”一次,要产生 1ms 的时间就需要“滴”1000 次。“满溢出”的前提条件是“桶里”一共需
要装入 65536 滴才溢出,因此,在 12MHz 的晶振下要产生 1ms 的定时中断,TH0 与 TL0 的初值应该是 64536
(65536 减去 1000 等于 64536),而 64536 变成十六进制 0xfc17,再分解到高 8 位 TH0 为 0xfc,低 8 位 TL0
为 0x17。
刚才的例子是假如晶振在 12MHz 的情况下所计算出来的结果,而本教程所用的晶振是 11.0592MHz,根据
11.0592MHz 产生 1ms 的定时中断,TH0 与 TL0 应该取值多少?根据刚才的计算方式:
初值=[溢出值]-([0.001 秒]/([晶振周期的 12 个]*([1 秒]/[晶振频率])))
初值=65536-(0.001/(12*(1/11059200)))
初值=65536-922 (注:922 是 921.6 的四舍五入)
初值=64614
初值=0xfc66
初值 TH0=0xfc
初值 TL0=0x66
【85.4 中断的总开关 EA 与局部开关 ET0。】
EA:中断的总开关。宽度是 1 位的位变量。此开关如果取 0,就会强行屏蔽所有的中断,因此,只要用
到中断,此开关必须取 1。
ET0:专门针对定时器 0 中断的局部开关。宽度是 1 位的位变量。此开关如果取 0,则会屏蔽定时器 0
的中断,如果取 1 则允许定时器 0 中断。如果要定时器 0 能产生中断,那么总开关 EA 与 ET0 必须同时都打
开(都取 1),两者缺一不可。

【85.5 定时器 0 的“自身原配开关”TR0。】
TR0:定时器的“自身原配开关”。宽度是 1 位的位变量。很多初学者会把 EA,ET0,TR0 三者搞不清。
定时器可以工作在“查询标志位”和“中断”这两种状态,也就是说在没有中断的情况下定时器也可以单独
使用的。TR0 是定时器 0 自身的发动引擎,要不要把这个发动引擎所产生的能量传输到中断的渠道,则取决
于中断开关 EA 和 ET0。TR0 是源头开关,EA 是中断总渠道开关,ET0 是中断分支渠道的定时器 0 开关。TR0
取 1 表示启动定时器 0,取 0 表示关闭定时器 0。
【85.6 定时器 0 的中断函数的书写格式。】
void 函数名() interrupt 1
{
...中断程序内容;
...此处省去若干代码
...中断程序内容;
...最后面的代码,要记得重装 TH0 与 TL0 的初值;
}
函数名可以随便取,只要不是编译器已经征用的关键字。这里的 1 是定时器 0 的中断号。不同的中断号
代表不同类型的中断,至于哪类中断对应哪个中断号,大家可以查找相关书籍和资料。本节用的定时器 0 处
于工作方式 1 的情况下,在即将退出中断之前,需要重装 TH0 与 TL0 的初始值。
【85.7 寄存器的名字来源。】
前面讲的寄存器都有固定的名字,而且这些名字都是唯一的,拼写的时候少一个字母或者多一个字母,
C 编译器都会报错不让你通过,因此问题来了,初学者刚接触一款单片机的时候,如何知道某个寄存器它特
定的唯一的名字?有两个来源。
第一个来源,可以打开 C 编译器的某个头文件(.h 格式)查看这些寄存器的名字。比如 51 单片机可以
查看 REG52.H 这个头文件。如何打开 REG52.H 这个文件?在 keil 源代码编辑器界面下,选中上面 REG52.H
这几个字符,在右键弹出的菜单下点击 Open ducument“REG52.H”即可。
第二个来源是直接参考一些现成的范例程序,这些范例程序网上很多,有的是原厂提供的,有的是热心
网友的分享,有的是技术书籍或者学习板开发板厂家提供的。
【85.8 如何快速配置寄存器。】
建议一边阅读芯片的数据手册,一边参考一些现成的范例程序,这些范例程序网上很多,有的是原厂提
供的,有的是热心网友的分享,有的是技术书籍或者学习板开发板厂家提供的。
【85.9 练习例程。】
现在编写一个定时中断程序,让两个 LED 灯闪烁,一个是在主函数里用累计主循环次数的方式实现(P0.0
控制),另一个是在定时中断函数里用累计定时中断次数的方式实现(P0.1 控制)。这两个闪烁的 LED 灯,一
个在 main 函数,一个是在中断函数,两路任务互不干涉独立运行,并行处理的“雏形”略显出来。

图 85.9.1 灌入式驱动 8 个 LED
#include "REG52.H"
#define CYCLE_SUM 5000 //主循环的次数
#define INTERRUPT_SUM 500 //中断的次数
sbit P0_0=P0^0; //在主循环里的 LED 灯
sbit P0_1=P0^1; //在定时中断里的 LED 灯
unsigned char Gu8CycleStep=0;
unsigned long Gu32CycleCnt=0; //累计主循环的计数器
unsigned char Gu8InterruptStep=0;
unsigned long Gu32InterruptCnt=0; //累计定时中断次数的计数器
void main()
{
TMOD=0x01; //设置定时器 0 为工作方式 1
TH0=0xfc; //产生 1ms 中断的 TH0 初始值
TL0=0x66; //产生 1ms 中断的 TL0 初始值
EA=1; //开总中断
ET0=1; //允许定时 0 的中断
TR0=1; //启动定时 0 的中断
while(1) //主循环
{
switch(Gu8CycleStep)