一、嵌套向量中断控制器
简介
NVIC是嵌套向量中断控制器,控制整一个芯片的中断功能,它是属于内核的一个外设。STM32中的NVIC是对内核的NVIC进行裁剪的,相当于NVIC中的一个子集。
NVIC寄存器
typedef struct
{
__IO uint32_t ISER[8]; // Offset: 0x000 中断使能寄存器
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; //Offset: 0x080 中断清除寄存器
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; // Offset: 0x100 中断使能悬起寄存器
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; // Offset: 0x180 中断清除悬起寄存器
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; // Offset: 0x200 中断有效位寄存器
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; // Offset: 0x300 中断优先级寄存器 (8Bit)
uint32_t RESERVED5[644];
__O uint32_t STIR; // Offset: 0xE00 软件触发中断寄存器
} NVIC_Type;
二、中断编程
配置中断优先级分组
在中断编程之前,先配置中断优先级的分组,设置抢占优先级和子优先级,抢占优先级高于子优先级,数字小的优先级高,当抢占优先级一样时,在比较子优先级,当子优先级相同时,比较硬件中断向量表。在NVIC中,一共有五个分组,分别是:
优先级分组 | 主优先级 | 子优先级 | 描述 |
---|---|---|---|
NVIC_PriorityGroup_0 | 0 | 0-15 | 主-0bit 子-4bit |
NVIC_PriorityGroup_1 | 0-1 | 0-7 | 主-1bit 子-3bit |
NVIC_PriorityGroup_2 | 0-3 | 0-3 | 主-2bit 子-2bit |
NVIC_PriorityGroup_3 | 0-7 | 0-1 | 主-3bit 子-1bit |
NVIC_PriorityGroup_4 | 0-15 | 0 | 主-4bit 子-0bit |
固件库函数:
/**
* @brief Configures the priority grouping: pre-emption priority and subpriority.
* (配置优先级分组:抢占优先级和子优先级)
* @param NVIC_PriorityGroup: specifies the priority grouping bits length.
* (参数:选择特定的优先级分组)
* This parameter can be one of the following values:
* (这个参数可以是以下值中的一个)
* @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority
* 4 bits for subpriority
* (0个抢占优先级,4个子优先级)
* @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority
* 3 bits for subpriority
* @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority
* 2 bits for subpriority
* @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority
* 1 bits for subpriority
* @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority
* 0 bits for subpriority
* @retval None
*/
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
// 设置优先级分组,NVIC的分组主要是内核外设的寄存器SCB控制
SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
NVIC初始化结构体
typedef struct
{
uint8_t NVIC_IRQChannel; // 指定中断源,不同的中断中断源不一样,这个参数的值参考stm32f10x.h 头文件里面的IRQn_Type结构体的定义,里面包含了完整的中断源
uint8_t NVIC_IRQChannelPreemptionPriority; //设置抢占优先级,这个参数具体的值由优先级分组来确定
uint8_t NVIC_IRQChannelSubPriority; //设置抢占优先级,这个参数具体的值由优先级分组来确定
FunctionalState NVIC_IRQChannelCmd; //设置中断使能或者失能
} NVIC_InitTypeDef;
编写中断服务函数
每个中断都有对应的中断服务函数,中断服务函数在启动文件 startup_stm32f10x_hd.s预先已经写好,目的是为了初始化中断向量表。因此,每一个中断函数的函数名必须与启动文件预先写好的函数名一致,不一致将会导致系统在中断向量表中找不到中断服务函数的入口,直接跳入到了预先写好的空函数,在里面无限循环,实现不了中断。
三、外部中断/事件控制器(EXTI)
简介
EXTI外部中断/事件控制器,管理着20个中断/事件线,每一个都对应着一个边沿检测器,用来实现输入信号的上升沿检测和下降沿检测。EXTI能够对每一个中断或者事件进行单独的配置,即能够单独配置为中断或者事件,以及触发事件的属性
功能框图
EXTI可分为两个部分的功能,一个是产生中断,一个是产生事件。
输入线:EXTI控制器有19个中断/事件输入线(互联型是20个),这些输入线通过寄存器设置成任意的GPIO,和一些外设事件。输入线得到的是电平变化的信号。
接下来进入到边沿检测电路:它能够根据上升沿触发选择寄存(EXTI_RTSR) 和下降沿触发选择寄存器 (EXTI_FTSR) 对应位的设置来控制信号触发,有效输出1,无效则输出0。可以是只有上升沿触发,只有下降沿触发,或者上升沿和下降沿都触发。
之后经过或门电路,一个输入是边沿检测电路出来的信号,另外一个是来自软件中断事件寄存器,两个输入中只要有一个输出1,结果都为1.
上面与门:与中断屏蔽器(EXTI_IMR)一起输出。与门电路要求输入都为 1 才输出 1,因此当中断屏蔽器为零,则停止中断。通过中断屏蔽器去实现是否产生中断。当中断屏蔽器为1时,电路的输出信号会被保存到挂起寄存器 (EXTI_PR) 内,如果确定最终电路输出为 1 就会把 EXTI_PR 对应位置置1。然后将EXTI_PR寄存器内容输出到NVIC内,从而实现系统中断事件控制。
下面与门:一个输入来自前面的信号,另一个输入来自事件屏蔽寄存器 (EXTI_EMR)。如果EXTI_EMR设置为0时,那不管前面的输入信号,结果都为0,起到了屏蔽事件的作用。
脉冲发生器电路:输入端输入信号为1,即输入有效,产生一个脉冲,如果输入端是无效信号(为0),则不输出脉冲。这个脉冲信号可以给其他外设电路使用,比如定时器TIM、模拟数字转换器ADC等。
产生中断线路目的是把输入信号输入到 NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输属于硬件级的。
AFIO端口复用
程序内部,部分外设使用是复用IO口,便可以开该复用时钟。在STM32F103VET6中USART3的IO口为TX/PB10,RX/PB11。假若你要是将给外设IO口映射到(TX/PC10,RX/PC11)或者(TX/PD8,RX/PD9),便需要开启AFIO时钟即可正常工作。
而GPIO的初始功能没有中断功能,因此需要用到端口的映射。AFIO中EXTI0 可以通过 AFIO 的外部中断配置寄存器 1(AFIO_EXTICR1) 的 EXTI0[3:0] 位选择配置为 PA0、PB0、PC0、PD0、PE0、PF0、PG0、PH0 或者 PI0。其他EXTI中断线同样的配置。一共有4个外部中断寄存器去实现GPIO映射到外部中断。
EXTI初始化结构体
typedef struct { 2 uint32_t EXTI_Line; // 中断/事件线
EXTIMode_TypeDef EXTI_Mode; // EXTI 模式
EXTITrigger_TypeDef EXTI_Trigger; // 触发类型
FunctionalState EXTI_LineCmd; // EXTI 使能
} EXTI_InitTypeDef;
-
EXTI_Line:EXTI 中断/事件线选择,可选 EXTI0 至 EXTI19
-
EXTI_Mode:EXTI 模式选择,可选为产生中断(EXTI_Mode_Interrupt) 或者产生事件 (EXTI_Mode_Event)。
-
EXTI_Trigger:EXTI 边沿触发事件,可选上升沿触发 (EXTI_Trigger_Rising)、下降沿触发 ( EXTI_Trigger_Falling) 或者上升沿和下降沿都触发 ( EXTI_Trigger_Rising_Falling)。
-
EXTI_LineCmd:控制是否使能 EXTI 线,可选使能 EXTI 线 (ENABLE) 或禁用 (DISABLE)。
四、编程流程
- 初始化需要产生中断的GPIO
- 配置中断嵌套中断向量表NVIC
- 初始化EXTI
- 编写中断服务函数
代码
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 配置NVIC为优先级组1 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 配置中断源:按键 */
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
/* 配置抢占优先级 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 配置子优先级 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中断通道 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void EXTI_Key_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
/*开启按键GPIO口的时钟和AFIO端口复用*/ RCC_APB2PeriphClockCmd((RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO),ENABLE);
/* 配置 NVIC 中断*/
NVIC_Configuration();
/* 选择按键用到的GPIO */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
/* 配置为浮空输入 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 选择EXTI的信号源 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
/* EXTI为中断模式 */
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
/* 上升沿中断 */
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
/* 使能中断 */
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
void LED_GPIO_Config(void)
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
/*开启LED相关的GPIO外设时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
/*选择要控制的GPIO引脚*/
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_5;
/*设置引脚模式为通用推挽输出*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
/*设置引脚速率为50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,初始化GPIO*/
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_5);
}
void KEY1_IRQHandler(void)
{
//确保是否产生了EXTI Line中断
if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET)
{
// LED 取反
GPIOB->ODR ^= GPIO_Pin_5;
//清除中断标志位
EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);
}
}
int main(void)
{
/* LED 端口初始化 */
LED_GPIO_Config();
EXTI_Key_Config();
/* 等待中断,由于使用中断方式,CPU不用轮询按键 */
while(1)
{
}
}