第2-5讲:I/O中断
-
- 学习目的
- 学习中断的相关概念。
- 掌握中断向量大于 31的处理方法。
- 掌握中断服务程序的编写。
- STC8A8K64D4 I/O中断
STC8A8K64D4单片机的所有GPIO都支持I/O 中断,且支持 4 种中断触发模式:下降沿中断、上升沿中断、低电平中断和高电平中断。
I/O中断的中断号如下表所示,P0端口到P7端口的中断号为37~44,都是大于31的,而KEIL中支持的中断号为0~31,因此,I/O中断的中断服务函数如果使用他们的中断号是无法编译的。
宏晶科技给出的解决方案是:借用第13号中断入口地址,详见《STC8A8K64D4系列单片机技术参考手册》中的《附录Q:关于中断号大于 31 在 Keil 中编译出错的处理》。在后面的程序编写小节,我们会说明编写程序时如何解决这个问题。
表3:I/O中断的中断号
中断号 | 中断向量 | 中断类型 |
37 | 012B H | P0口中断 |
38 | 0133 H | P1口中断 |
39 | 013B H | P2口中断 |
40 | 0143 H | P3口中断 |
41 | 014B H | P4口中断 |
42 | 0153 H | P5口中断 |
43 | 015B H | P6口中断 |
44 | 0163 H | P7口中断 |
从上表中,我们也可以看到,I/O中断是针对IO端口的,只有端口有独立的中断入口地址,不是每个GPIO都有独立的入口地址的,但是每个 GPIO 可独立设置是否开启I/O中断以及中断模式。
- 编程知识点:电平触发和边沿触发的区别
下图描述了边沿触发和电平触发(低电平触发为例,高电平触发类似)的时机。
图8-:电平触发和边沿触发的区别
- 下降沿触发:当电平从高电平转变为低电平转变时,触发中断。
- 上升沿触发:当电平从低电平转变为高电平转变时,触发中断。
- 电平触发(低电平触发为例,高电平触发类似):低电平时间内中断一直有效的,在低电平期间,当我们处理完中断,退出中断服务函数后,会再次触发中断。因此,我们使用电平中断时常采用这样的做法:在中断退出前关闭中断,等后面恰当时机再打开。
- 软件设计
- I/O中断应用步骤
- 软件设计
I/O中断的应用流程如下图所示,其中配置GPIO为输入以及开启GPIO的上/下拉电阻在前文已经讲解过,这里不再赘述。本节中,我们重点描述I/O中断初始化和中断服务程序的编写。
图8-:I/O中断应用步骤
-
-
-
- 初始化I/O中断
-
-
初始化I/O中断所要做的工作主要包括使能GPIO的I/O中断、配置I/O中断的触发方式以及中断优先级,最后开启总中断。
- 使能GPIO的I/O中断
I/O中断的开启和关闭由端口中断使能寄存器(PnINTE,n=0~7)控制,如下图所示。
图7-:端口中断使能寄存器
- PnINTE.x:端口中断使能控制位(n=0~7,x=0~7)
- 0: 关闭 Pn.x 口中断功能。
- 1: 使能 Pn.x 口中断功能。
- 配置I/O中断的触发方式
I/O中断的触发方式由端口中断模式配置寄存器(PnIM0,PnIM1,n=0~57)控制,如下图所示。
图8:端口中断模式配置寄存器(PnIM0,PnIM1)
PnIM1.x PnIM0.x的组合决定了端口中各个I/O的中断触发方式,如下表所示。
表8-:端口中断触发模式
PnIM1.x | PnIM0.x | Pn.x 口中断模式 |
0 | 0 | 下降沿中断 |
0 | 1 | 下降沿中断 |
1 | 0 | 低电平中断 |
1 | 1 | 高电平中断 |
- 配置示例:配置P0.0为低电平中断
P0IM1 |= 0x01; // P0IM1的位0设置为1
P0IM0 &= ~0x01; // P0IM0的位0设置为0
- 配置中断优先级
I/O中断的优先级由端口中断优先级控制寄存器(PINIPL,PINIPH)控制,如下图所示。STC8A8K64D4有P0~P7共8个端口,因此,STC8A8K64D4使用PINIPL和PINIPH中的位0~位7配置中断优先级。
图7-:中断优先级控制寄存器
- PxIPH,PxIP:Px(x=0~7)口中断优先级控制位
- 00: Px 口中断优先级为 0 级(最低级)。
- 01: Px 口中断优先级为 1 级(较低级)。
- 10: Px 口中断优先级为 2 级(较高级)。
- 11: Px 口中断优先级为 3 级(最高级)。
- 配置示例:配置P0端口的中断优先级为最高级3
PINIPH |= 0x01; // PINIPH的位0设置为1
PINIPL |= 0x01; // PINIPL的位0设置为1
-
-
-
- 编写中断服务函数
-
-
由于I/O中断的中断号都是大于31的,在 KEIL中无法直接编译,因此,根据宏晶科技提供的解决方法,我们可以借用0~31中断里面没有使用的中断号,在该中断的入口地址放一条跳转的代码,当该中断产生时,跳转语句执行,跳转到I/O中断服务函数,由此,解决中断号大于31,KEIL中无法直接编译的问题。
在借用中断号时,首先借用第13号中断入口地址,因为第13号是保留中断号,不会占用系统的其他中断,其次,再借用程序中未使用的中断号。
该方法是需要借助汇编代码实现的,具体的做法如下:
- 新建一个汇编语言文件isr.asm,并将该文件加入到工程的USER组。
- isr.asm文件中加入如下的代码,这段代码实现的是P1端口借用第13号中断入口地址,P1端口地址是0133H,如果是其他端口,修改这个中断入口地址即可。各个端口的中断入口地址可以在STC8A8K64D4的数据手册中查到(《STC8A8K64D4系列单片机技术参考手册》第958页:中断列表)。
代码清单:引用头文件
- CSEG AT 0133H ; P1口中断入口地址,如果是其他端口,修改这个中断入口地址即可
- JMP P1INT_ISR
- P1INT_ISR:
- JMP 006BH ; 借用13号中断的中断入口地址
- END
- 中断服务程序
isr.asm文件中借用的是13中断,因此,中断服务程序中的中断号就要使用13,这样,I/O中断产生后,就可以正确地跳转到I/O中断的中断服务函数了。下面是以P1端口I/O中断的中断服务函数示例,借用了13中断,中断产生后,在中断服务函数中读取P1端口中断标志,从而确定是P1端口的哪个I/O触发了中断。
代码清单:中断服务函数示例
- /*************************************************************************************
- 功能描述:P1口中断服务函数,借用第13号中断
- 参 数:无
- 返 回 值:无
- **************************************************************************************/
- void p1_isr (void) interrupt 13
- {
- u8 psw2_st;
- u8 intf;
- psw2_st = P_SW2;
- P_SW2 |= 0x80; //将EAXFR位置1,使能访问XFR(扩展RAM区特殊功能寄存器)
- intf = P1INTF; //读出P1端口中断标志,用来判断具体是P1口的哪个I/O产生了中断
- if(intf)
- {
- P1INTF = 0x00; //I/O中断标志必须软件清零
- if(intf & 0x01) //P1.0口中断
- {
- //用户功能代码
- }
- if(intf & 0x02) //P1.1口中断
- {
- //用户功能代码
- }
- if(intf & 0x04) //P1.2口中断
- {
- //用户功能代码
- }
- if(intf & 0x08) //P1.3口中断
- {
- //用户功能代码
- }
- if(intf & 0x10) //P1.4口中断
- {
- //用户功能代码
- }
- if(intf & 0x20) //P1.5口中断
- {
- //用户功能代码
- }
- if(intf & 0x40) //P1.6口中断
- {
- //用户功能代码
- }
- if(intf & 0x80) //P1.7口中断
- {
- //用户功能代码
- }
- }
- P_SW2 = psw2_st; //将EAXFR位置0,关闭访问XFR
- }
-
-
- 外部中断实验(使用一个端口的I/O中断)
-
- 注:本节的实验是在“实验2-3-2:触摸按键检测”的基础上修改,本节对应的实验源码是:“实验2-5-1:I/O中断(使用一个端口的I/O中断)”。
-
-
- 实验内容
-
-
- 配置P3.7为I/O中断(下降沿中断),这里使用P3.7为例,是因为P3.7连接了按键KEY1,方便测试。按下按键KEY1触发P3端口I/O中断,中断服务函数中翻转指示灯D1的状态(由亮变灭或由灭变亮)。
- P3端口中断号大于31的处理。
-
-
- 代码编写
-
-
- 新建一个名称为“gpio_int.c”的文件及其头文件“gpio_int.h”并保存到工程的“Source”文件夹,并将“gpio_int.c”加入到Keil工程中的“SOURCE”组。
- 引用头文件
因为在“main.c”文件中使用了“gpio_int.c”文件中的函数,所以需要引用下面的头文件“gpio_int.h”。
代码清单:引用头文件
- //引用I/O中断头文件
- #include "gpio_int.h"
- 编写跳转的汇编代码
本例中,我们只使用了一个端口的I/O中断,因此,我们借用13号中断即可。在STC8A8K64D4的数据手册中(《STC8H系列单片机技术参考手册》第417页:STC8H中断列表)查到P3的I/O中断入口地址是“0143H”,所以,中断跳转代码如下。
代码清单:汇编跳转代码
- CSEG AT 0143H ;P3口中断入口地址,如果是其他端口,修改这个中断入口地址即可
- JMP P3INT_ISR
- P3INT_ISR:
- JMP 006BH ;跳转到13号中断的入口地址
- END
- 编写中断初始化函数
中断初始化函数中将P3端口中的P3.7 I/O中断进行初始化,配置为下降沿触发,最低级0级的中断优先级,代码清单如下。
代码清单:中断初始化
- /*****************************************************************************************
- 功能描述:P3端口中的P3.6 I/O中断初始化,下降沿触发,优先级:0
- 参 数:无
- 返 回 值:无
- ******************************************************************************************/
- void p3_int_init(void)
- {
- P_SW2 |= 0x80; //将EAXFR位置1,使能访问XFR(扩展RAM区特殊功能寄存器)
- P3INTF &= ~0x80; //将P3.7 I/O中断标志位清"0"
- P3INTE |= 0x80; //使能P3.7的I/O中断
- P3IM1 &= ~0x80; //下降沿触发方式
- P3IM0 &= ~0x80;
- PINIPH &= ~0x08; //设置P3的中断优先级为0
- PINIPL &= ~0x08;
- P_SW2 &= 0x7F; //将EAXFR位置0,关闭访问XFR
- }
- 编写中断服务函数
本例中只使用了P3端口的P3.7的I/O中断,因此,中断服务函数里面只需判断P3.7的中断标志即可(P3INTF的位7)。另外,I/O中断的标志需要软件清零,这一点和外部中断不一样,外部中断硬件会自动清零。因此,中断服务函数中需要清零I/O中断标志(P3INTF),代码清单如下。
代码清单:中断服务函数
- /*****************************************************************************************
- 功能描述:P3口中断服务函数,我们使用了P3端口中的P3.7,因此,中断服务函数中判断P3.7的中断标志即可(P3INTF的位7)
- 参 数:无
- 返 回 值:无
- *****************************************************************************************/
- void p3_isr (void) interrupt 13
- {
- u8 psw2_st;
- u8 intf;
- psw2_st = P_SW2;
- P_SW2 |= 0x80; //将EAXFR位置1,以访问在XDATA区域的扩展SFR
- intf = P3INTF;
- if(intf)
- {
- P3INTF = 0x00; //清零中断标志
- if(intf & 0x80) //P3.7中断
- {
- led_toggle(LED_1); //翻转用户指示灯D1
- }
- }
- P_SW2 = psw2_st;
- }
- 主函数
主函数中配置P3.7为准双向输入并开启上拉电阻,接着调用P3端口的I/O中断初始化函数p3_int_init ()完成P3.7的I/O中断初始化,之后开启总中断即可。
代码清单:主函数
- /*****************************************************************************************
- 功能描述:主函数
- 参 数:无
- 返 回 值:int类型
- *****************************************************************************************/
- int main(void)
- {
- P2M1 &= 0xBF; P2M0 &= 0xBF; /配置P2.6为准双向口(指示灯D1)
- P3M1 &= 0x7F; P3M0 &= 0x7F; //配置P3.7为准双向口(按键KEY1)
- p3_int_init(); //初始化P3.7 I/O中断
- EA = 1; //允许总中断
- while(1)
- {
- }
- }
-
-
- 硬件连接
-
-
本实验需要使用LED指示灯D1和按键KEY1,因此需要用跳线帽短接复用引脚的指示灯(D1)和按键(KEY1)。
图8-:跳线帽短接
-
-
-
- 实验步骤
-
-
- 解压“…\第3部分:配套例程源码”目录下的压缩文件“实验2-5-1:I/O中断(使用一个端口的I/O中断)”,将解压后得到的文件夹拷贝到合适的目录,如“D\STC8”(这样做的目的是为了防止中文路径或者工程存放的路径过深导致打开工程出现问题)。
- 双击“…\gpio_int\project”目录下的工程文件“gpio_int.uvproj”。
- 点击编译按钮编译工程,编译成功后生成的HEX文件“gpio_int.hex”位于工程的“…\gpio_int\Project\Object”目录下。
- 打开STC-ISP软件下载程序,下载使用内部IRC时钟,IRC频率选择:24MHz。
- 程序运行后,按下按键KEY1触发I/O中断,中断服务函数中会翻转指示灯D1的状态(由亮变灭或由灭变亮),因此,可以观察到指示灯D1状态改变。
- 注意事项:
因为按键存在抖动,因此一次按键可能会多次触发中断,即指示灯状态会翻转多次。
-
-
- 外部中断实验(使用多个端口的I/O中断)
-
- 注:本节的实验是在“实验2-5-1:I/O中断(使用一个端口的I/O中断)”的基础上修改,本节对应的实验源码是:“实验2-5-2:I/O中断(使用多个端口的I/O中断)”。
-
-
- 实验内容
-
-
在“实验2-5-1:I/O中断(使用一个端口的I/O中断)”的基础上,增加P0.7(按键KEY3)的I/O中断,同样配置为下降沿中断,0级优先级。按下按键S5触发P4端口I/O中断,中断服务函数中翻转指示灯D2的状态(由亮变灭或由灭变亮)。
-
-
-
- 代码编写
-
-
本例中,主要的代码编写工作包含:跳转部分的汇编代码、P0.7的中断初始化和中断服务程序代码以及主函数中增加P0.7的配置。
- 编写跳转的汇编代码
本例中,我们使用了2个端口的I/O中断,因此,除了借用13号中断外,我们还借用了16号中断(外部中断4)。在STC8A8K64D4的数据手册中(《STC8A8K64D4系列单片机技术参考手册》第958页:中断列表)查到P0的I/O中断入口地址是“012BH”、外部中断4的中断入口地址是0083H,所以,编写中断跳转代码如下。
代码清单:汇编跳转代码
- CSEG AT 012BH ;P0口中断入口地址,如果是其他端口,修改这个中断入口地址即可
- JMP P0INT_ISR
- P0INT_ISR:
- JMP 0083H ;跳转到16号中断的入口地址
- CSEG AT 0143H ;P3口中断入口地址,如果是其他端口,修改这个中断入口地址即可
- JMP P3INT_ISR
- P3INT_ISR:
- JMP 006BH ;跳转到13号中断的入口地址
- END
- 编写中断初始化函数
中断初始化函数中将P0端口中的P0.7 I/O中断进行初始化,配置为下降沿触发,最低级0级的中断优先级,代码清单如下。
代码清单:中断初始化
- /*****************************************************************************************
- 功能描述:P0端口中的P0.7 I/O中断初始化,下降沿触发,优先级:0
- 参 数:无
- 返 回 值:无
- *****************************************************************************************/
- void p0_int_init(void)
- {
- P_SW2 |= 0x80; //将EAXFR位置1,使能访问XFR(扩展RAM区特殊功能寄存器)
- P0INTF &= ~0x80; //将P0.7 I/O中断标志位清"0"
- P0INTE |= 0x80; //使能P0.7的I/O中断
- P0IM1 &= ~0x80; //下降沿触发方式
- P0IM0 &= ~0x80;
- PINIPH &= ~0x01; //设置P0的中断优先级为0
- PINIPL &= ~0x01;
- P_SW2 &= 0x7F; //将EAXFR位置0,关闭访问XFR
- }
- 编写中断服务函数
本例中P0端口只使用了P0.7的I/O中断,因此,中断服务函数里面只需判断P0.7的中断标志即可(P0INTF的位7)。同样,中断服务函数中需要清零I/O中断标志(P0INTF),代码清单如下。
- /*****************************************************************************************
- 功能描述:P0口中断服务函数,我们使用了P0端口中的P0.7,因此,中断服务函数中判断P0.7的中断标志即可(P0INTF的位7)
- 参 数:无
- 返 回 值:无
- *****************************************************************************************/
- void p0_isr (void) interrupt 16
- {
- u8 psw2_st;
- u8 intf;
- psw2_st = P_SW2;
- P_SW2 |= 0x80; //将EAXFR位置1,以访问在XDATA区域的扩展SFR
- intf = P0INTF;
- if(intf)
- {
- P0INTF = 0x00; //清零I/O中断标志
- if(intf & 0x80) //P0.7中断
- {
- led_toggle(LED_2); //翻转用户指示灯D2
- delay_ms(100);
- }
- }
- P_SW2 = psw2_st;
- }
代码清单:中断服务函数
- /*****************************************************************************************
- 功能描述:P4口中断服务函数,我们使用了P4端口中的P4.4,因此,中断服务函数中判断P4.4的中断标志即可
- :(P4INTF的位4). P4口中断借用了中断16(外部中断4),因此,中断号用16
- 参 数:无
- 返 回 值:无
- *****************************************************************************************/
- void p4_isr (void) interrupt 16
- {
- u8 psw2_st;
- u8 intf;
- psw2_st = P_SW2;
- P_SW2 |= 0x80; //将EAXFR位置1,以访问在XDATA区域的扩展SFR
- intf = P4INTF;
- if(intf)
- {
- P4INTF = 0x00; //清零中断标志
- if(intf & 0x10) //P4.4中断
- {
- led_toggle(LED_2); //翻转用户指示灯D2
- }
- }
- P_SW2 = psw2_st; //将EAXFR位置0,关闭访问XFR
- }
- 主函数
主函数中增加配置P0.7为准双向输入以及调用P0端口的I/O中断初始化函数p0_int_init ()完成P0.7的I/O中断初始化的代码,之后开启总中断即可。
代码清单:主函数
- /**************************************************************************
- 功能描述:主函数
- 入口参数:无
- 返 回 值:int类型
- **************************************************************************/
- int main(void)
- {
- P2M1 &= 0x3F; P2M0 &= 0x3F; //设置P2.6~P2.7为准双向口(指示灯D1和D2)
- P3M1 &= 0x7F; P3M0 &= 0x7F; //配置P3.7为准双向口(按键KEY1)
- P0M1 &= 0x7F; P0M0 &= 0x7F; //配置P0.7为准双向口(按键KEY3)
- p0_int_init(); //初始化P0.7 I/O中断
- p3_int_init(); //初始化P3.7 I/O中断
- EA = 1; //允许总中断
- while(1)
- {
- }
- }
-
-
-
- 硬件连接
-
-
本实验需要使用LED指示灯D1、D2和按键KEY1、KEY3,因此需要用跳线帽短接复用引脚的指示灯(D1和D2)和按键KEY1。按键KEY3是独立引脚,没有和其他电路复用引脚,是没有短接跳线帽的操作的。
图4:跳线帽短接
-
-
-
- 实验步骤
-
-
- 解压“…\第3部分:配套例程源码”目录下的压缩文件“实验2-5-2:I/O中断(使用多个端口的I/O中断)”,将解压后得到的文件夹拷贝到合适的目录,如“D\STC8”(这样做的目的是为了防止中文路径或者工程存放的路径过深导致打开工程出现问题)。
- 双击“…\multi_gpio_int\project”目录下的工程文件“multi_gpio_int.uvproj”。
- 点击编译按钮编译工程,编译成功后生成的HEX文件“multi_gpio_int.hex”位于工程的“…\multi_gpio_int\Project\Object”目录下。
- 打开STC-ISP软件下载程序,下载使用内部IRC时钟,IRC频率选择:24MHz。
- 程序运行后,按下按键KEY1触发P3.7的I/O中断,中断服务函数中翻转指示灯D1的状态(由亮变灭或由灭变亮),因此,可以观察到指示灯D1状态改变;按下按键KEY3触发P0.7的I/O中断,中断服务函数中翻转指示灯D2的状态(由亮变灭或由灭变亮),因此,可以观察到指示灯D2状态改变。
- 注意事项:
因为按键存在抖动,因此一次按键可能会多次触发中断,即指示灯状态会翻转多次。