查看: 1792|回复: 0

零死角玩转stm32-初级篇之EXTI之按键中断实验

[复制链接]
  • TA的每日心情
    慵懒
    2015-5-29 12:01
  • 签到天数: 11 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    发表于 2013-7-22 23:33:07 | 显示全部楼层 |阅读模式
    分享到:
        8、EXTI之按键中断实验

       EXTI (External interrupt) 就是指外部中断,通过GPIO检测输入脉冲,引起中断事件,打断原来的代码执行流程,进入到中断服务函数中进行处理,处理完后,再返回到中断之前的代码中执行。
    前面提到,STM32的所有GPIO都可以用作外部中断源的输入端,利用这个特性,我们可以把按键轮询检测 改为由中断 来处理,大大提高软件执行的效率。
    8.1 STM32的中断和异常
    Cortex内核具有强大的异常响应系统,它把能够打断当前代码执行流程的事件分为异常(exception)中断(interrupt),并把它们用一个表管理起来,编号为0~15的称为内核异常,而16以上的则称为外部中断(外,相对内核而言),这个表就称为中断向量表。
    而STM32对这个表重新进行了编排,把编号从-3至6的中断向量定义为系统异常,编号为负 的内核异常不能被设置优先级,如复位(Reset)、不可屏蔽中断 (NMI)、硬错误(Hardfault)。从编号7开始的为外部中断,这些中断的优先级都是可以自行设置的。详细的STM32中断向量表见图 81,STM32中断向量表。



    图 81中断向量表

       这个表可以从《STM32中文参考手册》找到,但野火一般是从启动文件startup_stm32f10x_hd.s中查找的,因为不同型号的STM32芯片,中断向量表稍微有点区别,在启动文件中,已经有相应芯片可用的全部中断向量。而且在编写中断服务函数时,需要从启动文件中定义的中断向量表查找中断服务函数名。
      8.2 NVIC中断控制器
    STM32的中断如此之多,配置起来并不容易,因此,我们需要一个强大而方便的中断控制器NVIC (Nested Vectored Interrupt Controller)。NVIC是属于Cortex内核的器件,不可屏蔽中断 (NMI)和外部中断都由它来处理,而SYSTICK不是由NVIC来控制的。
      

    图82 NVIC在内核中的位置

       8.2.1 NVIC结构体成员
    当我们要使用NVIC来配置中断时,自然想到ST库肯定也已经把它封装成库函数了。查找库帮助文档,发现在Modules->STM32F10x_StdPeriph_Driver->misc 查找到一个NVIC_Init() 函数,对NVIC初始化,首先要定义并填充一个NVIC_InitTypeDef 类型的结构体。
    这个结构体有四个成员

    前面两个结构体成员都很好理解,首先要用NVIC_IRQChannel参数来选择将要配置的中断向量,用NVIC_IRQChannelCmd参数来进行使能(ENABLE)关闭(DISABLE)该中断。在NVIC_IRQChannelPreemptionPriority成员要配置中断向量的抢占优先级,在NVIC_IRQChannelSubPriority需要配置中断向量的响应优先级。对于中断的配置,最重要的便是配置其优先级,但STM32的同一个中断向量为什么需要设置两种优先级?这两种优先级有什么区别?
     8.2.2 抢占优先级和响应优先级
    STM32的中断向量具有两个属性,一个为抢占属性,另一个为响应属性,其属性编号越小,表明它的优先级别越高。
    抢占,是指打断其它中断的属性,即因为具有这个属性,会出现嵌套中断(在执行中断服务函数A的过程中被中断B打断,执行完中断服务函数B再继续执行中断服务函数A),抢占属性由NVIC_IRQChannelPreemptionPriority的参数配置。
    而响应属性则应用在抢占属性相同的情况下,当两个中断向量的抢占优先级相同时,如果两个中断同时到达,则先处理响应优先级高的中断,响应属性由NVIC_IRQChannelSubPriority的参数配置。
    例如,现在有三个中断向量:

    若内核正在执行C的中断服务函数,则它能被抢占优先级更高的中断A打断,由于B和C的抢占优先级相同,所以C不能被B打断。但如果B和C中断是同时到达的,内核就会首先响应响应优先级别更高的B中断。
     8.2.3 NVIC的优先级组
    在配置优先级的时候,还要注意一个很重要的问题,中断种类的数量。NVIC只可以配置16种 中断向量的优先级,也就是说,抢占优先级和响应优先级的数量由一个4位的数字来决定,把这个4位数字的位数 分配成抢占优先级部分和响应优先级部分。有5组分配方式:
    第0组: 所有4位用来配置抢占优先级,即NVIC配置的24 =16种中断向量都是只有抢占属性,没有响应属性。
    第1组:最高1位用来配置抢占优先级,低3位用来配置响应优先级。表示有21=2种级别的抢占优先级(0级,1级),有23=8种响应优先级,即在16种中断向量之中,有8种中断,其抢占优先级都为0级,而它们的响应优先级分别为0~7,其余8种中断向量的抢占优先级则都为1级,响应优先级别分别为0~7。
    第2组:2位用来配置抢占优先级,2位用来配置响应优先级。即22=4种抢占优先级,22=4种响应优先级。
    第3组:高3位用来配置抢占优先级,最低1位用来配置响应优先级。即有8种抢占优先级,2种响应2优先级。
    第4组:所有4位用来配置响应优先级。即16种中断向量具有都不相同的响应优先级。
    要配置这些优先级组,可以采用库函数NVIC_PriorityGroupConfig(),可输入的参数为NVIC_PriorityGroup_0 ~ NVIC_PriorityGroup_4,分别为以上介绍的5种分配组。
    于是,有读者觉得疑惑了,如此强大的STM32,所有GPIO都能够配置成外部中断,USART、ADC等外设也有中断,而NVIC只能配置16种中断向量,那在某个工程中使用了超过16个的中断怎么办呢?注意NVIC能配置的是16种 中断向量,而不是16个,当工程之中有超过16个中断向量时,必然有2个以上的中断向量是使用相同的中断种类,而具有相同中断种类的中断向量不能互相嵌套。
    STM2 单片机的所有I/O端口都可以配置为EXTI中断模式,用来捕捉外部信号,可以配置为下降沿中断,上升沿中断和上升下降沿中断这三种模式。它们以下图的方式连接到16个外部中断/事件线上
     8.3 EXTI外部中断
    STM32的所有GPIO都引入到EXTI外部中断线上,使得所有的GPIO都能作为外部中断的输入源。GPIO与EXTI的连接方式见图 03

    图 03 EXTI与GPIO连接图

       观察这个图知道,PA0~PG0 连接到EXTI0 、PA1~PG1连接到EXTI1、 ……、 PA15~PG15连接到EXTI15。这里大家要注意的是:PAx~PGx端口的中断事件都连接到了EXTIx,即同一时刻EXTx只能相应一个端口的事件触发,不能够同一时间响应所有GPIO端口的事件,但可以分时复用。它可以配置为上升沿触发,下降沿触发或双边沿触发。EXTI 最普通的应用就是接上一个按键,设置为下降沿触发,用中断来检测按键。
     8.4 中断检测按键实验分析
    8.4.1实验描述及工程文件清单

    8.4.2 配置工程环境
    本中断检测按键实验照例使用了GPIO和RCC片上外设,由于还使用到了中断,所以比上一个按键实验要多使用两个库文件,分别为FWlib/stm32f10x_exti.cFWlib/misc.c,必须把这两个文件也添加到工程之中。其中stm32f10x_exti.c文件包含了支持exti配置和操作的相关库函数;而misc.c文件则包含了NVIC的配置函数。本实验中我们还会在stm32f10x_it.c文件中编写中断服务函数。
    添加了所需要的库文件、用户文件之后,要在stm32f10x_conf.h文件中配置使用到的头文件。
    /**
    **********************************************************
    * @file    Project/STM32F10x_StdPeriph_Template/stm32f10x_conf.h
    * @author  MCD Application Team
    * @version V3.5.0
    * @date    08-April-2011
    * @brief   Library configuration file.
    *************************************************/
    #include "stm32f10x_exti.h"
    #include "stm32f10x_gpio.h"
    #include "stm32f10x_rcc.h"
    #include "misc.h" /
       8.4.5 main文件
    我们从main函数开始分析:
    /*
    * 函数名:main
    * 描述  :主函数
    * 输入  :无
    * 输出  :无
    */
    int main(void)
    {
    /* config the led */
    LED_GPIO_Config();
    LED1( ON );
    /* exti line config */
    EXTI_PE5_Config();
    /* wait interrupt */
    while(1)
    {
    }
    }
    使用LED_GPIO_Config() 配置好LED用到的I/O后,调用LED1()点亮一盏LED灯。这两个函数的具体讲解可参考前面的教程。
      8.4.6 配置外部中断
    现在我们重点分析下EXTI_PE5_Config() 这个函数,这是一个在用户文件exti.c中实现的函数,它完成了一般配置一个I/O为EXTI中断的步骤,主要为功能:
    1、使能EXTIx线的时钟和第二功能AFIO时钟
    2、配置EXTIx线的中断优先级
    3、配置EXTI 中断线I/O
    4、选定要配置为EXTI的I/O口线和I/O口的工作模式
    5、EXTI 中断线工作模式配置
    EXTI_PE5_Config()代码:
    /*
    * 函数名:EXTI_PE5_Config
    * 描述  :配置 PE5 为线中断口,并设置中断优先级
    * 输入  :无
    * 输出  :无
    * 调用  :外部调用
    */
    void EXTI_PE5_Config(void)
    {
    GPIO_InitTypeDef GPIO_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;
    /* config the extiline(PE5) clock and AFIO clock */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO,ENABLE);
    /* config the NVIC(PE5) */
    NVIC_Configuration();
    /* EXTI line gpio config(PE5) */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;  // 上拉输入
    GPIO_Init(GPIOE, &GPIO_InitStructure);
    /* EXTI line(PE5) mode config */
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource5);
    EXTI_InitStructure.EXTI_Line = EXTI_Line5;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿中断
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
    }
       8.4.7 AFIO时钟
    EXTI_PE5_Config()代码的第14行,调用RCC_APB2PeriphClockCmd() 时还输入了参数RCC_APB2Periph_AFIO,表示开启AFIO的时钟。见图 04。
     AFIO (alternate-function I/O),指GPIO端口的复用功能,GPIO除了用作普通的输入输出(主功能),还可以作为片上外设的复用输入输出,如串口,ADC,这些就是复用功能。大多数GPIO都有一个默认复用功能,有的GPIO还有重映射功能, 重映射功能是指把原来属于A引脚的默认复用功能,转移到了B引脚进行使用,前提是B引脚具有这个重映射功能
    当把GPIO用作EXTI外部中断 或使用重映射功能的时候,必须开启AFIO时钟,而在使用默认复用功能的时候,就不必开启AFIO时钟了。

    图 04 GPIO引脚功能说明

       8.4.8 NVIC初始化配置
    在EXTI_PE5_Config()代码的第17行调用了NVIC_Configuration(),这是用户编写的用来配置NVIC控制器的函数。其实现如下:
    /*
    * 函数名:NVIC_Configuration
    * 描述  :配置嵌套向量中断控制器NVIC
    * 输入  :无
    * 输出  :无
    * 调用  :内部调用
    */
    static void NVIC_Configuration(void)
    {
    NVIC_InitTypeDef NVIC_InitStructure;
    /* Configure one bit for preemption priority */
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    /* 配置P[A|B|C|D|E]5为中断源 */
    NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    }
    本代码的第13行调用了 NVIC_PriorityGroupConfig()库函数,把NVIC中断优先级分组设置为第1组。接下来开始向NVIC初始化结构体写入参数 .NVIC_IRQChannel = EXTI9_5_IRQn,表示要配置的为EXTI第5~9线的中断向量。因为按键PE5对应的EXTI线为EXTI5,而从EXTI5~EXTI9线,由于它们是使用同一个中断向量的,所以只能写入EXTI9_5_IRQn这个参数。这些可写入的参数可以在stm32f10x.h文件的IRQn类型定义中查找到。
    然后配置抢占优先级和响应优先级,因为这个工程简单,就直接把它设置为最高级中断。填充完结构体,别忘记最后要调用NVIC_Init() 函数来向寄存器写入参数。
    8.4.9 EXTI初始化配置
    回到EXTI_PE5_Config()代码中,配置好NVIC后,还要对GPIOE进行初始化,这部分和按键轮询的设置类似。
    接下来,调用GPIO_EXTILineConfig()函数把GPIOE,Pin5设置为EXTI输入线。

    图 05 EXTI中断源配置函数

       选择好了GPIO,开始填写EXTI的初始化结构体。从这些参数的名字,读者就已经可以知道野火是如何把它应用到按键检测中了吧?
    .EXTI_Line = EXTI_Line5;
    给EXTI_Line成员赋值。选择EXTI_Line5线进行配置,因为按键的PE5连接到了EXTI_Line5
    .EXTI_Mode = EXTI_Mode_Interrupt;
    给EXTI_Mode成员赋值。把EXTI_Line5的模式设置为为中断模式EXTI_Mode_Interrupt。这个结构体成员也可以赋值为事件模式EXTI_Mode_Event ,这个模式不会立刻触发中断,而只是在寄存器上把相应的事件标置位置1,应用这个模式要不停地查询相应的寄存器。
    .EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_Trigger成员赋值。把触发方式(EXTI_Trigger)设置为下降沿触发(EXTI_Trigger_Falling)
    .EXTI_LineCmd = ENABLE;
    EXTI_LineCmd成员赋值。把EXTI_LineCmd设置为使能。
    最后调用EXTI_Init()EXTI初始化结构体的参数写入寄存器。
     8.4.10 编写中断服务函数
    在这个EXTI设置中我们把PE5连接到内部的EXTI5GPIO配置为上拉输入,工作在下降沿中断。在外围电路上我们将PE5接到了key1上。当按键没有按下时,PE5始终为高,当按键按下时PE5变为低,从而PE5上产生一个下降沿跳变,EXTI5会捕捉到这一跳变,并产生相应的中断,中断服务程序在stm32f10x_it.c中实现。
     stm32f10x_it.c文件是专门用来存放中断服务函数的。文件中默认只有几个关于系统异常的中断服务函数,而且都是空函数,在需要的时候自已进行编写。那么中断服务函数名是不是可以自己定义呢?不可以。中断服务函数的名字必须要跟启动文件startup_stm32f10x_hd.s中的中断向量表定义一致。以下为启动文件中定义的部分向量表:
    DCD     EXTI0_IRQHandler          ; EXTI Line 0
    DCD     EXTI1_IRQHandler          ; EXTI Line 1
    DCD     EXTI2_IRQHandler          ; EXTI Line 2
    DCD     EXTI3_IRQHandler          ; EXTI Line 3
    DCD     EXTI4_IRQHandler          ; EXTI Line 4
    DCD     DMA1_Channel1_IRQHandler  ; DMA1 Channel 1
    DCD     DMA1_Channel2_IRQHandler  ; DMA1 Channel 2
    DCD     DMA1_Channel3_IRQHandler  ; DMA1 Channel 3
    DCD     DMA1_Channel4_IRQHandler  ; DMA1 Channel 4
    DCD     DMA1_Channel5_IRQHandler  ; DMA1 Channel 5
    DCD     DMA1_Channel6_IRQHandler  ; DMA1 Channel 6
    DCD     DMA1_Channel7_IRQHandler  ; DMA1 Channel 7
    DCD     ADC1_2_IRQHandler         ; ADC1 & ADC2
    DCD     USB_HP_CAN1_TX_IRQHandler  ; USB High Priority or CAN1 TX
    DCD     USB_LP_CAN1_RX0_IRQHandler ; USB Low  Priority or CAN1 RX0
    DCD     CAN1_RX1_IRQHandler       ; CAN1 RX1
    DCD     CAN1_SCE_IRQHandler       ; CAN1 SCE
    DCD     EXTI9_5_IRQHandler        ; EXTI Line 9..5
    DCD     TIM1_BRK_IRQHandler       ; TIM1 Break
    DCD     TIM1_UP_IRQHandler        ; TIM1 Update
    DCD     TIM1_TRG_COM_IRQHandler   ; TIM1 Trigger and Commutation
    DCD     TIM1_CC_IRQHandler        ; TIM1 Capture Compare
    DCD     TIM2_IRQHandler           ; TIM2
    DCD     TIM3_IRQHandler           ; TIM3
    DCD     TIM4_IRQHandler           ; TIM4
    DCD     I2C1_EV_IRQHandler        ; I2C1 Event
    DCD     I2C1_ER_IRQHandler        ; I2C1 Error
    DCD     I2C2_EV_IRQHandler        ; I2C2 Event
    DCD     I2C2_ER_IRQHandler        ; I2C2 Error
    DCD     SPI1_IRQHandler           ; SPI1
    DCD     SPI2_IRQHandler           ; SPI2
    DCD     USART1_IRQHandler         ; USART1
    DCD     USART2_IRQHandler         ; USART2
    DCD     USART3_IRQHandler         ; USART3
    DCD     EXTI15_10_IRQHandler      ; EXTI Line 15..10
    第18行,为EXTI9_5IRQHandler,表示为EXTI9~EXTI5中断向量的服务函数名。
    于是,我们就可以在stm32f10x_it.c文件中加入名为EXTI9_5_IRQHandler()的函数:
    /* I/O线中断,中断线为PE5 */
    void EXTI9_5_IRQHandler(void)
    {
    if(EXTI_GetITStatus(EXTI_Line5) != RESET) //确保是否产生了EXTI Line中断
    {
    // LED1 取反
    GPIO_WriteBit(GPIOC, GPIO_Pin_3,
    (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_3))));
    EXTI_ClearITPendingBit(EXTI_Line5);     //清除中断标志位
    }
    }
    其内容比较容易理解,进入中断后,调用库函数EXTI_GetITStatus() 来重新检查是否产生了EXTI_Line中断,接下来把LED取反,操作完毕后,调用EXTI_ClearITPendingBit() 清除中断标置位再退出中断服务函数。这两个函数的解释见图 06及图 07。
       

    图 06EXTI状态检查函数

     

    图 07 EXTI清除标志位函数

       这两种函数在ST库函数非常见,当我们要读取某外设的状态时,可调用该外设的XXX_GetFlagStatus()函数来获取该状态。一般也有XXX_ClearFlag()库函数可供调用,进行相应的标志位清除。
    中断服务程序比较简单,很容易读懂,但我们在写中断函数入口的时候要注意函数名的写法,函数名只有两种命名方法:
    1-> EXTI0_IRQHandler          ; EXTI Line 0
    EXTI1_IRQHandler          ; EXTI Line 1
    EXTI2_IRQHandler          ; EXTI Line 2
    EXTI3_IRQHandler          ; EXTI Line 3
    EXTI4_IRQHandler          ; EXTI Line 4
    2-> EXTI9_5_IRQHandler        ; EXTI Line 9..5
    EXTI15_10_IRQHandler      ; EXTI Line 15..10
    只要是中断线在5之后的就不能像0~4那样单独一个函数名,都必须写成EXTI9_5_IRQHandlerEXTI15_10_IRQHandler。假如写成EXTI5_IRQHandlerEXTI6_IRQHandler……EXTI15_IRQHandler这样子的话编译器是不会报错的,只是中断服务程序不能工作罢了。如果你不知道的话,会让你搞半天也不知问题出现在哪。
     8.4.11实验现象
    将野火STM32开发板供电(DC5V),插上JLINK,将编译好的程序下载到开发板,LED1亮,按下按键时LED1灭,再按下按键时LED1亮,如此循环。
    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /2 下一条

    手机版|小黑屋|与非网

    GMT+8, 2024-4-19 22:55 , Processed in 0.125264 second(s), 18 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.