定时器1代码

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

043.5.4 定时器1代码

#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 &= 0x0F; //高四位清0  保留低四位
	TMOD |=0x10;
	TH1 = 0XFC;
	TL1 = 0X66;//0xDC00;
	TF1 = 0;
	ET1 = 1;
	EA = 1;
	TR1 = 1;
	while(1)
  {
		Delay_xms(1000);	
  }
}

void Timer1_Routine(void)  interrupt 3
{
	TH1 = 0XFC;
	TL1 = 0X66;//0xDC00;
	tim_count++;
//	if(tim_count > 100)
//	{
//		LED3 = ~LED3;
//		tim_count = 0;
//	}
	tim_count = tim_count%500;
	if(0 ==  tim_count)
		LED4 = ~LED4;
}

一、定时器0代码优化

本节先优化定时器0实验中的软件计数,再使用定时器计算器验证初值,最后将程序完整迁移到定时器1。最终程序的工作链路是:

11.0592 MHz系统时钟
        │ 12T分频
        ▼
Timer1每约1 ms溢出一次
        │
        ▼
进入interrupt 3,重装0xFC66
        │
        ▼
tim_count累计并对500取余
        │ 达到500次
        ▼
P1.3翻转,LED4改变亮灭状态

1.1 例题1: 定时器0代码优化思路

计数器可以通过取余运算限制在固定区间内:

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

当除数为100时,tim_count的变化规律是:

初始值0
   │ 第1次中断:先加1,再对100取余
   ▼
1 → 2 → 3 → … → 98 → 99 → 0 → 1 → …
                         ▲
                         └─ 第100次中断时,100 % 100 = 0

余数一定满足$0\leq r<100$,所以变量在0~99之间循环。每当余数等于0,就表示又累计了100次中断。如果定时器每10毫秒中断一次,LED状态翻转间隔就是:

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

这段逻辑与注释中的if (tim_count > 100)并不完全等价。变量从0开始,每次先自增时,> 100要等到第101次中断才成立,对应约1.01秒;取余写法在第100次中断时准确触发。若采用比较方式,严谨写法是:

if (++tim_count >= 100)
{
    tim_count = 0;
    LED4 = ~LED4;
}

取余写法紧凑直观,但8051执行除法的成本通常高于递增和比较。对于LED实验,两种正确写法都可使用;对中断执行时间要求较高时,更适合使用递增、比较、清零。

编译并烧录后,LED4仍约每1秒翻转一次。也可以暂时将取余数设成4,观察它是否每4次中断触发一次,以验证软件分频逻辑。

1.2 例题2: 定时器0的配置与初值设置

定时器初值既可使用公式计算,也可以使用定时器计算器生成,但工具结果仍要与晶振频率、12T/6T设置和工作模式相互核对。

在11.0592 MHz晶振、12T、16位模式1下,初值公式是:

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

常用结果如下:

目标时间理论计数次数采用的整数次数16位初值高、低字节
10 ms921692160xDC00THx=0xDCTLx=0x00
1 ms921.69220xFC66THx=0xFCTLx=0x66

其中x代表定时器编号:Timer0使用TH0/TL0,Timer1使用TH1/TL1。10毫秒结果能够整除,因此0xDC00是精确的理论初值;1毫秒需要把921.6取成整数,定时器计算器选择922次,对应0xFC66

1.3 例题3: 定时器计算器的使用

使用下载软件右侧的“定时器计算器”时,应依次确认以下参数:

选项本实验设置选错后的影响
时钟频率11.0592 MHz初值与开发板实际计数速度不匹配
定时长度10 ms或1 ms生成的溢出周期不同
时间单位毫秒把微秒、毫秒混用会产生1000倍误差
定时器Timer0或Timer1生成的THx/TLxTRx/TFx编号不同
工作模式16位、不自动重载模式2是8位自动重载,计算方式不同
定时器时钟12T(FOSC/12)6T时计数速度是12T的2倍

设置完成后点击“生成C代码”,可以读取TMODTH0/TL0TH1/TL1TFxTRx等配置。10毫秒、Timer0、模式1、12T会得到TH0=0xDCTL0=0x00;1毫秒会得到TH0=0xFCTL0=0x66

计算器还可能生成:

AUXR &= 0x7F;

这条语句不能脱离具体芯片型号直接复制。本开发板使用的STC89C52RC确实具有地址为0x8EAUXR,但该型号只在AUXR.1AUXR.0定义了EXTRAMALEOFF,并且数据手册将它标为只写寄存器;AUXR.7不是Timer0/1的12T选择位,读改写形式也不适合只写寄存器。本芯片Timer0/1的6T或12T速度通过下载软件的“使能6T(双倍速)模式”选项设置。本实验不勾选该选项,使用12T,因此代码中不需要计算器生成的这条AUXR语句。

工具的正确使用原则是“生成后再核对”,而不是整段无条件复制:

  1. 对照芯片数据手册确认寄存器是否存在、各位含义是否相同。
  2. 对照下载选项确认12T或6T与计算器一致。
  3. 用公式复算计数次数和初值。
  4. 模式1必须在中断服务函数里再次装载THx/TLx

二、定时器1配置

课程中将5.2定时器0工程复制为5.3定时器1工程,再逐项迁移配置。Timer0和Timer1的基本计数原理相同,但控制位、计数寄存器、中断允许位和中断号必须成组切换:

功能Timer0Timer1
TMOD所属区域低4位高4位
16位模式1值低4位0001,即0x01高4位0001,即0x10
计数寄存器TH0/TL0TH1/TL1
溢出标志TF0TF1
运行控制TR0TR1
中断允许ET0ET1
Keil中断号interrupt 1interrupt 3
中断向量000BH001BH

EA是所有中断共用的总开关,从Timer0迁移到Timer1时仍然设置为1。函数名可以自行命名,但建议使用Timer1_Routine,让名称、所用硬件和中断号保持一致。

三、定时器1编程

3.1 定时器1的配置与修改

TMOD的高4位控制Timer1,低4位控制Timer0:

TMOD位:  7       6       5      4      3       2       1      0
字段:   GATE1   C/T1    M1_1   M0_1   GATE0   C/T0    M1_0   M0_0
         └──────── Timer1 ────────┘     └──────── Timer0 ────────┘

Timer1选择模式1时,高4位应为0001,因此只修改高4位的写法是:

TMOD &= 0x0F;  // 0000 1111:高4位清零,低4位保留
TMOD |= 0x10;  // 0001 0000:将Timer1高4位设置成0001

例如,假设执行前TMOD=0x2D

    0010 1101    TMOD
AND 0000 1111    0x0F
  = 0000 1101    保留Timer0低4位,清零Timer1高4位

    0000 1101
 OR 0001 0000    0x10
  = 0001 1101    Timer1高4位变为0001,Timer0低4位仍为1101

最终GATE1=0C/T1=0M1_1M0_1=01:Timer1由TR1控制启动,使用内部系统时钟,工作在16位模式1。直接执行TMOD=0x10也能配置Timer1,但会同时清除Timer0低4位;掩码操作更适合两个定时器可能同时使用的程序。

迁移时不能只把TMOD中的0换成1,而应根据寄存器功能逐项替换:

TH0/TL0 ─► TH1/TL1
TF0     ─► TF1
ET0     ─► ET1
TR0     ─► TR1
interrupt 1 ─► interrupt 3

3.2 定时器1相关寄存器介绍

Timer1模式1所需寄存器如下:

寄存器或位所在位置本程序值作用
TMOD.GATE1TMOD.70Timer1只受TR1控制,不与INT1门控
TMOD.C/T1TMOD.60选择内部时钟,作为定时器使用
TMOD.M1_1/M0_1TMOD.5/.401选择16位模式1
TH1/TL18DH/8BH0xFC/0x66组成16位计数初值0xFC66
TCON.TF1TCON.7初始化为0溢出标志,溢出时由硬件置1
TCON.TR1TCON.61启动Timer1
IE.ET1IE.31允许Timer1中断
IE.EAIE.71打开总中断

Timer1模式1的内部信号链可以理解为:

                           C/T1=0:内部时钟
11.0592 MHz SYSclk ──► ÷12 ───────────────┐
                                           ├─► 计数开关 ─► TL1 ─进位─► TH1
T1/P3.5外部脉冲 ─────── C/T1=1 ───────────┘       ▲                  │
                                                   │                  ▼
                              TR1 AND              │             溢出置TF1
                         (NOT GATE1 OR INT1) ──────┘                  │
                                                                      ▼
                                                         ET1=1且EA=1时请求中断
                                                                      │
                                                                      ▼
                                                          interrupt 3(001BH)

本实验不使用T2MOD。Timer2是另一套定时器,由T2CON等寄存器控制;Timer1只使用上表中的Timer1相关位。

3.3 定时器1的初值计算与设置

最终程序按以下顺序初始化:

TMOD &= 0x0F;
TMOD |= 0x10;
TH1 = 0xFC;
TL1 = 0x66;
TF1 = 0;
ET1 = 1;
EA = 1;
TR1 = 1;

不是所有相关位都应设置为1:TF1要先清零,GATE1C/T1M1_1也为0,只有M0_1ET1EATR1需要置1。将TR1放在最后,可以确保模式、初值和中断开关都准备完成后再开始计数。

定时器溢出时,硬件把TF1置1;CPU响应Timer1中断后,硬件会清除TF1。模式1不会自动恢复0xFC66,所以每次进入中断服务函数都要再次写TH1TL1

3.4 例题1: 定时器1的配置与中断处理

从Timer0工程迁移时,编译通过只能证明语法和链接没有错误,不能证明寄存器与中断源已经匹配。视频中的第一次烧录没有看到LED按预期闪烁,排查时应按以下顺序检查:

  1. 确认烧录软件打开的是5.3工程最新生成的HEX文件,可通过文件路径和时间戳判断。
  2. 确认TMOD高4位为0001,而不是继续配置低4位。
  3. 确认主函数使用TH1/TL1、TF1、ET1、TR1
  4. 确认中断函数也用TH1/TL1重新装载Timer1。
  5. 确认函数声明为interrupt 3
  6. 确认EA=1,且LED端口与程序中的LED4=P1^3一致。

下载软件中的“程序文件”时间戳非常有用:如果编译之后时间没有更新,往往说明加载了另一个工程的HEX文件,即使代码窗口看起来正确,板上运行的也可能不是刚编译的程序。

3.5 例题2: 定时器1中断函数修改与测试

Timer0迁移到Timer1时,中断服务函数内部也必须迁移:

void Timer1_Routine(void) interrupt 3
{
    TH1 = 0xFC;
    TL1 = 0x66;
    // 计数与LED处理
}

如果仍然写TH0/TL0,即使Timer1中断能够进入,重新装载的也是Timer0。Timer1第一次从0xFC66溢出后会从0x0000继续计数;在12T、11.0592 MHz下,从0计满一次约需71.111毫秒,后续周期便不再是约1毫秒。

如果寄存器已换成TH1/TL1,但声明仍是interrupt 1,Keil会把该函数放到Timer0的中断入口000BH。Timer1溢出时CPU查找的是001BH,因此这个函数仍不会因Timer1溢出而执行。两项必须同时正确:

Timer1配置正确
   ├─ 中断函数重装TH1/TL1
   └─ 中断号使用interrupt 3
          │
          ▼
    LED才能按Timer1节拍翻转

3.6 定时器1中断号与函数名的规范

传统8051兼容中断的查询次序如下:

中断源Keil中断号中断向量
外部中断000003H
Timer01000BH
外部中断120013H
Timer13001BH
串口40023H

因此Timer1函数必须声明为:

void Timer1_Routine(void) interrupt 3

3是中断查询次序号,不是中断优先级。函数名Timer1_Routine是供程序员和编译器符号管理使用的,硬件最终根据中断向量进入程序;函数名可以不同,但名称与功能一致能显著减少把Timer0和Timer1混用的风险。

3.7 例题3: 定时器1实现LED闪烁

最终中断函数先重新装载0xFC66,再累计500个约1毫秒节拍:

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

LED4与P1.3之间的实际电路为:

                    点亮时的电流方向
                          ─────►
+5 V ──► LED4 ──► R11(10 kΩ)──► P1.3
                                         │
                               P1.3输出0时吸收电流
                                         │
                                        GND

这是低电平点亮结构:P1.3输出0时,+5V → LED4 → R11 → P1.3 → GND形成回路,LED4点亮;P1.3输出1时,两端电位接近,LED4熄灭。LED4 = ~LED4;就是在输出0和1之间翻转。

电阻R11负责限制LED电流,避免LED和单片机I/O口过流。定时器和LED之间没有一根直接的硬件连线;Timer1位于单片机内部,它先产生中断,CPU再执行取反语句,最终由P1.3改变外部LED电路状态:

晶振X1 ─► 单片机内部Timer1 ─► TF1中断请求 ─► CPU执行中断函数
                                                        │
                                                        ▼
                                                P1.3输出翻转
                                                        │
                                                        ▼
                                            LED4外部电流通路改变

板上11.0592 MHz晶体X1连接在XTAL1/XTAL2之间,两端分别通过C9、C10(各22 pF)接地,与单片机内部振荡电路共同产生系统时钟。这个时钟在12T模式下除以12后驱动Timer1计数,最终把晶振的稳定频率转化为肉眼可见的LED节拍。

3.8 定时器1的计数频率与初值调整

最终程序使用:

TH1 = 0xFC;
TL1 = 0x66;

两者组成16位初值0xFC66。Timer1距离溢出还需计数:

$ 65536-\text{0xFC66}=65536-64614=922 $

12T模式下的计数频率为:

$ f_{T1}=\frac{11.0592\ \text{MHz}}{12}=921600\ \text{Hz} $

所以理论溢出时间为:

$ T=\frac{922}{921600}\ \text{s}\approx1.000434\ \text{ms} $

理想的1毫秒需要921.6次计数,硬件只能计整数次,取922次的理论误差约为+0.0434%。中断响应和软件重装寄存器还会带来少量额外延迟,因此实际周期会再略长一些。

代码行末的//0xDC00描述的是10毫秒初值;当前真正参与运行的是TH1=0xFCTL1=0x66组成的0xFC66。改变基础定时时间时,主函数和中断函数中的两组初值必须一起更新。

3.9 定时器1的闪烁频率调整与测试

基础中断约为1毫秒时,软件累计次数决定LED状态翻转间隔:

取余数理论翻转间隔完整亮灭周期观察效果
100约100 ms约200 ms闪烁很快,摄像头画面可能看似常亮或常灭
500约500 ms约1 s肉眼容易看清亮灭变化
1000约1 s约2 s每秒改变一次状态

最终代码采用tim_count % 500,因此LED4约每500毫秒翻转一次。这里的“每500毫秒闪烁一次”是指每500毫秒改变一次亮灭状态;从“亮→灭→再次亮”的完整周期约为1秒。

摄像头按固定帧率采样,快速闪烁的LED可能恰好在每帧曝光时处于相近状态,产生看似不闪或忽明忽暗的混叠现象。缩短拍摄距离、调整帧率,或者直接用肉眼观察,都能更清楚地确认约100毫秒翻转的效果。

调整闪烁节拍时应遵循以下顺序:设置TH1/TL1基础周期,计算需要累计的中断次数,同时修改中断函数中的重装值,编译并确认HEX时间戳,重新烧录,最后观察LED。主循环中的Delay_xms(1000)不是LED4的计时来源;Timer1中断会在延时函数执行期间照常发生。