定时器时间计算原理
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=0xDC和TL0=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连接在单片机的XTAL1和XTAL2之间,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中断
每个模块的作用如下:
- 6T/12T分频器:决定内部定时脉冲的速度。本实验在下载软件中不勾选“使能6T(双倍速)模式”,因此使用12T。
C/T选择器:C/T=0选择内部分频时钟;C/T=1选择外部引脚脉冲。外部脉冲只有在频率已知时才能换算成时间,否则它只代表事件个数。- 启动条件逻辑:本程序设置
GATE=0,所以TR0=1即可运行;若GATE=1,还要求INT0引脚为高电平。 TL0/TH0计数链:TL0是低8位,溢出后向TH0进位,两者共同组成16位数。TF0与中断:TH0再次溢出时,硬件把TF0置1;同时打开ET0和EA后,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 kHz或921600 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} $
容易出现差一错误的原因,是把“数值之差”误当成“到溢出的加法次数”:
| 状态 | 经过的加法次数 | 当前值 |
|---|---|---|
| 装载完成 | 0 | 0xDC00 |
| 第1次加1 | 1 | 0xDC01 |
| 到达最大值 | 9215 | 0xFFFF |
| 再加1并溢出 | 9216 | 0x0000,TF0=1 |
所以:
0xFFFF - 0xDC00 = 0x23FF = 9215 只表示两个数值的差
0x10000 - 0xDC00 = 0x2400 = 9216 才是到溢出所需的次数
初值必须用65536-N计算,因为溢出发生在0xFFFF再加1时,而不是刚到0xFFFF时。TH0和TL0分别装载高、低字节:
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 课程总结
定时器时间计算可以浓缩为以下步骤:
- 从电路图、芯片内部时钟或时钟树确定时钟源频率。
- 根据12T、6T或可编程预分频得到定时器计数频率。
- 用频率倒数求出单次计数时间。
- 用目标时间除以单次计数时间,得到所需计数次数。
- 根据计数器位宽和计数方式计算装载初值。
- 核对模式是否自动重装,并在需要时于中断函数中重新装载。
对于本实验,必须记住这条完整计算链:
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,都能够判断生成的参数是否与真实时钟和计数模式一致。