一、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
中定义,代码如下:
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):
#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再次进行了封装:
/*!
* @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.h
和pin_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.h
和fsl_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;
}
至此,代码就分析完毕啦~