学习笔记|IO中断|中断号大于31|中断优先级|简易中央门禁|STC32G单片机视频开发教程(冲哥)|第十六集:IO中断

文章目录

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。

相关推荐
鸿喵小仙女13 分钟前
C# WPF读写STM32/GD32单片机Flash数据
stm32·单片机·c#·wpf
lucy1530275107927 分钟前
MCU 功耗基准测试
科技·单片机·嵌入式硬件·智能家居·信号处理·工控主板
汤姆和佩琦1 小时前
2024-12-25-sklearn学习(20)无监督学习-双聚类 料峭春风吹酒醒,微冷,山头斜照却相迎。
学习·聚类·sklearn
m0_748240911 小时前
OpenMV与STM32通信全面指南
stm32·单片机·嵌入式硬件
好学近乎知o1 小时前
正则表达式(学习Django过程中可能涉及的)
学习·正则表达式·django
雨中奔跑的小孩1 小时前
爬虫学习案例8
爬虫·学习
jieshenai1 小时前
使用 VSCode 学习与实践 LaTeX:从插件安装到排版技巧
ide·vscode·学习
Cchengzu3 小时前
阿里巴巴2017实习生笔试题(二)
stm32·单片机·嵌入式硬件
灰太狼不爱写代码4 小时前
CUDA11.4版本的Pytorch下载
人工智能·pytorch·笔记·python·学习
重生之我是数学王子7 小时前
单片机 STM32入门
stm32·单片机·嵌入式硬件