i.MX RT1062 嵌套中断向量控制器NVIC

2021-12-28 09:42:41 浏览数 (1)

一、NVIC

NVIC全称Nested Vectored Interrupt Controller,嵌套中断向量控制器,属于ARM Cortex-M7内核的外设,使用方法基本和STM32相同,如果想了解更多关于 NVIC的描述,可以阅读ARM官方手册:

  • 《DDI0489D_cortex_m7_trm》(Cortex-M7技术参考手册)

1. 中断源(中断号)

中断系统首先需要可以产生中断信号的中断源,ARM Cortex-M7 内核中的 NVIC 最大支持 240 个中断源,NXP在 RT1062 中支持 174 个中断源:

代码语言:javascript复制
/** Interrupt Number Definitions */
#define NUMBER_OF_INT_VECTORS 174                /**< Number of interrupts in the Vector table */

RT1062支持的所有中断源使用枚举类型映射为中断号表示,类型为 IRQn_Type,在头文件MIMXRT1062.h中定义,代码如下:

代码语言:javascript复制
typedef enum IRQn {
  /* Auxiliary constants */
  NotAvail_IRQn                = -128,             /**< Not available device specific interrupt */

  /* Core interrupts */
  NonMaskableInt_IRQn          = -14,              /**< Non Maskable Interrupt */
  HardFault_IRQn               = -13,              /**< Cortex-M7 SV Hard Fault Interrupt */
  MemoryManagement_IRQn        = -12,              /**< Cortex-M7 Memory Management Interrupt */
  BusFault_IRQn                = -11,              /**< Cortex-M7 Bus Fault Interrupt */
  UsageFault_IRQn              = -10,              /**< Cortex-M7 Usage Fault Interrupt */
  SVCall_IRQn                  = -5,               /**< Cortex-M7 SV Call Interrupt */
  DebugMonitor_IRQn            = -4,               /**< Cortex-M7 Debug Monitor Interrupt */
  PendSV_IRQn                  = -2,               /**< Cortex-M7 Pend SV Interrupt */
  SysTick_IRQn                 = -1,               /**< Cortex-M7 System Tick Interrupt */

  /* Device specific interrupts */
  DMA0_DMA16_IRQn              = 0,                /**< DMA channel 0/16 transfer complete */
  //...
  GPIO6_7_8_9_IRQn             = 157               /**< GPIO6, GPIO7, GPIO8, GPIO9 interrupt */
} IRQn_Type;

在 RT1062 中有 174 个中断源, 其中有些中断信号是内核在运行程序的过程中发出的,大部分中断信号都是由外设产生的。

外设虽然可以产生中断信号,但默认时关闭的,需要我们手动去配置才可以产生,比如串口中断、定时器中断等

2. 中断使能

如果每个外设产生中断信号后都可以直接触发 CPU 去响应中断,那系统可就乱套了~

所以按照中断源的紧急程度,将这些中断源分为三类:

  • 不可用中断NotAvail_IRQn:系统保留,不使用
  • 不可屏蔽中断NonMaskableInt_IRQn(NMI):不可屏蔽中断,任何情况下都会被CPU响应
  • 可屏蔽中断:剩下的所有,都可以由NVIC控制是否被CPU响应

所以,NVIC->ICERx 寄存器用来控制每个中断源是否使能:

那么,如何来访问内核中NVIC的寄存器呢?这个时候CMSIS-Core就要派上用场啦!

CMSIS-Core 是ARM为了屏蔽不同厂商之间操作内核的差异,提供了一层抽象层,只要是ARM Cortex-M的内核都可以调用CMSIS-Core的API去操作,其核心就是一个头文件core_cm7.h(7是指Cortex-M7):

代码语言:javascript复制
#include "core_cm7.h"                  /* Core Peripheral Access Layer */

在该头文件中,我们可以方便的去调用 API 使能某个中断,如下:

代码语言:javascript复制
/**
  brief   Enable Interrupt
  details Enables a device specific interrupt in the NVIC interrupt controller.
  param [in]      IRQn  Device specific interrupt number.
  note    IRQn must not be negative.
 */
__STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn)
{
  if ((int32_t)(IRQn) >= 0)
  {
    __COMPILER_BARRIER();
    NVIC->ISER[(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
    __COMPILER_BARRIER();
  }
}

也可以方便的去调用 API 失能某个中断:

代码语言:javascript复制
/**
  brief   Disable Interrupt
  details Disables a device specific interrupt in the NVIC interrupt controller.
  param [in]      IRQn  Device specific interrupt number.
  note    IRQn must not be negative.
 */
__STATIC_INLINE void __NVIC_DisableIRQ(IRQn_Type IRQn)
{
  if ((int32_t)(IRQn) >= 0)
  {
    NVIC->ICER[(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
    __DSB();
    __ISB();
  }
}

细心的同学不难发现,这两个API传入的参数,刚好是在MIMXRT1062.h头文件中的中断号枚举类型 IRQn_Type,所以,你应该知道如何使用了吧~

在 NXP 提供的 FSL固件库fsl_common.h中,对CMSIS-Core提供的API再次进行了封装:

代码语言:javascript复制
/*!
 * @brief Enable specific interrupt.
 *
 * Enable LEVEL1 interrupt. For some devices, there might be multiple interrupt
 * levels. For example, there are NVIC and intmux. Here the interrupts connected
 * to NVIC are the LEVEL1 interrupts, because they are routed to the core directly.
 * The interrupts connected to intmux are the LEVEL2 interrupts, they are routed
 * to NVIC first then routed to core.
 *
 * This function only enables the LEVEL1 interrupts. The number of LEVEL1 interrupts
 * is indicated by the feature macro FSL_FEATURE_NUMBER_OF_LEVEL1_INT_VECTORS.
 *
 * @param interrupt The IRQ number.
 * @retval kStatus_Success Interrupt enabled successfully
 * @retval kStatus_Fail Failed to enable the interrupt
 */
static inline status_t EnableIRQ(IRQn_Type interrupt)
{
//...
}

同样,失能的API也有对应的封装:

代码语言:javascript复制
static inline status_t DisableIRQ(IRQn_Type interrupt)
{
//...
}

3. 中断优先级

174 个中断源非常丰富,但群龙无首可不行,需要按辈分来,大人说话小孩子是不可以打断的~

NVIC最大支持256个中断优先级,疯狂暗示它的中断优先级寄存器有8位起作用,但一般厂商生产MCU时,通常只用到 4 位就够了,所以 RT1062 最大支持 16 个中断优先级。

代码语言:javascript复制
#define __NVIC_PRIO_BITS               4         /**< Number of priority bits implemented in the NVIC */

4. 中断处理函数

中断处理函数的地址称为中断向量表(标号__Vectors),在RT1062在启动文件定义,每个中断源都提供了默认的中断处理函数弱定义,用户可以重新实现。

比如以Systick中断处理函数 SysTick_Handler 为例:

二、GPIO外设中断

RT1062每个GPIO外设的32个引脚都可以触发输入中断,但需要注意的是:每个GPIO外设只拥有两个中断编号(GPIO1有点特殊),以GPIO1外设为例:

  • GPIO1_Combined_0_15_IRQHandler:低16位的引脚共用该编号;
  • GPIO1_Combined_16_31_IRQHandler:高16位的引脚共用该编号;

GPIO外设拥有三个寄存器用于中断相关:

① GPIOx_IMR寄存器用于控制是否使能该引脚的中断:

② GPIOx_ICR1和GPIO_ICR2寄存器用于配置中断触发条件:

其中每 2 位用来配置一个引脚,比如ICR0和ICR1用来配置该外设第0个引脚:

③ 16个引脚共用一个中断源,所以需要查询中断状态寄存器 GPIOx_ISR 来判断哪个引脚发生了中断:

该标志位由硬件自动置位,软件中向该位写1清空标志位

三、按键检测

1. 基于SDK新建MDK工程

这里使用 NXP 官方提供的 MCUXpresso Config Tools。

修改按键使用的GPIO引脚,设置方向为输入、GPIO中断为低电平触发、默认开启上拉电阻:

查看时钟配置:

点击【更新源代码】,修改引脚会更新文件pin_mux.hpin_mux.c

2. 修改MDK工程

打开生成的keil工程,选择RAM调试版本:

修改按键引脚定义:

修改按键中断类型为低电平触发:

3. 调试

编译,点击调试,全速运行,在串口助手查看日志:

4. 代码分析

该实验的核心逻辑都在 source 文件夹中的 gpio_input_interrupt.c 文件中。

4.1. 通用逻辑

在main函数中,首先完成了IOMUX、时钟的初始化,这两个都可以使用Config Tool 工具来配置生成文件,并初始化了MPU、以及一个自带的组件Debug_console,方便在工程中使用 PRINTF 函数来打印,默认调试串口是LPUART1。

4.2. GPIO操作API

针对GPIO外设,FSL库提供对应的库函数,在fsl_gpio.hfsl_gpio.c中。

关于GPIO FSL库API在上一篇文章中有详细讲解,需要注意的是,这里需要在初始化按键GPIO的时候指明中断类型:

代码语言:javascript复制
/* Define the init structure for the input switch pin */
gpio_pin_config_t sw_config = {
    kGPIO_DigitalInput,
    0,
    kGPIO_IntLowLevel,
};
4.3. 中断相关API

(1)首先需要在 NVIC 外设使能GPIO中断,允许CPU接收到GPIO中断信号:

代码语言:javascript复制
static inline status_t EnableIRQ(IRQn_Type interrupt);

中断源在 MIMXRT1062.h头文件中枚举,这里是 GPIO4_Combined_16_31_IRQn

(2)然后需要使能GPIO中断,允许GPIO外设产生中断信号:

代码语言:javascript复制
/*!
 * @brief Enables the specific pin interrupt.
 *
 * @param base GPIO base pointer.
 * @param mask GPIO pin number macro.
 */
static inline void GPIO_PortEnableInterrupts(GPIO_Type *base, uint32_t mask)
{
    base->IMR |= mask;
}

(3)最后重新实现 GPIO 中断处理函数即可:

代码语言:javascript复制
/*!
 * @brief Interrupt service fuction of switch.
 */
void GPIO4_Combined_16_31_IRQHandler(void)
{
    /* clear the interrupt status */
    GPIO_PortClearInterruptFlags(EXAMPLE_SW_GPIO, 1U << EXAMPLE_SW_GPIO_PIN);
    /* Change state of switch. */
    g_InputSignal = true;
    SDK_ISR_EXIT_BARRIER;
}

至此,代码就分析完毕啦~

0 人点赞