前面我们学习了定时器的简单用法,并且设计了几个有趣的小项目
今天我们学习进阶操作——中断
1 认识中断
一个生活的小场景……
你拿着手机,屏幕上显示着Zi Jin Code的最新文章,一旁的蓝牙音响播放着悠扬的古风音乐……目不转睛之刻,朋友给你打来一个电话……你调小了蓝牙音箱的音量,接起了电话,你聊得正欢时,门外传来敲门声,班主任来了……
相信这样的场景,你应该也遇到不少,我们常常正在做一件事情,突然另一件事冒出来打断我们,让我们不得不赶快去处理这件事
事实上,单片机的世界里,这样的事件比比皆是。举个最简单的例子,在一个秒表电路中,单片机一直输出数码管扫描显示,这时候定时器溢出了,单片机不得不先放下手中的活,去处理时器溢出的操作,处理完后又返回到刚刚停下来的位置继续来执行输出显示的操作。这个过程中,单片机放下输出显示的任务,先跑去执行定时器溢出的一些操作,就是一次中断的过程,用图表示如下
单片机正常执行程序的过程中,一个特殊的事件让单片机停下现在正在执行的任务,跳转去处理这个事件的任务,处理完之后再返回到刚刚停下来的地方,继续执行最初放下来的任务,这个过程就叫中断,触发的这个事件叫做中断源
到此,相信我们已经初步了解中断的过程,思考以下,那之前我们写的代码(如下)是使用了定时器中断吗
#include< reg52.h >
sbit LED = P1^0;//LED接在P1.0
unsigned char Counter = 0;//溢出次数记录
void main()
{
TH0 = 0x00;
TL0 = 0x00;
TR0 = 1;//Timer0初始值设置,使能定时器
LED = 1;//初始化LED引脚电平值
while(1)
{
if(TF0 == 1)
{
Counter++;//定时器溢出次数++
TH0 = 0x00;//溢出以后要对定时器进行简单的重新装载初始值
TL0 = 0x00;
TF0 = 0;//重新设置定时器溢出位的数值
}
if(counter == 256)//定时器累计溢出256次
{
Counter = 0;//复位
LED = !LED;//LED电平取反
}
}
}
从程序运行流程分析,这并不是一个中断
我们的程序运行到while(1)的时候就进入我们设计的死循环,在这个死循环中有两个任务,第一个是判断Timer0有没有溢出以及如果溢出后的操作,第二个是判断定时器溢出次数是否累计达到我们的需求。
这个代码中,只有执行到if(TF0==0)的时候才会执行判断定时器是否溢出,倘若我们的程序执行到if(Counter==256)的代码的时候定时器已经溢出,那么机器也会继续执行完这个if的语句,直到再次执行到if(TF0==0)才会对timer溢出做出处理。显然不是定时器中断
这种使用定时器的优点是代码简单,而且应对简单的定时器任务的时候可以简洁的代码完成任务(比如说一秒点亮一个led灯),但是应对稍微复杂的任务(例如做一个单片机电子时钟,只有执行到判断Timer溢出的代码才做出判断,这样就可能发生定时不准确的问题了)
至此,希望你跟我一样,也明白了定时器和中断不是一回事,也希望大家不要把定时器和中断搞混
2 中断使用第一步, 中断的分类,认识中断源
C51中的中断,可以这样分类
中断符号 | 中断名称 | 作用 |
---|---|---|
IE0 | 外部中断0 | 对应外部中断INT0 |
IE1 | 外部中断1 | 对应外部中断INT1 |
T0 | Timer0中断 | 定时器溢出后触发的中断 |
T1 | Timer1中断 | |
T2 | Timer2中断 | |
ES | uart串口中断 | 串口中断 |
不难发现,按照功能分,一共有三类:
Timer中断,当定时器溢出的时候触发这个中断
外部中断,C51提供两组外部中断,当外部中断引脚的电平变化的时候触发中断
分别是外部中断0,对应INT0,在P3.2引脚
分别是外部中断0,对应INT1,在P3.3引脚
串口中断
串口中断等到我们使用串口的时候再介绍,这里先放一放。
在单片机中,凡是能触发中断信号就叫中断源,以上的定时器,外部中断,串口都是中断源。
换句话说,就是这些信号触发了单片机的中断。
3 中断使用第二步,打开中断
中断使能寄存器,地址0xA8,可位寻址
位 | 符号 | 复位值 | 功能说明 | 操作说明 |
---|---|---|---|---|
7 | EA | 0 | 总中断使能开关 | EA = 1使能中断总开关 |
6 | - | 0 | - | - |
5 | ET2 | 0 | Timer2中断使能开关 | 写1的时候启用中断,写0的时候不使用中断 |
4 | ES | 0 | 串口中断使能 | |
3 | ET1 | 0 | Timer1中断使能 | |
2 | EX1 | 0 | 外部中断INT1使能 | |
1 | ET0 | 0 | Timer0中断使能 | |
0 | EX0 | 0 | 外部中断INT0使能 |
下面说明一下,假设我们要使用Timer0中断,需要这样操作:
EA = 1;//中断总使能开关
//现在先对中断使能,确保我们能使用中断
ET0 = 1;//使能timer0中断
4 中断使用第三步,认识外部中断
前面我们知道了,C51的外部中断INT0,INT1和串口中断ES分别在P3.2,P3.3,P3.1。这里我们先不介绍串口中断,我们介绍两个外部中断INT0和INT1,串口中断我们学串口的时候再研究。
还记得我们之前学习定时器的时候提到的TCON寄存器吗
TCON寄存器
位 | 符号 | 功能描述 | 操作 | 复位值 |
---|---|---|---|---|
7 | TF1 | Timer1溢出检测 | 定时器中断标志 | 0 |
6 | TR1 | Timer1使能 | TR1=1开启定时器,写0关闭定时器 | 0 |
5 | TF0 | Timer0溢出检测 | 定时器中断标志 | 0 |
4 | TR0 | Timer0使能 | TR0=1开启定时器,写0关闭定时器 | 0 |
3 | IE1 | INT1外部中断请求标志 | 请求标识,值为1的时候表明中断向机器请求中断。机器响应后,自动写0 | 0 |
2 | IT1 | INT1中断触发设置 | 外部中断触发模式设置 | 0 |
1 | IE0 | INT0外部中断请求标志 | 请求标识,值为1的时候表明中断向机器请求中断。机器响应后,自动写0 | 0 |
0 | IT0 | INT0中断触发设置 | 外部中断触发模式设置 | 0 |
上次我们没有介绍低四位的功能,这回我们详细的介绍一下:
首先,IE1,IT1属于外部中断1,也就是外部中断INT1。IE0,IT0属于外部中断0,也就是外部中断INT0。
相信这点不难理解,接下来介绍IE,IT的功能
IE是外部中断请求标识,当外部中断被触发的时候,IE会变成1,表示外部中断在向CPU请求中断。当CPU响应这个外部中断的时候,IE会自动变成0。所以,IE就是用于中断请求的标识,一般使用的时候我们不用操作这一位寄存器
IT是中断触发方式,一共有两种触发方式
触发方式 | 说明 | IT设置值 |
---|---|---|
低电平触发 | 当外部中断引脚电平为低电平的时候触发 | 0 |
负跳变触发 | 当引脚电平由高电平转换到低电平的这个过程中触发(保持在高电平或者低电平都不会触发) | 1 |
这里讲一下什么叫负跳变,假设现在中断引脚电平为高电平,外部电路变化让外部中断触发引脚从高电平变化成低电平,这个时候外部中断引脚的电平变化是1—>0,这就是个负跳变的变化过程。就在这个过程中,触发了中断。1—>0的负跳变过程后,单片机外部中断IO但凡是保持在1或者保持在0都不会触发中断,只有再次发生电平1—>0的变化才会再次触发外部中断
而低电平触发,只要中断引脚的电平为低电平就能直接触发中断。
灵活使用外部中断IT模式的设置,配合我们的程序,玩出新花样!!!
这里不得不声明一下,之前我们学习定时器的时候,我们总是会写这样一行代码
这个代码之前我们说是判断定时器是否溢出的标志,溢出后需要我们手动清零
事实上,当我们使用定时器的时候,就已经不再需要我们手动清零了,如果我们使能定时器的中断,这里的TF就相当于IE一样的中断请求信号,当CPU接受请求(也就是CPU处理中断函数之后,这个TF就自动清零了)
所以我们使用定时器中断的时候就不必要使用
来进行手动清零了。
5 中断使用第四步,中断索引和中断函数的使用
前面我们学习了中断源和中断触发的配置。
中断触发了,我们总要让单片机做一点事情。单片机依靠中断索引知道是哪个中断触发了,并且找到中断函数,执行中断函数的内容。
先来看中断函数的格式,这个是一个典型的中断函数
void Timer1Interrupt() interrupt 1
{
counter++;
}
void 【中断函数名字】() interrupt 【中断索引编号】
{
counter++;
}
【中断函数名字】可以是任何符合函数名规则的函数名
但是括号后的关键字“interrupt”一定不能写错,这个是中断函数的关键字,关键字后面的数字是中断索引
中断索引,单片机靠这个找到中断
索引 | 中断名称 | 符号 | 功能 |
---|---|---|---|
0 | 外部中断0 | IE0 | 外部中断引脚INT0电平变化符合我们设置触发的类型的时候触发外部中断 |
1 | Timer0中断 | T0 | Timer0溢出的时候触发中断 |
2 | 外部中断1 | IE1 | 外部中断引脚INT1电平变化符合我们设置触发的类型的时候触发外部中断 |
3 | Timer1中断 | T1 | Timer1溢出的时候触发中断 |
4 | UART | ES | 串口中断 |
5 | Timer2 | T2 | Timer2溢出的时候触发中断 |
单片机靠这个索引,知道是哪个中断被触发了,并且找到这个中断触发要执行的对应函数上
小总结(一)中断的初始化
到这里,我们来简单总结,对中断进行初始化,中断初始化在main函数中完成,值得一提,必须放在main函数的死循环前面
中断使能寄存器,0xA8,可位寻址
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
符 | EA | - | ET2 | ES | ET1 | EX1 | ET0 | EX0 |
功能 | 中断总开关 | - | Timer2中断使能开关 | UART串口中断使能开关 | Timer1中断使能开关 | 外部中断1中断使能开关 | Timer0中断使能开关 | 外部中断0中断使能开关 |
操作 | 1:打开 | - | 1:打开,0:关闭 |
中断控制寄存器,0x88,可位寻址
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
符 | TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE 0 | IT0 |
功能 | 检测/复位定时1器溢出 | 使能定时器1 | 检测/复位定时器0溢出 | 使能定时器0 | 外部中断1请求信号 | 外部中断1触发信号设置 | 外部中断0请求信号 | 外部中断触0发信号设置 |
操作 | 定时器中断信号 | TR0 = 1使能定时器 | 定时器中断信号 | TR1 = 1使能定时器 | 自动处理,无需干预 | 设置触发模式 | 自动处理,无需干预 | 设置触发模式 |
这里还是再次说明一次
IE是中断请求触发器,当中断触发的时候自动写1,CPU请求后自动写0
IT设置中断触发模式
IT=1 | 负跳变模式,电平由1—>0触发 |
---|---|
IT=0 | 低电平触发模式,低电平的时候自动触发 |
我们使用了timer的中断,这时候TF相当于Timer的中断请求位,cpu响应中断后就自动清零,所以我们就不需要在中断函数里面对TF进行干预
小总结(二)中断索引与中断函数
索引 | 中断符号 | 说明 |
---|---|---|
0 | IE0 | 外部中断引脚INT0电平变化符合我们设置触发的类型的时候触发外部中断 |
1 | T0 | Timer0溢出的时候触发中断 |
2 | IE1 | 外部中断引脚INT1电平变化符合我们设置触发的类型的时候触发外部中断 |
3 | T1 | Timer1溢出的时候触发中断 |
4 | ES | 串口中断 |
5 | T2 | Timer2溢出的时候触发中断 |
中断函数格式
void [中断函数名] () interrupt [中断索引编号]
{
}
这里再次说明,中断函数写在main函数后面
[中断函数名]可以是任何一个符合函数名规则的函数名
interrupt是中断函数的关键字,必须写对
[中断索引编号参考以上的编号]
实践(一)timer中断
下面我们就来实践一下吧
这个程序里面对定时器Timer0初始化(设置一个基本的16位定时器),并且设置定时器中断
#include< reg51.h >
void main ()
{
EA = 1;//【第一步】总中断开关使能
TMOD = 0x01;//【第二步】设置定时器T0的模式为标准的16位定时器
TH0 = 0x00;//【第三步】设置定时器初始值
TL0 = 0xEE;
ET0 = 1;//【第四步】定时器Timer0中断使能
TR0 = 1;//【第五步】激活定时器
while(1)
{
}
}
void Tmr0Int () interrupt 1
{
TH0 = 0x00;
TL0 = 0xFF;//只需要重新设置初始值,不需要复位TF
}
分析一下代码,我们很容易的发现,新的代码里面多了两个对中断的操作:
EA = 1;//【第一步】总中断开关使能
ET0 = 1;//【第四步】定时器Timer0中断使能
EA=1使能总中断开关
ET0=1打开Timer0中断开关
还有一些注意事项:
定时器中断设置的顺序
EA=1 > TMOD设置 > 定时器初始值设置 > ET=1使能定时器中断 > TR=1使能定时器
初始化在main函数的死循环前
中断函数一般写在main函数后面
我们使用了timer的中断,这时候TF相当于Timer的中断请求位,cpu响应中断后就自动清零,所以我们就不需要在中断函数里面对TF进行干预
使用16位定时器中断,模式1,定时器溢出中断后我们仍然需要对定时器初始值进行干预
实践(二)timer中断 ****
下面我们就来实践一下吧
这里我们要用上外部中断0,设置外部中断触发的方式是低电平触发
以下是代码
#include< reg51.h >
void main ()
{
EA = 1;//【第一步】总中断开关使能
IT0 = 0;//【第二步】设置外部中断模式,低电平触发
EX0 = 1;//【第三步】打开外部中断0的使能开关
while(1)
{
}
}
void ExInit0 () interrupt 0
{
}
不难发现,这段代码比设置定时器中断的代码更简单,不过仍然有两个地方需要注意:
第一个当然是初始化操作的顺序
第二个是注意中断函数的索引编号绝对不能错
好了,介绍就到这里了,希望能帮助你更好的了解单片机,如果您觉得还不错的话欢迎关注我
全部0条评论
快来发表一下你的评论吧 !