中断
中断是单片机系统重点中的重点,因为有了中断,单片机就具备了快速协调多模块工作的能力,可以完成复杂的任务。
中断的产生背景
合理巧妙的利用中断,不仅可以使我们获得处理突发状况的能力,而且可以使单片机能够“同时”完成多项任务
定时器中断的应用
定时器一般用法都是采取中断方式来做的。
定时器和中断不是一回事,定时器是单片机模块的一个资源,确确实实存在的一个模块,而中断,是单片机的一种运行机制。
标准 51 单片机中控制中断的寄存器有两个,一个是中断使能寄存器,另一个是中断优先级寄存器。
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
符号 | EA | -- | ET2 | ES | ET1 | EX1 | ET0 | EX0 |
复位值 | 0 | -- | 0 | 0 | 0 | 0 | 0 | 0 |
位 | 符号 | 描述 |
7 | EA | 总中断使能位,相当于总开关 |
6 | -- | -- |
5 | ET2 | 定时器2中断使能 |
4 | ES | 串口中断使能 |
3 | ET1 | 定时器1中断使能 |
2 | EX1 | 外部中断1使能 |
1 | ET0 | 定时器0中断是使能 |
0 | EX0 | 外部中断0使能 |
中断使能寄存器IE的位0~5控制了6个中断使能,而第6位没有用到,第7位是宗门光开关。总开关就相当于我们家里或者学生宿舍的那个电源总闸门,而0~5位这6个位相当于每个分开关。那么也就是说,我们只要用到中断,就要写EA=1
这一句,打开中断总开关,然后用到哪个分中断,再打开相对应的控制位就可以了。
数码管动态显示,带鬼影
#include <reg52.h>
sbit ADDR0 = P1 ^ 0;
sbit ADDR1 = P1 ^ 1;
sbit ADDR2 = P1 ^ 2;
sbit ADDR3 = P1 ^ 3;
sbit ENLED = P1 ^ 4;
unsigned char code LedChar[] = //数码管显示字符转换表
{
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[] = //数码管显示缓冲区,初值为0xFF确保启动时都不亮
{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
void main(void)
{
unsigned char i = 0;//动态扫描的索引
unsigned int cnt = 0;//记录T0中断次数
unsigned long sec = 0;//记录经过的秒数
ENLED = 0;//使能U3,选择控制数码管
ADDR3 = 1;//因为需要动态改变ADDR0-2的值,所以不需要再初始化了
TMOD = 0x01;//设置T0为模式1
TH0 = 0xFC;//设置T0赋初值为0xFC67,定时1ms
TL0 = 0x67;
TR0 = 1;//启动T0
while (1)
{
if (1 == TF0)//判断T0是否溢出
{
TF0 = 0;//T0溢出后,清零中断标志
TH0 = 0xFC;//重新赋初值
TL0 = 0x67;
cnt++;//计数值加1
if (cnt >= 1000)//判读T0溢出是否达到1000次
{
cnt = 0;//达到1000次后计数值清零
sec++;//秒计数加1
//以下代码将sec按十进制位从低到高依次提取并转为数码管显示字符
LedBuff[0] = LedChar[sec % 10];
LedBuff[1] = LedChar[sec / 10 % 10];
LedBuff[2] = LedChar[sec / 100 % 10];
LedBuff[3] = LedChar[sec / 1000 % 10];
LedBuff[4] = LedChar[sec / 10000 % 10];
LedBuff[5] = LedChar[sec / 100000 % 10];
}
//以下代码完成数码管动态扫描刷新
switch (i)
{
case 0:
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 0;
i++;
P0 = LedBuff[0];
break;
case 1:
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 1;
i++;
P0 = LedBuff[1];
break;
case 2:
ADDR2 = 0;
ADDR1 = 1;
ADDR0 = 0;
i++;
P0 = LedBuff[2];
break;
case 3:
ADDR2 = 0;
ADDR1 = 1;
ADDR0 = 1;
i++;
P0 = LedBuff[3];
break;
case 4:
ADDR2 = 1;
ADDR1 = 0;
ADDR0 = 0;
i++;
P0 = LedBuff[4];
break;
case 5:
ADDR2 = 1;
ADDR1 = 0;
ADDR0 = 1;
i = 0;
P0 = LedBuff[5];
break;
default:
break;
}
}
}
}
数码管动态显示,不带鬼影
#include <reg52.h>
sbit ADDR0 = P1 ^ 0;
sbit ADDR1 = P1 ^ 1;
sbit ADDR2 = P1 ^ 2;
sbit ADDR3 = P1 ^ 3;
sbit ENLED = P1 ^ 4;
unsigned char code LedChar[] = //数码管显示字符转换表
{
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[] = //数码管显示缓冲区,初值为0xFF确保启动时都不亮
{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char i = 0;//动态扫描的索引
unsigned int cnt = 0;//记录T0中断次数
unsigned char flagls = 0;//1秒定时标志
void main(void)
{
unsigned long sec = 0;//记录经过的秒数
EA = 1;//使能总中断
ENLED = 0;//使能U3,选择控制数码管
ADDR3 = 1;//因为需要动态改变ADDR0-2的值,所以不需要再初始化了
TMOD = 0x01;//设置T0为模式1
TH0 = 0xFC;//设置T0赋初值为0xFC67,定时1ms
TL0 = 0x67;
ET0 = 1;//使能T0中断
TR0 = 1;//启动T0
while (1)
{
if(1 == flagls)//判断1秒定时标志
{
flagls = 0;//1秒定时标志清零
sec++;//秒计数加1
//以下代码将sec按十进制位从低到高依次提取并转为数码管显示字符
LedBuff[0] = LedChar[sec % 10];
LedBuff[1] = LedChar[sec / 10 % 10];
LedBuff[2] = LedChar[sec / 100 % 10];
LedBuff[3] = LedChar[sec / 1000 % 10];
LedBuff[4] = LedChar[sec / 10000 % 10];
LedBuff[5] = LedChar[sec / 100000 % 10];
}
}
}
/* 定时器中断服务函数 */
void InterruptTimer0() interrupt 1
{
TH0 = 0xFC;//重新赋初值
TL0 = 0x67;
cnt++;//中断次数计数值加1
if (cnt >= 1000)//中断1000次即1秒
{
cnt = 0;//清零计数值以重新开始下一秒计时
flagls = 1;//设置1秒定时标志位1
}
//以下代码完成数码管动态扫描刷新
P0 = 0xFF;//显示消隐
switch (i)
{
case 0:
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 0;
i++;
P0 = LedBuff[0];
break;
case 1:
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 1;
i++;
P0 = LedBuff[1];
break;
case 2:
ADDR2 = 0;
ADDR1 = 1;
ADDR0 = 0;
i++;
P0 = LedBuff[2];
break;
case 3:
ADDR2 = 0;
ADDR1 = 1;
ADDR0 = 1;
i++;
P0 = LedBuff[3];
break;
case 4:
ADDR2 = 1;
ADDR1 = 0;
ADDR0 = 0;
i++;
P0 = LedBuff[4];
break;
case 5:
ADDR2 = 1;
ADDR1 = 0;
ADDR0 = 1;
i = 0;
P0 = LedBuff[5];
break;
default:
break;
}
}
在程序中,有两个函数,一个是主函数,一个是中断服务函数。
中断服务函数的书写格式是固定的,首先中断服务函数前边void表示函数返回值为空,即中断服务函数不返回任何值,函数名是可以在满足命名规则的前提下随机取的,而后是 interrupt 这个关键字,一定不能错,这是中断所特有的关键字,另外后边还有个数字1,数字1是中断函数编号
中断函数编号 | 中断名称 | 中断标志位 | 中断使能位 | 中断向量地址 | 默认优先级 |
0 | 外部中断0 | IE0 | EX0 | 0x0003 | 1(最高) |
1 | T0中断 | TF0 | ET0 | 0x000B | 2 |
2 | 外部中断1 | IE1 | EX1 | 0x0013 | 3 |
3 | T1中断 | TF1 | ET1 | 0x001B | 4 |
4 | UART中断 | T1/R1 | ES | 0x0023 | 5 |
5 | T2中断 | TF2/EXF2 | ET2 | 0x002B | 6 |
如上表,如果需要使能T0中断,就要把它的中断使能位ET0置1,当它的中断标志位TF0变为1时,就会触发T0中断了,那么这时就应该执行中断函数了,单片机又是怎样找到这个中断函数呢?靠的就是中断向量地址,所以 interrupt 后面的中断函数编号的数字 x 技术根据中断向量得出的,它的计算方法是 x*8+3=向量地址。最后算出即为表中的值。
中断函数写好后,每当满足中断条件而触发中断后,系统就会自动来调用中断函数。
中断优先级
中断优先级有两种,一种是抢占优先级,一种是固有优先级。
IP这个寄存器的每一位,表示对应中断抢占优先级,每一位的复位值都是0,当我们把某一位设置为1的时候,这一位的优先级就比其他位的优先级高了。
当进入低优先级中断中执行时,如又发生高优先级的中断,则立刻进入高优先级中断执行,处理完高优先级极中断后,再返回处理低优先级中断,这个过程就叫做中断嵌套,也称为抢占。
所以抢占优先级的概念就是,优先级高的中断可以打断优先级低的中断的执行,从而形成嵌套。当然反过来,优先级低的中断是不能打断优先级高的中断的。
那么既然有抢占优先级,自然就有非抢占优先级,也称为固有优先级。在“终端查询序列”表中的最后一列给出的技术固有优先级,请注意,在中断优先级的编号中,一般是数字越小优先级越高。从表中可以看出一共有1~6共6级优先级,这里的优先级与抢占优先级的一个不同点就是,它不具有抢占的特性,也就是说即使在低优先级中断执行过程中又发生了高优先级的中断,那么这个高优先级的中断也只能等到低优先级中断完成后才能得到响应。既然不能抢占,那么这个优先级有什么作用呢?
答案是多个中断同时存在时的仲裁。比如说有多个中断同时发生了,当然实际上发生这种情况的概率很低,但另外一种情况就常见的多了,那就是出于某种原因我们暂时关闭了总中断,即EA=0,执行完一段代码后又重新使能了总中断,即EA=1,那么在这段时间里就很可能有多个中断都同时发生了,但因为总中断是关闭的,所以它们当时都得不到响应,而当总中断再次使能后,它们就会在同时请求响应了,很明显,这时也必须有个先后顺序才行,这就是非抢占优先级的作用。
抢占优先级和非抢占优先级的协同,可以使单片机中断系统有条不紊的工作,既不会无休止的嵌套,又可以保证必要时紧急任务得到优先处理。
本文来自博客园,作者:EricsT,转载请注明原文链接:https://www.cnblogs.com/EricsT/p/18534248