I.MX6U 裸机开发15.IRQ中断——GPIO中断处理

  • 一、向GPIO驱动添加中断处理函数
  • 1. GPIO 相关寄存器
  • (1)GPIOx_ICRn 按键GPIO设置中断模式
  • (2)使能GPIO对应的中断
  • (3)GPIOx_EDGE_SEL
  • (4)GPIO_ISR寄存器
  • 2. GIC 设置
  • (1)使能相应的中断ID
  • (2)中断优先级设置
  • (3)注册GPIO1_IO18的中断处理函数
  • 二、GPIO 中断代码
  • 1. 在 bsp_gpio.h 里定义中断触发类型枚举
  • 2. 修改配置结构体
  • 3. 使能、禁用GPIO中断及清除标志位
  • 4. 触发方式
  • 三、按键中断驱动
  • 1. bsp_esti.h
  • 2. bsp_esti.c
  • 3. main.c


一、向GPIO驱动添加中断处理函数

1. GPIO 相关寄存器

(1)GPIOx_ICRn 按键GPIO设置中断模式

I.MX6U 裸机开发15.IRQ中断——GPIO中断处理_寄存器

在《IMX6ULL参考手册》中可以看到,GPIOx_ICR1GPIOx_ICR2 是用于配置 GPIO 引脚中断触发模式的寄存器。每个寄存器控制 16 个 GPIO 引脚的中断配置。

  • GPIOx_ICR1:控制 GPIO 引脚 0 到 15 的中断配置。
  • GPIOx_ICR2:控制 GPIO 引脚 16 到 31 的中断配置。

每个引脚的中断配置占用 2 位,可以配置为以下几种模式:

  • 00:低电平触发
  • 01:高电平触发
  • 10:上升沿触发
  • 11:下降沿触发

具体配置方法如下:

// 配置 GPIO 引脚 0 为下降沿触发
GPIOx->ICR1 &= ~(3 << (0 * 2)); // 清除原有配置
GPIOx->ICR1 |= (2 << (0 * 2));  // 设置为下降沿触发

// 配置 GPIO 引脚 16 为上升沿触发
GPIOx->ICR2 &= ~(3 << ((16 - 16) * 2)); // 清除原有配置
GPIOx->ICR2 |= (3 << ((16 - 16) * 2));  // 设置为上升沿触发

通过设置 GPIOx_ICR1GPIOx_ICR2 寄存器,可以灵活配置每个 GPIO 引脚的中断触发模式。

开发板按下时,UART1_CTS端口接地,所以设置为下降沿触发 。

(2)使能GPIO对应的中断

GPIO_MIR 寄存器用于配置 GPIO 引脚的中断屏蔽。每个 GPIO 引脚对应 GPIO_MIR 寄存器中的一位,通过设置或清除这些位,可以使能或屏蔽相应引脚的中断。

设置示例:

// 屏蔽 GPIO 引脚 0 的中断
GPIOx->MIR |= (1 << 0);

// 使能 GPIO 引脚 0 的中断
GPIOx->MIR &= ~(1 << 0);

// 屏蔽 GPIO 引脚 16 的中断
GPIOx->MIR |= (1 << 16);

// 使能 GPIO 引脚 16 的中断
GPIOx->MIR &= ~(1 << 16);

(3)GPIOx_EDGE_SEL

GPIOx_EDGE_SEL 寄存器用于配置 GPIO 引脚的边沿选择。它允许选择 GPIO 引脚是对上升沿、下降沿还是双边沿(上升沿和下降沿)触发中断。

(4)GPIO_ISR寄存器

处理完中断后,通过 GPIO_ISR 寄存器清除中断标志位,写1清零。

2. GIC 设置

(1)使能相应的中断ID

开发板使用的按键是接在 GPIO1_IO18引脚,在 《IMX6ULL参考手册》P185:

I.MX6U 裸机开发15.IRQ中断——GPIO中断处理_嵌入式硬件_02


可以看到GPIO1_IO18对应的中断ID是67+32=99。

d MCIMX6X6Y2.h 里有定义:

GPIO1_Combined_16_31_IRQn = 99

(2)中断优先级设置

(3)注册GPIO1_IO18的中断处理函数

二、GPIO 中断代码

1. 在 bsp_gpio.h 里定义中断触发类型枚举

/**
 * @brief 中断触发类型枚举
 */
 typedef enum _gpio_interrupt_mode{
     kGPIO_NoIntmode = 0U, // 无中断
     kGPIO_IntLowLevel = 1U, // 低电平触发
     kGPIO_IntHighLevel = 2U, // 高电平触发
     kGPIO_IntRisingEdge = 3U, // 上升沿触发
     kGPIO_IntFallingEdge = 4U, // 下降沿触发
     kGPIO_IntRisingOrFallingEdge = 5U, // 上升沿或下降沿触发
 } gpio_interrupt_mode_t;

2. 修改配置结构体

/**
 * @brief GPIO 配置结构体
 */
typedef struct _gpio_pin_config
{
    gpio_pin_direction_t direction; // 输入输出
    uint8_t outputLogic;            // 输出电平
    gpio_interrupt_mode_t interruptMode; // 中断触发类型
} gpio_pin_config_t;

3. 使能、禁用GPIO中断及清除标志位

/**
* @brief 使能GPIO中断
*/
void gpio_enableint(GPIO_Type *base, int pin)
{
    base->IMR |= 1 << pin;
}

/**
* @brief 禁止GPIO中断
*/
void gpio_disableint(GPIO_Type *base, int pin)
{
    base->IMR &= ~(1 << pin);
}

/**
* @brief 清除中断标志位
*/
void gpio_clearintflags(GPIO_Type *base, int pin)
{
    base->ISR |= 1 << pin;
}

4. 触发方式

/**
* @brief GPIO中断初始化
*/
void gpio_interrupt_init(GPIO_Type *base, unsigned int pin, gpio_interrupt_mode_t mode) {
    volatile uint32_t *icr;
    uint32_t icrShift;

    icrShift = pin;
    // 清0防止 gpio_icr 寄存器设置无效
    base->EDGE_SEL &= ~(1 << pin);
    if (pin < 16) {
        icr = &(base->ICR1);
    } else {
        icr = &(base->ICR2);
        icrShift -= 16;
    }
    switch (mode) {
        case kGPIO_IntLowLevel:  // 低电平触发
            *icr &= ~(3 << (icrShift * 2));  // 每个IO占用2位,即0b11,移动次数为icrShift * 2
            break;
        case kGPIO_IntHighLevel: // 高电平触发
            *icr = (*icr & ~(3 << (icrShift * 2))) | (1 << (icrShift * 2));
            break;
        case kGPIO_IntRisingEdge: // 上升沿触发
            *icr = (*icr & ~(3 << (icrShift * 2))) | (2 << (icrShift * 2));
            break;
        case kGPIO_IntFallingEdge: // 下降沿触发
            *icr = (*icr & ~(3 << (icrShift * 2))) | (3 << (icrShift * 2));
            break;
        case kGPIO_IntRisingOrFallingEdge: // 上升沿或下降沿触发
            base->EDGE_SEL |= 1 << pin;
            break;
    }


}

三、按键中断驱动

1. bsp_esti.h

//
// Created by Xundh on 2024/11/19.
//

#ifndef LEARN_I_MX6U_BSP_ESTI_H
#define LEARN_I_MX6U_BSP_ESTI_H

/**
* @brief  初始化外部中断, GPIO1_IO18
*/
void exti_init(void);

/**
* @brief GPIO1_IO18 中断服务函数
*/
void gpio1_io18_irqhandler(unsigned int gicciar, void *param);
#endif //LEARN_I_MX6U_BSP_ESTI_H

2. bsp_esti.c

//
// Created by Xundh on 2024/11/19.
//
#include "bsp_exti.h"
#include "bsp_clk.h"
#include "bsp_gpio.h"
#include "bsp_delay.h"
#include "bsp_int.h"
#include "led.h"
#include "imx6u.h"

/**
* @brief  初始化外部中断, GPIO1_IO18
*/
void exti_init(void) {
    gpio_pin_config_t key_config;
    // 1. 设置复用为GPIO1_IO18
    IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
    // 2. 设置电气属性
    IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF0B0);
    // 3. 设置方向为输入
    key_config.direction = kGPIO_DigitalInput;
    // 4. 设置中断触发类型为下降沿触发
    key_config.interruptMode = kGPIO_IntFallingEdge;
    // 5. 初始化GPIO
    gpio_init(GPIO1, 18, &key_config);
    // 6. 使能GPIO1_IO18中断
    GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
    // 7. 中断服务函数注册
    sys_irq_handle_register(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_io18_irqhandler, NULL);
    // 8. GPIO 使能
    gpio_enableint(GPIO1, 18);
}

/**
* @brief GPIO1_IO18 中断服务函数
*/
void gpio1_io18_irqhandler(unsigned int gicciar, void *param) {
    static unsigned char state = 0;
    // 临时使用定时,生产环境不能这样用
    delay(10);
    if (gpio_pinread(GPIO1, 18) == 0) {
        state = !state;
        led_switch(LED0, state);
    }
    // 清除中断标志位
    gpio_clearintflags(GPIO1, 18);

}

上面程序编译后,查看反汇编文件:

I.MX6U 裸机开发15.IRQ中断——GPIO中断处理_#include_03

可以看到 0x87800000 放的并不是中断向量表,修改 start.S,将bss段定义移到清除BSS段前面。

3. main.c

#include "inc/main.h"
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "led.h"
#include "beep.h"
#include "key.h"
#include "bsp_int.h"
#include "bsp_exti.h"

int main(void)
{
    bsp_int_init();  /* 初始化中断 */
    imx6u_clkinit();    /* 初始化系统时钟 */
    clk_enable();   /* 使能外设时钟 */
    led_init();     /* 初始化LED */
    beep_init();    /* 初始化蜂鸣器 */
    exti_init();    /* 初始化外部中断 */

    while(1) {
//        led_on();
//        delay(1000);
//        led_off();
//        delay(1000);
    }
    return 0;
}

本代码实现按key0, LED0切换亮灭显示。