STM32F4xx中断
在主程序运行过程中出发了特定中断条件,使得CPU暂停当前正在运行的程序转而去处理中断程序,处理完成后返回主程序暂停位置。每个外设都可以产生中断,当有多个中断源同时申请中断时,CPU会根据优先级啊依次响应中断。
NVIC
NVIC(嵌套矢量中断控制器),统一管理中断相关的功能,是内核里面的一个外设,每个中断通道都拥有16个可编程的优先等级(4位进行设置),可对优先级进行分组,进一步设置抢占优先级和响应优先级。这里给出固件库头文件core_cm4.h
文件中NVIC结构体定义
typedef struct {
__IO uint32_t ISER[8]; // 中断使能寄存器
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; // 中断清除寄存器
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; // 中断使能悬起寄存器
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; // 中断清除悬起寄存器
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; // 中断有效位寄存器
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; // 中断优先级寄存器(8Bit wide)
uint32_t RESERVED5[644];
__O uint32_t STIR; // 软件触发中断寄存器
} NVIC_Type;
在配置中断的时候我们一般只用ISER、ICER和IP这三个寄存器,ISER用来使能中断,ICER用来失能中断,IP用来设置中断优先级。在NVIC 有一个专门的寄存器:中断优先级寄存器NVIC_IPRx(在F407中,x=0…981)用来配置外部中断的优先级,IPR宽度为8bit, 原则上每个外部中断可配置的优先级为0~255,数值越小,优先级越高。
处于精简设计目的,在F407中只使用了高4bit进行优先级分组,只能表示16级中断嵌套,这4bit又分为抢占优先级(高n位)和子优先级(4-n 位),抢占优先级高于子优先级。优先级的分组由内核外设SCB的应用程序中断及复位控制寄存器AIRCR的PRIGROUP[10:8]位决定,F407分为了5组,具体如下
设置优先级分组可调用库函数NVIC_PriorityGroupConfig()实现,仅需设置一次覆盖所有中断,有关NVIC中断相关的库函数都在库文件misc.c和misc.h中。
/**
* 配置中断优先级分组:抢占优先级和子优先级
* 形参如下:
* @arg NVIC_PriorityGroup_0: 0bit for抢占优先级
* 4 bits for 子优先级
* @arg NVIC_PriorityGroup_1: 1 bit for抢占优先级
* 3 bits for 子优先级
* @arg NVIC_PriorityGroup_2: 2 bit for抢占优先级
* 2 bits for 子优先级
* @arg NVIC_PriorityGroup_3: 3 bit for抢占优先级
* 1 bits for 子优先级
* @arg NVIC_PriorityGroup_4: 4 bit for抢占优先级
* 0 bits for 子优先级
* @注意 如果优先级分组为0,则抢占优先级就不存在,优先级就全部由子优先级控制
*/
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
// 设置优先级分组
SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
可参考stm32f4xx.h头文件里面的IRQn_Type结构体定义,这个结构体包含了所有的中断源。
EXTI
EXTI(External interrupt/event controller )外部中断/事件控制器,管理MCU多达** 23 个软件事件/中断请求**,每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。 EXTI可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。EXTI在APB2总线。
EXTI主要有两个功能,产生中断、产生事件。输入线可以通过寄存器设置为任意一个GPIO,也可以是外设事件。其中中断是在NVIC中控制,事件是由脉冲触发,中断和事件都可以被屏蔽。总结可得:产生中断线路目的是把输入信号输入到NVIC,属于软件级;产生事件线路目的就是传输一个脉冲信号给其他外设,属于硬件级。
中断/事件线
EXTI有23个中断/事件线,其中GPIO占用EXTI0至EXTI15,另外七根用于特定外设事件。
EXTI可以检测指定GPIO口的电平信号,当检测到特定电平信号时,EXTI将向NVIC发出中断申请,由NVIC根据优先级依次通知CPU响应中断。
例如EXTI0线,通过SYSCFG_EXTICR1寄存器的EXTI0[3:0]位选择对应的GPIO口。最终在实现上是完成EXTI到NVIC之间的通路即可。标准库函数对每个外设都建立了一个初始化结构体,EXTI结构体定义在stm32f4xx_exti.h文件中,初始化库函数定义在stm32f4xx_exti.c文件中
typedef struct {
uint32_t EXTI_Line; // 中断/事件线
EXTIMode_TypeDef EXTI_Mode; // EXTI模式
EXTITrigger_TypeDef EXTI_Trigger; // 触发事件
FunctionalState EXTI_LineCmd; // EXTI控制
} EXTI_InitTypeDef;
中断函数名是固定的,在startup_stm32f40xx.s启动文件中有定义
中断案例
内部串口中断
当串口接收到正确数据帧产生中断
/*
**********************************************************************************
* @brief USART1串口初始化
* PA9是USART1_TX,PA10是USART1_RX
* GPIO是AHB1总线,USART1是APB2总线
* @param none
* @return none
* @use Serial_Init()
**********************************************************************************
*/
void Serial_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
//USART1对应引脚复用映射
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
//配置PA9、PA10引脚
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置USART1
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,这里不选用
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //双工
USART_InitStructure.USART_Parity = USART_Parity_No; //校验位,这里不选用
USART_InitStructure.USART_StopBits = USART_StopBits_1; //1位长的停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长8位
USART_Init(USART1, &USART_InitStructure);
USART_ClearFlag(USART1, USART_FLAG_RXNE); //清除接收数据寄存器非空标志位
//配置USART中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //配置USART接收数据寄存器非空中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //选择第二组中断
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //配置USART1中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能NVIC
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE);
}
/*
**********************************************************************************
* @brief 中断函数,接收不定长文本数据帧,包头@,包尾\r\n
* @param none
* @return none
* @use 中断事件触发会自动跳转到此函数
**********************************************************************************
*/
void USART1_IRQHandler()
{
static uint8_t RxState = 0; //接收状态
static uint8_t pRxPacket = 0; //接收指针
if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
uint8_t RxData = USART_ReceiveData(USART1); //接收到的数据
if(RxState == 0) //默认状态0
{
if(RxData == '@')
{
RxState = 1; //成功接收到帧头,进入状态1
pRxPacket = 0;
}
}
else if(RxState == 1) //状态1
{
if(RxData == '\r') RxState = 2; //在状态1阶段看是否接收到帧尾,否则一直接收数据
else
{
Serial_RxPacket[pRxPacket] = RxData;
pRxPacket ++;
}
}
else if(RxState == 2) //状态2
{
if(RxData == '\n')
{
RxState = 0; //成果接收到帧尾,返回状态0
Serial_RxPacket[pRxPacket] = '\0';
printf("接收完毕!!\n");
for(uint8_t i = 0; i < pRxPacket; i++)
{
Serial_SendOneByte(Serial_RxPacket[i]);
}
}
}
else printf("error\n");
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //如果正常读取RDR数据会自动清除标志位
}
}
按键外部中断
按不同按键,串口打印不同按键值
#include "Key.h"
#include "Serial.h"
#include "Delay.h"
/*
**********************************************************************************
* @brief NVIC配置
* @param none
* @return none
* @use NVIC_Configuration()
**********************************************************************************
*/
static void NVIC_Configuration()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//按键1中断
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
//按键2中断
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStructure);
//WK_UP按键
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_Init(&NVIC_InitStructure);
}
/*
**********************************************************************************
* @brief 板载按键初始化,PE3、PE4分别对应KEY1、KEY0,按下是拉低电平,需要配置为上拉输入
按键WK_UP对应PA0,按下是拉高电平,需要配置为下拉输入
* @param none
* @return none
* @use Key_Init()
**********************************************************************************
*/
void Key_Init()
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); //使用GPIO外部中断时必须使能SYSCFG时钟
NVIC_Configuration();
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; //配置输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4; //配置引脚
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //配置为上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //配置引脚速度
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //配置引脚
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; //配置为下拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置EXTI
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0); //PA0连接到EXTI0
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource3); //PE3连接到EXTI3
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource4); //PE4连接到EXTI4
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line3 | EXTI_Line4; //PE3和PE4中断/事件线
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能中断/事件线
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //选择中断
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //由于按下拉低电平,下降沿触发
EXTI_Init(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line = EXTI_Line0; //PE0中断/事件线
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; //由于按下拉高电平,上升沿触发
EXTI_Init(&EXTI_InitStructure);
}
/*
**********************************************************************************
* @brief 获取板载按键值
* @param none
* @return 0、1、2
* @use Key_GetNum()
**********************************************************************************
*/
uint8_t Key_GetNum()
{
uint8_t KeyNum;
//K0按键置为1
if(GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4) == 0)
{
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4) == 0);
Delay_ms(20);
KeyNum = 1;
}
//K1按键置为2
if(GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3) == 0)
{
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3) == 0);
Delay_ms(20);
KeyNum = 2;
}
//WK_UP按键置为0
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 1)
{
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 1);
Delay_ms(20);
KeyNum = 0;
}
return KeyNum;
}
//中断函数
/*
**********************************************************************************
* @brief WK_UP按键触发中断
* @param
* @return
* @use
**********************************************************************************
*/
void EXTI0_IRQHandler(void)
{
printf("Key0\n");
EXTI_ClearITPendingBit(EXTI_Line0);
}
/*
**********************************************************************************
* @brief WK_UP按键触发中断
* @param
* @return
* @use
**********************************************************************************
*/
void EXTI3_IRQHandler(void)
{
printf("Key1\n");
EXTI_ClearITPendingBit(EXTI_Line3);
}
/*
**********************************************************************************
* @brief WK_UP按键触发中断
* @param
* @return
* @use
**********************************************************************************
*/
void EXTI4_IRQHandler(void)
{
printf("Key2\n");
EXTI_ClearITPendingBit(EXTI_Line4);
}