前置知识
在我们执行一个程序时,比如LED的流水灯循环,此时如果我们再写一个代码让按键控制流水灯向左或者向右,那么此时会发生,例如:灯向左->按下向右按键->灯向左完->灯向右
这里就会发现,按键按下后会有很大的延迟。
这里就会引入一个中断的概念,我们引入中断之后,就会消除这种延迟。
具体过程可以查看下图:

这里打个比喻:CPU在处理完一次循环LED流水灯之后,才回去检测是否有按键按下改变方向,如果引入中断,就相当于引入了一个响铃,当响铃响起,CPU就知道要改变方向,此时直接改变方向即可。那么CPU就可以专心于流水灯的点亮即可。
这里的机制就是,响铃响起,中断产生,CPU优先处理中断指向的任务(改变方向),回去继续处理中断前的任务(流水线)
所以本节要讲的就是如何引发中断
初始化定时器
定时器与中断系统的基本原理:


具体原理说明请自行百度
这里说明下列代码:
1.赋值TMOD控制定时器1的值:此时为16位定时器,TH0为16位高8位,TL0位16位低8位。
2.赋值TF0:TF0是中断标志,当定时器溢出时,此时TF0=1,说明引发中断,所以一般初始位0。
3.赋值TR0:为1说明定时器0工作,此处没有赋值TR1说明定时器1不工作。
3.TH0与TL0赋值:此处为简单的数学原理,目的是用两个8位来表示16位,定时器最多到65535,我们这里取64535开始,也就是1ms,那么代码就需要取得64535的16位二进制表示m
4.ET0、EA、PT0为连通中断过程。
cpp
void Timer0_Init()
{
TMOD = 0x01; //控制定时器:0000 0001
TF0 = 0; // 清空中断标志
TR0 = 1; // 开始计数
TH0 = 64535/256; // 16进制高位
TL0 = 64535%256; // 16进制低位
ET0 = 1;//连通IE中断过程
EA = 1;//连通IE中断过程
PT0 = 0;//连通IP中断过程
}
初始化定时器(优化版)
上述的代码只是基于对电路的理解来写的,实际上有更方便的方法,可以在STC-ISP软件里自动生成,但是,只能生成定时器的代码,连通中断等步骤还得自己添加:

这里简单说明一下为什么让TMOD进行与或操作,因为TMOD是一个不可寻址的变量,也就是我们不能单独控制一个位的值,只能同时控制整个TMOD的值,那么反过来,像之前控制单个LED灯闪烁的就是可寻址变量。
又因为是不可寻址,我们不能单独改变定时器1或者定时器2,所以我们采用与或的方式在不改变定时器1的条件下改变定时器0。
cpp
void Timer0_Init(void) //1毫秒@12.000MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1;//连通IE中断过程
EA = 1;//连通IE中断过程
PT0 = 0;//连通IP中断过程
}
中断函数
在定时器触发时,会跳转到中断函数处理中断任务:
这里解释一下,我们设置的定时器是1ms触发一次中断,由于定时器溢出时,会将TL0和TH0清零,所以我们在进入中断函数内部需要将这两个变量重新赋值到我们想要的值
接下来T0Count时用来计算中断次数,1ms一次中断可能过于快,那么我们就令其中断1000次,也就是1000ms才让他进入操作,进入操作时,同样的需要将T0Count归0,如何再进行操作。
cpp
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;//计算中断次数
if(T0Count >= 1000)//1000次中断后
{
T0Count = 0;//重新计算
P2_0 =~ P2_0;//灯泡闪烁
}
}
独立按键控制LED流水灯
这里会复用到之前的Delay与key函数,用来捕捉按下的按键,请读者自行复制粘贴
这里解释一下,左移和右移我们调用_crol_与_cror_函数,这样就不用判断移动溢出的情况,会自动从最左边跳到最右边。
我们利用中断的特性,在主函数中不断地捕捉独立按键的情况并根据按下的条件不断更改LEDMode,然后通过中断的特性让cpu不断地根据LEDMode的值来处理LED灯移动。
所以,原本我们以为主函数是在点亮灯,而中断函数是在调整方向;
实际上,是主函数在调整方向,而中断函数在点亮灯。
cpp
#include <REGX52.H>
#include <INTRINS.H>
#include "Timer0.h"
#include "Key.h"
unsigned char KeyNum,LEDMode;
void main()
{
P2 = 0xFE;
Timer0_Init();
while(1)
{
KeyNum = Key();
if(KeyNum)
{
if(KeyNum = 1)
{
LEDMode++;
if(LEDMode >= 2) LEDMode = 0;
}
}
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;//计算中断次数
if(T0Count >= 500)//1000次中断后
{
T0Count = 0;//重新计算
if(LEDMode == 0)
{
P2 = _crol_(P2,1);//左移1位
}
if(LEDMode == 1)
{
P2 = _cror_(P2,1);//右移一位
}
}
}
定时器闹钟
这里需要复用LCD1602等函数。
接下来只给出主模块的实现方式,其他解耦的模块请自行添加。
先解释一下:
与刚刚不同的是,我们这里主函数是用来LCD显示,而中断函数则用来进行计时。
所以我们只需要定义秒,分,时,然后把一下要显示的冒号给显示就好了,非常简单。
而在开头的全局变量赋值是为了更快的检测是否会跳转归零的情况,方便测试。
cpp
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"
unsigned char Sec=50,Min=59,Hour=59;
void main()
{
LCD_Init();
Timer0_Init();
//常驻显示一下定量
LCD_ShowString(1,1,"Clock:");
LCD_ShowString(2,1,":");
LCD_ShowString(2,3,":");
LCD_ShowString(2,6,":");
//循环显示变量
while(1)
{
LCD_ShowNum(2,1,Hour,2);
LCD_ShowNum(2,4,Min,2);
LCD_ShowNum(2,7,Sec,2);
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18;
TH0 = 0xFC;
T0Count++;
//每隔1s则计数,每60秒则清0算1分钟,每60分钟清0算1小时,每24小时清0重新计时
if(T0Count >= 1000)
{
T0Count = 0;
Sec++;
if(Sec >= 60)
{
Sec = 0;
Min++;
if(Min >= 60)
{
Min = 0;
Hour++;
}
if(Hour >= 24)
{
Hour = 0;
}
}
}
}