4.1 定时器
4.1.1 51时钟周期介绍
时钟周期:时钟周期T是时序中最小的时间单位,具体计算的方法就是 1/时钟源频率,89C51单片机开发板上常用的晶振是11.0592M,对于这个单片机系统来说,时钟周期=1/11059200 秒。
机器周期:是单片机完成一个操作的最短时间。
机器周期主要针对汇编语言而言,在汇编语言下程序的每一条语句执行所使用的时间都是机器周期的整数倍,而且语句占用的时间是可以计算出来的,而 C 语言一条语句的时间是不确定的,受到诸多因素的影响。
51单片机系列,在其标准架构下一个机器周期是 12 个时钟周期,也就是 12/11059200 秒。
现在很多的增强型51单片机,其速度都比较块,有的1个机器周期等于 4 个时钟周期,有的1个机器周期就等于1个时钟周期,也就是说大体上其速度可以达到标准 51 架构的 3 倍或 12倍。
定时器和计数器是单片机内部的同一个模块,通过配置 SFR(特殊功能寄存器)可以实现两种不同的功能大多数情况下是使用定时器功能。
顾名思义,定时器就是用来进行定时的,定时器内部有一个寄存器,让它开始计数后,这个寄存器的值每经过一个机器周期就会自动加1,可以把机器周期理解为定时器的计数周期。就像钟表,每经过一秒,数字自动加 1,而这个定时器就是每过一个机器周期的时间,也就是 12/11059200 秒,数字自动加 1。
4.1.2 定时器功能介绍
标准的51单片机内部有T0和T1两个定时器, T 就是 Timer 的缩写。
STC90C51RC/RD+系列单片机的定时器0和定时器1,与传统8051的定时器完全兼容,当在定时器1做波特率发生器时,定时器0可当两个8位定时器使用。
STC90C51RC/RD+系列单片机内部设置的两个16位定时器/计数器T0和T1都具有计数方式和定时方式两种工作方式。 对每个定时器/计数器(T0和T1),在特殊功能寄存器TMOD中都有一个控制位— C/T来选择T0或T1为定时器还是计数器。
定时器/计数器的核心部件是一个加法(也有减法)的计数器,其本质是对脉冲进行计数。只是计数脉冲来源不同:如果计数脉冲来自系统时钟,则为定时方式,此时定时器/计数器每12个时钟或每6个时钟得到一个计数脉冲,计数值加1;如果计数脉冲来自单片机外部引脚(T0为P3.3,T1为P3.3),则为计数方式,每来一个脉冲加1。
定时器/计数器0有4种工作模式:
模式0(13位定时器/计数器)
模式1(16位定时器/计数器)
模式2(8位自动重装模式)
模式3(两个8位定时器/计数器)
定时器/计数器1除模式3外,其他工作模式与定时器/计数器0相同,T1在模式3时无效,停止计数。
定时器的相关寄存器介绍,在STC单片机官方手册的137页有详细讲解。
手册下载地址: http://www.stcmcu.com
上面图片里就是使用定时器时需要配置的寄存器,其中TL0、TL1、TH0、TH1寄存器用于存放定时器的重装载值,分为高低位两个寄存器。
4.1.3 TCON寄存器
TCON寄存器用于控制定时器,比如: 定时器启动位、定时器溢出标志位等等。
TCON寄存器支持位寻址,在<reg51.h>头文件里也定义了每个位的功能,可以直接操作位进行赋值。
TF1和TF0:是定时器1和定时器0的溢出标志位,当定时器计数溢出时(就是定时器超时),该标志位置1。
TR1和TR0:是定时器1和定时器0的控制位。当赋值为1时,就启动定时器,开始计数。
其他的位是外部中断相关的控制,目前学习定时器时没有用到,先暂时不管。
4.1.4 TMOD寄存器
TMOD寄存器用于配置定时器的模式。
4.1.5 定时器重装值计算方法
51单片机标准架构下一个机器周期是12个时钟周期,如果晶振频率是11.059200MHZ,那一个机器周期的时间就是12/11.059200 微秒。
也就是说定时器的计数器+1的时间就是12/11.059200=1.085069us。
如果定时器工作在16位模式下,最大值可以存放: 0~65535范围的值,那么最大的定时时间就可以得知:65535*1.085069=71109.996915us=71.109996915ms ,大约是71毫秒的时间。
如果需要定时1000us,那么公式就是:x*1.085069=1000,得出x的值就是: 1000/1.085069=921 。
计算出定时器需要+921次刚好得到1000us,但是单片机工作在16位模式情况下,需要加满65535定时器才会溢出,所以需要给定时器赋初值。
65535-921=64614,这样定时器就可以从64614开始计数,当计数到65535时,定时器就会溢出,TF0就会置1,这时刚好经过1000us时间。
那么定时器的重装值寄存器就可以这样赋值:
u16 t0_data=64614;
TH0=t0_data>>8; //高位
TL0=t0_data; //低位
4.1.6 配置定时器0工作在16位定时器模式
下面代码里配置51单片机的定时器0工作在16位定时器模式。程序封装了计算重装值的函数,方便调用,程序没有使用中断,采用轮询方式检测定时器是否超时,在主函数里使用了一个计数变量,记录定时器超时的次数,方便记录更长的时间。
程序里的功能是,时间到达1秒钟,就是改变一次LED灯的状态。
(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)
示例代码:
#include <reg51.h> #define LED P0 //定义LED引脚 u16 T0_Update_data;//定时器0的初始值 void Timer0_16bit_Init(u16 us) { //当前实验板上的晶振实际频率为: 11.956MHZ u16 val=us/(12/11.956); //得到计数的时间,只要整数部分 T0_Update_data=65535-val; //得到重装载值 TMOD&=0xF0; //清除配置 TMOD|=0x01; //配置定时器0工作在16位定时器模式 TH0=T0_Update_data>>8; //定时器0高位重装值 TL0=T0_Update_data; //定时器0低位重装值 TR0=1; //启动定时器0 } //定时器0的重装值更新函数 void Timer0_Update(void) { TH0=T0_Update_data>>8; //定时器0高位重装值 TL0=T0_Update_data; //定时器0低位重装值 } int main() { u32 cnt=0; u8 i=0; Timer0_16bit_Init(1000); //配置定时器超时的时间为1000us LED=0x00; //关闭所有灯 while(1) { if(TF0) //判断定时器0定时时间是否到达 { cnt++;//记录超时次数 if(cnt==1000) { cnt=0; LED=~LED; //取反LED灯 } TF0=0; //清除标志位 Timer0_Update(); //重新给定时器的计数器赋值 } } }
4.1.7 配置定时器1工作在16位定时器模式
下面代码里配置51单片机的定时器1工作在16位定时器模式。程序封装了计算重装值的函数,方便调用,程序没有使用中断,采用轮询方式检测定时器是否超时,在主函数里使用了一个计数变量,记录定时器超时的次数,方便记录更长的时间。
程序里的功能是,时间到达1秒钟,就是改变一次LED灯的状态。
(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)
示例代码:
#include <reg51.h> #include "delay.h" #define LED P0 //定义LED引脚 u16 T1_Update_data;//定时器1的初始值 void Timer1_16bit_Init(u16 us) { //当前实验板上的晶振实际频率为: 11.956MHZ u16 val=us/(12/11.956); //得到计数的时间,只要整数部分 T1_Update_data=65535-val; //得到重装载值 TMOD&=0x0F; //清除配置 TMOD|=0x10; //配置定时器1工作在16位定时器模式 TH1=T1_Update_data>>8; //定时器1高位重装值 TL1=T1_Update_data; //定时器1低位重装值 TR1=1; //启动定时器1 } //定时器1的重装值更新函数 void Timer1_Update(void) { TH1=T1_Update_data>>8; //定时器1高位重装值 TL1=T1_Update_data; //定时器1低位重装值 } int main() { u32 cnt=0; u8 i=0; Timer1_16bit_Init(1000); //配置定时器超时的时间为1000us LED=0x00; //关闭所有灯 while(1) { if(TF1) //判断定时器0定时时间是否到达 { cnt++;//记录超时次数 if(cnt==1000) { cnt=0; LED=~LED; } TF1=0; //清除标志位 Timer1_Update(); //重新给定时器的计数器赋值 } } }
4.1.8 配置定时器0工作在8位自动重装载模式
下面代码里配置51单片机的定时器0工作在8位定时器自动重装载模式,在自动重装载模式下,每次定时器超时之后,就省去了手动赋重装值的过程,比较方便,但是定时器的每次最大定时时间变短了,计数器到达255就会溢出。
程序里封装了计算重装值的函数,方便调用,程序没有使用中断,采用轮询方式检测定时器是否超时,在主函数里使用了一个计数变量,记录定时器超时的次数,方便记录更长的时间。
程序里的功能是,时间到达500毫秒,就改变一次LED灯的状态。
(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)
示例代码:
#include <reg51.h> /* 配置定时器0工作在8位自动重装载模式 注意,时间不能超过定时器最大时间 255*(12/11.059200)=276us */ void Timer0_8bit_Init(u16 us) { //当前实验板上的晶振实际频率为: 11.956MHZ u16 val=us/(12/11.956); //得到计数的时间,只要整数部分 TMOD&=0xF0; //清除配置 TMOD|=0x02; //配置定时器0工作在8位自动重载模式 TL0=TH0=255-val;//得到重装载值; TR0=1; //启动定时器0 } #define LED P0 //定义LED引脚 int main() { u32 cnt=0; u8 i=0; Timer0_8bit_Init(100); //配置定时器超时的时间为100us LED=0x00; //关闭所有灯 while(1) { if(TF0) //判断定时器0定时时间是否到达 { cnt++;//记录超时次数 if(cnt==10*500) //500ms { cnt=0; LED=~LED; } TF0=0; //清除标志位 } } }