stm32外部中断的使⽤(含实例)
中断对于开发嵌⼊式系统来讲的地位绝对是⽏庸置疑的,在C51单⽚机时代,⼀共只有5个中断,其中2个外部中断,2个定时/计数器中断和⼀个串⼝中断,但是在STM32中,中断数量⼤⼤增加,⽽且中断的设置也更加复杂。今天就将来探讨⼀下关于STM32中的中断系统。
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个优先级。
有⼏点需要注意的是:
1)如果指定的抢占式优先级别或响应优先级别超出了选定的优先级分组所限定的范围,将可能得到意想不到的结果;
2)抢占式优先级别相同的中断源之间没有嵌套关系;
3)如果某个中断源被指定为某个抢占式优先级别,⼜没有其它中断源处于同⼀个抢占式优先级别,则可以为这个中断源指定任意有效的响应优先级别。
2 GPIO外部中断
STM32中,每⼀个GPIO都可以触发⼀个外部中断,但是,GPIO的中断是以组位⼀个单位的,同组间的外部中断同⼀时间只能使⽤⼀个。⽐如说,PA0,PB0,PC0,PD0,PE0,PF0,PG0这些为1组,如果我们使⽤PA0作为外部中断源,那么别的就不能够再使⽤了,在此情况下,我们只能使⽤类似于PB1,PC2这种末端序号不同的外部中断源。每⼀组使⽤⼀个中断标志EXTIx。EXTI0 – EXTI4这5个外部中断有着⾃⼰的单独的中断响应函数,EXTI5-9共⽤⼀个中断响应函数,EXTI10-15共⽤⼀个中断响应函数。
对于中断的控制,STM32有⼀个专⽤的管理机构:NVIC。对于NVIC的详细解释,可以参考《ARM Cort
ex-M3权威指南》,Joseph Yiu著,宋岩译,北京航空航天⼤学出版社出版,第8章NVIC与中断控制。中断的使能,挂起,优先级,活动等等部都是NVIC在管理的。因为我学习STM32重点在于如何开发程序,所以内部的⼀些东西,在此我就不详细说明了,有感兴趣的可以参看上⾯提到的那本数。
3 程序开发
其实上⾯那些基本概念和知识只是对STM32的中断系统有⼀个⼤概的认识,⽤程序说话将会更能够加深如何使⽤中断。使⽤外部中断的基本步骤如下:
1.      设置好相应的时钟;
2.      设置相应的中断;
3.      IO⼝初始化;
4.      把相应的IO⼝设置为中断线路(要在设置外部中断之前)并初始化;
5.      在选择的中断通道的响应函数中中断函数。
由于我⽤的奋⽃开发板没有引出相应的芯⽚引脚,所以只能⽤按键来触发相应的中断。根据原理图,K1/
K2/K3连接的是PC5/PC2/PC3,因此我将⽤EXTI5/EXTI2/EXTI3三个外部中断。PB5/PD6/PD3分别连接了三个LED灯。中断的效果是按下按键,相应的LED灯将会被点亮。
1.      设置相应的时钟
⾸先需要打开GPIOB、GPIOC和GPIOE(因为按键另外⼀端连接的是PE⼝)。然后由于是要⽤于触发中断,所以还需要打开GPIO复⽤的时钟。相应的函数在GPIO的学习笔记中有了详细了解释。详细代码如下:
void RCC_cfg()
{
//打开PE PD PC PB端⼝时钟,并且打开复⽤时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE| RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD |
RCC_APB2Periph_GPIOB |RCC_APB2Periph_AFIO, ENABLE);
}
设置相应的时钟所需要的RCC函数在stm32f10x_rcc.c中,所以要在⼯程中添加此⽂件。
2.      设置好相应的中断
设置相应的中断实际上就是设置NVIC,在STM32的固件库中有⼀个结构体NVIC_InitTypeDef,⾥⾯有相应的标志位设置,然后再
⽤NVIC_Init()函数进⾏初始化。详细代码如下:
void NVIC_cfg()
{
NVIC_InitTypeDefNVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                          //选择中断分组2
NVIC_InitStructure.NVIC_IRQChannel= EXTI2_IRQChannel;    //选择中断通道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= EXTI9_5_IRQChannel;  //选择中断通道5
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 2; //抢占式中断优先级设置为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority= 2;  //响应式中断优先级设置为2
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;                                  //使能中断
NVIC_Init(&NVIC_InitStructure);
由于有3个中断,因此根据前⽂所述,需要有3个bit来指定抢占优先级,所以选择第2组。⼜由于EXTI5-9共⽤⼀个中断响应函数,所
以EXTI5选择的中断通道是EXTI9_5_IRQChannel,详细信息可以在头⽂件中查询得到。⽤到的NVIC相关的库函数在stm32f10x_nivc.c中,需要将此⽂件复制并添加到⼯程中。具体位置可以查看关于GPIO的笔记。这段代码编译起来没有任何问题,但是在链接的时候就会报错,需要把STM32F10xR.LIB加⼊⼯程中,具体位置在…\Keil\ARM\RV31\LIB\ST\STM32F10xR.LIB。
3.      IO⼝初始化
void IO_cfg()
{
GPIO_InitTypeDefGPIO_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_5; //选择引脚2 3 5
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IN_FLOATING; //选择输⼊模式为浮空输⼊
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;          //输出频率最⼤50MHz
GPIO_Init(GPIOC,&GPIO_InitStructure);                                //设置PC.2/PC.3/PC.5
GPIO_InitStructure.GPIO_Pin= GPIO_Pin_3 |GPIO_Pin_6;                  //选择引脚3 6
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;          //输出频率最⼤50MHz
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;                  //带上拉电阻输出
GPIO_Init(GPIOD,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;                                        //选择引脚5
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;          //输出频率最⼤50MHz
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;                  //带上拉电阻输出
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
其中连接外部中断的引脚需要设置为输⼊状态,⽽连接LED的引脚需要设置为输出状态,初始化PE.2是为了使得按键的另外⼀端输出低电平。GPIO中的函数在stm32f10x_gpio.c中。
stm32怎么使用printf4.      把相应的IO⼝设置为中断线路
由于GPIO并不是专⽤的中断引脚,因此在⽤GPIO来触发外部中断的时候需要设置将GPIO相应的引脚和中断线连接起来,具体代码如下:void EXTI_cfg()
EXTI_InitTypeDefEXTI_InitStructure;
//清空中断标志
EXTI_ClearITPendingBit(EXTI_Line2);
EXTI_ClearITPendingBit(EXTI_Line3);
EXTI_ClearITPendingBit(EXTI_Line5);
//选择中断管脚PC.2 PC.3 PC.5
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource2);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource3);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource5);
EXTI_InitStructure.EXTI_Line= EXTI_Line2 | EXTI_Line3 | EXTI_Line5; //选择中断线路2 3 5
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。
5.      写中断响应函数
STM32不像C51单⽚机那样,可以⽤过interrupt关键字来定义中断响应函数,STM32的中断响应函数接⼝存在中断向量表中,是由启动代码给出的。默认的中断响应函数在stm32f10x_it.c中。因此我们需要把这个⽂件加⼊到⼯程中来。
在这个⽂件中,我们发现,很多函数都是只有⼀个函数名,并没有函数体。我们到EXTI2_IRQHandler()这个函数,这就是EXTI2中断响应的函数。我的⽬标是将LED灯点亮,所以函数体其实很简单:
voidEXTI2_IRQHandler(void)
{
//点亮LED灯
GPIO_SetBits(GPIOD,GPIO_Pin_6);
//清空中断标志位,防⽌持续进⼊中断
EXTI_ClearITPendingBit(EXTI_Line2);
}
voidEXTI3_IRQHandler(void)
{
GPIO_SetBits(GPIOD,GPIO_Pin_3);
EXTI_ClearITPendingBit(EXTI_Line3);
}
voidEXTI9_5_IRQHandler(void)
GPIO_SetBits(GPIOB,GPIO_Pin_5);
EXTI_ClearITPendingBit(EXTI_Line5);
}
由于EXTI5-9是共⽤⼀个中断响应函数,因此所有的EXTI5 – EXTI9的响应函数都写在这个⾥⾯。
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函数函数体中都是调⽤初始化配置函数,然后进⼊死循环,等待中断响应。
由于⽂中牵涉到很多库函数,我们可以通过查库函数说明⽂档来了解相应的函数有些什么作⽤,在《ARM®-based32-bit MCU STM32F101xx and STM32F103xx firmware library》中。⽹上也有中⽂版的说明⽂档可供参考。