TA的每日心情 | 慵懒 2015-5-29 12:01 |
---|
签到天数: 11 天 连续签到: 1 天 [LV.3]偶尔看看II
|
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.c和FWlib/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连接到内部的EXTI5,GPIO配置为上拉输入,工作在下降沿中断。在外围电路上我们将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_IRQHandler和EXTI15_10_IRQHandler。假如写成EXTI5_IRQHandler、EXTI6_IRQHandler……EXTI15_IRQHandler这样子的话编译器是不会报错的,只是中断服务程序不能工作罢了。如果你不知道的话,会让你搞半天也不知问题出现在哪。
8.4.11实验现象
将野火STM32开发板供电(DC5V),插上JLINK,将编译好的程序下载到开发板,LED1亮,按下按键时LED1灭,再按下按键时LED1亮,如此循环。 |
|