STC89C52单片机的定时器2有三个模式:捕获模式、自动重新装载(递增或递减计数)和波特率发生器。其中,捕获模式是最常见的一种模式,也是最常用的一种模式。在捕获模式中,通过T2CON中的EXEN2设置2个选项。如果EXEN2=0,定时器2作为一个16位定时器或计数器(由T2CON中C/T2位选择),溢出时置位TF2(定时器2溢出标志位) 。
定时器2的使用方法及实验结果
1. 16位自动重装模式:
a. EXEN2=0时:此时定时器2为16位自动重装的普通定时器,由陷阱寄存器提供重装的值,适用于定时精度要求高、定时时间长(16位)的情况。
b. EXEN2=1时,根据递减计数使能位DCEN的置位和清0可分为两种情况:
i. T2MOD=0x00(DCEN=0;默认情况):此时16位自动重新装载可由外部T2EX的负跳变和溢出任意一种触发,并都能产生中断。
ii. T2MOD=0x01(DCEN=1):此时允许T2EX控制计数的方向;T2EX=0时,重装的值为0FF和0FF,递减计数与陷阱寄存器预存值相等时,置位TF2产生中断。T2EX=1时;自动重装值为陷阱寄存器中的值,溢出时置位TF2产生中断。
2. 波特率发生器模式:
T2CON的TCLK和RCLK位为0(默认)时,串行口发送和接收的波特率由定时器1提供;置位为1时,由定时器2提供。可以一个通过定时器1,一个通过定时器2,这样可以获得发送和接收时不同的波特率。注意:定时器2作为定时器时,递增频率为晶振频率的12分频;而定时器2作为波特率发生器时,它的递增频率为晶振频率的2分频。模式1和模式3的波特率=(振荡器频率/32) * (65535-N)。
定时器2为计数模式时,外部时钟信号由T2(P1^0)引脚进入。当定时器2作为波特率发生器时,TH2溢出并不会置位TF2,因此无需禁止定时器2中断。若EXEN2位被置位,可以将T2EX作为附加的外部中断。在定时器2作为波特率发生器时,不要对TH2和TL2读写,可以读取陷阱寄存器,但不要写入。当访问定时器2的陷阱寄存器时,应关闭定时器(TR2清0)。
52系列单片机可设定定时器/计数器2通过T2(p1^0)引脚输出时钟。P1^0除了可作为普通I/O口外,还可以作为定时器2的外部计数输入和时钟信号输出。当C/T2=0且T2MOD的T2OE位为1时,可将定时器2选为时钟信号发生器,自动装初值。设置公式如下:
时钟信号输出频率 = (振荡器频率 / 4) * (65535 - N)
在时钟输出模式下,计数器溢出不会产生中断请求。这种功能相当于定时器2可同时作为波特率发生器和时钟发生器。由于此时外部中断并未被暂用,若在设置上不冲突,可能同时还可以响应T2EX引入的外部信号。然而,这仅是猜测,尚未经过实验证明。
单片机对于外来脉冲信号具有计数功能,但有要求:计数脉冲的最高频率 = 振荡器的频率 / 24。为了确保给定电平在电平变化之前能被采样一次,该电平至少要维持一个机器周期。以下是STC89C52单片机定时器2实现时钟系统的程序:
```c
////////////////////////////////////////////////////////////////////////
#include
#define uchar unsigned char //定义unsigned char 为 uchar
#define uint unsigned int //定义unsigned int 为 uint
uchar cTime_10ms_counter; //中断次数计数单元
void Timer2Init() {
TMOD &= 0xF0; //清零T2的控制位
TMOD |= 0x02; //设置T2为模式2(8位自动重装载)
TH2 = 0xFC; //设置高8位初值
TL2 = 0x67; //设置低8位初值
ET2 = 1; //允许T2中断
EA = 1; //允许总中断
TR2 = 1; //启动T2
}
void main() {
Timer2Init(); //初始化定时器2
while (1) {
//主循环中执行其他任务
}
}
void T2_ISR() interrupt 1 {
TH2 = 0xFC; //重新加载高8位初值
TL2 = 0x67; //重新加载低8位初值
cTime_10ms_counter++; //中断次数计数单元加1
}
```
```c
uchar cTime_1s_ok; // 判断是否为1s的变量
uchar uDis_buff[6]; // 显示缓冲区,存放要显示的6个字符的段码值
uchar cTime[3]; // 时、分、秒计数单元
#define Time_1s_Sign 100 // 根据中断周期,判断是否到一秒的标志
sbit led = P1 ^ 0;
sbit Duan = P2 ^ 6; // 定义数码管的段选使能端
sbit Wei = P2 ^ 7; // 定义数码管的位选使能端
#define Digital_tube_Wei_Enable Wei = 1; // 开启控制数码管的位选使能端
#define Digital_tube_Wei_Disable Wei = 0; // 关闭控制数码管的位选使能端
#define Digital_tube_Duan_Enable Duan = 1; // 开启控制数码管的段选使能端
#define Digital_tube_Duan_Disable Duan = 0; // 关闭控制数码管的段选使能端
#define Digital_tube_Duan P0 // 定义数码管数据端口
uchar code Dis_table[] = // 将BCD码转换成数码管扫描码的数组
{0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x40, 0x00};
uchar code Dis_Position[] = // 定义数码管位选的数组
{0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf};
/////////////////////////////////////////////////////////////////////////
void delay_ms(unsigned int ms) // 毫秒延时函数
{
int iNumber = ms; // 记录Delay_MS的数值
int iValue = ms; // 要延时毫秒所要进行的循环数值,本数值为实际测得
while (iNumber--) // 通过循环的形式完成延时
{
for (int i = 0; i < iValue; i++) // 本实验是在所用晶振为12M的前提下实现的毫秒延时,本函数是通过循环的形式完成,所以如果改变了晶振的频率,请做相应的改变
{
;
}
}
}
```
以下是重构后的代码:
```cpp
// 延迟函数
void DelayMs(uint Delay_MS)
{
uint iNumber, iValue;
for (iNumber = 0; iNumber < Delay_MS; iNumber++) // 用for语句实现单片机的延时
{
iValue = 107; // 107这个数值是通过测定而得
while (iValue--);
}
}
// 数码管显示函数
{
Digital_tube_Duan_Enable; // 使能数码管的段选
Digital_tube_Duan = Dis_table[cData]; // 输入所要显示的数值
Digital_tube_Duan_Disable; // 关闭数码管的段选
Digital_tube_Wei_Enable; // 使能数码管的位选
Digital_tube_Duan = Dis_Position[cNumber]; // 点亮特定的数码管的公共端
Digital_tube_Wei_Disable; // 关闭数码管的位选
DelayMs(1); // 调整时序,以实现稳定显示
}
```
函数名称:Time_to_disbuffer
函数功能:把要在数码管上显示的数值进行取余、取整,即对数值进行分割,以便显示在分离的数码管。
参数介绍:cNumber1:记录时、分、秒计数单元数组的变量;cNumber2:记录显示缓冲区数组的变量。
返回值:无
注意事项:无
```c
void Time_to_disbuffer()
{
uchar cNumber1, cNumber2 = 0;
for (cNumber1 = 0; cNumber1 <= 2; cNumber1++)
{
uDis_buff[cNumber2++] = cTime[cNumber1] / 10; // 对cTime的数值取整,即取cTime的十位
uDis_buff[cNumber2++] = cTime[cNumber1]; // 对cTime的数值取余,即取cTime的个位
}
}
```
/////////////////////////////////////////////////////////////////////////
函数名称:Init_time2
函数功能:配置定时器2,配置的模式是定时器1采用16位定时器模式,在定时器1的输入数值寄存器输入特定的数值,使其每次中断的周期为10ms,同时允许定时器1中断,并打开总中断。
参数介绍:无
返回值:无
注意事项:无
```c
void Init_time1(void)
{
TH2 = (65535 - 10000) / 256;
TL2 = (65535 - 10000) % 6; // 定时器0的,写入数值寄存器的低8位
EA = 1; // 总中断打开
ET2 = 1; // 定时器T0允许中断
TR2 = 1; // 定时器T0开始工作
}
```
```c
void Timer_Display()
{
uchar cNumber;
for (cNumber = 0; cNumber < 6; cNumber++)
{
One_DigitalTube_display(uDis_buff[cNumber], cNumber);
if (cNumber == 1 || cNumber == 3)
{
Digital_tube_Duan_Enable; // 使能数码管的段选
Digital_tube_Duan = 0x80; // 输入所要显示的数值
Digital_tube_Duan_Disable; // 关闭数码管的段选
Digital_tube_Wei_Enable; // 使能数码管的位选
Digital_tube_Duan = Dis_Position[cNumber]; // 点亮特定的数码管的公共端
Digital_tube_Wei_Disable; // 关闭数码管的位选
DelayMs(1);
}
}
}
void main()
{
Init_time1(); // 初始化定时器1
cTime[0] = 23, cTime[1] = 58, cTime[2] = 58;
Time_to_disbuffer(); // 把预设时间送入显示缓冲区
while (1)
{
if (cTime_1s_ok) // 判断是否到1秒了
{
cTime_1s_ok = 0;
if (++cTime[2] >= 60) // 每次自加一,同时判断是否到1分钟
{
cTime[2] = 0;
if (++cTime[1] >= 60) // 每次自加一,同时判断是否到1小时了
{
cTime[1] = 0;
if (++cTime[0] >= 24) // 每次自加一,同时判断是否到1天了
{
cTime[0] = 0;
}
}
}
}
Timer_Display(); // 在数码管上显示实现
}
}
```
以下是重构后的代码:
```c
void Interrupt_handler_time2(void) interrupt 5
{
TF2 = 0;
TH2 = (65535 - 10000) / 256; // 定时10毫秒
TL2 = (65535 - 10000) % 6;
cTime_10ms_counter++;
if (cTime_10ms_counter == Time_1s_Sign) // 判断是否到达一秒
{
cTime_10ms_counter = 0;
cTime_1s_ok = 1;
}
}
void Time_to_disbuffer()
{
cTime[1] = 0;
if (++cTime[0] >= 24) // 每次自加一,同时判断是否到24点了
{
cTime[0] = 0;
}
}
void Timer_Display()
{
Time_to_disbuffer(); // 新调整好的时间送入显示缓冲区
}
```
定时器在单片机应用中非常常见,特别是对于需要精确控制时间的应用。然而,传统的16位定时器计数器最大只能计数到65536次,这就限制了我们定时的精度。例如,如果我们想要定时1秒,实际上我们需要计数921600次,这已经超过了16位定时器的计数范围。
为了解决这个问题,我们可以采用每次定时10ms的方式,这样只需要循环100次就可以实现1秒的定时。然后,由于1秒被缩小为100毫秒,也就是每次需要计数9216次。
但是,当使用这种方式时,如果我们在每次定时10ms时从0开始计数,我们就无法知道何时会达到9216次。因此,我们需要在计数到9216次后停止计数,并检查TCON的TF位以确定是否已经完成了10ms的定时。
计算公式如下:
计数器初始值 = 最大计数次数 - 需要计数次数
如果我们选择每次定时10ms,那么计数器的初始值应该是65536 - 9216。
接下来,我们需要将这个初始值分解为高位和低位。因为我们的计数器是16位的,所以它由两个8位组成。每个8位的最大计数次数是256。因此:
计数器高位 = 初始值/256
计数器低位 = 初始值%256
以下是一个基于STC89C52RC单片机的定时器示例代码:
```c
#include
// 如果你的单片机没有用74hc138扩展IO口,下面代码可略
sbit enableG1 = P1^3;
sbit enableG2 = P1^4;
sbit selectC = P1^2;
sbit selectB = P1^1;
sbit selectA = P1^0;
void hc138()
{
enableG1 = 1;
enableG2 = 0;
selectC = 1;
selectB = 1;
selectA = 0;
}
typedef unsigned char uint8;
typedef unsigned int uint16;
void main(void)
{
uint16 counter;
hc138();
TMOD = 0X01; //设置定时器模式为模式1(16位定时器)
TH0 = (65536 - 922) / 256; //设置定时器高8位初值
TL0 = (65536 - 922) % 256; //设置定时器低8位初值
TR0 = 1; //启动定时器
TF0 = 0; //清除TF标志位
TH0 = (65536 - 922) / 256; //重新加载高8位初值以重置定时器计数值
TL0 = (65536-922) % 256; //重新加载低8位初值以重置定时器计数值
TF0 = 0; //清除TF标志位以准备下一次启动定时器
int led_flag = 0; //定义一个变量用于控制LED灯的状态
```c
#include
uint8_t P0 = 1; // 初始化P0为1
uint8_t TH0 = (65536 - 922) / 256; // 计算TH0的值
uint8_t TL0 = (65536 - 922) % 256; // 计算TL0的值
uint8_t counter = 0; // 初始化计数器
void timer0_isr() interrupt 1
{
TH0 = (65536 - 922) / 256; // 重新计算TH0的值
TL0 = (65536 - 922) % 256; // 重新计算TL0的值
counter++; // 计数器加1
}
if(counter == 64) // 当计数器达到64时
{
counter = 0; // 将计数器清零
P0 = ~P0; // 对P0进行异或操作,取反
}
```