8.【NXP 号令者RT1052】开发——实战-外部中断
8.【NXP 号令者RT1052】开发——实战-外部中断
在前面几章的学习中,我们掌握了 RT1052 的 IO 口最基本的操作。本章我们将介绍如何将 RT1052 的 IO 口作为外部中断输入,在本章中,我们将以中断的方式,实现我们在第六章所实现的功能。
8.1 RT1052 外部中断简介
我们在第六章详细讲解过IO,那么这一章就使用外部中断来实现之前的效果——通过按键实现LED的亮灭,也会同时介绍外部中断。
RT1052 的每个 IO 口都可以作为中断输入,这点很好很强大。要把 IO 口作为外部中断输入,有以下几个步骤:
8.1.1 初始化 IO 口为输入
这一步设置你要作为外部中断输入的 IO 口的状态,可以设置为上拉/下拉输入,也可以设置为浮空输入(PKE=0),但浮空的时候外部一定要带上拉,或者下拉电阻。否则可能导致中断不停的触发。这一步通过两个函数实现:IOMUXC_SetPinMux 和 IOMUXC_SetPinConfig。以KEY_UP 对应的 IOMUXC_SNVS_WAKEUP 为例,先将其设置为 GPIO 功能,并且上拉输入:
IOMUXC_SetPinMux(IOMUXC_SNVS_WAKEUP_GPIO5_IO00,0);
IOMUXC_SetPinConfig(IOMUXC_SNVS_WAKEUP_GPIO5_IO00,0xF080);
可以看出和之前按键输入实验是一样的,所以我们也可以直接调用一次按键初始化函数KEY_Init()来完成按键对应的 IO 的复用功能设置。
8.1.2 开启对应 IO 口的中断功能
RT1052 的 IO 口,在设置成输入模式下,如果要使能某个 IO 口的中断功能,则需要设置GPIOx→IMR 对应位为 1,才可以触发中断。这一步通过 GPIO_PinInit 函数实现。同样以 KEY按键为例,KEY_UP 按键对应 GPIO5_IO00,设置此 IO 为中断功能的代码如下:
gpio_pin_config_t int_config;int_config.direction=kGPIO_DigitalInput; /输入
int_config.interruptMode=kGPIO_IntFallingEdge; //下降沿触发中断
int_config.outputLogic=1; //默认高电平
GPIO_PinInit(GPIO5,0,&int_config); //初始化 GPIO5_00
通过上述代码设置 GPIO5_IO00 为下降沿触发中断,默认为高电平,其他 IO 同理设置即
可
8.1.3 设置中断触发条件
这一步,我们要配置中断的触发条件,RT1052 可以配置成:低电平触发、高电平触发、上升沿触发、下降沿触发和任意边沿触发。号令者四个按键都是低电平有效,所以,我们设置为下降沿触发即可。这一步也是通过 GPIO_PinInit 函数设置(设置 GPIO 为输入模式,以及中断触发条件,都是通过 GPIO_PinInit 函数实现的)
8.1.4 开启并配置中断优先级
这一步,我们就是配置中断中断优先级,以及使能,对 RT1052 的中断来说,只有配置了NVIC 的设置,并开启才能被执行,否则是不会执行到中断服务函数里面去的,以 KEY_UP 为例:
//抢占优先级位 3,0 位子优先级
RT1052_NVIC_SetPriority(GPIO5_Combined_0_15_IRQn, 3, 0);
EnableIRQ(GPIO5_Combined_0_15_IRQn); //使能 GPIO5_0~15IO 的中断
上述代码中实验函数 RT1052_NVIC_SetPriority 来设置中断优先级,然后调用函数EnableIRQ 来开启相应的中断.
8.1.5 编写中断服务函数
这是中断设置的最后一步,中断服务函数,是必不可少的,如果在代码里面开启了中断,但是**没编写中断服务函数,就可能引起硬件错误,从而导致程序崩溃!**所以在开启了某个中断后,一定要记得为该中断编写服务函数。在中断服务函数里面编写你要执行的中断后的操作。通过以上几个步骤的设置,我们就可以正常使用外部中断了。
KEY_UP 控制 DS0,DS1 互斥点亮;KEY2 控制 DS0,按一次亮,再按一次灭;KEY1 控制 DS1,效果同 KEY2;KEY0 则同时控制 DS0 和 DS1,按一次,他们的状态就翻转一次。
8.2 硬件设计
和第六章一样
8.3 软件设计
软件设计我们还是在之前的工程上面增加,本章我们要用到按键,所以要先将 key.c 添加到HARDWARE 组下,然后在 HARDWARE 文件夹下新建 EXTI 的文件夹。然后打开 USER 文件夹下的工程,新建一个 exti.c 的文件和 exti.h 的头文件,保存在 EXTI 文件夹下,并将 EXTI 文件夹加入头文件包含路径(即设定编译器包含路径)。


我们在 exti.c 里输入如下代码:
#include "exti.h"
#include "led.h"
#include "delay.h"
#include "key.h"//外部中断初始化
void EXTIX_Init(void)
{gpio_pin_config_t int_config;KEY_Init(); //初始化按键//KEY_UPint_config.direction=kGPIO_DigitalInput; //输入int_config.interruptMode=kGPIO_IntFallingEdge; //下降沿触发中断int_config.outputLogic=1; //默认高电平GPIO_PinInit(GPIO5,0,&int_config); //初始化GPIO5_00 //KEY1int_config.direction=kGPIO_DigitalInput; //输入int_config.interruptMode=kGPIO_IntFallingEdge; //下降沿触发中断int_config.outputLogic=1; //默认高电平GPIO_PinInit(GPIO5,1,&int_config); //初始化GPIO5_01//KEY0int_config.direction=kGPIO_DigitalInput; //输入int_config.interruptMode=kGPIO_IntFallingEdge; //下降沿触发中断int_config.outputLogic=1; //默认高电平GPIO_PinInit(GPIO1,5,&int_config); //初始化GPIO1_05//KEY2int_config.direction=kGPIO_DigitalInput; //输入int_config.interruptMode=kGPIO_IntFallingEdge; //下降沿触发中断int_config.outputLogic=1; //默认高电平GPIO_PinInit(GPIO3,26,&int_config); //初始化GPIO3_26//使能WKUP(GPIO5_00)和KEY1(GPIO5_01)中断GPIO_PortEnableInterrupts(GPIO5,1<<0); //GPIO5_00中断使能GPIO_PortEnableInterrupts(GPIO5,1<<1); //GPIO5_01中断使能RT1052_NVIC_SetPriority(GPIO5_Combined_0_15_IRQn,3,0);//抢占优先级位3,0位子优先级EnableIRQ(GPIO5_Combined_0_15_IRQn); //使能GPIO5_0~15IO的中断//使能KEY0(GPIO1_05)中断GPIO_PortEnableInterrupts(GPIO1,1<<5); //GPIO1_05中断使能RT1052_NVIC_SetPriority(GPIO1_Combined_0_15_IRQn,4,0);//抢占优先级位3,0位子优先级EnableIRQ(GPIO1_Combined_0_15_IRQn); //使能GPIO1_0~15IO的中断//使能KEY2(GPIO3_26)中断GPIO_PortEnableInterrupts(GPIO3,1<<26); //GPIO3_26中断使能RT1052_NVIC_SetPriority(GPIO3_Combined_16_31_IRQn,5,0);//抢占优先级位5,0位子优先级EnableIRQ(GPIO3_Combined_16_31_IRQn); //使能GPIO3_16~31 IO的中断
}//GPIO1_0~15共用的中断服务函数
void GPIO1_Combined_0_15_IRQHandler(void)
{static u8 led0sta=1,led1sta=1;if(GPIO_GetPinsInterruptFlags(GPIO1)&(1<<5)) //判断是否为GPIO1_5中断{ //消抖延时,这里为了方便演示例程所以在中断中使用了延时函数来做消抖,//在实际的项目中绝对禁止在中断中使用延时函数!!!!!!!delay_ms(10); if(GPIO_PinRead(GPIO1,5)==0) //KEY0(GPIO1_5)是否保持按下状态{led1sta=!led1sta;led0sta=!led0sta;LED1(led1sta);LED0(led0sta); }}GPIO_PortClearInterruptFlags(GPIO1,1<<5); //清除中断标志位__DSB(); //数据同步屏蔽指令
}//GPIO3_16~31共用的中断服务函数
void GPIO3_Combined_16_31_IRQHandler(void)
{if(GPIO_GetPinsInterruptFlags(GPIO3)&(1<<26)) //判断是否为GPIO3_26中断{//消抖延时,这里为了方便演示例程所以在中断中使用了延时函数来做消抖,//在实际的项目中绝对禁止在中断中使用延时函数!!!!!!!delay_ms(10); if(GPIO_PinRead(GPIO3,26)==0) //KEY2(GPIO3_25)是否保持按下状态{LED0_Toggle;}}GPIO_PortClearInterruptFlags(GPIO3,1<<26); //清除中断标志位__DSB(); //数据同步屏蔽指令
}//GPIO5_0~15共用的中断服务函数
void GPIO5_Combined_0_15_IRQHandler(void)
{static u8 led0sta=1,led1sta=1;if(GPIO_GetPinsInterruptFlags(GPIO5)&(1<<0)) //判断是否为GPIO5_0中断{delay_ms(10); if(GPIO_PinRead(GPIO5,0)==0) //KEYUP(GPIO5_00)是否保持按下状态{led1sta=!led1sta;led0sta=!led1sta;LED1(led1sta);LED0(led0sta);}}if(GPIO_GetPinsInterruptFlags(GPIO5)&(1<<1)) //判断是否为GPIO5_1中断 {delay_ms(10); if(GPIO_PinRead(GPIO5,1)==0) //(KEY1)GPIO5_01是否保持按下状态 {LED1_Toggle;}}GPIO_PortClearInterruptFlags(GPIO5,1<<0); //清除中断标志位GPIO_PortClearInterruptFlags(GPIO5,1<<1); //清除中断标志位__DSB(); //数据同步屏蔽指令
}
exit.h:
#ifndef _EXTI_H
#define _EXTI_H
#include "sys.h"void EXTIX_Init(void);
#endif
exti.c 文件总共包含 4 个函数。一个是外部中断初始化函数 void EXTIX_Init(void),另外 3个都是中断服务函数,接下来我们分别介绍这 4 个函数。
EXTIX_Init 函数说明
EXTIX_Init 用于外部中断初始化,步骤如下:
-
调用 KEY_Init,将按键 IO 配置为 GPIO(ALT5 功能)。
-
使用 GPIO_PinInit 设置中断触发条件(下降沿触发),并配置为输入模式。
注意:虽然
KEY_Init已经调用过GPIO_PinInit,但该函数可多次调用,以最后一次配置为准。 -
再次调用 GPIO_PinInit,使能对应 IO 的中断功能。
-
通过 RT1052_NVIC_SetPriority 设置中断优先级。
相关中断号宏(如 GPIO1_Combined_0_15_IRQn、GPIO3_Combined_16_31_IRQn)定义在 MIMXRT1052.h 头文件中。
中断服务函数说明
1. GPIO1_Combined_0_15_IRQHandler
- 对应 GPIO1_IO00 ~ GPIO1_IO15 共用的中断服务函数。
- 本实验使用 GPIO1_IO05 (KEY0)。
- 处理流程:
- 调用
GPIO_GetPinsInterruptFlags判断是否由 KEY0 触发。 - 延时 10ms 消抖(仅用于演示,实际项目中禁止在中断中使用延时)。
- 再次读取 KEY0 电平,若仍为低电平则确认按下,执行操作(如点灯)。
- 清除中断标志,退出中断。
- 调用
2. GPIO3_Combined_16_31_IRQHandler
- 对应 GPIO3_IO16 ~ GPIO3_IO31 共用的中断服务函数。
- 本实验用于检测 KEY2 是否按下。
- 处理逻辑与
GPIO1_Combined_0_15_IRQHandler类似。
3. GPIO5_Combined_0_15_IRQHandler
- 对应 GPIO5_IO00 ~ GPIO5_IO03(仅 3 个 IO)。
- 本实验用于检测 KEY0 和 KEY_UP。
- 处理逻辑与
GPIO1_Combined_0_15_IRQHandler相同,只是需要同时判断两个 IO。
注意!!!
- 中断函数共用:多个 IO 口共享同一个中断服务函数,需要在 ISR 内部判断具体触发源。
- 消抖处理:示例中使用延时函数消抖仅为演示,实际工程中应采用 定时器消抖 或 状态机,避免在中断中阻塞。
这里给大家说明一下:RT1052 的 5 组 IO 口,每组 IO 口的低 16 位占用一个中断,高 16 位占用一个中断,这样,每组 IO 口使用 2 个中断服务函数即可搞定。当每组 IO 口有多个中断的时候,需要在中断服务函数里面判断中断来源(ISR 寄存器),然后再执行相关处理。另外,RT1052 所有中断服务函数的名字,都已经在 startup_MIMXRT1052.s 里面定义好了,如果有不知道的,去这个文件里面找就可以了。
这部分代码就很简单了,我们这里不多废话,保存就可以了。接着我们在 main.c 里面写入如下内容:
#include "sys.h"
#include "lpuart.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "exti.h"int main(void)
{u8 key=0;u8 led0sta=1,led1sta=1; //LED0,LED1的当前状态u8 len; //接收数据长度u16 times=0; //延时计数器MPU_Memory_Protection(); //初始化MPURT1052_Clock_Init(); //配置系统时钟DELAY_Init(600); //延时函数初始化LPUART1_Init(115200); //初始化串口1RT1052_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);//优先级分组4LED_Init(); //初始化LED
#ifdef KEY_DEBUGKEY_Init(); //初始化KEY
#endif
#ifdef LPUART_DEBUGLED0(0); //先点亮红灯
#endifEXTIX_Init(); //初始化外部中断while(1){printf("Int example driver!\r\n");delay_ms(1000);
#ifdef KEY_DEBUGkey=KEY_Scan(0); //得到键值if(key){ switch(key){ case WKUP_PRES: //控制LED0,LED1互斥点亮led1sta=!led1sta;led0sta=!led1sta;break;case KEY2_PRES: //控制LED0翻转led0sta=!led0sta;break;case KEY1_PRES: //控制LED1翻转 led1sta=!led1sta;break;case KEY0_PRES: //同时控制LED0,LED1翻转 led0sta=!led0sta;led1sta=!led1sta;break;}LED0(led0sta); //控制LED0状态LED1(led1sta); //控制LED1状态}else delay_ms(10);
#endif // KEY_DEBUG
#ifdef LPUART_DEBUGif(LPUART_RX_STA&0x8000){ len=LPUART_RX_STA&0x3fff;//得到此次接收到的数据长度printf("\r\n发送的消息为:\r\n");LPUART_WriteBlocking(LPUART1,LPUART_RX_BUF,len);//发送接收到的数据printf("\r\n\r\n");//插入换行LPUART_RX_STA=0;}else{times++;if(times%5000==0){printf("\r\nALIENTEK RT1052开发板 串口实验\r\n");}if(times%200==0)printf("请输入数据,以回车键结束\r\n"); if(times%30==0)LED0_Toggle;//闪烁LED,提示系统正在运行.delay_ms(10); }
#endif // LPUART_DEBUG}
}
该部分代码很简单,在初始化完中断后,点亮 LED0,就进入死循环等待了,这里死循环里面通过一个 printf 函数来告诉我们系统正在运行,在中断发生后,就执行相应的处理。
编译,下载验证一下


总结
相比之前写的在while(1)中轮询的方式,这种方式显然更方便以及响应更快,在实际应用中需要注意的是中断等级和中断处理部分。
OK!谢谢大家!
