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

作者:
上传时间为: 2013-07-22 11:33 PM

    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亮,如此循环。

全部评论 ()

创建讨论帖子

登录 后参与评论
系统提示