AT89S51内部两个16位定时器/计数器:T0(P3.4),T1(P3.5),定时器/计数器T0由特殊寄存器TH0,TL0构成,T1由特殊功能寄存器TH1,TL1构成
T0,T1都有定时器和计数器两种工作模式,两种模式实质都是对脉冲信号进行计数,只不过技术信号来源不同。
计数器模式 是对T0(P3.4)和T1(P3.5)两个引脚上的外部脉冲进行计数
定时器模式是对系统时钟信号(fosc)经12分频后的内部脉冲信号(机器周期Tcy)计数.由于系统时钟频率fosc是定值,可根据数值计算出定时时间T(定时时间T = n * Tcy).
振荡周期Tosc = 1/fosc, 机器周期Tcy = 12Tosc = 12/fosc)
例如:fosc = 12Hz, Tcy = 1us
T0,T1属于加1计数器,即每记一个脉冲,计数器加1.(51单片机)
T0,T1具有4种工作方式(方式0,1,2,3)
特殊功能寄存器TMOD用于选择定时器/计数器T0,T1的工作模式和工作方式.
特殊功能寄存器TCON用于控制T0,T1的启动和停止计数,同时包含了T0,T1状态.
计数器起始计数从初值开始,单片机复位时计数器初值为0,也可给计数器装入一个新的初值(0 ~ 2^16 - 1).
若计数器溢出会产生中断,称溢出中断,最大计数值2^16 = 65536, 此时刻发生溢出中断, 最大初值2^16 - 1 = 65535
寄存器地址89H, 不可按位操作, 高4位控制T1, 低4位控制T0.
D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|
GATE | C/T’ | M1 | M0 | GATE | C/T’ | M1 | M0 |
D7-D4 : T1方式字段, D3-D0 : T0方式字段
M1 | M0 | 工作方式 |
---|---|---|
0 | 0 | 方式0, 13位定时器/计数器 |
0 | 1 | 方式1, 16位定时器/计数器 |
1 | 0 | 方式2, 8位的常数自动重新装载的定时器/计数器 |
1 | 1 | 方式3, 仅适用于T0, 此时T0分成2个8位计数器, T1停止计数 |
方式0为兼容早期8048的13位定时器/计数器
**C : Counter, T : Timer **
TCON字节地址88H, 位地址为88H~8FH.
D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|
TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
当计数器计数溢出时, 该位置1. 使用查询方式时, 此位可提供CPU查询, 但应注意查询后, 用软件及时该位清0. 使用中断方式时, 作为中断请求标志位, 进入终端服务程序后由硬件自动清0.
该位可由软件置1和清0
方式1和方式0差别仅存在于计数器的位数不同, 方式1为16位计数器, 由THx高8位和TLx低8位构成(x = 0,1), 方式0为13位计数器, 有关控制状态位含义(GATE, C/T’, TFx, TRx)与方式0相同.
该方式为13位定时/计数器的计数最大值位2^13 = 8196
方式0和方式1最大特点是计数溢出后, 计数器为全0. 因此在循环定时或循环计数应用时就存在用指令反复装入计数初值的问题, 这会影响定时精度(指令本身执行也需要时间), 方式2就是为解决此问题而设置的.
当M1, M0 = 1, 0时, 工作方式2. 8位自动重装载方式
低8位用来计数/定时, 高8位用来保存初值, 硬件自动完成装入初值的操作
最大计数/定时范围2^8 = 256
方式3是为增加一个附加的8位定时器/计数器而设置的, 从而使AT89S51具有3个定时器/计数器. 方式3只适用于T0, T1不能工作在方式3
T0在方式3下, 分为了两个8位定时器/计数器
计数器模式时, 计数脉冲来自外部输入引脚T0或T1. 当输入信号产生负跳变时, 计数值增1.
由于确认一次负跳变要花2个机器周期, 即24个振荡周期, 因此外部输入的计数脉冲的最高频率为系统振荡器频率的1/24
对外输入信号占空比没有限制, 但为确保某一给定电平在变化前能被采样1次, 该电平至少保持1个机器周期.
设置晶振频率12MHz, 则机器周期为1us, 一次溢出中断时间的总耗时计算65536 * 1us = 65…ms
题目要求0.5s即500ms, 500ms > 65ms,故需软件计数, 500ms约溢出中断次数为100次
可用软件编写计时器对中断次数进行计数
D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|
GATE | C/T’ | M1 | M0 | GATE | C/T’ | M1 | M0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
TMOD初始化值则为0x01
设定时时间5ms(即5,000us), 设T0计数初值为X, 假设晶振频率为11.0592MHz, 则定时时间为:
定时时间 = (2^16 - X) * Tcy = (2^16 - X) * 12/晶振频率
则 5000 = (2^16 - X) * 12 / 11.0592, 得X = 60,928
转换成16进制为0xee00, 其中0xee装入TH0, 0x00装入TL0.
X --> 65536(产生溢出)
个数n = 65536 - X
定时时间T = n * Tcy == 5ms == 5,000us
机器周期Tcy = 12 / 11.0592 us
本例采用定时器T0中断, 因此需将IE寄存器中的EA,ET0位置1
将定时器控制寄存器TCON中的TR0置1, 则启动定时器T0
/*
* 功能实现: 使用方式1定时中断控制LED闪亮
* 编写环境: Keil5
* 硬件仿真: Proteus 8 Professional
* 日期: 2022-11-05
*/
#include
char i = 100; // 用于软件计数
int main(void)
{
TMOD = 0x01; // 定时器T0为方式1
TH0 = 0xee; // 设置定时器初值
TL0 = 0x00;
P1 = 0x00; // P1口8个LED点亮
EA = 1; // 总中断开
ET0 = 1; // T0中断开
TR0 = 1; // 启动T0
while(1) // 循环等待
{
;
}
return 0;
}
void timer0() interrupt 1 // T0中断程序
{
TH0 = 0xee; //重新赋初值
TL0 = 0x00;
// 软件计数, 100次溢出中断后, LED状态进行转换
i--; // 循环次数减1
if (i <= 0)
{
P1 = ~P1; // P1口按位取反, LED状态转换
i = 100; // 重置循环次数
}
return;
}
如图,T1采用计数模式,方式1中断,计数输入引脚T1(P3.5)上外接按钮开关,作为计数信号输入。按4次按钮开关后,P1口的8只LED闪烁不停。
所以, TMOD寄存器应初始化为0x50.
由于每按1次按钮开关,计数1次,按4次后,P1口的8只LED闪烁不停. 因此计数器初值为65536 - 4 = 65532, 所以, TH1 = (65536 - 4) / 256, TL1 = (65536 - 4) % 256.
由于采用T1中断,因此需将IE寄存器的EA, ET1位置1.
将寄存器TCON中TR1 = 1, 则启动T1计数.
#include
void Delay(unsigned int i);
int main(void)
{
TMOD = 0x50; // 设置定时器T1位方式1计数
TH1 = (65536 - 4) / 256; // 向TH1写入初值的高8位
TL1 = (65536 - 4) % 256; // 向TL1写入初值的低8位
EA = 1; // 总中断允许
ET1 = 1; // 定时器T1中断允许
TR1 = 1; // 启动定时器T1
while (1)
{
;
}
return 0;
}
void Delay(unsigned int i)
{
unsigned int j;
for ( ; i > 0; i--)
{
for (j = 0; j < 125; j++)
{
;
}
}
return;
}
void T1_int(void) interrupt 3 // T0中断时interrupt为0, T1中断时interrupt为3
{
while (1) // 一般中断函数不会设置为死循环
{
// 单次的计数中断所以不需要重新赋初值
P1 = 0xff; // 8位LED全灭
Delay(500); // 延时500ms
P1 = 0; // 8位LED全亮
Delay(500); // 延时500ms
}
return;
}
采用T0方式1的定时中断方式, 控制AT89C51的P1.1引脚输出100Hz的方波.
f = 100Hz, T = 10ms, 定时时间为10/2 = 5ms(单高电平或低电平的时间) < 65ms, 所以定时时间T = 5ms
假设时钟频率fosc = 12MHz, 工作在方式1时最大时间为65536ms
TMOD = 0x01
5ms = 5000us = (2^16 - X) * 12 / 12, 解得X = 60,536
转换为16进制, 则初值X = 0xEC78, TH0 = 0xEC, TL0 = 0x78
因操作为P1.1需提前定义,如下
sbit Pulse = P1^1;
// LED状态转换
Pulse = !Pulse;
/*
* 功能实现: 采用T0方式1的定时中断方式, 控制AT89C51的P1.1引脚输出100Hz的方波.
* 编写环境: Neovim + Keil5
* 硬件仿真: Proteus 8 Professional
* 日期: 2022-11-05
*/
#include
sbit Pulse = P1^1; // 定义P1.1口为Pulse
int main(void)
{
TMOD = 0x01; // 定时器T0为方式1
TH0 = 0xEC; // 设置定时器初值
TL0 = 0x78;
/*
* 计算过程:
* f = 100Hz, T = 10ms, 定时时间为10/2 = 5ms(单高电平或低电平的时间) < 65ms, 所以定时时间T = 5ms
* 假设时钟频率fosc = 12MHz, 工作在方式1时最大时间为65536ms
* 5ms = 5000us = (2^16 - X) * 12 / 12, 解得X = 60,536
* 转换为16进制, 则初值X = 0xEC78, TH0 = 0xEC, TL0 = 0x78
* TH0 = (65536-5000)/256, TL0 = (65536-5000)%256
*/
Pluse = 0; // P1.1输出低电平
EA = 1; // 总中断开
ET0 = 1; // T0中断开
TR0 = 1; // 启动T0
while(1) // 循环等待
{
;
}
return 0;
}
void timer0() interrupt 1 // T0中断程序
{
TH0 = 0xEC; //重新赋初值, 循环计时的时候需要重新赋初值
TL0 = 0x78;
P1 = ~P1; // P1口按位取反, LED状态转换
Pluse = !Pluse; // P1.1口按位取反, 高低电平转换
return;
}
采用T0方式1的定时中断方式, 控制AT89C51的P1.1引脚输出100Hz, 占空比为20%的脉冲.
高电平时间1/5 * T, T = 1/100 = 10ms; 所以高电平持续时间为2ms, 低电平持续时间为8ms.
定时器定时2ms, 设计软件计数器在中断一次后计4次中断(即8ms
)后置低电平
TMOD = 0x01;
/*
* 2ms = 1us * 2000, 故n = 2000
* TH0 = (2^16 - n) / 256
*/
TH0 = (65536 - 2000) / 256;
TL0 = (65536 - 2000) % 256;
/*
* 功能实现: 采用T0方式1的定时中断方式, 控制AT89C51的P1.1引脚输出100Hz, 占空比为20%的脉冲
* 编写环境: Neovim + Keil5
* 日期: 2022-11-15
*/
#include
sbit Pulse = P1^1; // 定义P1.1口为Pulse
unsigned char count = 0;
int main(void)
{
TMOD = 0x01; // 定时器T0为方式1
TH0 = (65536 - 2000) / 256; // 设置定时器初值
TL0 = (65536 - 2000) % 256;
Pluse = 0; // P1.1输出低电平
EA = 1; // 总中断开
ET0 = 1; // T0中断开
TR0 = 1; // 启动T0
while(1) // 循环等待
{
;
}
return 0;
}
void timer0() interrupt 1 // T0中断程序
{
TH0 = (65536 - 2000) / 256; //重新赋初值
TL0 = (65536 - 2000) % 256;
count++;
if (1 == count) // 2ms过后, 高电平持续时间结束, 需将P1.1变为低电平
{
Pluse = 0;
}
else if (5 == count) // (5-1) * 2ms过后, 低电平持续时间结束, 需将P1.1变为高电平
{
Pluse = 1;
count = 0; // 一个脉冲周期结束, 将count清零
}
return;
}
Proteus虚拟仿真. 使用T0, 采用方式2定时中断, 在P1.0引脚上输出周期为400us, 占空比为25%的矩形脉冲, 要求在P1.0引脚上接有虚拟示波器, 观察P1.0引脚输出的矩形脉冲波形.
TMOD = 0000 0010
高电平持续时间为1/4 * 400us = 100us, 低电平持续时间为300us
中断定时为100us, 设计软件计数器, 中断次数为1时跳变为低电平, 中断次数为(4 - 1)时, 跳变为高电平, 循环往复
12MHz时:
n = 100us / 1us = 100
TL0 = 2^8 - 100, TL1 = 2^8 - 100
方式2为8位, 且TH0用于储存初值, 故TL0和TH0二者数值相同, 不存在将十六进制数分位的问题
/*
* 功能实现: 使用T0, 采用方式2定时中断, 在P1.0引脚上输出周期为400us, 占空比为25%的矩形脉冲
* 编写环境: Neovim + Keil5
* 硬件仿真: Proteus 8 Professional
* 日期: 2022-11-15
*/
#include
sbit Pluse = P1^0; // 定义P1.0口为Pulse
unsigned char count = 0;
int main(void)
{
TMOD = 0x01; // 定时器T0为方式1
TH0 = 256 - 100; // 设置定时器初值
TL0 = 256 - 100;
Pluse = 0; // P1.1输出低电平
EA = 1; // 总中断开
ET0 = 1; // T0中断开
TR0 = 1; // 启动T0
while(1) // 循环等待
{
;
}
return 0;
}
void timer0() interrupt 1 // T0中断程序
{
TH0 = 256 - 100; // 重新赋值
TL0 = 256 - 100;
count++;
if (1 == count) // 100us过后, 高电平持续时间结束, 需将P1.1变为低电平
{
Pluse = 0;
}
else if (4 == count) // (4-1) * 100us过后, 低电平持续时间结束, 需将P1.1变为高电平
{
Pluse = 1;
count = 0; // 一个脉冲周期结束, 将count清零
}
return;
}