1 、简介
ARM Coetex-M3内核共支持256个中断,其中16个内部中断,240个外部中断和可编程的256级中断优先级的设置。STM32目前支持的中断共84个(16个内部+68个外部),还有16级可编程的中断优先级的设置,仅使用中断优先级设置8bit中的高4位。
STM32可支持68个中断通道,已经固定分配给相应的外部设备,每个中断通道都具备自己的中断优先级控制字节PRI_n(8位,但是STM32中只使用4位,高4位有效),每4个通道的8位中断优先级控制字构成一个32位的优先级寄存器。68个通道的优先级控制字至少构成17个32位的优先级寄存器。
4bit的中断优先级可以分成2组,从高位看,前面定义的是抢占式优先级,后面是响应优先级。按照这种分组,4bit一共可以分成5组
- 第0组:所有4bit用于指定响应优先级;
- 第1组:最高1位用于指定抢占式优先级,后面3位用于指定响应优先级;
- 第2组:最高2位用于指定抢占式优先级,后面2位用于指定响应优先级;
- 第3组:最高3位用于指定抢占式优先级,后面1位用于指定响应优先级;
- 第4组:所有4位用于指定抢占式优先级。
所谓抢占式优先级和响应优先级,他们之间的关系是:具有高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应,即中断嵌套。
当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理。如果这两个中断同时到达,则中断控制器根据他们的响应优先级高低来决定先处理哪一个;如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序决定先处理哪一个。每一个中断源都必须定义2个优先级。
有几点需要注意的是:
- 如果指定的抢占式优先级别或响应优先级别超出了选定的优先级分组所限定的范围,将可能得到意想不到的结果;
- 抢占式优先级别相同的中断源之间没有嵌套关系;
- 如果某个中断源被指定为某个抢占式优先级别,又没有其它中断源处于同一个抢占式优先级别,则可以为这个中断源指定任意有效的响应优先级别。
2 、GPIO外部中断
STM32中,每一个GPIO都可以触发一个外部中断,但是,GPIO的中断是以组为一个单位的,同组间的外部中断同一时间只能使用一个。比如说,PA0,PB0,PC0,PD0,PE0,PF0,PG0这些为1组,如果我们使用PA0作为外部中断源,那么别的就不能够再使用了,在此情况下,我们只能使用类似于PB1,PC2这种末端序号不同的外部中断源。每一组使用一个中断标志EXTIx。EXTI0 – EXTI4这5个外部中断有着自己的单独的中断响应函数,EXTI5-9共用一个中断响应函数,EXTI10-15共用一个中断响应函数。
线 0~15:对应外部 IO 口的输入中断。
GPIO引脚 | 中断标志位 | 中断处理函数 |
PA0~PG0 | EXTI0 | EXTI0_IRQHandler |
PA1~PG1 | EXTI1 | EXTI1_IRQHandler |
PA2~PG2 | EXTI2 | EXTI2_IRQHandler |
PA3~PG3 | EXTI3 | EXTI3_IRQHandler |
PA4~PG4 | EXTI4 | EXTI4_IRQHandler |
PA5~PG5 | EXTI5 | EXTI9_5_IRQHandler |
PA6~PG6 | EXTI6 | |
PA7~PG7 | EXTI7 | |
PA8~PG8 | EXTI8 | |
PA9~PG9 | EXTI9 | |
PA10~PG10 | EXTI10 | EXTI15_10_IRQHandler |
PA11~PG11 | EXTI11 | |
PA12~PG12 | EXTI12 | |
PA13~PG13 | EXTI13 | |
PA14~PG14 | EXTI14 | |
PA15~PG15 | EXTI15 |
线 16:连接到 PVD 输出。
线 17:连接到 RTC 闹钟事件。
线 18:连接到 USB 唤醒事件。
对于中断的控制,STM32有一个专用的管理机构:NVIC。
3、 程序开发
上面那些概念只是对STM32的中断系统有一个大概的认识,用程序说话将会更能够加深如何使用中断。使用外部中断的基本步骤如下:
- 设置好相应的时钟;
- 设置相应的中断;
- IO口初始化;
- 把相应的IO口设置为中断线路(要在设置外部中断之前)并初始化;
- 在选择的中断通道的响应函数中中断函数
由于我用的开发板没有引出相应的芯片引脚,所以只能用按键来触发相应的中断。根据原理图,K0/K1/K2连接的是PF2/PF3/PF4,因此我将用EXTI2/EXTI3/EXTI4三个外部中断。PG13/PG14/PG15分别连接了三个LED灯。中断的效果是按下按键,相应的LED灯将会被点亮。
3.1、设置相应的时钟
首先需要打开GPIOG和GPIOF(因为按键另外一端连接的是PF口)。然后由于是要用于触发中断,所以还需要打开GPIO复用的时钟(需要用到外设的重映射功能时才需要使能AFIO的时钟)。相应的函数在GPIO的学习笔记中有了详细了解释。详细代码如下:
void RCC_cfg() { //打开PG PF端口时钟,并且打开复用时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG | RCC_APB2Periph_GPIOF | RCC_APB2Periph_AFIO, ENABLE); }
设置相应的时钟所需要的RCC函数在stm32f10x_rcc.c中,所以要在工程中添加此文件
3.2、设置好相应的中断
设置相应的中断实际上就是设置NVIC,在STM32的固件库中有一个结构体NVIC_InitTypeDef,里面有相应的标志位设置,然后再用NVIC_Init()函数进行初始化。详细代码如下:
void NVIC_cfg()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //选择中断分组2
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQChannel; //用3.5的库所有IQRChannel全部变成IQRn选择中断通道2
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占式中断优先级设置为0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应式中断优先级设置为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQChannel; //选择中断通道3
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占式中断优先级设置为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //响应式中断优先级设置为1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQChannel; //选择中断通道5
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占式中断优先级设置为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //响应式中断优先级设置为2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断
NVIC_Init(&NVIC_InitStructure);
}
用3.5的库,所有的USART1_IRQChannel,全部变成:USART1_IRQn。
#include<stm32f10x_lib.h>和#include<stm32f10x.h>的区别:
- #include<stm32f10x_lib.h>是ST公司V2.0的库函数使用的头文件,
- #include<stm32f10x.h>是ST公司V3.5及以后版本统一使用的库函数头文件了,说白了就是把原来的stm32f10x_lib.h,换成了#include<stm32f10x.h>,规范了代码,不需要包含那么多的头文件了。
由于有3个中断,因此根据前文所述,需要有3个bit来指定抢占优先级,所以选择第2组。又由于EXTI5-9共用一个中断响应函数,所以EXTI5选择的中断通道是EXTI9_5_IRQChannel,详细信息可以在头文件中查询得到。用到的NVIC相关的库函数在stm32f10x_nivc.c中,需要将此文件复制并添加到工程中。
3.3、IO口初始化
void IO_cfg() { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //选择引脚2 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出频率最大50MHz GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //带上拉电阻输出 GPIO_Init(GPIOE,&GPIO_InitStructure); GPIO_ResetBits(GPIOE,GPIO_Pin_2); //将PE.2引脚设置为低电平输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4; //选择引脚2 3 4 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //选择输入模式为浮空输入 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出频率最大50MHz GPIO_Init(GPIOF,&GPIO_InitStructure); //设置Pf.2/PF.3/PF.5 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14|GPIO_Pin_15; //选择引脚13,14,15 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出频率最大50MHz GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //带上拉电阻输出 GPIO_Init(GPIOD,&GPIO_InitStructure); }
其中连接外部中断的引脚需要设置为输入状态,而连接LED的引脚需要设置为输出状态,初始化PE.2是为了使得按键的另外一端输出低电平。GPIO中的函数在stm32f10x_gpio.c中。
3.4、 把相应的IO口设置为中断线路
由于GPIO并不是专用的中断引脚,因此在用GPIO来触发外部中断的时候需要设置将GPIO相应的引脚和中断线连接起来,具体代码如下:
void EXTI_cfg() { EXTI_InitTypeDef EXTI_InitStructure; //清空中断标志 EXTI_ClearITPendingBit(EXTI_Line2); EXTI_ClearITPendingBit(EXTI_Line3); EXTI_ClearITPendingBit(EXTI_Line4); //选择中断管脚PF.2 PF.3 PF.4 GPIO_EXTILineConfig(GPIO_PortSourceGPIOF, GPIO_PinSource2); GPIO_EXTILineConfig(GPIO_PortSourceGPIOF, GPIO_PinSource3); GPIO_EXTILineConfig(GPIO_PortSourceGPIOF, GPIO_PinSource4); EXTI_InitStructure.EXTI_Line = EXTI_Line2 | EXTI_Line3 | EXTI_Line4; //选择中断线路2 3 4 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //设置为中断请求,非事件请求 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; //设置中断触发方式为上下降沿触发 EXTI_InitStructure.EXTI_LineCmd = ENABLE; //外部中断使能 EXTI_Init(&EXTI_InitStructure); } EXTI_cfg中需要调用到的函数都在stm32f10x_exti.c。
3.5、写中断响应函数
STM32不像C51单片机那样,可以用过interrupt关键字来定义中断响应函数,STM32的中断响应函数接口存在中断向量表中,是由启动代码给出的。默认的中断响应函数在stm32f10x_it.c中。因此我们需要把这个文件加入到工程中来。
在这个文件中,我们发现,很多函数都是只有一个函数名,并没有函数体。我们找到EXTI2_IRQHandler()这个函数,这就是EXTI2中断响应的函数。我的目标是将LED灯点亮,所以函数体其实很简单:
void EXTI2_IRQHandler(void) { //点亮LED灯 GPIO_SetBits(GPIOG,GPIO_Pin_13); //清空中断标志位,防止持续进入中断 EXTI_ClearITPendingBit(EXTI_Line2); } void EXTI3_IRQHandler(void) { GPIO_SetBits(GPIOG,GPIO_Pin_14); EXTI_ClearITPendingBit(EXTI_Line3); } void EXTI4_IRQHandler(void) { GPIO_SetBits(GPIOG,GPIO_Pin_15); EXTI_ClearITPendingBit(EXTI_Line5); }
由于EXTI5-9是共用一个中断响应函数,因此所有的EXTI5 – EXTI9的响应函数都写在这个里面。
3.6、 写主函数
#include "stm32f10x_lib.h" void RCC_cfg(); void IO_cfg(); void EXTI_cfg(); void NVIC_cfg(); int main() { RCC_cfg(); IO_cfg(); NVIC_cfg(); EXTI_cfg(); while(1); }
main函数前是函数声明,main函数函数体中都是调用初始化配置函数,然后进入死循环,等待中断响应
大部分参考:http://blog.sina.com.cn/s/blog_49cb42490100rp7b.html