STM32----------NVIC和EXTI

注意:     NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn ;          (3.5版本库已经由TIM2_IRQchannel   改为     TIM2_IRQn)

NVIC——Nested VectoredInterrupt Controller(嵌套中断向量控制器)

STM32有43个channel的settable的中断源:AIRC(ApplicationInterrupt and Reset Register)寄存器中有用于指定优先级的4bits。这4个bits用于分配pre-emption优先级和sub优先级,在STM32的固件库中定义如下:

//-------------------------------PreemptionPriority Group ------------------------------------------------
#define NVIC_PriorityGroup_0 ((u32)0x700)//0 bits for pre-emption priority 4 bits for subpriority
#define NVIC_PriorityGroup_1 ((u32)0x600)//1 bits for pre-emption priority 3 bits for subpriority
#define NVIC_PriorityGroup_2 ((u32)0x500)//2 bits for pre-emption priority 2 bits for subpriority
#define NVIC_PriorityGroup_3 ((u32)0x400)// 3 bits for pre-emption priority 1 bits for subpriority
#define NVIC_PriorityGroup_4 ((u32)0x300)//4 bits for pre-emption priority 0 bits for subpriority

形象化的理解是:你是上帝,造了43个人,这么多人要分社会阶级和社会阶层了;因为“阶级”的词性比较重;“阶层”比较中性,所以pre-emption优先级->阶级;每个阶级内部,有一些阶层,sub优先级->阶层;

如果按照NVIC_PriorityGroup_4这么分,就分为了16个阶级每个阶级有0个阶层;阶级高的人,可以打断阶级低的正在做事的人(嵌套),最多可以完成1个中断和15级嵌套。每个阶级你来指定这43人中,谁进入该阶级;

一个人叫EXTI0_IRQChannel,你指定他进入“阶级8”,则

NVIC_InitStructure.NVIC_IRQChannel =EXTI0_IRQChannel;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 8;//指定抢占式优先级别1,可取0-15

在同一阶级内部,一个人在做事的时候,另外一个人不能打断他;(pre-emption优先级别相同的中断源之间没有嵌套关系)。还有,如果他们两个同时想做事,因为没有阶层,那么就根据Vector table中的物理排序,让排名靠前的人去做。

又有1个人SPI1_IRQChannel,设定如下

NVIC_InitStructure.NVIC_IRQChannel =SPI1_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 0; //指定抢占式优先级别1,可取0-15

SPI1_IRQChannel的阶级高,EXTI0_IRQChannel做事的时候可以打断(嵌套)。

如果按照NVIC_PriorityGroup_3这么分,就分为了8个阶级(1个阶级是1个preemption优先级),每个阶级内有2个阶层(sub优先级);高阶级的人,可以打断低阶级的正在做事的人(嵌套),最多可以完成1个中断和7级嵌套。每个阶级(每个preemption优先级),你来指定这43人中,谁进入该阶级;

一个人叫EXTI0_IRQChannel,你指定他进入“阶级3”,则:

NVIC_InitStructure.NVIC_IRQChannel =EXTI0_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 3; // <span style="color:#ff0000;">指定抢占式优先级别1,可取0-7</span>

还需要指定他的阶层:

NVIC_InitStructure.NVIC_IRQChannelSubPriority= 0; // 指定响应优先级别0,可取0-1
另有1个人叫EXTI9_5_IRQChannel,他的阶级和阶层设定如下
NVIC_InitStructure.NVIC_IRQChannel =EXTI9_5_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 3; // 指定抢占式优先级别0,可取0-7
NVIC_InitStructure.NVIC_IRQChannelSubPriority= 1; // 指定响应优先级别1

那么这两个人是同一阶级的兄弟,一个人在做事的时候,另外一个人不能打断他(preemption优先级别相同的中断源之间没有嵌套关系)。如果他们两个同时想做事,因为前者的阶层高,所以前者优先。

还有一个人叫USART1_IRQChannel,他的阶级和阶层设定如下

NVIC_InitStructure.NVIC_IRQChannel =USART1_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 2; // 指定抢占式优先级别0,可取0-7
NVIC_InitStructure.NVIC_IRQChannelSubPriority= 1; // 指定响应优先级别1
USART1_IRQChannel的优先级最高,当前面两个人做事的时候,他都可以打断(嵌套)。

依次类推:如果按照NVIC_PriorityGroup_0这么分,那么没有阶级,只有16个阶层了。需要给各个人指定阶层编号。sub优先级的范围0-15当一个人做事的时候,另外的人不能打断他(就没有嵌套了);当多人同时想做事的时候,按照阶层编号的排序,排名靠前的先做事。阶层编号一样的人同时想做事,那么按照Vector Table硬件排序,排名靠前的先做。

EXTI

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 Cortex-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()

{      #ifdef VECT_TAB_RAM
NVIC_SetVectorTable(NVIC_VectTab_RAM,0x0);
#else
NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x0);                      
#endif 

//第一,代码可以在RAM中运行,那么中断向量也在RAM中,这个时候需要这个功能。比如我们可以不用把程序下载到FALSH而是在RAM中,这样每次重新编译程序和DEBUG可以避免FLASH被“磨损”。
第二,某些应用可能需要在运行时可以变更的中断向量表,这个时候也需要这个功能。

       NVIC_InitTypeDef   NVIC_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组。又由于EXTI 5-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中。

 

4.       把相应的IO口设置为中断线路

由于GPIO并不是专用的中断引脚,因此在用GPIO来触发外部中断的时候需要设置将GPIO相应的引脚和中断线连接起来,具体代码如下:

void EXTI_cfg()

{

       EXTI_InitTypeDef EXTI_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灯点亮,所以函数体其实很简单:

void EXTI2_IRQHandler(void)

{

       //点亮LED灯

      GPIO_SetBits(GPIOD,GPIO_Pin_6);

       //清空中断标志位,防止持续进入中断

      EXTI_ClearITPendingBit(EXTI_Line2);

}

 

void EXTI3_IRQHandler(void)

{

      GPIO_SetBits(GPIOD,GPIO_Pin_3);

       EXTI_ClearITPendingBit(EXTI_Line3);

}

 

void EXTI9_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®-based 32-bit MCU STM32F101xx andSTM32F103xx firmware library》中。网上也有中文版的说明文档可供参考。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值