NVIC(嵌套向量中断控制器)
NVIC简介
NVIC 是嵌套向量中断控制器,控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设。但是各个芯片厂商在设计芯片的时候会对 Cortex-M3 内核里面的 NVIC 进行裁剪,把不需要的部分去掉,所以说 STM32的 NVIC 是 Cortex-M 3 的 NVIC 的一个子集。
优先级的定义
在 NVIC 有一个专门的寄存器:中断优先级寄存器 NVIC_IPRx,用来配置外部中断的优先级, IPR宽度为 8bit,原则上每个外部中断可配置的优先级为 0~255,数值越小,优先级越高。但是绝大多数 CM3 芯片都会精简设计,以致实际上支持的优先级数减少,在 F103 中,只使用了高 4bit,如下所示
用于表达优先级的这 4bit,又被分组成抢占优先级和子优先级。如果有多个中断同时响应,抢占
优先级高的就会抢占抢占优先级低的优先得到执行,如果抢占优先级相同,就比较子优先级。如
果抢占优先级和子优先级都相同的话,就比较他们的硬件中断编号,编号越小,优先级越高。
优先级分组
优先级的分组由内核外设 SCB 的应用程序中断及复位控制寄存器 AIRCR 的 PRIGROUP[10:8] 位
决定, F103 分为了 5 组,具体如下:主优先级 = 抢占优先级
设置优先级分组可调用库函数 NVIC_PriorityGroupConfig() 实现,有关 NVIC 中断相关的库函数都
在库文件 misc.c 和 misc.h 中。
中断编程
1.使能外设某个中断,这个具体由每个外设的相关中断使能位控制。比如串口有发送完成中
断,接收完成中断,这两个中断都由串口控制寄存器的相关中断使能位控制。
2.初始化 NVIC_InitTypeDef 结构体,配置中断优先级分组,设置抢占优先级和子优先级,使
能中断请求。 NVIC_InitTypeDef 结构体在固件库头文件 misc.h 中定义。
typedef struct {
uint8_t NVIC_IRQChannel; // 中断源
uint8_t NVIC_IRQChannelPreemptionPriority; // 抢占优先级
uint8_t NVIC_IRQChannelSubPriority; // 子优先级
FunctionalState NVIC_IRQChannelCmd; // 中断使能或者失能
} NVIC_InitTypeDef;
3. 编写中断服务函数
在启动文件 startup_stm32f10x_hd.s 中我们预先为每个中断都写了一个中断服务函数,只是这些
中断函数都是为空,为的只是初始化中断向量表。实际的中断服务函数都需要我们重新编写,为
了方便管理我们把中断服务函数统一写在 stm32f10x_it.c 这个库文件中。
关于中断服务函数的函数名必须跟启动文件里面预先设置的一样,如果写错,系统就在中断向量
表中找不到中断服务函数的入口,直接跳转到启动文件里面预先写好的空函数,并且在里面无限
循环,实现不了中断。
EXTI(外部中断/事件控制器)
EXTI 简介
EXTI(External interrupt/event controller)—外部中断/事件控制器,管理了控制器的 20 个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的
检测。 EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触
发事件的属性。
EXTI 功能框图
编号 1 是输入线, EXTI 控制器有 19 个中断/事件输入线,这些输入线可以通过寄存器设置为任
意一个 GPIO,也可以是一些外设的事件。输入线一般是存在电平变化的信号。
编号 2 是一个边沿检测电路,它会根据上升沿触发选择寄存器 (EXTI_RTSR) 和下降沿触发选择
寄存器 (EXTI_FTSR) 对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如
果检测到有边沿跳变就输出有效信号 1 给编号 3 电路,否则输出无效信号 0。而 EXTI_RTSR 和
EXTI_FTSR 两个寄存器可以控制需要检测哪些类型的电平跳变过程,可以是只有上升沿触发、只
有下降沿触发或者上升沿和下降沿都触发。
编号 3 电路实际就是一个或门电路,它的一个输入来自编号 2 电路,另外一个输入来自软件中断
事件寄存器 (EXTI_SWIER)。 EXTI_SWIER 允许我们通过程序控制就可以启动中断/事件线,这在
某些地方非常有用。我们知道或门的作用就是有 1 就为 1,所以这两个输入随便一个有有效信号
1 就可以输出 1 给编号 4 和编号 6 电路。
编号 4 电路是一个与门电路,它的一个输入是编号 3 电路,另外一个输入来自中断屏蔽寄存器
(EXTI_IMR)。与门电路要求输入都为 1 才输出 1,导致的结果是如果 EXTI_IMR 设置为 0 时,那
不管编号 3 电路的输出信号是 1 还是 0,最终编号 4 电路输出的信号都为 0;如果 EXTI_IMR
设置为 1 时,最终编号 4 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单
的控制 EXTI_IMR 来实现是否产生中断的目的。编号 4 电路输出的信号会被保存到挂起寄存器
(EXTI_PR) 内,如果确定编号 4 电路输出为 1 就会把 EXTI_PR 对应位置 1。
编号 5 是将 EXTI_PR 寄存器内容输出到 NVIC 内,从而实现系统中断事件控制。
编号 6 电路是一个与门,它的一个输入来自编号 3 电路,另外一个输入来自事件屏蔽寄存器 (EXTI_EMR)。如果EXTI_EMR 设置为 0 时,那不管编号 3 电路的输出信号是 1 还是 0,最终编号 6 电路输出的信号都为 0;如果 EXTI_EMR 设置为 1 时,最终编号 6 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单的控制 EXTI_EMR 来实现是否产生事件的目的。
编号 7 是一个脉冲发生器电路,当它的输入端,即编号 6 电路的输出端,是一个有效信号 1 时就
会产生一个脉冲;如果输入端是无效信号就不会输出脉冲。
编号 8 是一个脉冲信号,就是产生事件的线路最终的产物,这个脉冲信号可以给其他外设电路使
用,比如定时器 TIM、模拟数字转换器 ADC 等等,这样的脉冲信号一般用来触发 TIM 或者 ADC
开始转换。
每个 GPIO 外设有 16 个引脚,所以进来 16 根线,但 EXTI 只有 16 个 GPIO 的通道,如果每个引脚占用一通道,那 EXTI 的 16 个通道显然就不够用了。所以,在这里会有一个 AFIO 中断引脚选择的电路模块,用于对不同的 GPIO 外设的相同引脚进行选择,选择其中一个接入EXTI 的 GPIO_Pin 通道。这就是所有的 GPIO 口都能触发中断,但相同的Pin不能同时触发中断的原因。
通过 AFIO 选择后的 16 个通道,就接到了EXTI 边沿检测及控制电路上,与外加 PVD 输出、RTC 闹钟、USB 唤醒、以太网唤醒共同组成了EXTI 的20个输入信号。然后经过 EXTI 电路之后,分为了两种输出,即接入 NVIC 的中断响应和接入其它外设的事件响应。
注意: 本来 20 路输入,应该有20路输出,但在这里,外部中断的 9~5 和 15~10 被分到了一个通道里。也就是说,外部中断的 9~5 会触发同一个中断函数,15~10 会触发同一个中断函数。在编程的时候,我们在这两个中断函数里需要再根据标志位来区分具体是哪个中断进来的。
中断/事件线
EXTI 有 20 个中断/事件线,每个 GPIO 都可以被设置为输入线,占用 EXTI0 至 EXTI15,还有另
外七根用于特定的外设事件,4 根特定外设中断/事件线由外设触发。
AFIO复用IO口
-
AFIO主要用于引脚复用功能的选择和重定义
-
在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择
固件库EXTI初始化函数
typedef struct {
uint32_t EXTI_Line; // 中断/事件线
EXTIMode_TypeDef EXTI_Mode; // EXTI 模式
EXTITrigger_TypeDef EXTI_Trigger; // 触发类型
FunctionalState EXTI_LineCmd; // EXTI 使能
} EXTI_InitTypeDef;
1) EXTI_Line: EXTI 中断/事件线选择,可选 EXTI0 至 EXTI19,可参考表 EXTI 中断 _ 事件线
选择。
2) EXTI_Mode: EXTI 模式选择,可选为产生中断 (EXTI_Mode_Interrupt) 或者产生事件
(EXTI_Mode_Event)。
3) EXTI_Trigger: EXTI 边沿触发事件,可选上升沿触发 (EXTI_Trigger_Rising)、下降沿触发 (
EXTI_Trigger_Falling) 或者上升沿和下降沿都触发 ( EXTI_Trigger_Rising_Falling)。
4) EXTI_LineCmd:控制是否使能 EXTI 线,可选使能 EXTI 线 (ENABLE) 或禁用 (DISABLE)。
编程
利用两个按键分别利用中断显示led灯的不同现象
1.初始化KEY1和KEY2,并设置中断
#include "bsp_exti.h"
static void NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_Initstruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_Initstruct.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_Initstruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_Initstruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Initstruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_Initstruct);
NVIC_Initstruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_Initstruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_Initstruct.NVIC_IRQChannelSubPriority = 3;
NVIC_Initstruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_Initstruct);
}//设置KEY1和KEY2的中断优先级,KEY1可以打断KEY2
void EXTI_KEY1_Config(void)
{
EXTI_InitTypeDef EXTI_Initstruct;
GPIO_InitTypeDef KEY_Initstruct;
NVIC_Config();
/* 初始化要连接到EXTI的GPIO */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
KEY_Initstruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;
KEY_Initstruct.GPIO_Pin=GPIO_Pin_0;
KEY_Initstruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&KEY_Initstruct);
/* 初始化EXTI产生中断/事件 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//中断引脚选择
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
EXTI_Initstruct.EXTI_Line = EXTI_Line0;
EXTI_Initstruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_Initstruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Initstruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_Initstruct);
}
void EXTI_KEY2_Config(void)
{
EXTI_InitTypeDef EXTI_Initstruct;
GPIO_InitTypeDef KEY_Initstruct;
NVIC_Config();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
KEY_Initstruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;
KEY_Initstruct.GPIO_Pin=GPIO_Pin_13;
KEY_Initstruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&KEY_Initstruct);
/* 初始化EXTI产生中断/事件 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//中断引脚选择
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource13);
EXTI_Initstruct.EXTI_Line = EXTI_Line13;
EXTI_Initstruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_Initstruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Initstruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_Initstruct);
}
使用 GPIO_InitTypeDef 和 EXTI_InitTypeDef 结构体定义两个用于 GPIO 和 EXTI 初始化配
置的变量,关于这两个结构体前面都已经做了详细的讲解。
使用 GPIO 之前必须开启 GPIO 端口的时钟;用到 EXTI 必须开启 AFIO 时钟。
调用 NVIC_Configuration 函数完成对按键 1、按键 2 优先级配置并使能中断通道。
作为中断/事件输入线时需把 GPIO 配置为输入模式,具体为浮空输入,由外部电路完全决定引脚
的状态。
GPIO_EXTILineConfig 函数用来指定中断/事件线的输入源,它实际是设定外部中断配置寄存器的
AFIO_EXTICRx 值,该函数接收两个参数,第一个参数指定 GPIO 端口源,第二个参数为选择对
应 GPIO 引脚源编号。
我们的目的是产生中断,执行中断服务函数, EXTI 选择中断模式,按键 1 使用上升沿触发方式,
并使能 EXTI 线。
按键 2 基本上采用与按键 1 相关参数配置。
2.在stm32f10x_it.h中编写中断函数
//LED1_ON 第一个灯亮 LED1_OFF 第一个灯灭
//LED2_ON 第二个灯亮 LED1_OFF 第二个灯灭
//LED3_ON 第三个灯亮 LED1_OFF 第三个灯灭
void EXTI0_IRQHandler(void)//KEY1中断函数
{
LED1_OFF;
LED2_OFF;
LED3_OFF;
unsigned char i=0;
if( EXTI_GetITStatus(EXTI_Line0)!=RESET)//中断标志位
{
for( i=0;i<100;i++)
{
LED3_ON;
SOFT_DELAY;
LED3_OFF;
SOFT_DELAY;
}
LED3_OFF;
}
EXTI_ClearITPendingBit(EXTI_Line0);//清除中断标志位
}
void EXTI15_10_IRQHandler(void)
{
LED1_OFF;
LED2_OFF;
LED3_OFF;
unsigned char i=0;
if( EXTI_GetITStatus(EXTI_Line13)!=RESET)
{
for( i=0;i<100;i++)
{
LED1_ON;
SOFT_DELAY;
LED1_OFF;
SOFT_DELAY;
LED2_ON;
SOFT_DELAY;
LED2_OFF;
SOFT_DELAY;
LED3_ON;
SOFT_DELAY;
LED3_OFF;
SOFT_DELAY;
}
LED1_OFF;
LED2_OFF;
LED3_OFF;
}
EXTI_ClearITPendingBit(EXTI_Line13);
}
3,在主函数开启中断
#include "stm32f10x.h" // 相当于51单片机中的 #include <reg51.h>
#include "bsp_led.h"
#include "bsp_exti.h"
#define SOFT_DELAY Delay(0x0FFF);
#define SOFF_DELAY Delay(0x0FFFFFF);
void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
int main(void)
{
EXTI_KEY1_Config();
LED_GPIO_Config();
EXTI_KEY2_Config();
while(1)
{
}
// 来到这里的时候,系统的时钟已经被配置成72M。
}