Cortex-M4集成了嵌套式矢量型中断控制器(Nested Vectored Interrupt Controller (NVIC))来实现高效的异常和中断处理。NVIC实现了低延迟的异常和中断处理,以及电源管理控制。它和内核是紧密耦合的。
凡是打断程序顺序执行的事件都称为异常(exception),比如HardFault,外部中断等。所以中断也可以说是异常的一种。
一、中断的作用
全局中断使能位控制着“所有”中断,它如果关闭,则会屏蔽其它中断。
在使用过程中,常关闭全局中断,以防止其他中断的干扰。
当GPIO模拟某个时序时,在GPIO传输数据过程中,若被某个中断干扰,会导致时序不准确问题,通常的做法是关闭全局中断,数据传输完成后打开全局中断
,
同样在RTOS中对全局变量的保护基本上都使用了全局中断。
__disable_irq();
//数据传输
__enable_irq();
但关中断时间较长,导致串口接收FIFO溢出,数据丢失。
所以需要注意:全局中断关闭时间都不是太长,且严禁出现长时间关中断,严禁出现关中断时间不可控,这样会影响实时性。
时间不可控比如:长链表操作、环形缓冲区操作、循环操作等。
二、中断优先级
异常包括系统异常(异常编号1-15)和外部中断(异常编号16往上)。HardFault(-1)这三个异常优先级都是负数且固定不变,优先级高于其他异常,除了这三个异常之外其他异常优先级都是可以编程的。
1、中断抢占
每个中断优先级都包含两个部分,一部分称作抢占优先级,另一部分称作子优先级。
(1)抢占优先级(中断嵌套)
在执行低中断优先级中断函数时,高优先级中断到来,低优先级中断被打断,执行高优先级中断,高优先级中断执行完后回到低优先级中断继续执行。
具有高抢占优先级的中断能够抢占低抢占优先级的中断,也称之为中断嵌套。
(2)抢占优先级相同,子优先级不同
当两个中断的抢占优先级设置为相同级别时,这两个中断不会出现中断嵌套。
如果其中一个中断正在执行时,另外一个中断到来,后来的中断将会等到前一个中断执行完才能执行。如果这两个中断都处在等待响应状态,执行条件到来时,首先响应子优先级高的中断。
(3)抢占优先级相同,子优先级也相同
这种情况也不会出现中断嵌套,内核将会按照向量表中的排位选择,优先执行靠前的异常。
2、优先级分组
在Cortex-M4内核中,优先级分组是一个用于配置中断优先级的重要机制。它决定了在8位的中断优先级字段中,多少位用于表示抢占优先级,多少位用于表示子优先级。
Cortex-M4内核允许通过配置优先级分组寄存器(例如,ARM Cortex-M3中的AIRCR寄存器)来设置不同的优先级分组方案。优先级分组寄存器通常包含几位用于配置优先级的字段。通过设置这些位,可以选择不同的分组方式,从而决定抢占优先级和子优先级的位数分配。
具体的分组方式可以有多种,例如:
- 分组0:所有8位都用于表示子优先级,没有抢占优先级。
- 分组1:最高1位用于表示抢占优先级,最低的7位用于表示子优先级。
- 分组2:最高2位用于表示抢占优先级,最低的6位用于表示子优先级。
- 分组3:最高3位用于表示抢占优先级,最低的5位用于表示子优先级。
- 分组4:所有8位都用于表示抢占优先级,没有子优先级。
不同的分组方式可以根据具体的应用需求来选择。抢占优先级用于决定在中断发生时哪个中断应该被优先处理,而子优先级则在抢占优先级相同的情况下用于进一步区分中断的优先级。
示例:
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
nvic_irq_enable((uint8)USBFS_IRQn,2U,0U);
nvic_irq_enable((uint8)USBFS_WKUP_IRQn,1U,0U);
三、中断配置
在使用ARM Cortex-M系列微控制器时,中断处理是一个非常重要的部分。在Cortex-M4(或类似的Cortex-M系列)中配置中断示例如下。(实际的实现细节可能会根据您使用的具体硬件和库有所不同。)
1、选择中断源:
首先确定要使用的中断源。这可能是外部中断、定时器中断或其他内部中断。
2、配置中断优先级:
Cortex-M4支持中断优先级。根据应用的需求为每个中断分配优先级
3、配置NVIC (Nested Vectored Interrupt Controller):
NVIC是Cortex-M系列中的中断控制器。需配置NVIC以启用和配置所需的中断。
4、编写中断服务程序 (ISR):
为每个中断编写中断服务程序。当中断发生时,这些程序将被执行。
以下示例基于ARM Cortex-M4 内核的 GD32 微控制器。
示例1:如何配置一个外部中断(EXTI)(例如,来自某个GPIO引脚的中断)
#include "gd32f3xx.h" //确保已经包含了必要的头文件
// 假设您已经有一个系统时钟初始化的函数
void SystemClock_Config(void);
// EXTI0 的中断服务程序
void EXTI0_IRQHandler(void)
{
if (RESET != exti_interrupt_flag_get(EXTI_0)) // 检查 EXTI0 的中断标志
{
// 清除中断标志
exti_interrupt_flag_clear(EXTI_0);
// 在这里处理中断逻辑
}
}
int main(void)
{
// 系统时钟初始化
SystemClock_Config();
// GPIO 端口时钟使能
rcu_periph_clock_enable(RCU_GPIOA);
// 配置 GPIOA 的第 0 号引脚为中断模式
gpio_mode_set(GPIOA, GPIO_MODE_IT, GPIO_PUPD_NONE, GPIO_PIN_0);
gpio_interrupt_enable(GPIOA, GPIO_INT_FALLING); // 下降沿触发
// 配置 EXTI 的中断线 0
exti_interrupt_flag_clear(EXTI_0); // 清除可能存在的挂起中断
exti_line_config(EXTI_SOURCE_GPIOA, EXTI_LINE_0);
exti_mode_set(EXTI_MODE_INTERRUPT, EXTI_LINE_0);
exti_trigger_type_set(EXTI_TRIGGER_FALLING, EXTI_LINE_0); // 下降沿触发
// 配置 NVIC
nvic_irq_enable(EXTI0_IRQn, 0, 0); // 使能 EXTI0 的中断,并设置优先级
while (1)
{
// 主循环代码
}
}
四、扩展示例
配置内部中断(如定时器中断)
配置相关步骤为:
1、初始化定时器:
首先,需要初始化定时器,并设置其参数,如预分频值、自动重载值等,以决定定时器的计数周期和中断触发频率。
2、配置定时器中断:
启用定时器的中断功能,并设置中断触发条件(如溢出中断、比较匹配中断等)。
3、配置NVIC:
使用嵌套向量中断控制器(NVIC)来配置中断的优先级和使能中断。
4、编写中断服务程序(ISR):
编写一个中断服务程序,该程序将在定时器中断发生时被执行。
#include "gd32f3xx.h"
// 假设您已经有一个系统时钟初始化的函数
void SystemClock_Config(void);
// 定时器中断服务程序
void TIMERx_IRQHandler(void) // 这里的x代表具体的定时器编号,如TIMER2
{
if (RESET != timer_interrupt_flag_get(TIMERx, TIMER_INT_FLAG_UP)) // 检查定时器的中断标志
{
// 清除中断标志
timer_interrupt_flag_clear(TIMERx, TIMER_INT_FLAG_UP);
// 在这里处理中断逻辑
}
}
int main(void)
{
// 系统时钟初始化
SystemClock_Config();
// 定时器时钟使能
rcu_periph_clock_enable(RCU_TIMERx); // 这里的x代表具体的定时器编号,如TIMER2
// 定时器初始化结构体定义和初始化
timer_parameter_struct timer_initpara;
timer_initpara.prescaler = 7199; // 预分频值
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 9999; // 自动重载值
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;
timer_init(TIMERx, &timer_initpara); // 初始化定时器
// 启动定时器
timer_enable(TIMERx);
// 配置定时器中断
timer_interrupt_enable(TIMERx, TIMER_INT_FLAG_UP); // 使能定时器中断
// 配置NVIC
nvic_irq_enable(TIMERx_IRQn, 0, 0); // 使能定时器的中断,并设置优先级
while (1)
{
// 主循环代码
}
}
以上示例中:首先初始化了系统时钟,然后使能了定时器的时钟。接着,定义了定时器的初始化参数,并调用timer_init函数来初始化定时器。之后,我们启动了定时器,并配置了定时器中断。最后,使用nvic_irq_enable函数来使能定时器的中断,并设置了其优先级。