本文是在学习51单片机的中断系统的简单性总结,着重于51单片机的中断系统的工作原理及如何使用。
首先需要知道51单片机的引脚(如下示意图),其中P3口除了作为输入/输出I/O口,还有第二功能,与中断有关的引脚:
P3.0/RXD
:作为串口接收数据;P3.1/TXD
:作为串口发送数据;P3.2/INT0
:具有外部中断0功能;P3.3/INT1
:具有外部中断1功能;P3.4/T0
:具有定时/计数器0功能;P3.5/T1
:具有定时/计数器1功能;以外部中断0为例,从左侧开始看,IT0控制中断触发方式是下降沿还是低电平,IE0作为中断请求标志由硬件置位发送中断申请。当EX0=1且EA=1时,不考虑中断优先级(中断优先级置1或置0都可),IE0被置位即会进入中断入口。
以定时/计数器0为例,TF0作为定时/计数器溢出中断请求标志由硬件置位发送中断申请或清0。当ET0=1且EA=1时,不考虑中断优先级,TF0置位时会进入中断。
以串口中断为例,RI作为串口接收中断标志位,TI作为串口发送中断标志位,当接收或发送一帧串口数据时会置位,发送中断申请。当ES=1且EA=1时,不考虑中断优先级,RI或TI被置位后即进入中断。
然后要知道中断响应的条件:① 中断源有中断请求;② 此中断源的中断允许位为1;③ CPU开中断。
以外部中断0为例,P3.2口作为外部中断0的中断源。
首先需要IT0设置中断触发方式是下降沿触发中断还是低电平触发中断。
如果是下降沿触发中断即IT0=1,那么P3.2口由高电平变为低电平时就会触发中断,并会置IE0=1,产生中断申请。
最后,在编码时,要编写中断响应条件的代码,还要编写中断响应后的中断处理函数的代码。
中断处理函数的格式如下, 其中返回值类型为空void,函数名自定义,interrupt表示该函数是中断处理函数,0表示中断号。
void user_def_func_name() interrupt 0
{
// 中断处理
}
根据中断类型不同,中断号也不同,中断号如下:
中断源符号 | 名称 | 中断引起原因 | 中断号 |
---|---|---|---|
/INT0 | 外部中断0 | P3.2引脚低电平或下降沿信号 | 0 |
T0 | 定时器0中断 | 定时/计数器0计数回0溢出 | 1 |
/INT1 | 外部中断1 | P3.3引脚低电平或下降沿信号 | 2 |
T1 | 定时器1中断 | 定时/计数器1计数回0溢出 | 3 |
TI/RI | 串行口中断 | 串口通信完成一帧数据发送或接收引起中断 | 4 |
STC89C5X系列单片机提供了4个外部中断:外部中断0、外部中断1、外部中断2、外部中断3。51系列单片机一定有基本的2个外部中断,不全有4个中断,可通过查看芯片手册确认。通常使用基本的2个外部中断:外部中断0和外部中断1。这两个外部中断使用方法一致。
与外部中断0/1相关的寄存器有TCON,IE,IP,
IE-中断允许控制寄存器
CPU对中断系统所有中断以及对某个中断源的开放和屏蔽是由中断允许寄存器IE控制的。IE各位说明如下:
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
字节地址:A8H | EA | ES | ET1 | EX1 | ET0 | EX0 | ||
IE | CPU总中断允许位 | 串行口中断允许位 | 定时/计数器1中断允许位 | 外部中断1允许位 | 定时/计数器中断允许位 | 外部中断0允许位 |
TCON中的各位控制中断请求及中断触发方式。TCON各位说明如下:
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
字节地址:A8H | TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
TCON | 定时/计数器T1 溢出中断请求标志位 |
定时/计数器T1运行控制位 | 定时/计数器T0 溢出中断请求标志位 |
定时/计数器T0运行控制位 | 外部中断1 中断请求标志位 |
外部中断1 触发方式控制位 |
外部中断0 中断请求标志位 |
外部中断0 触发方式控制位 |
其中外部中断触发控制位(IT0/IT1),为高电平时,为边沿触发方式;为低电平时,为电平触发方式。
IP-中断优先级控制寄存器
。
以独立按键通过外部中断0控制LED亮灭为例。
要触发外部中断0中断,需要INT0引脚产生有效信号,外部中断0允许标志位EX0=1且CPU总中断允许位EA=1。
要使INT0引脚产生有效信号,需要外部中断0触发方式控制位IT0选择中断触发方式,IT0置1选择下降沿触发。通过按键连接到INT0引脚,当按键按下时,INT0引脚由高电平变为低电平,产生了一个下降沿,就会触发外部中断0请求中断。CPU响应外部中断0的中断申请,进入外部中断0中断处理函数中,在该函数中控制LED的亮灭。
proteus设计原理图如下:
代码设计。首先编写外部中断0响应条件代码,即外部中断0的初始化函数。如下:
// 外部中断0中断响应条件设置,即外部中断0初始化
void Int0Init()
{
// 1. 设置外部中断0触发方式,选择下降沿触发
IT0=1;
// 2. 设置外部中断0允许标志位为1
EX0=1;
// 3. 设置CPU总中断允许位为1
EA=1;
}
CPU收到外部中断0的中断申请后,进入中断处理函数中。在中断处理函数中控制LED的亮灭。如下:
// 外部中断0中断处理函数,当CPU响应中断后进入该处理函数,在该函数中控制LED亮灭
void Int0() interrupt 0
{
delay(10);
if(0==K1)
{
LED=~LED;
}
}
在主函数中调用外部中断0初始化函数,然后进入循环等待状态。当按键按下时,外部中断0触发中断请求,CPU响应中断请求,进入中断处理函数,中断处理函数结束后,继续返回主函数执行,因为主函数一直在循环状态,所以当下一次按键再次按下时,会再次进入中断处理函数中。主函数代码如下:
void main()
{
Int0Init();
while(1);
}
仿真结果:
外部中断1与外部中断0使用类似。以上个示例为例,把外部中断0改成外部中断1。
proteus设计如下:
代码设计与外部中断0类似。外部中断1初始化函数如下:
// 外部中断1中断响应条件设置,即外部中断1初始化
void Int1Init()
{
// 1. 设置外部中断1触发中断方式,选择下降沿触发
IT1=1;
// 2, 设置外部中断1中断允许位为1
EX1=1;
// 3. 设置CPU总中断允许位为1
EA=1;
}
外部中断1中断处理函数如下:
// 外部中断1中断处理函数,当CPU响应中断INT1后进入该处理函数,在该函数中控制LED亮灭
void Int1() interrupt 2
{
delay(10);
if(0==K2)
{
LED=~LED;
}
}
主函数中将外部中断0的初始化函数Int0Init
改成外部中断1的初始化函数Int1Init
。
仿真结果如下,此时通过外部中断0已经不能控制LED亮灭,因为外部中断0此时不会响应,不会进入到外部中断0中断处理函数中。
如果想外部中断0和外部中断1都能控制LED,那么在主函数中添加调用外部中断0的初始化函数Int0Init
即可。
STC89C5X系列单片机提供了3个定时器:定时器1、定时器1、定时器2。51系列单片机一定有基本的2个定时器,不全有3个定时器,可通过查看芯片手册确认。通常使用的是基本的2个定时器:定时器0和定时器1。这两个定时器使用方式大致类似。
首先需要了解与CPU时序有关的几个周期:振荡周期、状态周期、机器周期、指令周期。
以51单片机外接12MHz的晶振为例,相关周期的具体值为:
振荡周期=1/12MHz=1/12us;
状态周期=2振荡周期=1/6us;
机器周期=6状态周期=12*振荡周期=1us;
指令周期=1~4 * 机器周期=1 ~ 4us;
STC89C5X系列单片机内有两个可编程的定时/计数器T0、T1和一个特殊功能定时器T2。
定时/计数器的实质是加1计数器,是一个16位的计数器,由高8位和低8位两个寄存器THx和TLx组成。其中x代表0或1,当使用定时器0时,x为0,当使用定时器1时,x为1。
随着输入脉冲,加1计数器进行自加1,即每来一个脉冲,计数器就自动加1。当加到计数器全为1时,即THx为FFH,TLx为FFH时,再输入一个脉冲,计数器就会溢出,此时THx=TLx=00H,并且计数器的溢出也会使对应的中断请求标志位(TF0或TF1)置1,向CPU发出中断请求。如果定时器中断允许位(ET0或ET1为1)且CPU总中断EA为1,那么CPU响应中断。
这里需要计算计数初值。加入THx=TLx=00H,那么要计数65536次计数器才会溢出,对于外接12MHz的晶振来说,经过了65536us的时间。通常用来定时1ms的时间,即计数1000次,那么计数初值应该为65536-1000=64536。
当定时器工作于定时模式,则表示定时时间已到;当定时器工作于计数模式,则表示计数值已满。
定时/计数器的工作由特殊功能寄存器TCON、TMOD控制。
TCON-控制寄存器
TCON低四位用于控制外部中断,高四位用于控制定时/计数器的启动和中断申请。各位结构如下:
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
字节地址:88H | TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
定时器1溢出中断请求标志位 | 定时器1运行控制位 | 定时器0溢出中断请求标志位 | 定时器0运行控制位 | 外部中断1中断请求标志位 | 外部中断1中断触发方式位 | 外部中断0中断请求标志位 | 外部中断0中断触发方式位 |
TF1是定时器1溢出中断请求标志位。T1计数溢出时由硬件自动置TF为1,CPU响应中断后TF1由硬件自动清0。T1工作时,CPU可随时查询TF1的状态。TF1也可由软件置1或清0,同硬件置1或清0的效果一样。TF0功能与TF1类似,是定时器0溢出中断请求标志位。
TR1是T1运行控制位,TR1置1时,T1开始工作;TR1置0时,T1停止工作。软件控制TR1置1或清0。TR0功能与TR1类似,是定时器0运行控制位。
TMOD-定时器工作方式寄存器
TMOD用于设置定时器的工作方式,低四位用于T0,高四位用于T1。各位结构如下:
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
字节地址:89H | GATE | C/T | M1 | M0 | GATE | C/T | M1 | M0 |
TMOD | 门控位 | 定时or计数模式选择位 | 工作方式设置位 | 门控位 | 定时or计数模式选择位 | 工作方式设置位 |
定时/计数器工作方式设置 | ||
M1M0 | 工作方式 | 说明 |
00 | 0 | 13位定时/计数器 |
01 | 1 | 16位定时/计数器 |
10 | 2 | 8位自动重装定时/计数器 |
11 | 3 | T0分成两个8位定时/计数器,T1此方式停止计数 |
定时/计数器有四种工作方式:工作方式0、工作方式1、工作方式2、工作方式3。
工作方式0为13位计数,由TLx的低5位和THx的8位组成。TLx的低5位溢出时向THx进位;THx溢出时,置位TCON的TFx标志,向CPU发出中断请求。
工作方式0结构图如下,下图以定时器0为例,GATE通过一个非门和INT0引脚连接一个或门,再与TR0位通过与门连接控制定时器工作,每当到来一个机器周期后,计数器TH0和TL0自加1,直到TH0溢出,置位TF0,向CPU发出中断申请。
当GATE=1时,经过非门后为0,那么此时需要INT0引脚为高电平和TR0置1共同作用下才能启动定时器工作;
当GATE=0时,经过非门后为1,此时不需要INT0引脚,只需TR0置1就可启动定时器工作。
当GATE=1,TR0=1,外中断引脚为高电平启动计数,外中断引脚为低电平停止计数。这种方式常用来测量外中断引脚上正脉冲的宽度。
计数初值X与计数个数N的关系为: X = 2 13 − N X=2^{13} - N X=213−N。或者直接对计数个数取补码+1得到。
方式1的计数位数是16位,TLx作为低8位,THx作为高8位,组成16位的加1计数器。工作方式1的结构图如下所示:
工作方式1计数初值X与计数个数N的关系为: X = 2 16 − N X=2^{16}-N X=216−N。
方式2是自动重装8位计数方式。工作方式2适合于用作较精确的脉冲信号发生器。方式2结构图如下:
工作方式2计数初值X与计数个数N的关系为: X = 2 8 − N X=2^{8}-N X=28−N。
工作方式3只适用于定时/计数器T0,定时器T1处于方式3时停止计数。
工作方式3将T0分成两个独立的8位计数器TLx和THx,方式3结构如下图:
中断响应的三个条件:①中断源有中断请求;②此中断源的中断允许位为1;③CPU开中断。
第一个条件:中断源有中断请求。对于定时器来说,当计数器溢出时会向CPU申请中断。要满足条件,就需要给定时器设置工作方式,设定计数的初值,设置TRx为1启动计数。
第二个条件:此中断源的中断允许位为1,置ET0或ET1为1.
第三个条件:CPU开中断,置EA=1。
通过定时器0控制LED实现闪烁,时间间隔为1s。
因为定时器属于51单片机内部资源,只需要使用软件控制即可。
proteus设计原理图可复用外部中断0的设计。这里使用工作方式1,仅使用TR0位控制启动计数,工作于定时模式。
计时1s可以采用定时1ms,定时1000次的方式。定时1ms的初值为 2 16 − 1000 = 64536 = F C 18 H 2^{16}-1000=64536=FC18H 216−1000=64536=FC18H,即TH0=0xFC,TL0=0x18。
当CPU响应中断后需要在中断处理函数中将计数初值设置为FC18H,否则实现不了下一次定时1ms。
软件设计思路,首先需要设置中断响应的几个条件,即定时器初始化。定时器0初始化代码如下:
// 定时器0中断响应条件设置,即定时器0初始化
void Timer0Init()
{
// 1. 设置工作方式1、设置初值
TMOD|=0x01;
TH0=0xFC;
TL0=0x18;
// 2. 打开定时器0的中断允许标志位
ET0=1;
// 3. 打开CPU总中断允许标志位
EA=1;
// 4. 启动计数
TR0=1;
}
定时器0中断处理函数如下:
// 定时器0冲断处理函数,CPU响应定时器0中断进入该处理函数,在该函数中计算定时1s来控制LED亮灭
void Timer0() interrupt 1
{
static u16 i;
TH0=0xFC;
TL0=0x18;
i++;
if(1000==i)
{
i=0;
LED=~LED;
}
}
主函数中调用定时器0初始化函数Timer0Init
。
仿真结果如下:
定时器1与定时器0使用类似。
定时器1初始化函数如下:
// 定时器1中断响应条件设置,即定时器1初始化
void TImer1Init()
{
// 1. 设置工作方式1、设置初值
TMOD|=0x10;
TH1=0xFC;
TL1=0x18;
// 2. 打开定时器1中断允许标志位
ET1=1;
// 3. 打开CPU总中断允许位
EA=1;
// 4. 启动计数
TR1=1;
}
定时器1中断处理函数如下:
// 定时器0冲断处理函数,CPU响应定时器0中断进入该处理函数,在该函数中计算定时1s来控制LED亮灭
void Timer1() interrupt 3
{
static u16 i;
i++;
TH1=0xFC;
TL1=0x18;
if(1000==i)
{
i=0;
LED=~LED;
}
}
主函数中调用定时器1初始化函数即可。