《DN8P1开发指南_V1.0》第十一章 外部中断实验
第十一章 外部中断实验
上一章,我们介绍了 RA8P1 的 I/O Port 作为输出的使用。本章,我们将通过按键外部中断实验,学习 RA8P1 外部中断的使用方法。我们将利用开发板上的按键触发外部中断,在中断回调函数中改变 LED 状态,从而观察外部中断的触发效果。
本章分为如下几个小节:
10.1 外部中断简介
10.2 硬件设计
10.3 程序设计
10.4 下载验证
11.1 外部中断简介
中断是 MCU 处理突发事件的一种机制。程序正常执行主循环时,如果外部按键、外设模块或系统异常产生中断请求,CPU 会暂停当前程序,转去执行对应的中断服务程序;中断服务程序执行完成后,再返回原来的程序继续执行。使用中断可以避免 CPU 一直轮询按键状态,提高程序实时性和执行效率。
RA8P1 的中断控制由 ICU(Interrupt Controller Unit)和 Arm Cortex-M 内核中的 NVIC 共同完成。ICU 负责检测中断源、选择中断事件、保存中断标志,并将中断请求送往 CPU;NVIC 负责 CPU 侧的中断使能、优先级和异常入口响应。
根据 RA8P1 用户手册,ICU 支持的中断相关功能主要包括:
1.检测外部引脚中断和外设中断事件;
2.将中断事件输出到 CPU、DTC 或 DMAC;
3.保存中断状态标志;
4.支持 IRQi 数字滤波,滤除宽度小于 3 个采样周期的毛刺;
5.支持低功耗唤醒相关中断配置;
6.支持 TrustZone 场景下的安全/非安全中断属性配置。
如下图10.1.1为ICU 负责中断事件检测和路由,NVIC 负责 CPU 侧中断响应;外部 IRQ 事件经 ICU 后进入 CPU 中断处理流程。

图10.1.1 ICU和CPU的中断路径
RA8P1支持NMI和IRQi外部中断输入,其中IRQi范围为IRQ0到IRQ31。如下图10.1.2 ICU寄存器地址映射所示。

图10.1.2 ICU寄存器地址映射
11.1.1 外部中断工作过程
RA8P1 外部中断使用 IRQi 引脚输入外部中断请求,i 的范围为 0 到 31。外部信号进入 IRQi 后,ICU 根据 IRQCRi 寄存器中配置的触发方式判断是否产生中断。触发方式可以选择下降沿、上升沿、双边沿或低电平。
外部中断的大致工作过程如下:
配置对应 I/O 引脚为 IRQ 功能;
配置 IRQCRi,选择触发方式和是否启用数字滤波;
通过 IELSRn 选择中断事件,并把事件连接到 CPU 中断;
设置 NVIC 中断优先级并使能中断;
外部按键触发 IRQi;
ICU 检测到有效触发条件,置位中断标志;
CPU 响应中断,进入 FSP 配置的回调函数;
在回调函数中执行用户代码。
使用 FSP 开发时,上述底层寄存器配置大多由配置工具生成。用户重点关注 External IRQ 模块的通道号、触发方式、数字滤波、回调函数和优先级。
11.1.2 IRQ寄存器介绍
IRQCRi 是 IRQ Control Register,用于配置 IRQi 外部中断检测方式和数字滤波。i 的范围为 0 到 31。本章使用的按键分别连接到 IRQ12-DS 和 IRQ13-DS,因此对应 IRQCR12 和 IRQCR13。
IRQCRi 中与本章关系最密切的位如下:

IRQMD[1:0] 的配置含义如下:

按键实验通常选择下降沿触发。因为很多开发板按键一端接地,另一端通过上拉保持高电平,按下时信号由高变低,产生下降沿。
11.1.3 IELSRn 寄存器介绍
IELSRn 是 ICU Event Link Setting Register,用于把某个事件源连接到 ICU 的中断通道。n 的范围为 0 到 95。对于 CPU 中断,需要设置 IELSRn.IELS[9:0] 选择中断事件,并将 IELSRn.DTCE 设置为 0,表示该事件作为 CPU 中断请求,而不是 DTC 触发请求IELSRn 中常用位如下:

在 FSP 工程中,用户通常不直接写 IELSRn,而是在 Stacks 中添加 External IRQ 模块,并设置通道号、回调函数和优先级。FSP 生成的代码会完成事件选择、NVIC 配置和中断回调绑定。
11.1.4 数字滤波功能
机械按键存在抖动,按下或松开瞬间可能产生多个高低电平跳变。RA8P1 的 IRQi 支持数字滤波功能,可以对 IRQ 输入信号进行采样,并滤除宽度小于 3 个采样周期的脉冲。
启用 IRQi 数字滤波需要配置两项:
通过 IRQCRi.FCLKSEL[1:0] 选择采样时钟,可选 PCLKB、PCLKB/8、PCLKB/32、PCLKB/64;将 IRQCRi.FLTEN 设置为 1,使能数字滤波。
11.2 硬件设计
1. 例程功能
BOOT与KEY0控制LED0开启与关闭;KEY1与KEY2控制LED1开启与关闭;KEY3控制LED0与LED1状态翻转。
2. 硬件资源
1)LED 灯
LED0 – P108
LED1 – PD06
2)按键
BOOT – P201(IRQ4)
KEY0 – P003(IRQ29)
KEY1 – P005(IRQ10-DS)
KEY2 – P010(IRQ14)
KEY3 – P011(IRQ16)
3. 原理图
按键硬件部分的原理图,如图 10.2.1 所示:

图 10.2.1 按键原理图
这里需要注意的是:KEY0、KEY1、KEY2、KEY3和BOOT采样到按键另一端的低电平为有效电平,由于按键外部没有上下拉电阻,所以需要在 RA8P1内部设置上下拉。
**11.3 程序设计 **
11.3.1 FSP配置流程
注意:LED的FSP配置流程,我们已经在第八章节讲解过,这里我们不再赘述。
在FSP配置PinsInterrupt:IRQ路径下配置相关管脚中断初始化,如下图所示。

Stacks 标签页中,点击"New Stack",添加5个IRQ实例,如下图所示。

3.选中Stacks 标签页中新创建的IRQ实例,点击"属性",修改相关参数配置。

另外四个IRQ实例属性按照此配置模式一样操作即可,这里不再进行过多展示。(注意函数命名方式保持独立)

11.3.2 程序解析
1. 按键外部中断驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。按键外部中断驱动源码包括两个文件:key_exti.c 和 key_exti.h。
下面我们先解析 key_exti.h 程序中的引脚定义、中断通道定义与函数声明。
/* 按键引脚宏定义(外部中断引脚) */
#define BOOT_GPIO_PIN BSP_IO_PORT_02_PIN_01 /* BOOT按键对应引脚:P201 */
#define KEY0_GPIO_PIN BSP_IO_PORT_00_PIN_03 /* KEY0按键对应引脚:P003 */
#define KEY1_GPIO_PIN BSP_IO_PORT_00_PIN_05 /* KEY1按键对应引脚:P005 */
#define KEY2_GPIO_PIN BSP_IO_PORT_00_PIN_10 /* KEY2按键对应引脚:P010 */
#define KEY3_GPIO_PIN BSP_IO_PORT_00_PIN_11 /* KEY3按键对应引脚:P011 */
#define BOOT_IRQ_CHANNEL (4U) /* BOOT按键中断通道号 */
#define KEY0_IRQ_CHANNEL (29U) /* KEY0按键中断通道号 */
#define KEY1_IRQ_CHANNEL (10U) /* KEY1按键中断通道号 */
#define KEY2_IRQ_CHANNEL (14U) /* KEY2按键中断通道号 */
#define KEY3_IRQ_CHANNEL (16U) /* KEY3按键中断通道号 */
extern volatile bool boot_press; /* BOOT按键按下标志位 */
extern volatile bool key0_press; /* KEY0按键按下标志位 */
extern volatile bool key1_press; /* KEY1按键按下标志位 */
extern volatile bool key2_press; /* KEY2按键按下标志位 */
extern volatile bool key3_press; /* KEY3按键按下标志位 */
/* 函数声明 */
void key_exti_init(void);
void key_external_irq_callback(external_irq_callback_args_t *p_args);
BOOT_GPIO_PIN ~ KEY3_GPIO_PIN 分别是 5 个按键对应的 GPIO 引脚宏定义,和硬件原理图上的引脚一一对应。BOOT_IRQ_CHANNEL ~ KEY3_IRQ_CHANNEL 是各引脚绑定的 ICU 外部中断通道号,用于在回调函数中区分触发中断的按键。
5个按键按下标志位使用 volatile 修饰,确保中断服务函数与主循环中读取的变量值一致,避免编译器优化导致的读取错误。最后声明了外部中断初始化函数和中断回调函数,供外部调用。
注意:所有按键引脚均已通过 FSP 配置工具设置为外部中断模式,开启内部上拉,触发方式为下降沿触发(按键按下时电平由高变低,触发中断),系统启动时完成端口基础配置,驱动中仅需完成中断通道的打开与使能。
下面我们再解析 key_exti.c 的程序,这里包含中断通道操作封装函数、初始化函数和中断回调函数。
首先是静态封装函数 key_exti_open_enable,其定义如下:
static void key_exti_open_enable(icu_instance_ctrl_t * p_ctrl, external_irq_cfg_t const * p_cfg)
{
/* 打开指定ICU外部中断通道,绑定FSP生成的配置 */
fsp_err_t err = R_ICU_ExternalIrqOpen(p_ctrl, p_cfg);
assert(FSP_SUCCESS == err);
/* 使能该外部中断通道,允许按键边沿触发回调 */
err = R_ICU_ExternalIrqEnable(p_ctrl);
assert(FSP_SUCCESS == err);
}
该函数为内部静态函数,封装了外部中断通道的打开与使能操作:先调用 R_ICU_ExternalIrqOpen 绑定 FSP 生成的中断配置,再调用 R_ICU_ExternalIrqEnable 使能该中断通道,允许引脚边沿触发中断回调。函数中使用 assert 检查操作返回值,确保配置执行成功。
接下来是按键外部中断初始化函数 key_exti_init,其定义如下:
void key_exti_init(void)
{
key_exti_open_enable(&g_external_irq4_ctrl, &g_external_irq4_cfg);
key_exti_open_enable(&g_external_irq29_ctrl, &g_external_irq29_cfg);
key_exti_open_enable(&g_external_irq10_ctrl, &g_external_irq10_cfg);
key_exti_open_enable(&g_external_irq14_ctrl, &g_external_irq14_cfg);
key_exti_open_enable(&g_external_irq16_ctrl, &g_external_irq16_cfg);
}
该函数依次对 5 个按键对应的外部中断通道调用打开与使能函数,完成所有按键中断的初始化配置。每个通道对应一个物理按键,注释中标注了对应引脚与 IRQ 通道号,与硬件连接完全对应。
最后是全局按键标志位与统一中断回调函数:
/* 按键按下标志位,volatile保证中断与主程序读取一致 */
volatile bool boot_press = false; /* BOOT按键按下标志位 */
volatile bool key0_press = false; /* KEY0按键按下标志位 */
volatile bool key1_press = false; /* KEY1按键按下标志位 */
volatile bool key2_press = false; /* KEY2按键按下标志位 */
volatile bool key3_press = false; /* KEY3按键按下标志位 */
void key_external_irq_callback(external_irq_callback_args_t *p_args)
{
bsp_io_level_t key_level = BSP_IO_LEVEL_HIGH;
R_BSP_SoftwareDelay(10, BSP_DELAY_UNITS_MILLISECONDS); /* 延时10ms消抖 */
/* 根据触发的IRQ通道读取对应按键电平,确认仍为按下后再置位 */
switch(p_args->channel)
{
case BOOT_IRQ_CHANNEL:
R_IOPORT_PinRead(&g_ioport_ctrl, BOOT_GPIO_PIN, &key_level);
if(BSP_IO_LEVEL_LOW == key_level)
{
boot_press = true;
}
break;
case KEY0_IRQ_CHANNEL:
R_IOPORT_PinRead(&g_ioport_ctrl, KEY0_GPIO_PIN, &key_level);
if(BSP_IO_LEVEL_LOW == key_level)
{
key0_press = true;
}
break;
case KEY1_IRQ_CHANNEL:
R_IOPORT_PinRead(&g_ioport_ctrl, KEY1_GPIO_PIN, &key_level);
if(BSP_IO_LEVEL_LOW == key_level)
{
key1_press = true;
}
break;
case KEY2_IRQ_CHANNEL:
R_IOPORT_PinRead(&g_ioport_ctrl, KEY2_GPIO_PIN, &key_level);
if(BSP_IO_LEVEL_LOW == key_level)
{
key2_press = true;
}
break;
case KEY3_IRQ_CHANNEL:
R_IOPORT_PinRead(&g_ioport_ctrl, KEY3_GPIO_PIN, &key_level);
if(BSP_IO_LEVEL_LOW == key_level)
{
key3_press = true;
}
break;
default:
break;
}
}
5个由volatile修饰的全局布尔变量,作为对应按键的按下事件标志,分别映射 BOOT、KEY0~KEY3 五路按键。volatile关键字可禁止编译器对该变量进行读写优化,确保中断上下文与主循环访问时始终读取内存最新值,避免多执行上下文下的数据不一致。标志位采用“中断内置位、主循环中查询并清零”的使用逻辑,实现中断事件与业务处理的解耦。
key_external_irq_callback是ICU外部中断的统一回调入口,任意一路按键触发外部中断时,该函数都会在中断上下文中被调用。函数先执行 10ms 软件延时完成按键机械消抖,再通过回调参数p_args的channel成员定位触发中断的通道,读取对应GPIO引脚的实际电平;二次确认引脚仍处于有效低电平状态后,才置位对应的按键标志位,有效滤除按键机械抖动引发的误触发。
2. hal_entry.c 代码
在 hal_entry.c 里面编写如下代码:
void hal_entry(void)
{
delay_init(1000000); /* 初始化延时函数,配置系统时钟 */
led_init(); /* 初始化LED GPIO引脚 */
key_exti_init(); /* 初始化按键外部中断(配置中断通道、使能中断) */
while(1)
{
/* 判断 BOOT 是否按下(中断触发后置位标志) */
if(boot_press)
{
boot_press = false;
LED0(0);
}
/* 判断 KEY0 是否按下(中断触发后置位标志) */
if(key0_press)
{
key0_press = false; /* 清除按键按下标志,等待下一次触发 */
LED0(1);
}
/* 判断 KEY1 是否按下(中断触发后置位标志) */
if(key1_press)
{
key1_press = false; /* 清除按键按下标志,等待下一次触发 */
LED1(0);
}
/* 判断 KEY2 是否按下(中断触发后置位标志) */
if(key2_press)
{
key2_press = false;
LED1(1);
}
/* 判断 KEY3 是否按下(中断触发后置位标志) */
if(key3_press)
{
key3_press = false;
LED0_TOGGLE();
LED1_TOGGLE();
}
}
}
hal_entry 是 FSP 工程的系统启动入口点。程序首先完成系统级初始化:初始化延时函数、LED 外设和按键外部中断。
与轮询式按键扫描不同,本实验主循环无需主动扫描引脚电平,仅需循环查询各按键的按下标志位:当对应标志位为真时,先清除标志位,再执行对应的 LED 控制逻辑。中断方式的优势在于按键响应及时,且主循环无需持续扫描 IO,可空闲处理其他任务。
各按键的控制逻辑与轮询按键实验一致:BOOT 按下点亮 LED0,KEY0 按下熄灭 LED0;KEY1 按下点亮 LED1,KEY2 按下熄灭 LED1;KEY3 按下同时翻转 LED0 与 LED1 状态。
11.4 下载验证
在下载好程序后,我们可以按下 BOOT、KEY0、KEY1、KEY2、KEY3 来观察 LED 灯的变化,验证是否和预期结果一致:
• BOOT 按下时 LED0 点亮,KEY0 按下时 LED0 熄灭;
• KEY1 按下时 LED1 点亮,KEY2 按下时 LED1 熄灭;
• KEY3 按下时 LED0 与 LED1 状态同时翻转。
至此,本章的学习就结束了。本章学习了 RA8P1 的 GPIO 作为外部中断线的使用方法,掌握了 ICU 外部中断的配置流程、中断回调函数的编写以及中断事件标志的使用方式。大家可以对比轮询按键与中断按键两种实现的差异,理解不同场景下的选型思路。

浙公网安备 33010602011771号