面向ARM Cortex-M 系列。
1. GPIO 中断核心原理
ARM 架构 MCU 的 GPIO 中断设计遵循 "分组复用 + 寄存器控制" 原则,不同厂商芯片的核心逻辑一致,仅寄存器命名和中断分组细节有差异。
1.1 中断分组与中断号分配
多数 ARM MCU 不会为每个 GPIO 引脚分配独立中断号,而是按 "GPIO 组 + 引脚范围" 分组复用中断号,常见分组方式有两种:
- 按引脚范围分组:每组 GPIO(如 GPIO1、GPIO2)的低 16 个引脚(0~15)共用一个中断号,高 16 个引脚(16~31)共用另一个中断号(如 NXP RT1052、STM32F4)。
- 按 GPIO 组分组:多个 GPIO 组(如 GPIO1+GPIO2、GPIO3+GPIO4)共用一个中断号(如 NXP RT1176 部分配置)。
无论哪种分组,中断触发后需通过 "中断状态寄存器" 判断具体是哪个引脚触发中断。
1.2 核心控制寄存器
每组 GPIO 的中断功能由 4 类核心寄存器控制,所有 ARM MCU 的寄存器逻辑一致,仅命名可能不同(如 "中断屏蔽寄存器" 可能命名为 GPIOx_IMR 或 GPIOx_IER):
寄存器类型 | 核心功能 | 操作逻辑 |
---|---|---|
中断屏蔽寄存器 | 控制单个 GPIO 引脚是否允许触发中断 | 32 位寄存器,对应 32 个引脚;某位置 1 = 允许该引脚中断,置 0 = 禁止(复位默认 0) |
中断状态寄存器 | 标记单个 GPIO 引脚是否发生中断 | 32 位寄存器,对应 32 个引脚;中断触发后硬件自动置 1,需软件写 1 清除(关键!) |
中断触发配置寄存器 | 配置单个 GPIO 引脚的中断触发条件(电平 / 边沿触发) | 每个引脚占用 1~2 位;常见配置:低电平、高电平、上升沿、下降沿、双边沿 |
中断清除寄存器 | 单独清除指定引脚的中断标志(部分 MCU 将此功能整合到 "中断状态寄存器") | 写 1 清除对应引脚中断标志,与 "中断状态寄存器" 操作逻辑一致 |
2. 硬件设计原则
GPIO 中断的硬件设计需满足 :
- 电平匹配:确保 GPIO 引脚输入电平与 MCU 的 IO 电平一致。
- 上下拉配置 :
- 若输入信号默认无电平(如悬空引脚),需配置 MCU 内部上拉 / 下拉电阻(推荐上拉),避免引脚电平漂浮导致误中断。
- 若外部信号已自带上下拉(如传感器输出),则 MCU 内部上下拉可关闭,避免冲突。
3. 软件设计通用实现
GPIO 中断配置步骤
1. GPIO 引脚基础配置(输入 / 输出、中断触发方式)
/**
* @brief 配置GPIO引脚为中断输入模式
* @param gpio_base: GPIO端口基地址(如GPIOA、GPIOB等)
* @param pin: 引脚号(0~15/31,取决于芯片)
* @param trig_mode: 触发方式(上升沿/下降沿/双边沿)
*/
void gpio_irq_pin_config(GPIO_TypeDef *gpio_base, uint32_t pin, uint8_t trig_mode) {
// 1. 使能GPIO时钟(不同芯片时钟使能寄存器不同,需适配)
// 示例:STM32F4使能GPIOA时钟
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// 示例:NXP RT1176使能GPIO7时钟
// CCM->CCGR6 |= CCM_CCGR6_GPIO7(CCM_CCGR_ON);
// 2. 配置引脚为输入模式
gpio_base->MODER &= ~(0x3 << (pin * 2)); // 清除原有配置
gpio_base->MODER |= (0x0 << (pin * 2)); // 00=输入模式
// 3. 配置上拉/下拉(根据硬件需求,可选)
gpio_base->PUPDR &= ~(0x3 << (pin * 2));
gpio_base->PUPDR |= (0x1 << (pin * 2)); // 01=上拉(或02=下拉)
// 4. 配置中断触发方式(边沿检测)
// 注意:不同芯片寄存器名不同,以下为通用逻辑
if (trig_mode & 0x01) { // 上升沿触发
gpio_base->RTSR |= (1 << pin); // 上升沿触发使能
gpio_base->FTSR &= ~(1 << pin); // 关闭下降沿(若需双边沿则不关闭)
}
if (trig_mode & 0x02) { // 下降沿触发
gpio_base->FTSR |= (1 << pin); // 下降沿触发使能
gpio_base->RTSR &= ~(1 << pin); // 关闭上升沿(若需双边沿则不关闭)
}
// 5. 清除引脚中断标志(避免初始状态触发)
gpio_base->PR |= (1 << pin); // 写1清除标志(多数ARM芯片逻辑)
}
2. 中断控制器(NVIC)配置
/**
* @brief 配置NVIC中断控制器(使能中断、设置优先级)
* @param irq_n: 中断向量号(如GPIOA_IRQn、GPIO7_IRQn)
* @param priority: 中断优先级(0~15,0为最高,受芯片位数限制)
*/
void nvic_irq_config(IRQn_Type irq_n, uint32_t priority) {
// 1. 清除中断挂起标志(若有)
NVIC_ClearPendingIRQ(irq_n);
// 2. 设置中断优先级(ARM Cortex-M使用抢占优先级+子优先级,需先配置分组)
// 配置优先级分组(系统级,通常在main中初始化一次)
// 示例:分组2(2位抢占优先级,2位子优先级)
NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);
// 设置抢占优先级和子优先级(此处简化为抢占优先级=priority,子优先级=0)
NVIC_SetPriority(irq_n, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), priority, 0));
// 3. 使能中断
NVIC_EnableIRQ(irq_n);
}
3. 使能 GPIO 中断(连接 GPIO 与 NVIC)
/**
* @brief 使能指定GPIO引脚的中断(连接到NVIC)
* @param gpio_base: GPIO端口基地址
* @param pin: 引脚号
*/
void gpio_irq_enable(GPIO_TypeDef *gpio_base, uint32_t pin) {
// 使能引脚级中断(不同芯片寄存器名可能为IMR/IE等)
gpio_base->IMR |= (1 << pin); // 中断屏蔽寄存器:允许引脚中断信号输出到NVIC
}
三、中断服务程序(ISR)实现
/**
* @brief GPIO中断服务函数(需与中断向量表绑定,函数名固定)
* 注意:不同芯片的中断服务函数名不同(如GPIOA_IRQHandler、GPIO7_IRQHandler)
*/
void GPIOx_IRQHandler(void) { // 替换为实际函数名(如GPIO7_IRQHandler)
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
GPIO_TypeDef *gpio_base = GPIO7; // 替换为实际GPIO端口
uint32_t pin = 0; // 替换为实际引脚号
// 1. 检查中断标志(确认是目标引脚触发)
if (gpio_base->PR & (1 << pin)) {
// 2. 清除中断标志(必须!否则会重复触发)
gpio_base->PR |= (1 << pin); // 写1清除
// 3. 中断处理逻辑(简洁!避免耗时操作)
// 示例1:直接执行简单处理(如翻转LED)
// LED_GPIO->ODR ^= (1 << LED_PIN);
// 示例2:通过信号量唤醒任务(推荐,适合复杂处理)
if (gpio_irq_sem != NULL) {
xSemaphoreGiveFromISR(gpio_irq_sem, &xHigherPriorityTaskWoken);
}
}
// 4. 若使用FreeRTOS,且有高优先级任务被唤醒,请求任务切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}