目录
1.什么是定时器
2.什么是中断
3.什么是定时器中断
4.配置定时器
4.1学习建议
4.2查看手册定时器章节
4.3定时时间计算
4.4示例代码
5.配置定时器0的中断
5.1查看手册中断章节
5.2示例代码
6.定时器0中断配置完整代码示例
7.定时器实现50%亮度的LED
8.定时器中断实现呼吸灯
9.定时器中断搭建前后台任务系统
10.定时器中断实现无延时按键消
定时器,顾名思义,就是用来定时的;计数器,顾名思义,就是用来计数的。初学的小伙伴就会有疑问了,为什么定时器又叫计数器呢?其实就是同一个外设能用作两种功能罢了。
中断即中途打断。因某条件触发而打断当前的事,转而去干别的事情,完后回来继续干。单片机的中断也同理:
上面的这个比例跟单片机的定时器中断是一样的,首先我们在程序上配置定时器(设闹钟),然后程序进入主循环(睡觉)等待中断的到来,若中断标志位被硬件置一(闹钟响了),程序则跳转到中断服务函数执行里面的代码(弹射起来关闹钟),然后回到主循环内被中断的地方继续执行(继续睡觉)。
在这里我想给初学的小伙伴一个建议,那就是一定要学会找手册与读手册!点开这个链接去STC官网下载对应型号的芯片用户手册,人家的手册写的很详细,上面那个中断的解释就是从里面截取的。手册里什么都有,外设的说明,寄存器说明,应用举例,汇编代码以及C代码示例等等。
在学习的时候,你如果是看的人家的视频教程,可以边看视频边对照着手册学习。就算是以后学STM32也是一样,一定要学会找手册,读手册!只有这样你才能学的明白,学的透彻,打好基础!
1.先看第一页,信息量巨大,解释了为什么定时器又是计数器,以及其本质(提示:单片机原理课要考哦!)
其实没有高亮出的那条也很有意思,因为51单片机只有3个定时器(定时器0,1,2),当定时器1用作波特率发生器时,可能导致定时器不够用了,这时定时器0就可以配置到工作模式3:双8位定时器/计数器,即拆分成两个8位定时器,只不过要从定时器1借用中断标志位。
2.紧接着手册里面列出了与定时器相关的寄存器,由于我们要配置的是定时器0中断,所有我就高亮出了只与定时器0相关的位与寄存器。看图:
也就只有TCON寄存器的TF0位,TR0位;TMOD寄存器的低四位;寄存器TL0;寄存器TH0。我们需要配置关心的也就这几位而已。
3.在这里我就先贴出定时器0的结构图,好对照,看看上面涉及的位与寄存器在下图哪里。
4.我们接着手册看,第二页就开始讲解每个寄存器位的作用,首先是TCON寄存器,请对照上图。
TF0解析:他说TF0为定时器/计数器溢出中断标志,这个就类比是闹钟响了嘛,系统会不断的查询此位,如果TF0=1,就会跳转到中断服务函数。他说T0被允许计数:看上图,其实就是control那个开关闭合,前面的脉冲能输入到寄存器TL0,每来一个脉冲,TL0就加一,每当TL0计数到2^8=256后TH0就加一,这样就组成一个16位的计数器,最大能计到2^16=65536。计到最大值后就溢出了嘛,此时TF0就会被硬件置一产生溢出中断。
TR0解析:他说当GATE=0,TR0=1时定时器0就开始计数,对应上图:GATE=0,经过非门(取反)则或门(只要有输入为1,输出则为1)输入1,那么输出则为1,INT0位无效。由于或门输出接到了与门(只有输入都为1,输出才为1)输入,与门的另一个输入TR0也=1,那么与门输出=1,control开关闭合,前面的脉冲才能输入到TL0。
总结TCON初始配置: TF0=0;TR0=1;
5.继续看手册,TMOD寄存器,也是需要对照定时器0结构图看哦,这里每一位他都讲的很清楚了,我就不再扣了。只是需要注意这个寄存器是不可位寻址的!不知道什么意思请百度
我后面的示例代码配置的是模式2:8位自动重装载所以TMOD=0x02;
6.然后手册就对定时器的四种模式进行了解析其实也就是TL0与TH0的组合。都用则是16位,最大计数到65536,但是这样的话就不能硬件自动重装载初始值。只用TL0用来计数的话,就只有8位,最大计数到256发生溢出中断,此时就能把TH0的值自动硬件装载到TL0。也可以TL0只用低5位,TH0全用。
TL0跟TH0是可以赋初值的,比如定时器配置成工作模式2:8位自动重装载,你想计数100就溢出,那么往TL0写入256-100=156就。TH0为重新装载的值,如果你想要每计数100就发生溢出中断,那么TH0也要写入156。
假如我们配置定时器0工作在模式1:16位定时/计数模式,每1ms产生一中断。
SYSCLK即晶振频率,假如用的是12MHZ晶振,那么SYSclk=12MHZ,读下图黄色高亮部分,默认是12T模式,则计数脉冲频率=SYSclk/12=1MHZ。对应计数周期即为1us,TL0每1us自加1。我们想要定时1ms,那计数器初值是不是等于65536-1000?那么TL0=(65536-1000)%256;TH0=(65536-1000)/256。
示例:定时器0中断
晶振频率:12Mhz
工作模式:2
定时时间:100us
/*********定时器0初始化***************/
void Timer0_Init()
{
TMOD=0x02; //8位自动重装载定时器/计数器模式
TH0=156; //重装载值
TL0=156; //定时器初始值 0~256
TF0=0; //清定时器0中断标志位
ET0=1; //开定时器0中断
EA=1; //开总中断
TR0=1; //打开定时器0
}
注:ET0与EA是下节内容,先不管。
1.先理解一下啥是中断嵌套吧
2.中断查询次序即默认中断优先级。在相同优先级下,将按默认优先级处理
3.中断号是啥?有啥用?其实中断查询次序号就是中断号,中断号是用来标明中断服务函数的,告诉系统这个函数是中断服务函数,发生中断后你就跳转到这个函数执行里面的代码。
4.中断结构图,看下面图里红色部分
ET0:就一个单独的开关,默认是断开状态。前面定时器0的溢出中断信号作为开关的输入
EA:所有中断信号的总开关,默认也是断开状态。
只有这两个开关都闭合,定时器01的溢出中断信号才能被系统检测到,单片机才会执行相应的中断服务函数。
中断优先级控制寄存器:IP,XICON,IPH。可以看到默认都设置到了最高优先级中断1,1。这里具体配置就不再详解,请自行阅读手册。
5.综上所述,我们只需要闭合开关ET0和EA就行使能定时器0中断。那我们继续看手册这两个位位于哪个寄存器吧,具体需要怎么配置。
可以看到,这两个控制位位于中断允许寄存器IE,这个寄存器是可位寻址的,ET0=1,EA=1就能使能定时器0中断。
可以看到就只是在前面示例代码的基础上加上ET0=1;EA=1;允许定时器0中断而已。
/*********定时器0初始化***************/
void Timer0_Init()
{
TMOD=0x02; //8位自动重装载定时器/计数器模式
TH0=156; //重装载值
TL0=156; //定时器初始值 0~256
TF0=0; //清定时器0中断标志位
ET0=1; //开定时器0中断
EA=1; //开总中断
TR0=1; //打开定时器0
}
此示例代码只是配置了定时器0在工作模式2下每100us中断一次,不做任何任务。
/*************************************
STC89C52RC定时器中断测试
*************************************
* @author:NachoNEKO
* @date:2023/10/30
* @brief:STC89C52RC定时器0中断测试
**************************************/
#include
/**************变量声明***************/
/***************函数声明**************/
/*********定时器0初始化***************/
void Timer0_Init()
{
TMOD=0x02; //8位自动重装载
TH0=156; //自动重装载值
TL0=156; //装填初值
TF0=0; //清定时器0中断标志位
ET0=1; //开定时器0中断
EA=1; //开总中断
TR0=1; //打开定时器0
}
void main()
{
Timer0_Init();
while(1)
{
}
}
void Timer0_irt() interrupt 1 //定时器0中断 0.1ms/次
{
}
/*************************************
STC89C52RC定时器中断测试
*************************************
* @author:NachoNEKO
* @date:2023/10/30
* @brief:STC89C52RC定时器0中断测试
**************************************/
#include
/**************变量声明***************/
sbit LED = P2^0; //#define LED P20
/***************函数声明**************/
/*********定时器0初始化***************/
void Timer0_Init()
{
TMOD=0x02; //8位自动重装载
TH0=156; //自动重装载值
TL0=156; //装填初值
TF0=0; //清定时器0中断标志位
ET0=1; //开定时器0中断
EA=1; //开总中断
TR0=1; //打开定时器0
}
void main()
{
Timer0_Init();
while(1)
{
}
}
void Timer0_irt() interrupt 1 //定时器0中断 0.1ms/次
{
static unsigned int counter1=0;
if(++counter1==100) counter1=0; //10ms
if(counter1<50) LED=0; //模拟100HZ 50%占空比PWM
else LED=1;
}
在上面代码的基础上,思考,若让比较值慢慢变化起来会达到什么效果?怎样才能让比较值逐渐变起来?
解析:定义一个全局变量compare代替50,compare在主循环里面每10ms自加/减一次。compare自加到100则自减,自减到0则自加,如此循环。是不是就实现呼吸灯的效果了?
/*************************************
STC89C52RC定时器中断测试
*************************************
* @author:NachoNEKO
* @date:2023/10/30
* @brief:STC89C52RC定时器0中断测试
**************************************/
#include
/**************变量声明***************/
sbit LED = P2^0; //#define LED P20
bit turn_flag=1; //呼吸状态翻转标志
unsigned char compare=0;
/***************函数声明**************/
void delay_ms(unsigned char xms);
void LED_Breathing(void);
/*********定时器0初始化***************/
void Timer0_Init()
{
TMOD=0x02; //8位自动重装载
TH0=156; //自动重装载值
TL0=156; //装填初值
TF0=0; //清定时器0中断标志位
ET0=1; //开定时器0中断
EA=1; //开总中断
TR0=1; //打开定时器0
}
void main()
{
Timer0_Init();
while(1)
{
LED_Breathing(); //呼吸灯
delay_ms(10); //阻塞延时10ms
}
}
void Timer0_irt() interrupt 1 //定时器0中断 0.1ms/次
{
static unsigned int counter1=0;
if(++counter1==100) counter1=0; //10ms
if(counter1100) turn_flag=0;
}
else{
compare--;
if(compare<1) turn_flag=1;
}
}
void delay_ms(unsigned char xms)
{
while(xms--){
unsigned char data i, j;
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
现在我们再思考,如果上述的主循环内还有其他业务函数,此业务函数要求每5ms被执行一次,那么此处的10ms阻塞延时函数还能用嘛?程序应该如何改才能实现10ms渐变呼吸灯的同时5ms执行一次此业务函数呢?
模拟前后台任务系统:这里把定时器中断服务函数当作后台,主要负责分配调度任务;主函数当作前台处理任务。
/*************************************
STC89C52RC定时器中断测试
*************************************
* @author:NachoNEKO
* @date:2023/10/30
* @brief:STC89C52RC定时器0中断测试,利
* 用定时器中断搭建前后台系统,充分释放C51性能
**************************************/
#include
/**************变量声明***************/
sbit LED1= P2^0; //#define LED P20
sbit LED2= P2^1;
bit turn_flag = 1;
bit task2_flag=0;
bit task3_flag=0;
unsigned char compare=0;
/***************函数声明**************/
void LED_Breathing(void);
/*********定时器0初始化***************/
void Timer0_Init()
{
TMOD=0x02; //8位自动重装载
TH0=156; //自动重装载值
TL0=156; //初始值
TF0=0; //清定时器0中断标志位
ET0=1; //开定时器0中断
EA=1; //开总中断
TR0=1; //打开定时器0
}
void main()
{
Timer0_Init();
while(1)
{
/***********长时间,周期型任务**********/
if(task2_flag){ //任务2:呼吸灯
LED_Breathing();
task2_flag=0;
}
if(task3_flag){ //任务3:闪灯
LED2=!LED2;
task3_flag=0;
}
}
}
void Timer0_irt() interrupt 1 //定时器0中断 0.1ms/次
{
static unsigned char counter1=0;
static unsigned int counter2=0;
counter1++;
if(counter1>100){ //10ms 100HZ PWM
counter1=0;
task2_flag=1;
}
counter2++;
if(counter2>5000){ //500ms
counter2=0;
task3_flag=1;
}
/**********短时间任务************/
if(counter1100) turn_flag=0;
}
else{
compare--;
if(compare<1) turn_flag=1;
}
}
可以看到,代码里面没有用到任何的阻塞延时函数。
还有需要注意的是,执行中断内程序的总时间不能超过每次中断的时间,否则连续不停的中断会导致主循环内的程序得不到执行,程序卡顿。也不要在中断内执行除法运算,51速度有限,执行一条除法运算时间大概在0.6ms左右。
接上述,我们加入按键任务,实现以下操作:
示例代码:https://download.csdn.net/download/qq_64346054/88517434?spm=1001.2014.3001.5503