蓝桥杯单片机组备赛指南请查看这篇文章:戳此跳转蓝桥杯备赛指南文章
本文章针对蓝桥杯-单片机组比赛开发板所写,代码可直接在比赛开发板上使用。
型号:国信天长4T开发板(绿板),芯片:IAP15F2K61S2
(使用国信天长蓝板也可以完美兼容,与绿板几乎无差别)
1.编程目的
通过对外部中断的学习,了解外部中断的操作过程,并实现独自完成外部中断的编程,通过控制led灯光进行展示效果。
//设置按键S5进行中断操作,L1闪烁为主函数,自动运行,L8常按亮为中断执行程序。
2.外部中断原理
2.1 单片机的中断系统
单片机的中断系统具有很大的用处,赛题中必定会用到。中断系统的响应流程如下:当有中断信号输入时,单片机停止当前的工作,转而去先执行中断服务函数要求的工作,等中断服务函数要求的工作全部做完,单片机又返回去继续执行先前的工作。
比赛用单片机最多允许两层中断的嵌套。即在低级的中断服务函数执行过程中,收到了更高级的中断信号,而去先执行更高级的中断工作,执行完再返回继续执行低级的中断工作,低级的中断工作做完,再回到最原本的代码继续执行。
比赛用芯片IAP15F2K61S2 涉及的中断共有14个,这里介绍常用的5个:
中断号 | 中断名 | 中断名解释 |
0 | INT0 | 外部中断0 |
1 | T0 | 定时/计数器中断0 |
2 | INT1 | 外部中断1 |
3 | T1 | 定时/计数器1 |
4 | TI/RI | 串口中断 |
5 | T2 | 定时/计数器中断2 |
本文聚焦外部中断,只对外部中断0和外部中断1进行讲解,他们的中断号对应为0和2。比赛用开发板中还有INT2,INT3,INT4,共计5个外部中断信号源的介绍,将在专栏“蓝桥杯-单片机组进阶”中讲解。
其余中断的操作请参考本专栏下的其他文章。
2.2 什么叫外部中断?
我们对比赛用开发板的芯片进行观察,可以发现与INT0和INT1相关的两个引脚,分别是P3.2和P3.3
此时如果我们要使用外部中断0,那么可以对P3.2引脚输入低电平,通过观察独立按键的原理图可以得知,当按键S5被按下时,刚好会向单片机的P3.2引脚输入低电平,因此我们可以将按键S5作为一个中断的信号源。
当按下S5时,P3.2引脚读取到低电平,单片机内部产生中断响应,暂停当前程序而优先执行中断服务程序,执行完中断服务程序后返回继续执行当前程序。编程时,我们不需要对单片机引脚P3.2读取到低电平、按键S5被按下等过程进行编程,因为中断的响应只需要通过对寄存器的设置,单片机自动识别中断信号并自动响应。我们只需要关注中断产生之后,我们要做什么(中断服务函数)。
2.3 设置外部中断用到的寄存器
两级中断允许控制:IE(interrupt enable)
EA | - | ET2 | ES | ET1 | EX1 | ET0 | EX0 |
IE中各位的功能如下:
EA—中断允许总开关控制位。EA=0,所有的中断请求被屏蔽。EA=1,所有的中断请求被开放。
ES—串行口中断允许位。ES=0,禁止串行口中断。ES=1,允许串行口中断。
ET1—定时器/计数器T1溢出中断允许位。ET1=0,禁止T1溢出中断。ET1=1,允许T1溢出中断。
EX1—外部中断1中断允许位。EX1=0,禁止外部中断1中断。EX1=1,允许外部中断1中断。
ET0—定时器/计数器T0的溢出中断允许位。ET0=0,禁止T0溢出中断。ET0=1,允许T0溢出中断。
EX0—外部中断0中断允许位。EX0=0,禁止外部中断0中断。EX0=1,允许外部中断0中断。
该寄存器允许位寻址,在编程时,如果只用到一个外部中断0,则我们只需要设置总开关EA和外部中断0允许位EX0 。
两级优先级控制:IP(interrupt priority)
- | - | PT2 | PS | PT1 | PX1 | PT0 | PX0 |
中断优先级寄存器IP各位含义:
PS—串行口中断优先级控制位,1—高级;0—低级。
PT1—T1中断优先级控制位,1—高级;0—低级。
PX1—外部中断1中断优先级控制位,1—高级;0—低级。
PT0—T0中断优先级控制位,1—高级;0—低级。
PX0—外部中断0中断优先级控制位,1—高级;0—低级。
各中断源的中断优先级关系的基本规则:
(1)低优先级可被高优先级中断,高优先级不能被低优先级中断。
(2)任何一种中断(不管是高级还是低级)一旦得到响应,不会再被它的同级中断源所中断。如果某一中断源被设置为高优先级中断,在执行该中断源的中断服务程序时,则不能被任何其他的中断源的中断请求所中断。
当不对其进行设置时,则按照默认优先级执行。如果我们只用到外部中断0的话,则不需要对其进行设置。默认优先级与中断号顺序相同。
特殊功能寄存器:TCON(timer controller)
TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
TCON寄存器中与中断系统有关各标志位功能如下:
TF1—定时器/计数器T1的溢出中断请求标志位。当启动T1计数后,T1从初值开始加1计数,当最高位产生溢出时,硬件 置TF1为“1”,向CPU申请中断,响应TF1中断时,TF1标志硬件自动清“0”,TF1也可由软件清“0”。
TF0—定时器/计数器T0溢出中断请求标志位,与TF1类似。
IE1—外部中断请求1中断请求标志位。
IE0—外部中断请求0中断请求标志位,与IE1类似。
IT1—选择外中断请求1为跳沿触发还是电平触发方式。0--电平触发方式,加到INT0*脚上的外中断请求输入信号为低电平有效,并把IE1置“1”。转向中断服务程序时,则由硬件自动把IE1清“0”。1--跳沿触发方式,加到INT1*脚上的外中断请求输入信号从高到低的负跳变有效,并把IE1置“1”。转向中断服务程序时,则由硬件自动把IE1清“0”。
IT0—选择外中断请求0为跳沿触发方式还是电平触发方式,与IT1类似。
该寄存器允许位寻址,在编程时,如果只用到一个外部中断0,则我们只需要设置IT0从而确定外部中断0的触发方式 。
3.中断在编程中的函数
3.1 外部中断初始化函数
功能是在主函数运行时,将需要用到的中断功能进行设置,使得响应的中断正常运行。例如:
在该函数中,通过设置IT0使得外部中断0为边沿触发,设置EX0和EA使得外部中断0正常开启。
3.2 外部中断服务函数
功能是在单片机响应中断后,去执行我们需要他执行的功能。注意,该函数内部不要设置返回值,通过不要写判断或循环等语句,可以通过设置全局标志的方式实现数据传递。例如:
在该段程序中,我们定义了一个全局变量state_INT0,用于在中断响应后对该变量置一,从而可以在程序中通过对该变量进行查询,获知中断是否产生。编程时,需要注意中断服务函数后面需要加上关键字:interrupt X。X为该中断的中断号,外部中断0为0
4.代码参考
代码效果://设置按键S5进行中断操作,L1闪烁为主函数,L8常按亮为中断执行程序
//设置按键S5进行中断操作,L1闪烁为主函数,L8常按亮为中断执行程序
#include < REG52.h >
#include < INTRINS.h >
sbit S5 = P3^2;
sbit L1 = P0^0;
sbit L2 = P0^1;
void select_HC173 ( unsigned char channal )
{
switch ( channal )
{
case 4:
P2 = ( P2 & 0x1f ) | 0x80 ;
break;
case 5:
P2 = ( P2 & 0x1f ) | 0xa0 ;
break;
case 6:
P2 = ( P2 & 0x1f ) | 0xc0 ;
break;
case 7:
P2 = ( P2 & 0x1f ) | 0xe0 ;
break;
}
}
void Delay2ms ()
{
unsigned char i,j;
_nop_ ();
_nop_ ();
i=22;
j=128;
do
{
while ( --j );
}while ( --i );
}
void clean_shake ( unsigned char t )
{
while ( t-- )
{
Delay2ms ();
}
}
//========================================
void Init_INT0 ()
{
IT0 = 1;
EX0 = 1;
EA = 1;
}
unsigned char state_INT0 = 0;
void Service_INT0 () interrupt 0
{
state_INT0 = 1;
}
void LED_INT0 ()
{
if ( state_INT0 == 1 )
{
L2 = 0;
clean_shake ( 600000 );
L2 = 1;
clean_shake ( 600000 );
}
state_INT0 = 0;
}
//=========================================
void LED_L1_running ()
{
select_HC173 ( 4 );
L1 = 0;
clean_shake ( 20 );
L1 = 1;
clean_shake ( 20 );
}
void main ()
{
Init_INT0 ();
while ( 1 )
{
LED_L1_running ();
LED_INT0 ();
}
}
5.编程思路的小点点
中断的操作主要是涉及两个步骤:
1.对中断进行正确的初始化,并在主函数中运行初始化函数;
2.正确利用中断服务函数,要注意加上中断号,且其内部尽量减少程序语句。并且通过适当的方式将“中断产生”这个事件传递出来,从而进行执行。对于直接在中断服务函数中写入需要执行的程序,以及在中断服务中设置一个标志变量两种不同的方式,他们的对比如下:
第一种方式:在中断服务函数中接入程序体,比较简洁。容易出现一个问题:中断前的代码和中断瞬间的代码同时执行,如主函数是L1闪烁,中断是L8灯闪烁,如果中断发生时L1正好亮了,那么L1会一直亮直到中断函数L8闪烁结束。
第二种方式:在中断服务函数中修改标志变量的值,在外部函数中判断标志变量的值来决定是否执行中断程序。这种方式会将主函数与中断的运行函数拆成两个函数,就会在收到中断后等主函数结束当前阶段再进入中断状态,避免L1和L8同时亮的情况。注意标志变量要定义全局变量,中断服务函数主体尽可能减少操作量。