定时器时间计算原理

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

044.5.5 定时器时间计算原理

#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;
}

一、定时器知识复盘

定时器的本质是一个由稳定脉冲驱动的加法计数器。要计算定时时间,需要依次回答四个问题:

时钟从哪里来?
      │
      ▼
分频后每秒有多少个计数脉冲?
      │
      ▼
计数器每加1需要多长时间?
      │
      ▼
从初值到溢出一共需要加多少次?

代码中的TH0=0xDCTL0=0x00共同组成初值0xDC00。Timer0并不是看到这个数就知道“10毫秒”,而是从0xDC00开始,每收到一个计数脉冲加1;第9216个计数脉冲到来时发生16位溢出并请求中断。10毫秒来自“单次计数时间 × 9216次”,不是寄存器数值自身携带的时间单位。

1.1 复盘内容

本节从板载晶振出发,沿着数据手册中的Timer0模式1框图,依次说明时钟源、12T分频、C/T选择、运行门控、16位计数、溢出标志和初值计算。最后再把同一套“时钟源—分频—计数—溢出”思想推广到STM32定时器。

1.1.1 定时器计一个数用时

定时器“计一个数”是指16位计数器的值加1,例如:

0xDC00 ──一个计数脉冲──► 0xDC01
0xDC01 ──一个计数脉冲──► 0xDC02
   …
0xFFFE ──一个计数脉冲──► 0xFFFF
0xFFFF ──一个计数脉冲──► 0x0000,同时TF0置1

单次计数时间只由计数脉冲频率决定,初值不会改变计数器加1的速度。初值决定的是距离溢出还剩多少次加1:

$ N=2^{16}-X=65536-X $

其中$X$是16位初值,$N$是从该初值运行到溢出所需的计数次数。定时时间为:

$ T=N\times t_{tick} $

因此,初值的完整推导顺序是:先确定单次计数时间$t_{tick}$,再根据目标时间计算次数$N$,最后计算$X=65536-N$。定时器计算器只是把这三步自动完成。

1.1.2 定时器技术原理
1.1.2.1 找到时钟源

任何计时都必须有稳定的时间基准。人按“一秒、两秒、三秒”计时,是把秒作为基准;单片机则依赖周期性时钟信号。

开发板的时钟电路如下:

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

X1连接在单片机的XTAL1XTAL2之间,C9、C10分别从晶体两端接地。晶体、两只负载电容和芯片内部的反相放大器共同构成Pierce振荡器:

  • 晶体利用压电效应,在谐振点附近呈现很高的频率选择性,使振荡频率稳定在约11.0592 MHz。
  • C9、C10参与形成晶体需要的负载电容,并为交流信号提供对地通路;它们不是用来把时钟“直接12分频”的电容。
  • 单片机内部振荡放大器补偿回路损耗,使微小噪声能够起振并维持稳定振荡。
  • 产生的时钟先作为SYSclk进入芯片内部,再由定时器的分频和选择电路处理。

11.0592 MHz表示每秒约有11059200个时钟周期。单个晶振时钟周期为:

$ T_{osc}=\frac{1}{11.0592\times10^6}\ \text{s} \approx90.422\ \text{ns} $

如果晶振停振、焊接不良或负载电容不合适,CPU和定时器都会失去可靠的时间基准,程序通常无法正常运行。因此,看到“定时不准”时,除了检查软件初值,也要确认晶振频率和下载软件中的6T/12T选项。

1.1.2.2 定时器计数原理

TMOD中的C/T位决定计数脉冲来自哪里:

C/T工作方式脉冲来源典型用途
0定时方式系统时钟经12T或6T分频后的内部脉冲产生固定时间间隔
1计数方式外部T0/P3.4或T1/P3.5引脚上的脉冲统计外部事件数量

Timer0模式1的内部结构可以展开为:

                    ┌── 12T模式:SYSclk ÷ 12 ──┐
11.0592 MHz SYSclk ─┤                          ├──┐
                    └──  6T模式:SYSclk ÷ 6  ──┘  │ C/T=0
                                                  ├─► 脉冲选择器
外部T0/P3.4脉冲 ──────────────────────────────────┘ C/T=1
                                                          │
                                                          ▼
                                            ┌────────────────────────┐
TR0 ───────────────────────────────────────►│                        │
GATE、INT0 ─► 启动条件逻辑 ────────────────►│      计数允许开关      │
                                            └───────────┬────────────┘
                                                        │
                                                        ▼
                                                TL0(低8位)
                                                        │ 进位
                                                        ▼
                                                TH0(高8位)
                                                        │ 溢出
                                                        ▼
                                                     TF0=1
                                                        │ ET0=1且EA=1
                                                        ▼
                                                  Timer0中断

每个模块的作用如下:

  1. 6T/12T分频器:决定内部定时脉冲的速度。本实验在下载软件中不勾选“使能6T(双倍速)模式”,因此使用12T。
  2. C/T选择器C/T=0选择内部分频时钟;C/T=1选择外部引脚脉冲。外部脉冲只有在频率已知时才能换算成时间,否则它只代表事件个数。
  3. 启动条件逻辑:本程序设置GATE=0,所以TR0=1即可运行;若GATE=1,还要求INT0引脚为高电平。
  4. TL0/TH0计数链TL0是低8位,溢出后向TH0进位,两者共同组成16位数。
  5. TF0与中断TH0再次溢出时,硬件把TF0置1;同时打开ET0EA后,CPU响应Timer0中断。

在12T定时方式下,Timer0的计数频率是:

$ f_{tick}=\frac{f_{osc}}{12} =\frac{11.0592\ \text{MHz}}{12} =0.9216\ \text{MHz} =921.6\ \text{kHz} $

这意味着每秒有921600个计数脉冲。单次计数时间是频率的倒数:

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

因此“每过一个机器周期,定时器加1”在本实验中的准确含义是:当前Timer0采用12T兼容计数速度,一个定时器计数周期包含12个晶振周期。其他芯片、其他定时器或6T设置可能不同,必须以相应数据手册和时钟配置为准。

1.1.2.3 定时器计一个数用时

需要区分三个容易混淆的周期:

名称本实验中的含义计算结果
时钟周期晶振输出一个周期$1/11.0592\ \text{MHz}\approx90.422\ \text{ns}$
机器周期当前12T设置下由12个时钟周期组成$12\times90.422\ \text{ns}\approx1.085069\ \mu\text{s}$
指令周期CPU执行一条指令所需时间由该指令需要的机器周期数决定,不是固定值

机器周期也常称CPU周期,用来描述CPU完成一个基本操作阶段所需的时间。一条指令可能包含取指、译码、执行等阶段,因此不同指令可以占用不同数量的机器周期。定时器是独立硬件,CPU执行哪条指令并不会让其停下来;只要运行条件满足,它就按照定时器计数时钟持续加1。

本实验中,Timer0每加1的时间为:

$ t_{tick}=\frac{12}{11.0592\ \text{MHz}} =1.085069444\ldots\ \mu\text{s} $

不要过早把它截断成1.085再参与大次数运算,否则会引入不必要的累计误差。更稳妥的方法是保留频率形式,直接计算$N=T\times f_{osc}/12$。

机器周期计算公式

频率$f$和周期$T$互为倒数:

$ T=\frac{1}{f} $

在12T模式下,晶振频率、定时器计数频率和单次计数时间之间的关系是:

$ f_{tick}=\frac{f_{osc}}{12} $

$ t_{tick}=\frac{1}{f_{tick}}=\frac{12}{f_{osc}} $

如果$f_{osc}$使用MHz,计算出的$t_{tick}$单位就是微秒;如果$f_{osc}$使用Hz,结果单位就是秒。

11.0592MHz的12T分频

代入开发板晶振频率:

$ f_{tick}=\frac{11.0592}{12}\ \text{MHz} =0.9216\ \text{MHz} $

$ t_{tick}=\frac{1}{0.9216}\ \mu\text{s} =1.085069444\ldots\ \mu\text{s} $

这里必须保留小数点:11.0592 ÷ 12 = 0.9216 MHz,也可写成921.6 kHz921600 Hz,不能写成9216 MHz。

如果切换到6T模式:

$ f_{tick,6T}=\frac{11.0592}{6}\ \text{MHz}=1.8432\ \text{MHz} $

$ t_{tick,6T}\approx0.542535\ \mu\text{s} $

6T下计数速度是12T的2倍。若仍装载0xDC00,溢出时间会由10毫秒缩短到约5毫秒,所以下载设置必须与初值公式一致。

10毫秒计数次数与初值

10毫秒等于10000微秒。使用精确频率关系计算所需次数:

$ N=T\times f_{tick} =10000\ \mu\text{s}\times0.9216\ \frac{\text{次}}{\mu\text{s}} =9216\ \text{次} $

也可以写成:

$ N=\frac{10000\ \mu\text{s}}{1.085069444\ldots\ \mu\text{s/次}} =9216\ \text{次} $

16位计数器的模是$2^{16}=65536$,所以初值为:

$ X=65536-9216=56320=\text{0xDC00} $

容易出现差一错误的原因,是把“数值之差”误当成“到溢出的加法次数”:

状态经过的加法次数当前值
装载完成00xDC00
第1次加110xDC01
到达最大值92150xFFFF
再加1并溢出92160x0000TF0=1

所以:

0xFFFF - 0xDC00 = 0x23FF = 9215   只表示两个数值的差
0x10000 - 0xDC00 = 0x2400 = 9216  才是到溢出所需的次数

初值必须用65536-N计算,因为溢出发生在0xFFFF再加1时,而不是刚到0xFFFF时。TH0TL0分别装载高、低字节:

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

TH0 = 0xDC;
TL0 = 0x00;

模式1不自动重装初值,因此主函数初始化时装载一次,中断服务函数开头还要再次写入TH0/TL0。理论上的10毫秒不包括中断响应和软件重装所耗费的少量时间,实测周期通常会略长;对精度要求较高时需要测量并补偿。

1.1.3 STM32单片机定时器原理

STM32F103ZET6属于大容量产品,具有TIM1~TIM8共8个定时器,可按功能分为:

类型定时器主要特点
基本定时器TIM6、TIM7主要用于基本定时、更新中断和DAC触发
通用定时器TIM2~TIM5支持定时、外部计数、输入捕获、输出比较和PWM
高级控制定时器TIM1、TIM8在通用功能基础上增加互补输出、死区、刹车等电机控制功能

STM32定时器比51单片机的Timer0/1功能丰富,但计时本质相同:先确定定时器时钟,再预分频,然后按设定方向计数,到达更新条件后置位标志并可产生中断。

RCC时钟树
    │
    ▼
定时器输入时钟 f_TIM
    │ 预分频器 PSC
    ▼
计数时钟 f_CNT = f_TIM / (PSC + 1)
    │
    ▼
计数器 CNT:向上 / 向下 / 中心对齐计数
    │ 到达自动重装值ARR或更新条件
    ▼
更新事件:中断、PWM更新、触发其他外设等

向上计数时,常用溢出周期公式是:

$ T=\frac{(PSC+1)(ARR+1)}{f_{TIM}} $

常见实验包括普通定时器中断、PWM呼吸灯、电机和舵机控制、输入捕获以及微秒延时。STM32定时器的输入时钟不应一律写死为72 MHz,它由RCC时钟树和APB分频设置决定;在常见的72 MHz系统配置下,某些定时器时钟确实可以是72 MHz,实际项目仍应先查看时钟树。

51单片机使用固定的12T或6T选择,而STM32通常提供可编程预分频寄存器PSC和自动重装寄存器ARR。两者表面寄存器不同,核心关系仍是:

$ \text{定时时间}=\text{一次计数所需时间}\times\text{有效计数次数} $

1.1.4 课程总结

定时器时间计算可以浓缩为以下步骤:

  1. 从电路图、芯片内部时钟或时钟树确定时钟源频率。
  2. 根据12T、6T或可编程预分频得到定时器计数频率。
  3. 用频率倒数求出单次计数时间。
  4. 用目标时间除以单次计数时间,得到所需计数次数。
  5. 根据计数器位宽和计数方式计算装载初值。
  6. 核对模式是否自动重装,并在需要时于中断函数中重新装载。

对于本实验,必须记住这条完整计算链:

11.0592 MHz ÷ 12
      = 0.9216 MHz
      = 0.9216次/微秒

10 ms = 10000微秒
10000 × 0.9216 = 9216次

65536 - 9216 = 56320 = 0xDC00

从0xDC00经过9216次加1后溢出,产生约10 ms定时中断

掌握这条链路后,无论使用定时器计算器、51单片机还是STM32,都能够判断生成的参数是否与真实时钟和计数模式一致。