文章目录
- 1.什么是IO中断?
- 2.IO中断的用法
-
- [13.1.3 端口中断模式配置寄存器(PxIM0,PxIM1)](#13.1.3 端口中断模式配置寄存器(PxIM0,PxIM1))
- [13.1.1 端口中断使能寄存器(PxINTE)](#13.1.1 端口中断使能寄存器(PxINTE))
- 13.1.2端口中断标志寄存器(PxINTF)
- [13.2 范例程序](#13.2 范例程序)
- 3.1.编写P35口的IO中断
- 3.中断优先级的设置
-
- [3.1 为什么会出现数码管在一位一位的闪动向右刷新过去?](#3.1 为什么会出现数码管在一位一位的闪动向右刷新过去?)
- 3.2.想让数码管刷新不被打断?有什么办法
- 4.实战小练
- 总结
- 课后练习:
1.什么是IO中断?
13 普通1/O口均可中断,不是传统外部中断
STC32G系列支持所有的IO中断,且支持4种中断模式:下降沿中断、上升沿中断、低电平中断、高电平中断。每组IO口都有独立的中断入口地址,且每个IO可独立设置中断模式。
注:STC32G12K128-Beta版芯片的普通IO口下降沿中断和上升沿中断暂时不要使用。可以使用低电平和中高电平中断进行实验。
大家思考下它和外部中断有什么区别?
它不是传统的外部中断,可以说51单片机里面这一个IO中断是首创史无前例的。
2.IO中断的用法
13.1.3 端口中断模式配置寄存器(PxIM0,PxIM1)
它有4种中断模式,配置端口的模式使用:PnIM1.x和PnIMO.x来配置。
本节要做一个P35的一个端口,可以找到我们的P35并且设置它为低电平中断。高位设置为1,低位设置为0。
那么找到我们的P35的一个端口
那也就是我们把P3IM1的这一个位,设置为1,写P35IM0的位设置为0,就可以设置我们的一个模式了。
写的时候就是P3IM1等于0X20(B5置1),P3IM0等于0X00(B5置0)是不是就搞定了。
13.1.1 端口中断使能寄存器(PxINTE)
PnINTE.x:端口中断使能控制位(n=0~7,x=0~7)
0:关闭Pn.x口中断功能
1:使能Pn.x口中断功能
故设置:P35INTE = 0x20就可以使能P35。
13.1.2端口中断标志寄存器(PxINTF)
PnINTF.x:端口中断请求标志位(n=O~7,x=0~7)
0: Pn.x口没有中断请求
1: Pn.x口有中断请求,若使能中断,则会进入中断服务程序。标志位需软件清0。
可以在需要的时候手动清空一下,和定时器一样。
13.2 范例程序
本节是以一个低电平中断来做示例。
5.9 关于中断号大于31在Keil中编译出错的处理
方法1:借用13号中断向量
0~31号中断中,第13号是保留中断号,我们可以借用此中断号,自己研究,本次课程暂不采用。
使用5.9.1 使用网上流行的中断号拓展工具,官网下载:中断号拓展工具,须填写验证码后下载。
完成后选择打开keil的安装目录,自动执行补丁程序。
提示已修改过,安装成功。
3.1.编写P35口的IO中断
中断改变SEGO的数值(显示0-9),延时500ms(仅为了课堂演示效果,实际工程中不能加延时),观察定时器刷新数码管。
数码管一个一个刷新过去,SEGO显示也会自动增加。
实现:
复制上一节的工程,并改名为12.IO中断,修改exit.h和exit.h,增加初始化函数 P3Exit_Init和中断服务函数P3Exit_Isr。
中断号根据手册查询,P3中断为40:
建议把中断程序复制到主函数中去执行:
修改后的exit.c:
C
//========================================================================
// 函数名称:P3Exit_Init
// 函数功能:P3口的IO中断初始化
// 入口参数:无
// 函数返回:无
// 当前版本: VER1.0
// 修改日期: 2023
// 当前作者:
// 其他备注:
//========================================================================
void P3Exit_Init(void)
{
P3IM0= 0x00;
P3IM1 =0xff; //低电平中断
P3INTE=0x20; //P35中断 0010 0000=0x20
}
/* //复制这个文件的时候,记得把这个中断函数复制到主程序
//这个是属于用户型的一个文件(用户需要在里面编写自己的功能),建议将其放在主程序main函数之后,方便更好的引用
void P3Exit_Isr(void) interrupt 40 //中断号为0
{
//编写用户程序,放在这里仅做提醒
}
*/
在demo.c的main函数中加入IO中断初始化,需在总中断EA=1之前执行初始化:P3Exit_Init();
注释掉上节测试中用到的SEG及按键代码段,中断函数中的SEG0累加代码也可以注释掉。
这里为了演示的效果,在中断处理函数中增加了延时代码,实际代码中严禁增加延时代码,一定要记住。
编写中断服务函数模板:
C
void P3Exit_Isr(void) interrupt 40 //中断号为0
{
//编写用户中断服务程序
u8 intf;
intf = P3INTF; //读取P2INTF寄存器的值
if( intf )
{
P3INTF = 0; //判断前先手动清空
if( intf && 0X01 ) //P30按下
{
}
}
}
将模板加入demo.c中的main函数,并将判断端口改为P35,即判断条件改为:intf && 0X04
seg_led.h文件中,将数码管初始化改为不显示:u8 Show_Tab[8] = {20,20,20,20,20,20,20,20};
修改后的函数P3Exit_Isr为:
C
void P3Exit_Isr(void) interrupt 40 //中断号为0
{
//编写用户中断服务程序
u8 intf;
intf = P3INTF; //读取P2INTF寄存器的值
if( intf )
{
P3INTF = 0; //判断前先手动清空中断标志位,必须软件清空
if( intf && 0X04 ) //P35按下,这里存在错误,应改为:if( intf && 0X20 ) //P35按下 0010 0000 = 0X20
{
SEG0 ++; //数码管循环显示0-9
if( SEG0 > 9)
SEG0 = 0;
delay_ms(500); //这边是为了演示一个功能,正式代码中中不能再中断中加延时
}
}
}
编译下载,P35是下排的第5个按钮。按键按下,无任何反应。
把原来初始化显示的横杠全放出来,看看效果。还是无反应,代码存在错误。
排查:判断条件应该为:if( intf && 0X20 ) //P35按下 0010 0000 = 0X20
修改后,重新编译,按动P35,实验发现数码管在一位一位的闪动刷新过去,SEG0显示自加。
3.中断优先级的设置
3.1 为什么会出现数码管在一位一位的闪动向右刷新过去?
因为Timer0_Isr的中断服务函数中执行SEG_LED_Show,循环刷新8位数码管和LED,每10ms完整刷新一次,如果被打断,就会出现上个步骤的显示效果。
中断时可以设置优先级的,看中断结构图:
右侧中断优先级控制之下有4条线,说明它可以设置4个优先级。P0-P7也都是这样可以设置0-3的四级优先级。
高优先级可以打断低优先级,同一优先级中断源靠前的可以先执行,再执行靠后的。
定时器0的中断优先级为:
PT0H,PT0:定时器0中断优先级控制位
00:定时器0中断优先级为0级(最低级)
01:定时器0中断优先级为1级(较低级)
02:定时器0中断优先级为2级(较高级)
03:定时器0中断优先级为3级(最高级)
P3的中断优先级控制位:
PxIPH,PxIP:Px口中断优先级控制位
00:Px口中断优先级为0级(最低级)
01:Px口中断优先级为1级(较低级)
02:Px口中断优先级为2级(较高级)
03:Px口中断优先级为3级(最高级)
初始值都为0,均为最低优先级。
相同优先级,靠前的中断源先执行,执行完之后在执行低中断源,且一个中断源在执行的时候不能被打断。
定时器0和P3中断都是最低优先级,定时器0中断号1,P3中断号40,执行完定时器0,再执行P3,再执行定定时器0,再执行...
同时触发,定时器0也会先执行,不能被中断。
3.2.想让数码管刷新不被打断?有什么办法
1)定时器0中断优先级提高,让定时器可以打断P3口中断
根据上图,可以将定时器0改为最高优先级。
那就是PT0H和PT0的这个位都置1(11:定时器О中断优先级为3级(最高级))。
从定时器0初始化函数Timer0_Init声明跳转至函数体,增加:
C
IP = 0X02; //设置为最高优先级,11,0000 0010 =0X02
IPH = 0X02; //设置为最高优先级,11,0000 0010 =0X02
编译后执行,数码管的显示不会被按键打断了。达到了预期效果。
如果2个优先级一样,想要其中一个中断持续执行而且不会被打断,或者他能打断别人,就可以把优先级提高。
系统里出现多个中断的时也可以给他们这样子排排序。
2)定时器0工作模式设置为模式3,不可屏蔽中断。
TMOD &= 0x30; //设置定时器模式 0011 0000 =0x30
4.实战小练
简易中央门禁控制系统
1.用8个按键代表每个门的门锁开关,8个LED作为每个门的工作状态,点亮表示门己经打开,.熄灭表示门关闭。
2.如遇突发火灾,按下应急按钮立刻打开所有门锁,方便人逃生
3.按下应急按钮后,所有按钮门锁不能上锁,保持畅通。
4.松开应急按钮后,倒计时5秒后恢复之前的状态,并可以操作门锁,
实现:
涉及到8个按键,将矩阵键盘里的代码复制过来,保留适合本例的部分:
C
if(TIM_10MS_Flag == 1) //将需要延时的代码部分放入
{
TIM_10MS_Flag = 0; //TIM_10MS_Flag 变量清空置位
BEEP_RUN(); //蜂鸣运行
KEY_NUM = MateixKEY_Read(); //当前矩阵按键的键值1-8,代表哪个按键被按
//SEG7 = KEY_NUM; //在数码管最后一位显示
if( KEY_NUM > 0) //如果有按键按下
{
//BEEP_ON(2); //蜂鸣20ms
}
}
8个LED作为每个门的工作状态,需要赋初始值:u8 LOCK_State = 0XFF; //门锁初始状态,全部上锁
LED = LOCK_State; //开机之前将门锁状态赋值给LED
前述程序已经读取到了按键的键值,用键值去做判断。MateixKEY_Read()函数返回的数值是1-8,
门锁的状态可以直接异或,即每个位单独取反,但是需要的数值只要0-7即可,直接减1:
C
LOCK_State ~= (1<<(KEY_NUM-1)); //获取当前是第几个按钮按下,数值是1-8的范围,减1 ,转换为0-7
//以KEY_NUM=1为例,1<<1= 0010,异或(0010 NOR)后,得到1111 1101,实现了1位的低电平,与LED状态一致。
这样就实现了用8个按键控制8个门锁的状态。
修改后代码为:
C
if(TIM_10MS_Flag == 1) //将需要延时的代码部分放入
{
TIM_10MS_Flag = 0; //TIM_10MS_Flag 变量清空置位
BEEP_RUN(); //蜂鸣运行
KEY_NUM = MateixKEY_Read(); //当前矩阵按键的键值1-8,代表哪个按键被按下
//SEG7 = KEY_NUM; //在数码管最后一位显示
if( KEY_NUM > 0) //如果有按键按下
{
//BEEP_ON(2); //蜂鸣20ms
LOCK_State ^= (1<<(KEY_NUM-1)); //获取当前是第几个按钮按下,数值是1-8的范围,减1 ,转换为0-7
//以KEY_NUM=1为例,1<<1= 0010,异或(0010 NOR)后,得到1111 1101,实现了1位的低电平,与LED状态一致。
}
LED = LOCK_State; //输出门锁状态
}
接下来实现"2.如遇突发火灾,按下应急按钮立刻打开所有门锁,方便人逃生"。
拟用P35作为总控开关,在P3Exit_Isr中断里修改添加。
首先门锁要打开:LED0 = 0X00; //打开所有门锁
持续按下的时候,数码管显示5s倒计时,SEG0 = 5; //5S倒计时
5S倒计时需要重新增加变量,u16 Time_CountDown = 0; //全局变量放在main函数外部,文件里所有地方都可以调用.
修改后的P3Exit_Isr:
C
void P3Exit_Isr(void) interrupt 40 //中断号为0
{
//编写用户中断服务程序
u8 intf;
intf = P3INTF; //读取P2INTF寄存器的值
if( intf )
{
P3INTF = 0; //判断前先手动清空中断标志位,必须软件清空
if( intf && 0X20 ) //P35按下 0010 0000 = 0X20
{
LED0 = 0X00; //打开所有门锁
SEG0 = 5; //5S倒计时,数码管持续显示
if( Time_CountDown > 0)
Time_CountDown = 500;//5S倒计时变量
//SEG0 ++; //数码管循环显示0-9
// if( SEG0 > 9)
// SEG0 = 0;
// delay_ms(500); //这边是为了演示一个功能,正式代码中中不能再中断中加延时 }
}
}
}
如果Time_CountDown大于0,说明还一直卡在这个中断里,增加判断:
C
if(TIM_10MS_Flag == 1) //将需要延时的代码部分放入
{
TIM_10MS_Flag = 0; //TIM_10MS_Flag 变量清空置位
if( Time_CountDown == 0) //如果没有按下过应急按钮,按下后触发中断变为500,只有在0的时候才能控制按钮
{
KEY_NUM = MateixKEY_Read(); //当前矩阵按键的键值1-8,代表哪个按键被按下
BEEP_RUN(); //蜂鸣运行
if( KEY_NUM > 0) //如果有按键按下
{
LOCK_State ^= (1<<(KEY_NUM-1)); //获取当前是第几个按钮按下,数值是1-8的范围,减1 ,转换为0-7
//以KEY_NUM=1为例,1<<1= 0010,异或(0010 NOR)后,得到1111 1101,实现了1位的低电平,与LED状态一致。
}
LED = LOCK_State; //输出门锁状态
SEG0 = 0x20; //如果按键能按动,将数码管熄灭
}
else //按下了应急按钮,
{
Time_CountDown--; //时间倒计时
SEG0 = Time_CountDown/100+1; //500要显示为单位s,除100
}
}
数码管初始化为熄灭:u8 Show_Tab[8] = {20,20,20,20,20,20,20,20};
编译运行。依次按键,门锁代表的灯亮,8个按键控制8个门锁,也能单独控制。但是蜂鸣器不响。
在按键按下的判断中,增加蜂鸣代码:BEEP_ON(2); //蜂鸣20ms
重新编译下载,按下P35,数码管显示5,LED全亮(门全开),按动其他按钮无用。松开,数码管开始倒计时,
0的时候熄灭,门锁恢复之前的状态。
总结
1.了解外部和IO中断的区别,什么时候用哪个中断.
外部中断只有单次触发,要么就是按下或者松开,也就是上升、下降沿,或者单纯的下降沿才能执行;
IO中断暂时只能用高电平中断或者低电平中断,他是可以持续进入中断的,像本例,如果一致按下P35,不允许进行其他操作,
就可以用这个IO中断。那如果说你只要按下一次,然后立马就报警的那种,1次就可以,什么时候用哪个,一定要区分清楚。
2.学会使用IO中断,设置中断的模式
3.学会中断向量号的扩展
4.学会中断优先级的使用
课后练习:
1.今天的小实验倒计时5秒用的一位数码管,改成4位数码管,倒计时50秒,最小单位为10ms。
2.尝试编写P54口的中断,让数码管1从0-9计数
3.修改中断优先级,让P54口的中断优先级高于P35,让他可以打断P35。