目录
3.配置NVIC寄存器,初始化NVIC_InitTypeDef;
一、STM32中断应用概览
(1)简介
STM32 中断非常强大,每个外设都可以产生中断,所以中断的讲解放在哪一个外设里面去讲都不合适,这里单独抽出一章来做一个总结性的介绍。
中断:把正常正在运行的程序打断,运行中断服务函数,运行完之后再回到主程序,与51大体相仿。
外部中断,体现在外设水平,系统异常,体现在内核水平。下面,中断就是异常,异常就是中断。
以上向量表中,灰色标住的是体现在内核水平的(异常),其余的是外设水平的(外部中断)。
中断由NVIC这个外设来控制。
NVIC:嵌套向量中断控制器,属于内核外设,管理着包括部分内核和片上所有外设的中断相关的功能。 两个重要的库文件:core_cm3.h(定义所有内核上面的寄存器,NVIC寄存器就在这里面)和misc.h(相关的函数)。
对于中断而言,最重要的是优先级,由NVIC->IPRx(中断优先级寄存器)设定。打开内核寄存器的手册,
这个表里面的0-80对应着向量表里面外部中断的位置0-59,每一位都有8位位宽,真正起作用的只有4位。
函数:
NVIC_SetPriority (IRQn_Type IRQn, uint32_t priority)
// core_cm3.h 1586行
两个形参:中断号和优先级。中断号对应手册向量表的位置编号。
配置的时候先判断中断号是否大于0,小于0是内核的中断;大于0是外部中断。如果是外部中断,让优先级左移4位。如果是小于0,就配置另一个寄存器,SCB->SHP。
(2)中断编程的顺序:
1-使能中断请求
2-配置中断优先级分组
3-配置NVIC寄存器,初始化NVIC_InitTypeDef;
4-编写中断服务函数
1.使能中断请求:
这个与具体外设相关。以串口为例,发送数据完成之后,产生一个中断。我们去具体寄存器寻找,在控制寄存器 1(USART_CR1)里面有一位如下图,将这一位置即可。
2.中断优先级分组:
内核和外设的寄存器的优先级哪个更高取决于优先级分组,这个则是由寄存器SCB->AIRCR:PRIGROUP[10:8] 来配置的。
函数:
NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
// misc.c 96行
优先级分组1:一个位表示主优先级,剩下三个位表示子优先级。
判断优先级:分组>主优先级>子优先级.如果都相同,则看硬件中断编号(中断向量表)。
3.配置NVIC寄存器,初始化NVIC_InitTypeDef;
初始化的成员在 //misc.h 50行
typedef struct
{
uint8_t NVIC_IRQChannel; //指定中断源
uint8_t NVIC_IRQChannelPreemptionPriority; //设置抢占优先级(主优先级),
//中断优先级分组不同,抢占优先级的位数不同。
//如果分组为1,主优先级位数为1,
//那么主优先级只能为0或1
uint8_t NVIC_IRQChannelSubPriority; //设置子优先级如果前面分组为1,主优先级为1,
//子优先级就为3,2的3次方为8
//(即0-78个数随便写),让他等于0即可。
FunctionalState NVIC_IRQChannelCmd; //使能或者失能
} NVIC_InitTypeDef;
注意:
无论是抢占优先级(主优先级)还是响应优先级(子优先级),优先级数值越小,就代表优先级越高。
优先级较高的中断可以打断优先级较低的中断。
抢占优先级相同且响应优先级相同的中断,假如同时发生,会按照硬件内部固定的优先级执行。
中断优先级的分组对内核和外设同样适用。当比较的时候,只需要把内核外设的中断优先级的四个位按照外设的中断优先级来分组来解析即可,即人为的分出抢占优先级和子优先级。
4.编写中断服务函数
中断服务函数名一定要与中断向量表里面的名称一样。工程很大的话,就把所有的中断放在一个文件下,一般把中断服务函数放在stm32f10x_it.c中。
二、EXTI—外部中断/事件控制器
(1)简介
EXTI管理了控制器的20个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。
外部:芯片里的GPIO。可以对外输出0或1,输入的时候也可以检测输入的是0还是1.当出现0和1之间变化的时候,就会产生一个中断,也可以产生一个事件。
(2)EXTI结构图
红色是中断,绿色是事件。
输入线:EXTI 0-15是连接到GPIO的(每一个GPIO端口都有16个引脚),当我们使用EXTI0的时候,控制的是所有端口的第0个引脚。具体是哪个端口,在AFIO_EXTICR1、2、3、4这4个寄存器的EXTIx[3:0]位控制。(x属于0-15)
边沿检测电路:检测上升沿还是下降沿,由上升沿触发选择寄存器(EXTI_RTSR)和下降沿触发选择寄存器(EXTI_FTSR)控制。
或门:收到边沿检测电路传出的信号和软件中断事件寄存器(EXTI_SWIER)控制。软件中断事件寄存器返回1之后,就不受边沿检测电路控制。
在经过或门之后,分为两路:当中断屏蔽寄存器和请求挂起寄存器相关位都置1的时候就会产生中断,交给NVIC,再交给内核相应。如果事件屏蔽寄存器相关位置1,就会把这个1信号给脉冲发生器,产生脉冲(脉冲就是高电平)。脉冲:ITM定时器开始计数、触发ADC的采集。【作为触发信号】
(3)初始化结构体成员
EXTI_InitTypeDef
1-EXTI_Line:用于产生中断/事件线(框图里的输入线)
2-EXTI_Mode:EXTI模式(中断/事件)(中断屏蔽寄存器/事件屏蔽寄存器)
3-EXTI_Trigger:触发(上升沿/下降沿/上下)
4-EXTI_LineCmd:使能或者失能(IMR/EMR)
(4)程序设计:
通过两个按键控制,PA0上升沿(按下)触发,PC13下降沿(弹开)触发。产生一次中断,LED反转一次。
思路设计:
先配置外设(按键等)用到的外部中断(EXTI)
功能:初始化外设(按键等)要连接到 EXTI 的 GPIO。
配置 EXTI 结构体,初始化 EXTI,产生中断信号。
再配置NVIC中断控制器
功能:配置中断优先级分组,再初始化 NVIC 结构体,然后初始化 NVIC 去处理中断。
1.初始化要连接到EXTI的GPIO。
1.观察外设(GPIO)端口结构体的成员:端口、速度、输出/入方式,我们把常用到的端口、宏定义(与硬件相关的)用我们熟悉的/好理解的名字在bsp_led.h文件里面重新define宏定义一下。一般情况下,GPIO端口的重新宏定义包括APB2时钟、具体哪个GPIOX(ABCD)端口、GPIOX的管脚pin。
2.在bsp_led.c文件里面把GPIO初始化好,详细步骤参见上面初始化I/O口的步骤:打开GPIO的时钟;配置外设初始化结构体;调用外设初始化函数,把配置好的结构体成员写到寄存器里面。
GPIO_InitTypeDef GPIO_InitStruct;//外设的初始化结构体变量
EXTI_InitTypeDef EXTI_InitStruct;//外设的初始化结构体变量
NVIC_Config();
/*第一步:初始化要连接到 EXTI 的 GPIO*/
/*1:打开外设的时钟(RCC寄存器控制)*/
RCC_APB2PeriphClockCmd(KEY1_EXTI_GPIO_CLK |KEY2_EXTI_GPIO_CLK,ENABLE);
/*2:配置外设初始化结构体*/
GPIO_InitStruct.GPIO_Pin = KEY1_EXTI_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = KEY2_EXTI_GPIO_PIN;
/*3:调用外设(GPIO)初始化函数,把配置好的结构体成员写到寄存器里面*/
GPIO_Init(KEY1_EXTI_GPIO_PORT,&GPIO_InitStruct);//初始化端口
GPIO_Init(KEY2_EXTI_GPIO_PORT,&GPIO_InitStruct);//初始化端口
2.初始化EXTI用于产生中断/事件
和第一步一样,先观察该外设(EXTI)的初始化结构体成员:
typedef struct
{
uint32_t EXTI_Line; //哪一条输入线(总共20条)
EXTIMode_TypeDef EXTI_Mode; //这条线的模式(中断/事件)
EXTITrigger_TypeDef EXTI_Trigger; //触发选择(上升/下降/同时)
FunctionalState EXTI_LineCmd; //使能
}EXTI_InitTypeDef;
把需要宏定义的变量定义好:
#define KEY1_EXTI_GPIO_PORTSOURCE GPIO_PortSourceGPIOA//中断输入线属于哪个端口
#define KEY2_EXTI_GPIO_PORTSOURCE GPIO_PortSourceGPIOC
#define KEY1_EXTI_GPIO_PINSOURCE GPIO_PinSource0//中断输入线属于哪个引脚
#define KEY2_EXTI_GPIO_PINSOURCE GPIO_PinSource13
#define KEY1_EXTI_LINE EXTI_Line0//选择哪条输入线
#define KEY2_EXTI_LINE EXTI_Line13
根据前面讲解的功能框图,打开AFIO的时钟;确定中断输入线用到哪个端口、哪个引脚(函数作用为选择用作EXTI线的GPIO引脚);配置初始化结构体;调用 EXTI 初始化函数,把配置好的结构体成员写到寄存器里面。至此中断信号已经产生。
到这就可以发现,这些配置寄存器的方法都大体相同。
/*第二步:初始化EXTI外设*/
/*1:打开外设的时钟(RCC寄存器控制)*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
/*2.确定中断输入线用到哪个端口、哪个引脚*/
GPIO_EXTILineConfig(KEY1_EXTI_GPIO_PORTSOURCE, KEY1_EXTI_GPIO_PINSOURCE);
/*3.配置初始化结构体*/
EXTI_InitStruct.EXTI_Line = KEY1_EXTI_LINE;//选择哪条输入线
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;//这条线的模式(中断/事件)
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;//触发选择(上升/下降/同时)
EXTI_InitStruct.EXTI_LineCmd = ENABLE;//使能
/*4.调用 EXTI 初始化函数,把配置好的结构体成员写到寄存器里面*/
EXTI_Init(&EXTI_InitStruct);
/*2.确定中断输入线用到哪个端口、哪个引脚*/
GPIO_EXTILineConfig(KEY2_EXTI_GPIO_PORTSOURCE, KEY2_EXTI_GPIO_PINSOURCE);
/*3.配置初始化结构体*/
EXTI_InitStruct.EXTI_Line = KEY2_EXTI_LINE;//选择哪条输入线
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;//这条线的模式(中断/事件)
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;//触发选择(上升/下降/同时)
EXTI_InitStruct.EXTI_LineCmd = ENABLE;//使能
/*4.调用 EXTI 初始化函数,把配置好的结构体成员写到寄存器里面*/
EXTI_Init(&EXTI_InitStruct);
//至此,中断信号已经产生
3.初始化NVIC,用于处理中断
先配置中断优先级分组,再初始化NVIC的初始化结构体,然后调用初始化NVIC外设函数,把配置好的结构体成员写到寄存器里面。
static void NVIC_Config(void)//配置NVIC中断控制器 静态的函数,表示只能在这个文件中使用
{
NVIC_InitTypeDef NVIC_InitTStruct;//初始化结构体的变量
/*第一步:配置中断优先级分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/*第二步:初始化 NVIC 的初始化结构体*/
NVIC_InitTStruct.NVIC_IRQChannel = KEY1_EXTI_IRQN;
NVIC_InitTStruct.NVIC_IRQChannelPreemptionPriority = 1;//前面配置的中断优先级分组为1,主优先级只有一个位来表示
NVIC_InitTStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitTStruct.NVIC_IRQChannelCmd = ENABLE;
/*第三步:调用初始化NVIC外设函数,把配置好的结构体成员写到寄存器里面*/
NVIC_Init(&NVIC_InitTStruct);
/*第二步:初始化 NVIC 的初始化结构体*/
NVIC_InitTStruct.NVIC_IRQChannel = KEY2_EXTI_IRQN;
NVIC_InitTStruct.NVIC_IRQChannelPreemptionPriority = 1;//前面配置的中断优先级分组为1,主优先级只有一个位来表示
NVIC_InitTStruct.NVIC_IRQChannelSubPriority = 1;//这样key1的中断优先级就大于key2的
NVIC_InitTStruct.NVIC_IRQChannelCmd = ENABLE;
/*第三步:调用初始化NVIC外设函数,把配置好的结构体成员写到寄存器里面*/
NVIC_Init(&NVIC_InitTStruct);
}
4.编写中断服务函数
首先,有一个检测是否产生中断的函数,如果产生了中断,就……
void KEY1_EXTI_IRQHandler(void)
{
if(EXTI_GetITStatus(KEY1_EXTI_LINE) != RESET )//如果产生了中断
{
LED1_TOGGLE;//LED1 亮灭反转
}
EXTI_ClearITPendingBit(KEY1_EXTI_LINE);//清除相关的中断标志位
}
void KEY2_EXTI_IRQHandler(void)
{
if(EXTI_GetITStatus(KEY2_EXTI_LINE) != RESET )//如果产生了中断
{
LED2_TOGGLE;//LED1 亮灭反转
}
EXTI_ClearITPendingBit(KEY2_EXTI_LINE);//清除相关的中断标志位
}
5.main函数
等待外部中断的到来,然后去中断服务函数里面执行程序。
int main(void)
{
/*在程序来到main函数这里的时候,系统时钟已经配置成72M*/
LED_GPIO_Config();//初始化函数
/*等待外部中断的到来,然后去中断服务函数里面执行程序*/
EXTI_KEY_Config();
while(1)
{
}
}