定时器0代码

Tutorial: 电子电路 Category: 嵌入式 Published: 2026-08-29 08:09:36 Views: 0 Likes: 0 Comments: 0

042.5.3 定时器0代码

#include "reg52.h"
#include <intrins.h>
 
sbit LED1 = P1^0;
sbit LED2 = P1^1;
sbit LED3 = P1^2;
sbit LED4 = P1^3;
 
//sbit KEY1 = P3^2;
//sbit KEY2 = P3^3;

//sbit SUN = P3^7;

typedef unsigned char uchar;
typedef unsigned int uint;

uint tim_count = 0;
void Delay_xms(uint xms)	//@11.0592MHz
{
	uchar data i, j;
	while(xms)
	{
		_nop_();
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
    xms--;		
	}

}

void main()
{
	//初始化
	//TMOD = 0x01;
	TMOD &= 0xF0; //低四位清0  保留高四位
	TMOD |=0x01;
	TH0 = 0XDC;
	TL0 = 0X00;//0xDC00;
	TF0 = 0;
	ET0 = 1;
	EA = 1;
	TR0 = 1;
	while(1)
  {
		Delay_xms(1000);	
  }
}

void Timer0_Routine(void)  interrupt 1
{
	TH0 = 0XDC;
	TL0 = 0X00;//0xDC00;
	tim_count++;
//	if(tim_count > 100)
//	{
//		LED3 = ~LED3;
//		tim_count = 0;
//	}
	tim_count = tim_count%100;
	if(0 ==  tim_count)
		LED4 = ~LED4;
}

一、定时器0代码

这段程序让定时器0每隔约10毫秒产生一次中断,再用软件累计100次中断,将LED4的状态每隔约1秒翻转一次。完整链路如下:

11.0592 MHz晶振
        │
        ▼
12T分频:11.0592 MHz ÷ 12 = 921.6 kHz
        │
        ▼
Timer0从0xDC00递增到0xFFFF,再溢出
        │  共计数9216次,约10 ms
        ▼
TF0置位 ──► Timer0中断 ──► 重装0xDC00
                               │
                               ▼
                         累计100次中断
                               │
                               ▼
                         LED4状态翻转

1.1 定时器0代码编写过程

  • 定时器0代码的基本步骤是:选择工作模式、装载计数初值、清除溢出标志、打开中断、启动定时器、编写中断服务函数。
  • 工程可以由4.2外部中断实验复制并重命名为5.2,再删除外部中断初始化和对应的中断服务函数,保留端口定义与毫秒延时函数。
  • 本程序没有单独封装定时器初始化函数,而是在main()开头依次完成初始化;功能上与调用独立的Timer0_Init()函数相同。
  • while(1)中的Delay_xms(1000)代表主程序仍在执行自己的任务。定时器0到期后,CPU会暂时中断主循环,执行Timer0_Routine(),返回后再继续主循环。因此,LED4的1秒节拍来自定时器中断,并不依赖这个延时函数。

程序使用定时器0的模式1,即由TH0TL0共同组成16位加法计数器:

         高8位TH0              低8位TL0
      ┌──────────┐          ┌──────────┐
      │  0xDC    │          │  0x00    │
      └────┬─────┘          └────┬─────┘
           └────────┬────────────┘
                    ▼
             16位初值0xDC00
                    │ 每来一个定时脉冲加1
                    ▼
           0xDC00 → … → 0xFFFF
                    │ 再来一个脉冲
                    ▼
           0x0000,同时TF0置1

模式1不会自动恢复0xDC00。如果中断服务函数不重新写入TH0TL0,下一轮就会从0x0000开始计数,周期将不再是10毫秒。

1.2 定时器0代码初始化过程

TMOD是定时器0和定时器1共用的8位工作模式寄存器,高4位属于定时器1,低4位属于定时器0:

76543210
定时器T1T1T1T1T0T0T0T0
位名GATE1C/T1M1_1M0_1GATE0C/T0M1_0M0_0
本实验T0设置0001

定时器0低4位0001的含义是:

作用
GATE00只由TR0控制启动,不要求INT0引脚同时为高电平
C/T00使用内部系统时钟,工作在定时方式
M1_0 M0_001选择模式1,即16位定时器/计数器

直接执行TMOD = 0x01;会把8位全部改写:定时器0确实会进入模式1,但定时器1的高4位也会被清零。在明确不使用定时器1、并且希望同时初始化两个定时器时,这种写法可以使用;在各模块分别配置外设的程序中,保留无关位更安全。

程序分两步只修改低4位:

TMOD &= 0xF0;
TMOD |= 0x01;

第一句等价于TMOD = TMOD & 0xF0;。按位与运算只有“两个输入位都为1”时结果才是1,所以它会保留高4位并清零低4位:

ABA & B
000
010
100
111

第二句等价于TMOD = TMOD | 0x01;。按位或运算只要有一个输入位是1,结果就是1,因此它只把最低位置1:

ABA | B
000
011
101
111

假设操作前TMOD = 0x1D,完整过程为:

原值:  0001 1101    (0x1D)
掩码:  1111 0000    (0xF0)
按位与:0001 0000    (0x10,高4位保留,低4位清零)

结果:  0001 0000    (0x10)
掩码:  0000 0001    (0x01)
按位或:0001 0001    (0x11,T0低4位变为0001)

初始化代码的执行顺序及作用如下:

代码作用
TMOD &= 0xF0;清零定时器0的模式控制位,保留定时器1设置
`TMOD= 0x01;`
TH0 = 0xDC; TL0 = 0x00;装入10毫秒定时初值0xDC00
TF0 = 0;清除可能残留的定时器0溢出标志
ET0 = 1;允许定时器0提出中断请求
EA = 1;打开CPU总中断开关
TR0 = 1;最后启动定时器0

ET0EA必须同时为1,中断请求才能被CPU响应;TR0只负责让定时器开始计数。即使TR0 = 1,只要ET0EA为0,定时器仍可计数和置位TF0,但CPU不会进入定时器0中断服务函数。

1.3 定时器0代码编写过程

16位计数器共有$2^{16}=65536$个状态,范围是0x00000xFFFF。在传统8051的12T定时方式下,定时器每12个晶振周期加1。板载晶振频率为11.0592 MHz,因此计数频率和单次计数时间为:

$ f_{timer}=\frac{11.0592\ \text{MHz}}{12}=921.6\ \text{kHz} $

$ t_{tick}=\frac{1}{921600}\ \text{s}\approx1.085069\ \mu\text{s} $

如果从0开始计满65536次,单次溢出的最长时间约为:

$ 65536\times1.085069\ \mu\text{s}\approx71.111\ \text{ms} $

所以16位定时器不能在一次溢出中直接得到1秒,程序先产生10毫秒的基础节拍,再累计100次。

晶振电路由X1、C9和C10组成:X1是11.0592 MHz晶体,连接在单片机的XTAL1XTAL2之间;C9、C10各为22 pF,分别从晶体两端接地。

                         X1:11.0592 MHz
单片机 XTAL2 o────────────[ 晶体 ]────────────o XTAL1 单片机
              │                                │
            C9 22 pF                         C10 22 pF
              │                                │
             GND                              GND

晶体不是给定时器单独供电的元件。它与单片机内部振荡放大器、两只负载电容共同构成振荡回路,产生稳定的系统时钟。C9、C10既参与形成晶体要求的负载电容,也帮助振荡器稳定启动;它们不是串联在定时器计数通路中的“分频电容”。系统时钟在芯片内部经过12T分频后,才成为本实验定时器0的计数脉冲。

需要特别确认下载软件中的定时器速度设置。本实验按12T计算,且下载界面的“使能6T(双倍速)模式”未选中。如果芯片改为6T模式,定时器每6个时钟加1,10毫秒内会计数18432次,初值应改为0xB800;继续使用0xDC00会使中断周期约缩短为5毫秒。

定时初值计算公式

设模式1的装载初值为$X$、期望定时时间为$T$、晶振频率为$f_{osc}$。在12T模式下,从$X$计数到溢出需要$65536-X$个计数脉冲:

$ T=(65536-X)\times\frac{12}{f_{osc}} $

移项得到初值:

$ X=65536-\frac{T\times f_{osc}}{12} $

单位必须成对匹配:

  • 若$T$使用秒,$f_{osc}$就使用赫兹;
  • 若$T$使用微秒,$f_{osc}$就使用兆赫兹,因为$1\ \text{MHz}=1$个周期/微秒。

因此,在本实验采用的“微秒 + 兆赫兹”单位下,可以直接写成:

$ X=65536-\frac{T_{\mu s}\times f_{MHz}}{12} $

这个公式只适用于当前的16位模式1和12T设置。位数或每次计数所需的晶振周期改变后,$65536$或分母也要随之改变。

10毫秒初值计算

10毫秒等于10000微秒。代入11.0592 MHz晶振频率:

$ N=\frac{10000\times11.0592}{12}=9216 $

这里的$N$是10毫秒内需要的计数次数。装载初值应为:

$ X=65536-9216=56320 $

将56320转换为十六进制:

$ 56320=\text{0xDC00} $

16位值按高、低字节分别写入两个8位寄存器:

0xDC00 = 1101 1100 0000 0000₂
           └─TH0─┘ └─TL0─┘

TH0 = 0xDC
TL0 = 0x00

因此程序中的TH0 = 0xDC; TL0 = 0x00;与10毫秒目标一致。硬件计数过程是0xDC00 → … → 0xFFFF → 0x0000;从0xDC00到溢出恰好经过$0x10000-0xDC00=0x2400=9216$个计数脉冲。

1.4 中断服务函数

Keil C51使用interrupt 1声明定时器0中断服务函数:

void Timer0_Routine(void) interrupt 1

这里的1是Keil规定的中断号,不是中断优先级,也不是“第1行代码”。定时器0的中断向量地址为000BH。满足TF0 = 1ET0 = 1EA = 1等条件后,CPU跳转到该向量,再执行此函数;CPU响应中断时会清除TF0

函数一开始重新装载初值:

TH0 = 0xDC;
TL0 = 0x00;

这一步使下一次溢出仍以10毫秒为基础周期。随后进行软件分频:

tim_count++;
tim_count = tim_count % 100;
if (0 == tim_count)
    LED4 = ~LED4;

tim_count每次中断先加1,再对100取余,数值按1, 2, …, 99, 0循环。每当它变回0,就说明正好经过100次中断:

$ 100\times10\ \text{ms}=1000\ \text{ms}=1\ \text{s} $

注释中的if (tim_count > 100)若启用,会在第101次中断才进入条件,得到约1.01秒。希望不用取模并严格累计100次时,可以写成if (++tim_count >= 100),然后在条件内将计数器清零。当前实际执行的取模写法没有这个差一问题。

LED4在电路中接到P1.3,连接关系为:

              电流方向(点亮时)
                    ─────►
+5 V ──► LED4 ──► R11(10 kΩ)──► P1.3
                                      │
                         P1.3输出0时内部下拉到GND

这是低电平点亮电路:

P1.3输出LED两端是否形成电流通路LED4状态
0+5V → LED4 → R11 → P1.3 → GND
1P1.3接近+5V,LED两端压差很小

LED4 = ~LED4;每执行一次就把P1.3的逻辑状态取反,因此LED4每隔约1秒改变一次亮灭状态。这里“每隔1秒闪烁”指每1秒翻转一次;若观察完整的“亮→灭→亮”周期,周期约为2秒。

最后还要注意两个实际误差来源:

  • 模式1需要在中断函数中用软件重装初值。从溢出、CPU响应中断到写完TL0之间会经过若干机器周期,所以实测周期会比理论10毫秒略长;普通指示灯实验通常可以忽略,精密定时则要测量并补偿。
  • 中断服务函数应尽量短。8051执行取模运算的代价高于简单比较;资源紧张时可采用递增、比较、清零的写法。若主循环也要读取由中断修改的tim_count,应把它声明为volatile,并注意8051读取16位变量时的原子性;本程序只在中断函数中使用该变量,不存在这个共享问题。

下载运行后,主循环持续执行延时函数,定时器0仍独立计数并周期性打断主程序;LED4约每1秒翻转一次,说明“硬件10毫秒节拍 + 软件累计100次”的定时链路已经工作。