【51单片机】单一外中断的原理与应用
一、 任务目标
-
在Proteus和普中单片机板上分别完成按键中断控制流水灯的实验。
1)在Proteus和普中单片机板上分别完成采用定时计数器控制LED灯每隔1s周期性亮灭的实验(即上次实验任务相同的任务)。
要求Keil仿真中的虚拟逻辑仪对LED管脚进行波形观察,测量真实的周期数,并与上次采用软件循环进行周期定时的精度进行对比,看哪一种方式更加精准。
2)采用计数器中断,实现按4次按钮开关后,P1口的8只LED闪烁不停。
-
思考题
能否换一种更合理的方式,不在中断函数使用延时循环,实现同样的功能。
二、中断系统的工作原理
2.1 中断响应
中断技术主要用于实时监测与控制,要求单片机能及时地响应中断请求源提出的服务请求,并快速响应与及时处理。
以下是AT89S51的中断系统结构。
如图,在使用中断时(以INT0为例),先打开总允许EA,其次打开源允许EX,之后再打开允许中断IT0。
因此打开中断INT0的代码为
EA=1;
EX0=1;
IT0=1;
2.2 中断服务函数
2.2.1 一般形式
中断服务函数一般形式为:
函数类型 函数名(形式参数表)interrupt n using n
关键字interrupt后面的n是中断号,对于8051单片机,n的取值为0~4,编译器从8×n+3处产生中断向量。
2.2.2 注意事项
(1)中断函数没有返回值,如果定义一个返回值,将会得到不正确结果。因此应将中断函数定义为void类型,明确说明无返回值。
(2)中断函数不能进行参数传递,如果中断函数中包含任何参数声明都将导致编译出错。
(3)任何情况下都不能直接调用中断函数。后面代码可以看到,我们在主函数中不会直接调用中断函数。
(4)如在中断函数中再调用其他函数,则被调用的函数所用的寄存器区必须与中断函数使用的寄存器区不同。
三、实验过程
3.1 按键控制流水灯
3.1.1 LED灯定时亮灭
#include <REGX52.H>
#include<intrins.h>
void Delay_ms(int ms)
{
int i,j;
for(i=0;i<50;i++)
{
for(j=0;j<ms;j++)
{
_nop_();
}
}
}
void main()
{
int i,temp=0xFE;
P1=temp;
while(1)
{
for(i=0;i<8;i++)
{
P1=~(0x01<<i);
//0x01即0000 0001,<<是左移1位,即0000 0010,~是取反。即意味着从1111 1110变为1111 1101
Delay_ms(1000);
}
}
}
3.1.2 KEIL波形监测
两个波形分别是analog(模拟信号)与bit信号的波形。
可以观察到,bit波形形成周期性变化,每个周期耗时10.21613s,平均每次LED闪烁变化耗时近似1s。
相较于我们上次实验中软件仿真更加直观,且精确度更高。
值得一提的是,这里的波形之所以会这样变化是因为P1引脚信号从0xfe到0x7f,数值分别从254到253、251、247、239、223、191、127的变化。因此呈现如下波形。
3.2 计数器中断控制
#include <REGX52.H>
#define uchar unsigned char
int count=0;
uchar display[9]={0xff,0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
void Delay_ms(int ms)
{
int j;
for(;ms>0;ms--)
{
for(j=0;j<50;j++)
{;}
}
}
void main()
{
unsigned int a;
EA=1;
EX0=1;
IT0=1;
while(1)
{
P1=0x00;
}
}
void int0(void) interrupt 0 using 0
{
unsigned int a;
EX0=0;
count++;
if(count==4)
{
while(1)
{
for(a=0;a<9;a++)
{
Delay_ms(500);
P1=display[a];
}
}
}
EX0=1;
}
四、思考拓展
4.1 中断函数优化
在中断服务函数中执行耗时操作(如延时循环)通常是不推荐的,因为这会导致中断响应时间的延长,并且可能影响到主程序的执行效率。
因此我们的优化思路是在中断服务函数中仅设置状态标志,然后在主循环中根据这个标志来执行相应的操作。
对于当前的示例,我们可以定义几种不同的LED灯模式(比如模式0:全亮,模式1:流水灯),然后在外部中断发生时切换这些模式。
4.2 执行代码
因此执行代码应该如下:
#include <REGX52.H>
#define uchar unsigned char
//流水灯状态数组
uchar display[9]={0xff,0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
//定义几个LED模式
#define MODE_ALL_ON 0 //全亮
#define MODE_STRING 1 //流水灯
//LED模式标志,初始状态为全亮
uchar ledMode=MODE_ALL_ON;
//LED闪烁次数计数器
uchar blinkCount=0;
//按键次数计数器
uchar Count=0;
//延时函数
void Delay_ms(int ms)
{
int j;
for(;ms>0;ms--)
{
for(j=0;j<50;j++){}
}
}
//外部中断0的中断服务函数
void int0() interrupt 0 using 0
{
//初始化闪烁次数计数器
blinkCount=0;
//按键次数++
Count++;
//切换LED模式
if(Count==4)
{
ledMode=(ledMode==MODE_ALL_ON)?MODE_STRING:MODE_ALL_ON;
Count=0;
}
//清除外部中断0标志
EX0=1;
}
void main()
{
unsigned int a;
EA=1;
EX0=1;
IT0=1;
while(1)
{
switch(ledMode){
case MODE_ALL_ON:
P1=0x00; //全亮
break;
case MODE_STRING:
if(blinkCount<3){ //流水闪烁3次
for(a=0;a<9;a++)
{
Delay_ms(500);
P1=display[a];
}
blinkCount++;
}
else{
ledMode=MODE_ALL_ON;//流水闪烁3次后切换回全亮模式
blinkCount=0; //重置闪烁次数计数器
}
break;
default:
P1=0x00;
break;
}
}
}
4.3效果演示
五、 总结与思考
本篇实验暂时只用到一个中断INT0,而没有思考实现多重中断条件下的代码。
1、多重中断需要考虑优先级,而本次实验只用了一个中断无需考虑优先级,但是使用中断时需要将中断打开,每次中断实现后将中断关闭,返回原本程序流程。
2、为提高程序执行效率,中断函数中不建议使用延时函数。因此在使用中断函数时,可以设置标志位,通过中断的打开与关闭改变标志位,这样缩减了中断时间,提高了程序的运行效率,且增强了代码的可读性。
以上则是我本次的探究内容,如有错误请各位大佬多多指教!