一、先前知识
定时器/计数器
定时器和计数器最基本的工作原理就是计数。作为定时器时,计数信号的来源选择周期性的内部时钟脉冲;用作计数器时,计数信号的来源选择非周期的外部输入信号。不管是定时器还是计数器,本质上都是计数器。
51单片机有两个定时器/计数器T0和T1,为16位加法器,由低八位TL和高八位TH两个寄存器构成,所有的工作模式中最大计数值为65535个计数脉冲。
该加1计数器的计数脉冲来源有2个:
①系统时钟震荡输出的12分频;
②T0或T1引脚输入的外部脉冲信号。
每接收到一个计数脉冲,计数器就会加1,当计数值累计至全为1时(8位255,13位8191,16位65535),再输入一个计数脉冲,计数器便会溢出回零,并且计数器的溢出是TCON寄存器的TF0或TF1位置1,同时向内核提出中断请求。如果定时器/计数器工作于定时模式,则表示间隔定时时间到;如果工作于计数模式,则表示计数值已满。
以定时器0工作模式1为例,定时10ms,把计算得到的初值写入TH0和TL0寄存器即可:
TH0 = 0xdb;或者TH0 = (65535-10000)/256;
TL0 = 0xef;或者TL0 = (65535-10000)%256;
这里我们一般使用后面的表示方法,因为比较直观而且不用我们再算成16进制。对于这里的“/256和%256”,其实就跟我们平时使用的10进制数一样,低位取余“%”,高位取商“/”。256就是2^16。
那么如果为工作模式0(13位定时器),则应该除以(取余)2^5=32,先满足高八位填满,再填剩下的。
定时器定时初值的计算
该计算公式是以定时器工作模式1为例(2^16),不同的工作模式定时最大值不同,工作模式是几位定时器,就是2的多少次方。根据所需要的不超过最大定时值的定时时间来计算要给的初值。
图一、定时器定时初值的计算
定时器中断用到的寄存器
每个中断源都对应着一个固定的入口地址,也就是中断向量,它们分别是:
0 0x0003: INT0
1 0x000B: TF0
2 0x0013: INT1
3 0x001B: TF1
4 0x0023: RI/TI
图二、IE寄存器
图三、IP寄存器
图四、TCON寄存器
图五、SCON寄存器
图六、TMOD寄存器
总开关,总中断EA,只有当总中断EA接上之后,同时相应的中断EX0、ET0等也接上,外部中断才能控制内核。
图七、中断寄存器的顺序
关于定时器中断服务函数程序的编写(与外部中断服务函数类似,框架一样,只是函数里面的内容不一样)
一般情况下,中断的处理函数有两个,其一为中断初始化函数,其二为中断服务函数。
初始化函数就是一个普通的函数,而中断服务函数有着特殊的格式:
①中断函数没有返回值,也不能对其输入参数;
②函数名后面要跟一个关键字“interrupt”,说明这是一个中断服务函数;
③在关键字“interrupt”后面要跟上中断号,说明这个中断服务函数是为哪个中断服务的
例如:
void 函数名() interrupt 中断号
以上关于定时器中断的介绍并不像书上以及老师们讲的那么专业,都只是本人学习过程中的理解总结而已。如果读者想要了解的更完整、更精细,可以查看相关方面的书和资料。也欢迎各位给我提出意见。
二、模块控制实现过程简述
要控制LED模块,就需要先往74HC138译码器中写入二进制数100(十进制数为4),输出就是Y4为低电平,经过一个或非门输出的Y4C就为高电平,再输入74HC573锁存器。然后控制LED灯的点亮。然后用定时器设置初值时间控制LED灯的闪烁。
三、所要实现的功能
用定时器T0的模式一,定时实现每1秒L1指示灯闪烁一下,即周期为2。每5秒L8指示灯闪烁一下,即周期为10。程序循环实现上述功能。
四、代码实现
①参数以及引脚定义
// LED灯引脚定义
sbit L1 = P0^0;
sbit L8 = P0^7;
// 参数定义
unsigned char count = 0; // 定时器0计数
②138译码器通道选择函数
// 通道选择
void HC138_Init( unsigned char channel )
{
switch( channel )
{
case 0:
P2 = ( P2 & 0x1f ) | 0x00; // 0
break;
case 4:
P2 = ( P2 & 0x1f ) | 0x80; // Y4C
break;
case 5:
P2 = ( P2 & 0x1f ) | 0xa0; // Y5C
break;
case 6:
P2 = ( P2 & 0x1f ) | 0xc0; // Y6C
break;
case 7:
P2 = ( P2 & 0x1f ) | 0xe0; // Y7C
break;
}
}
③初始化系统-关闭目前不需要的蜂鸣器和继电器
// 系统初始化
void System_Init(void)
{
// 关闭LED灯
HC138_Init( 4 );
P0 = 0xff;
// 关闭蜂鸣器和继电器
HC138_Init( 5 );
P0 = 0xaf; // 1010 1111
HC138_Init( 0 ); // 关闭通道选择
P0 = 0xff; // 初始化P0的值
}
④定时器0初始化函数
// 定时器0初始化函数
void Timer0_Init(void)
{
TMOD = 0x01; // 使用定时器0工作模式1
ET0 = 1;
TR0 = 1;
EA = 1;
// 计数初值
TH0 = ( 65535 - 50000 ) / 256;
TL0 = ( 65535 - 50000 ) % 256;
}
⑤定时器0服务函数
// 定时器0服务函数
void Timer0() interrupt 1
{
// 由于是不能自动重装载的,所以要再次赋初值
TH0 = ( 65535 - 50000 ) / 256;
TL0 = ( 65535 - 50000 ) % 256;
HC138_Init( 4 );
count++;
if( count % 20 == 0 ) // 一秒翻转一次电平
{
L1 = ~L1;
}
if( count == 100 ) // 五秒翻转一次电平
{
L8 = ~L8;
count = 0;
}
}
⑥整个函数展示
#include <STC15F2K60S2.H>
// LED灯引脚定义
sbit L1 = P0^0;
sbit L8 = P0^7;
// 参数定义
unsigned char count = 0; // 定时器0计数
// 通道选择
void HC138_Init( unsigned char channel )
{
switch( channel )
{
case 0:
P2 = ( P2 & 0x1f ) | 0x00; // 0
break;
case 4:
P2 = ( P2 & 0x1f ) | 0x80; // Y4C
break;
case 5:
P2 = ( P2 & 0x1f ) | 0xa0; // Y5C
break;
case 6:
P2 = ( P2 & 0x1f ) | 0xc0; // Y6C
break;
case 7:
P2 = ( P2 & 0x1f ) | 0xe0; // Y7C
break;
}
}
// 系统初始化
void System_Init(void)
{
// 关闭LED灯
HC138_Init( 4 );
P0 = 0xff;
// 关闭蜂鸣器和继电器
HC138_Init( 5 );
P0 = 0xaf; // 1010 1111
HC138_Init( 0 ); // 关闭通道选择
P0 = 0xff; // 初始化P0的值
}
// 定时器0初始化函数
void Timer0_Init(void)
{
TMOD = 0x01; // 使用定时器0工作模式1
ET0 = 1;
TR0 = 1;
EA = 1;
// 计数初值
TH0 = ( 65535 - 50000 ) / 256;
TL0 = ( 65535 - 50000 ) % 256;
}
// 定时器0服务函数
void Timer0() interrupt 1
{
// 由于是不能自动重装载的,所以要再次赋初值
TH0 = ( 65535 - 50000 ) / 256;
TL0 = ( 65535 - 50000 ) % 256;
HC138_Init( 4 );
count++;
if( count % 20 == 0 ) // 一秒翻转一次电平
{
L1 = ~L1;
}
if( count == 100 ) // 五秒翻转一次电平
{
L8 = ~L8;
count = 0;
}
}
int main(void)
{
System_Init(); // 系统初始化
Timer0_Init(); // 定时器0初始化
while(1);
}