定时器基本概念
在之前的学习中,涉及到delay的延时模块都是在stc-isp里通过软件延时选项生成代码拷贝过来实现的,其实这就是让CPU不断运算,通过运算时间来规定延时时间,这其实会造成CPU资源的浪费,而且在CPU不断运算数数的时候,无法处理其他重要信号,导致程序经常会反应不及时。所以,需要使用到定时器。
C51的定时器和计数器是同一个硬件电路支持的,通过寄存器的配置不同,就可以将他当作定时器或者计数器来使用,标准的C51有2个定时器/计数器:T0和T1,他们的使用方法一致,C52相比C51多了一个T2。 可以理解为:当计数器使用的时候,数外面的信号,读取针脚的数据; 当定时器使用的时候,靠内部电路数数。
定时器如何定时? 每经过一个机器周期,就加1
什么是晶振? 下图中两个红圈中的就是两个晶振,上面刻写着该晶振的频率,即时钟频率,单位是兆,晶振是数字电路的“心脏”,晶振的好坏会直接影响系统的稳定性。
什么是时钟周期(震荡周期)? 频率的倒数就是周期(1/f = T),所以,时钟周期就是时钟频率的倒数。时钟周期是计算机中最基本的,最小的时间单位,在一个时钟周期内,CPU仅完成一个最基本的动作。
什么是机器周期(CPU周期)? 计算机在一条指令的执行过程中有若干个阶段,如取指,译码,执行等,每一阶段完成一个基本操作,完成一个基本操作所需要的时间称为机器周期。一般情况下,一个机器周期由若干个时钟周期组成。
所以,当一个晶振的频率是11.0592MHz时,即11059200Hz, 所以一个时钟周期就是(1/11059200),查阅手册(不同芯片可能不同),一个机器周期等于12个时钟周期,所以,一个机器周期就是(12/11059200),而一个机器周期之后,定时器就加1,所以,定时器加1,过了(12/11059200)约为 1.085微秒
那么如果想要数出20ms,应该怎么办?需要操作控制定时器的寄存器,寄存器允许16位bit来计数,并且设置了一个溢出的标志位,所以每过(2^16 * 1.085)约为 71毫秒,溢出的标志位就会发生跳变,因此如果想要数出20ms,让定时器从约51毫秒开始数,51毫秒对应 (51000/1.085)约为47004次,47004转化为4位16进制是0xB79C,将0xB79C传入寄存器让其从这个时候开始计数并观察溢出的标志位即可。
实际的寄存器操作
理解了原理之后,同样打开stc-isp,选择右上方的“定时器计算器”,并且填入相应的频率等参数:
即可生成控制定时器来进行定时操作的代码!代码所做的事情就是之前的实现思路,代码的定时初值是0xB800,和我算出的0xB79C只差了一点点,应该是计算的方法稍有差异,但是大体一致。同时,以上代码涉及到很多寄存器操作,接下来一一进行讲解。
按位操作:寄存器通常是8位,且有时每一位都有对应的作用,而我想修改其中的若干位而其他位不变,这时就需要按位操作,“TMOD &= 0xF0”的意义是"与等于“,即TMOD的每一位和1111 0000 来做与操作,1和x相与等于x, 0和x相与等于0,因此这条命令的意义就是将TMOD的高4位不变,第四位 置“0”!
同理,“TMOD |= 0x01”的意义是"或等于“,即TMOD的每一位和0000 0001 来做或操作,0和x相或等于 x , 1和x相或等于 1,因此这条命令的意义就是将TMOD的高7位不变,最低位 置“1”!
两条命令先后执行,就是先将第四位清零,再将最低位置1,其他不变。
当然,其实这两句可以直接变成TMOD = 0xF1; 但是这样写更专业也更有容错性。
总结:清0用0来‘&’,置1用1来‘ | ’
AUXR寄存器,可以通过禁止ALE信号输出来降低单片机时钟对外界的电磁辐射(EMI),它有8位,但只有BIT0和BIT1有作用,AUXR的这句代码在stc-isp中生成的注释不太准确,而且这句赋值在实际应用中也可以注释掉,TMOD寄存器的赋值更为重要。
定时器模式寄存器,TMOD寄存器,共8位,低4位对应定时器/计数器0,高4位对应定时器/计数器1,其中低四位(.0 .1 .2. 3)中的第0位和第1位,M1,M0配合控制定时器的模式选择:16位应该对应 0 1
第2位控制作为计数器还是定时器使用(定时器0计数器1);第3位为GATE信号
TCON寄存器中的TR0位(bit4),为定时器T0的运行控制位。该位由软件置位和清零。给TR0“1”就允许开始定时/计数。
关于上文提到的溢出标志位,其实就是TCON寄存器的TF0位(bit5),为定时器/计数器T0溢出中断标志:当定时器最高位产生溢出时,由硬件置“1”TF0,并向CPU请求中断,一直保持CPU响应该中断时,才将硬件清“0”TF0(TF0也可由程序查询清“0”,即如果不用中断,则需要软件写代码重新置0)
综上,在完成定时器初始化之后,再通过对以上寄存器的位操作,就可以真正实现对于定时器/计数器T0的操控。
中断的基本概念
中断系统是为使CPU具有对外界紧急事件的实时处理能力而设置的。当CPU正在处理某件事情的时候外界发生了紧急事件请求,要求CPU暂停当前的工作,转而去处理这个紧急的事件,处理完之后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。
当有多个中断源同时向CPU请求中断的时候,CPU会根据优先级排序处理中断。 如果CPU在处理一个中断时,出现了一个比该中断优先级还高的中断时,若CPU停止当前中断转而去处理那个高优先级的中断,处理完成后再返回原来的中端。这样的过程叫做中断的嵌套。
如果设置新增加的特殊功能寄存器IPH中的相应位,可将中断优先级设为四级,如果只设置IP或XICON,那么中断优先级就只有两级。
在代码中,如果想要把一个函数定义为中断处理函数:
实际的寄存器操作
在实际代码中,除了要把函数定义为中断处理函数外,中断的实现同样需要对寄存器的操作:
对于定时器0请求的中断,需要先配置中断允许寄存器,然后再配置中断优先级控制寄存器。
中断允许寄存器IE 和 辅助中断控制寄存器XICON
如上图所示,对于中断允许寄存器IE, 允许定时器0 的中断需要 EA和ET0都置1
如上图所示,对于辅助中断控制寄存器XICON,它不控制定时器0的中断使能和优先级,但是他和其他中断的使能或优先级有关,如果需要可以根据图中的信息来调用,此处我暂时不用。
结合中断 通过定时器0 来控制LED灯闪烁的代码实现
#include "reg52.h"
//sbit vibe = P3^3; //震动传感器的 数字量信号输出DO 接在了P3.3
sbit led1 = P3^7;
//sbit jidian = P1^1; //继电器的IN接在了P1.1
//sbit D0 = P1^2;
//sbit D1 = P1^3;
//sbit D2 = P1^4;
//sbit D3 = P1^5;
int cnt = 0;
void Timer0Init(void) //20毫秒@11.0592MHz
{
// AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x00; //设置定时初值
TH0 = 0xB8; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1;
EA = 1; //打开中断!
}
void main()
{
led1 = 1;
Timer0Init();
while(1){
//使用中断后,main函数里的while 1 里啥都不用写
}
}
//timer0的中断处理程序 //中断程序一般写在main函数的后面 //定时器0溢出时将触发这个中断函数
void zhongduan() interrupt 1
{
cnt++;
TL0 = 0x00; //重新给初值,这个很重要!!!!因为定时器0的初始化写在main函数while1的外面,所以只会执行一次,只有中断函数会在每次定时器溢出后被调用,所以必须在中断函数里重新给初值!
TH0 = 0xB8;
if(cnt == 49){
cnt = 0;
led1 = !led1; //每经过(50*20毫秒 = )1秒,led1的亮灭状态翻转一次
}
}