第5章〓按键与中断处理 本章将介绍嵌套向量中断控制器NVIC的工作原理,阐述STM32F103ZET6微控制器外部输入中断的工作原理,然后,以用户按键为例,详细解释NVIC中断的寄存器类型和库函数类型的程序设计方法。 本章的学习目标: 了解NVIC中断响应方法; 熟悉GPIO中断响应方法; 熟练应用寄存器或库函数进行GPIO中断程序设计。 5.1NVIC中断工作原理◆ 嵌套向量中断控制器NVIC相关的中断管理工作主要有开放中断、关闭中断、设置中断请求标志、读中断请求标志、清除中断请求标志和配置中断优先级等。嵌套向量中断控制器NVIC的寄存器有ISER0、ISER1、ICER0、ICER1、ISPR0、ISPR1、ICPR0、ICPR1、IABR0、IABR1、IPR0~IPR14和STIR,如表51所示。 表51NVIC寄存器 序号地址寄存器名称描述 1 0xE000E100ISER0 0xE000E104ISER1 中断开放寄存器ISER0[0]~ISER0[31]、ISER1[0]~ISER1[27]依次对应中断号为0~59的中断,各位写0无效,写1开放中断 2 0xE000E180ICER0 0xE000E184ICER1 中断关闭寄存器ICER0[0]~ICER0[31]、ICER1[0]~ICER1[27]依次对应中断号为0~59的中断,各位写0无效,写1关闭中断 3 0xE000E200ISPR0 0xE000E204ISPR1 中断设置请求状态寄存器ISPR0[0]~ISPR0[31]、ISPR1[0]~ISPR1[27]依次对应中断号为0~59的中断,各位写0无效,写1请求中断 4 0xE000E280ICPR0 0xE000E284ICPR1 中断清除请求状态寄存器ICPR0[0]~ICPR0[31]、ICPR1[0]~ICPR1[27]依次对应中断号为0~59的中断,各位写0无效,写1清除中断标志 5 0xE000E300IABR0 0xE000E304IABR1 中断活跃位寄存器(只读)IABR0[0]~IABR0[31]、IABR1[0]~IABR1[27]依次对应中断号为0~59的中断,各位读出1,表示相应中断活跃 60xE000E400~ 0xE000E438IPR0~ IPR14中断优先级寄存器共有16个优先级,优先级号从0~15,优先级号0表示的优先级最高,优先级号15表示的优先级最低 70xE000EF00STIR软件触发中断寄存器第[8:0]位域有效,写入0~59中的某一中断号,则触发相应的中断 下面以ISER0和ISER1为例,介绍开放中断的方法。 根据表51,ISER0[0]~ISER0[31]对应中断号为0~31的NVIC中断,而ISER1[0]~ISER1[27]对应中断号为32~59的NVIC中断。由表25可知,外部中断2的中断号为8,而USART2中断的中断号为38,开放这两个中断的语句依次为: ISER0= (1uL<<8); ISER1 = (1uL<<6); 设中断号为IRQn,则这两个语句也可以写为如下统一的语句形式: ISER0 = 1uL<< (IRQn & 0x1F); ISER1 = 1uL<< (IRQn & 0x1F); 上述开放中断的方法被用在CMSIS库文件中。 在CMSIS库头文件core_cm3.h中定义了NVIC中断的相关操作,这里重点介绍开放中断、关闭中断、设置中断请求标志、读中断请求标志、清除中断请求标志、设置中断优先级和获取中断优先级的函数,如程序段51所示。 程序段51NVIC中断相关的CMSIS库函数(摘自core_cm3.h文件) 1typedef struct 2{ 3__IO uint32_t ISER[8U];//偏移地址:0x000(可读/可写) 中断设置使能寄存器 4 uint32_t RESERVED0[24U]; 5__IO uint32_t ICER[8U];//偏移地址:0x080(可读/可写) 中断清除使能寄存器 6 uint32_t RSERVED1[24U]; 7__IO uint32_t ISPR[8U];//偏移地址:0x100(可读/可写) 中断设置请求寄存器 8 uint32_t RESERVED2[24U]; 9__IO uint32_t ICPR[8U];//偏移地址:0x180(可读/可写) 中断清除请求寄存器 10 uint32_t RESERVED3[24U]; 11__IO uint32_t IABR[8U];//偏移地址:0x200(可读/可写) 中断活跃标志位寄存器 12 uint32_t RESERVED4[56U]; 13__IO uint8_tIP[240U];//偏移地址:0x300(可读/可写) 中断优先级寄存器(8位) 14 uint32_t RESERVED5[644U]; 15__Ouint32_t STIR;//偏移地址:0xE00(只写) 软件触发中断寄存器 16}NVIC_Type; 17 18#define SCS_BASE (0xE000E000UL) 19#define NVIC_BASE(SCS_BASE +0x0100UL) 20#define NVIC ((NVIC_Type *)NVIC_BASE) 21 第1~16行自定义结构体类型NVIC_Type,各成员的位置与表51中各个寄存器的位置对应,再结合第18~20行可知,NVIC为指向首地址0xE000E100的结构体指针,这样(结合表51),NVIC>ISER[0]指向的地址即为ISER0寄存器的地址,NVIC>ISER[1]指向的地址即为ISER1寄存器的地址,以此类推,NVIC>STIR指向的地址即为STIR寄存器的地址。 22__STATIC_INLINE void NVIC_EnableIRQ(IRQn_Type IRQn)// 开中断 23{ 24NVIC->ISER[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F)); 25} 26 第22~25行为开放NVIC中断函数NVIC_EnableIRQ,形参为IRQn_Type类型的变量,该自定义类型定义在stm32f10x.h文件中,如程序段52所示。第24行根据IRQn的值设置ISER[0]或ISER[1]相应的位,即开放IRQn对应的NVIC中断。 27__STATIC_INLINE void NVIC_DisableIRQ(IRQn_Type IRQn)// 关中断 28{ 29NVIC->ICER[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F)); 30} 31 第27~30行为关闭NVIC中断函数NVIC_DisableIRQ,形参为IRQn_Type类型的变量。第29行根据IRQn的值向ICER[0]或ICER[1]相应的位写入1,关闭IRQn对应的NVIC中断。 32__STATIC_INLINE void NVIC_SetPendingIRQ(IRQn_Type IRQn)// 中断请求 33{ 34NVIC->ISPR[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F)); 35} 36 第32~35行为设置中断请求标志的函数NVIC_SetPendingIRQ,形参为IRQn_Type类型的变量。第34行根据IRQn的值向ISPR[0]或ISPR[1]相应的位写入1,设置IRQn对应的NVIC中断请求标志,使该NVIC中断处于请求态。 37__STATIC_INLINE uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn)// 处于请求态时返回1 38{//否则返回0 39return((uint32_t) ((NVIC->ISPR[(uint32_t)(IRQn) >> 5] & (1 << ((uint32_t)(IRQn) & 0x1F)))?1:0)); 40} 41 第37~40行为获取NVIC中断请求状态的函数NVIC_GetPendingIRQ,形参为IRQn_Type类型的变量。第39行根据IRQn的值读出它对应的ISPR[0]或ISPR[1]的位,如果IRQn中断处于请求态,则返回1; 否则返回0。 42__STATIC_INLINE void NVIC_ClearPendingIRQ(IRQn_Type IRQn) // 清除中断请求标志 43{ 44NVIC->ICPR[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F)); 45} 46 第42~45行为清除NVIC中断请求标志的函数NVIC_ClearPendingIRQ,形参为IRQn_Type类型的变量。第44行根据IRQn的值向ICPR[0]或ICPR[1]相应的位写入1,清除IRQn对应的NVIC中断标志。 47__STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) 48{ 49if((int32_t)IRQn < 0) 50{ //为Cortex-M3系统异常:MemManage,BusFault,UsageFault,SVC,DebugMon设定优先级 51SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); 52} 53else 54{ // 为中断: IRQn,n=0~59设定优先级 55NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); 56} 57} 第47~57行为设置异常和中断优先级的函数NVIC_SetPriority,形参有两个: (1)IRQn_Type类型的变量IRQn为中断号; (2)无符号32位整型变量priority为设置的优先级号数值。第49~52行设置中断号为-12~-1的异常的优先级号; 第53~56行设置中断号为0~59的中断的优先级号。这里的“__NVIC_PRIO_BITS”为宏定义的常数4,因此,priority的取值为0~15。注意: 中断优先级号小的中断具有较高的优先级; 如果有多个中断被设置为相同的优先级,则中断号小的中断优先级高。 58__STATIC_INLINE uint32_t NVIC_GetPriority(IRQn_Type IRQn) 59{ 60if ((int32_t)(IRQn) < 0) 61{ 62return(((uint32_t)SCB->SHP[(((uint32_t) IRQn) & 0xFuL)-4UL] >> (8U - __NVIC_PRIO_BITS))); 63} 64else 65{ 66 return(((uint32_t)NVIC->IP[((uint32_t) IRQn)] >> (8U - __NVIC_PRIO_BITS))); 67} 68} 第58~68行为获取异常和中断优先级的函数NVIC_GetPriority,形参为IRQn_Type类型的变量IRQn,返回值为中断的优先级号。对于中断号小于0的异常,第60~63行获取异常的优先级号; 否则,第65~67行获取中断号为0~59的NVIC中断的优先级号。 程序段51中,第47~68行的代码需要访问中断优先级寄存器IPR0~IPR14,这些寄存器的结构如图51所示。 图51中断优先级配置寄存器 由图51可知,每个IPR寄存器用于设置4个NVIC中断的优先级,32位的IPR寄存器的4字节的低4位均无效,只有高4位有效,故可以设置的优先级号为0~15。根据图51,如果设置EXTI2中断的优先级号为10,则需要将IPR2的第[7:4]位域设为10。当两个中断具有不同的优先级号时,优先级号小的中断优先级高; 当两个中断具有相同的优先级号时,中断号小的中断优先级高。 可配置优先级的异常的优先级号由3个系统手柄优先级寄存器(SHPR1~SHPR3)设置,其地址依次为0xE000ED18、0xE000ED1C和0xE000ED20,如表52所示。 表52异常号4~15的优先级配置寄存器 序号异常号异 常 名 称位域配 置 名 称寄存器 14MemManage[7:0]PRI_4 25BusFault[15:8]PRI_5 36UsageFault[23:16]PRI_6 47保留[31:24]PRI_7 SHPR1 58保留[7:0]PRI_8 69保留[15:8]PRI_9 710保留[23:16]PRI_10 811SVCall[31:24]PRI_11 SHPR2 912Debug Monitor[7:0]PRI_12 1013保留[15:8]PRI_13 1114PendSV[23:16]PRI_14 1215SysTick[31:24]PRI_15 SHPR3 程序段52自定义枚举类型IRQn_Type(摘自stm32f10x.h文件) 1typedef enum IRQn 2{ 3// Cortex-M3处理器异常号 4 NonMaskableInt_IRQn = -14,// 不可屏蔽中断 5 MemoryManagement_IRQn = -12,// 4 Cortex-M3存储器管理异常 6 BusFault_IRQn= -11,// 5 Cortex-M3 总线出错异常 7 UsageFault_IRQn= -10,// 6 Cortex-M3 Usage Fault异常 8 SVCall_IRQn= -5, // 11 Cortex-M3 SV调用异常 9 DebugMonitor_IRQn = -4, // 12 Cortex-M3 调试监测器异常 10PendSV_IRQn = -2, // 14 Cortex-M3 请求SV中断 11SysTick_IRQn = -1, // 15 Cortex-M3 系统节拍定时中断 12 13// STM32中断号 14WWDG_IRQn= 0,// 加窗看门狗中断 15PVD_IRQn = 1,// PVD通过EXTI侦测中断 16TAMPER_IRQn = 2,// Tamper中断 17RTC_IRQn = 3,// RTC 中断 18FLASH_IRQn= 4,// Flash 中断 19RCC_IRQn= 5,// RCC 中断 20EXTI0_IRQn = 6,//外部中断0 21EXTI1_IRQn = 7,//外部中断1 22EXTI2_IRQn = 8,//外部中断2 23EXTI3_IRQn = 9,//外部中断3 24EXTI4_IRQn = 10, //外部中断4 25DMA1_Channel1_IRQn= 11, // DMA1通道1中断 26DMA1_Channel2_IRQn= 12, // DMA1通道2中断 27DMA1_Channel3_IRQn = 13, // DMA1通道3中断 28DMA1_Channel4_IRQn = 14, // DMA1通道4中断 29DMA1_Channel5_IRQn = 15, // DMA1通道5中断 30DMA1_Channel6_IRQn = 16, // DMA1通道6中断 31DMA1_Channel7_IRQn = 17, // DMA1通道7中断 32ADC1_2_IRQn= 18, // ADC1和ADC2中断 33USB_HP_CAN1_TX_IRQn = 19, // USB设备高优先级中断或CAN1发送中断 34USB_LP_CAN1_RX0_IRQn = 20,//USB设备低优先中断或CAN1接收0中断 35CAN1_RX1_IRQn = 21, // CAN1接收1中断 36CAN1_SCE_IRQn = 22, // CAN1 SCE中断 37EXTI9_5_IRQn= 23, //外部中断5~外部中断9 38TIM1_BRK_IRQn = 24, // TIM1终止中断 39TIM1_UP_IRQn= 25, // TIM1 更新中断 40TIM1_TRG_COM_IRQn = 26, // TIM1触发补偿中断 41TIM1_CC_IRQn= 27,// TIM1捕获比较中断 42TIM2_IRQn = 28, // TIM2中断 43TIM3_IRQn = 29, // TIM3中断 44TIM4_IRQn = 30, // TIM4中断 45I2C1_EV_IRQn= 31, // I2C1 Event中断 46I2C1_ER_IRQn= 32, // I2C1 Error中断 47I2C2_EV_IRQn= 33, // I2C2 Event中断 48I2C2_ER_IRQn= 34, // I2C2 Error中断 49SPI1_IRQn = 35, // SPI1中断 50SPI2_IRQn = 36, // SPI2中断 51USART1_IRQn = 37, // USART1中断 52USART2_IRQn = 38, // USART2中断 53USART3_IRQn = 39, // USART3中断 54EXTI15_10_IRQn= 40, //外部中断10~外部中断15 55RTCAlarm_IRQn= 41, // RTC报警中断(借助外部中断) 56USBWakeUp_IRQn = 42,// USB设备唤醒中断(借助外部中断) 57TIM8_BRK_IRQn = 43, // TIM8终止中断 58TIM8_UP_IRQn= 44, // TIM8更新中断 59TIM8_TRG_COM_IRQn= 45, // TIM8触发补偿中断 60TIM8_CC_IRQn= 46, // TIM8捕获比较中断 61ADC3_IRQn = 47, // ADC3中断 62FSMC_IRQn = 48, // FSMC中断 63SDIO_IRQn = 49, // SDIO中断 64TIM5_IRQn = 50, // TIM5中断 65SPI3_IRQn = 51, // SPI3中断 66UART4_IRQn= 52, // UART4中断 67UART5_IRQn= 53, // UART5中断 68TIM6_IRQn = 54, // TIM6中断 69TIM7_IRQn = 55, // TIM7中断 70DMA2_Channel1_IRQn= 56, // DMA2通道1中断 71DMA2_Channel2_IRQn= 57, // DMA2通道2中断 72DMA2_Channel3_IRQn= 58, // DMA2通道3中断 73DMA2_Channel4_5_IRQn= 59 // DMA2通道4和通道5中断 74} IRQn_Type; 上述代码对应表25,由于IRQn_Type为指定了成员值的枚举类型,因此,可以用强制类型转换将IRQn_Type类型的变量转化为整型。例如,程序段51第24行的(uint32_t)(IRQn),就是将IRQn转化为无符号32位整型。如果IRQn为EXTI2,则(uint32_t)(IRQn)为8。 现在,就可以直接调用CMSIS库中关于中断的函数实现对NVIC中断的管理了。例如,关闭EXTI2中断、开放EXTI2中断和清除EXTI2中断标志位的语句依次为: NVIC_DisableIRQ(EXTI2_IRQn); NVIC_EnableIRQ(EXTI2_IRQn); NVIC_ClearPendingIRQ(EXTI2_IRQn); 5.2GPIO外部输入中断◆ 根据寄存器AFIO_EXTICR1~AFIO_EXTICR4(见表43),可从GPIO口中选择16个引脚配置为16个外部中断的输入端,如图52所示。 图52外部中断输入端引脚配置方法 EXTI模块共有19根线路,除了外部中断EXTI0~EXTI15外,还有EXTI16、EXTI17和EXTI18,这三根线路分别与PVD输出、RTC报警事件和USB唤醒事件相连接。EXTI模块共有6个寄存器,即中断屏蔽寄存器EXTI_IMR、事件屏蔽寄存器EXTI_EMR、上升沿触发选择寄存器EXTI_RTSR、下降沿触发选择寄存器EXTI_FTSR、软件触发事件寄存器EXTI_SWIER和中断请求寄存器EXTI_PR。EXTI模块寄存器的基地址为0x4001 0400,下面详细介绍各个寄存器的情况。 中断屏蔽寄存器EXTI_IMR的偏移地址为0x0,复位值为0x0,只有第[18:0]位有效,第i位对应EXTIi,清零表示屏蔽该线路上的中断请求,置1表示打开该线路上的中断请求。 事件屏蔽寄存器EXTI_EMR的偏移地址为0x04,复位值为0x0,只有第[18:0]位有效,第i位对应EXTIi,清零表示屏蔽该线路上的事件请求,置1表示打开该线路上的事件请求。 上升沿触发选择寄存器EXTI_RTSR的偏移地址为0x08,复位值为0x0,只有第[18:0]位有效,第i位的名称为TRi,清零表示关闭上升沿触发中断或事件,置1表示打开上升沿触发中断或事件。 下降沿触发选择寄存器EXTI_FTSR的偏移地址为0x0C,复位值为0x0,只有第[18:0]位有效,第i位的名称也记为TRi,清零表示关闭下降沿触发中断或事件,置1表示打开下降沿触发中断或事件。 软件触发事件寄存器EXTI_SWIER的偏移地址为0x10,复位值为0x0,只有第[18:0]位有效,第i位记为SWIERi,当EXTIi中断有效且SWIERi=0时,向SWIERi中写入1,将触发中断请求。 中断请求寄存器EXTI_PR的偏移地址为0x14,只有第[18:0]位有效,第i位称为PRi,如果第i个中断触发了,则PRi自动置1,向PRi写入1清零该位,同时清零SWIERi位中的1。 5.3用户按键中断实例◆ 结合图311和图36可知,STM32F103ZET6微控制器的PE2、PE3和PE4分别与网络标号KEY2、KEY1和KEY0相连接; 结合图313和图33可知,PB8与网络标号BEEP相连接,控制蜂鸣器的开启与关闭。 本节拟设计工程,实现如下功能: (1) KEY2按键作为外部中断EXTI2输入端,当按下KEY2按键时LED0灯点亮; (2) KEY1按键作为外部中断EXTI3输入端,当按下KEY1按键时LED1灯熄灭; (3) KEY0按键作为外部中断EXTI4输入端,当按下KEY0按键时,如果蜂鸣器原来是开启的,则关闭蜂鸣器; 否则,开启蜂鸣器。 视频讲解 5.3.1寄存器类型工程实例 在工程01的基础上,新建“工程03”,保存在目录“D:\STM32F103ZET6工程\工程03”下,此时的工程03与工程01完全相同。现在,修改main.c和includes.h文件,并新建bsp.c、bsp.h、beep.c、beep.h、key.c、key.h、exti.c和exti.h文件(新建的文件均保存在目录“D:\STM32F103ZET6工程\工程03\BSP”下),然后,将bsp.c、beep.c、key.c和exti.c文件添加到“BSP”分组下,建设好的工程如图53所示。 图53工程03工作窗口 修改后的main.c和includes.h文件如程序段53和程序段54所示,新创建的文件bsp.c、bsp.h、beep.c、beep.h、key.c、key.h、exti.c和exti.h分别如程序段55~程序段512所示。 程序段53文件main.c 1//Filename: main.c 2 3#include "includes.h" 4 5void Delay(Int32U); 6 7int main(void) 8{ 9BSPInit(); 10 11for(;;) 12{ 13LED(1,LED_ON); 14Delay(500); 15LED(1,LED_OFF); 16Delay(500); 17} 18} 19 20void Delay(Int32U u) 21{ 22volatile Int32U i,j; 23for(i=0;i<u;i++) 24for(j=0;j<12000;j++); 25} 对比程序段47,这里的主程序文件main.c中,第9行调用BSPInit函数实现外设的初始化,该函数在程序段55中介绍; 在第11~17行的无限循环体中,实现LED1灯的循环闪烁功能。 程序段54文件includes.h 1//Filename: includes.h 2 3#include "stm32f10x.h" 4 5#include "vartypes.h" 6#include "bsp.h" 7#include "led.h" 8#include "key.h" 9#include "exti.h" 10#include "beep.h" 文件includes.h为总的包括头文件,包括了工程03中全部的用户程序头文件,如第5~10行所示。 程序段55文件bsp.c 1//Filename: bsp.c 2 3#include "includes.h" 4 5void BSPInit(void) 6{ 7LEDInit(); 8KEYInit(); 9EXTIKeyInit(); 10BEEPInit(); 11} 文件bsp.c只有一个函数BSPInit,在该函数中调用了LED初始化函数LEDInit、按键初始化函数KEYInit、外部输入中断初始化函数EXTIKeyInit和蜂鸣器初始化函数BEEPInit,如第7~10行所示,因此,BSPInit函数是总的系统初始化函数。 程序段56文件bsp.h 1//Filename: bsp.h 2 3#ifndef_BSP_H 4#define_BSP_H 5 6void BSPInit(void); 7 8#endif 文件bsp.h是文件bsp.c对应的头文件,用于声明bsp.c中实现的函数。这里第6行声明了函数BSPInit,该函数定义在文件bsp.c中。 程序段57文件beep.c 1//Filename: beep.c 2 3#include "includes.h" 4 5void BEEPInit(void) 6{ 7RCC->APB2ENR |= (1uL<<3); //打开PB时钟源 8GPIOB->CRH |= (1uL<<0); 9GPIOB->CRH &=~(7uL<<1);//PB8口工作在推挽输出模式下(10MHz) 10 11GPIOB->ODR &= ~(1uL<<8); //关闭蜂鸣器 12} 13 14void BEEP(void) 15{ 16GPIOB->ODR ^=(1uL<<8); 17} 文件beep.c用于驱动蜂鸣器,包括蜂鸣器初始化函数BEEPInit和蜂鸣器工作函数BEEP。由于PB8口与蜂鸣器控制端相连接,所以在初始化函数BEEPInit中,应首先打开PB口的时钟源(第7行),接着配置PB8口工作在推挽输出模式下(第8~9行),然后,使PB8输出低电平(第11行),即关闭蜂鸣器。第14~17行的函数BEEP中,只有第16行所示的一条语句,即调用BEEP函数时,如果PB8原来输出低电平,则输出高电平(蜂鸣器鸣叫); 如果PB8原来输出高电平,则输出低电平(关闭蜂鸣器)。 程序段58文件beep.h 1//Filename: beep.h 2 3#ifndef_BEEP_H 4#define_BEEP_H 5 6void BEEPInit(void); 7void BEEP(void); 8 9#endif 文件beep.h中声明了文件beep.c中定义的函数BEEPInit和BEEP。 程序段59文件key.c 1//Filename: key.c 2 3#include "includes.h" 4 5void KEYInit(void) 6{ 7RCC->APB2ENR |= (1uL<<6); 8RCC->APB2ENR |= (1uL<<0); 9 10GPIOE->CRL &=~((7uL<<8) | (7uL<<12) | (7uL<<16)); 11GPIOE->CRL |=(1uL<<11) | (1uL<<15) | (1uL<<19); 12 13GPIOE->ODR |= (7uL<<2); 14} 文件key.c包含了KEYInit函数,3个按键KEY0~KEY2依次占用了PE4、PE3和PE2口,所以第7行打开PE口的时钟源,第8行打开AFIO口的时钟源(参考图37)。第10~11行配置PE2~PE4口为带上拉的输入口,第13行使PE2~PE4口均输出高电平,相当于为3个按键KEY2、KEY1和KEY0提供上拉电平。 程序段510文件key.h 1//Filename: key.h 2 3#ifndef_KEY_H 4#define_KEY_H 5 6void KEYInit(void); 7 8#endif 文件key.h中声明了KEYInit函数,该函数位于key.c文件中,用于初始化3个按键KEY0~KEY2。 程序段511文件exti.c 1//Filename: exti.c 2 3#include "includes.h" 4 5void EXTIKeyInit(void) 6{ 7AFIO->EXTICR[0] |= (1uL<<2)<<8; 8AFIO->EXTICR[0] &= ~(((3uL<<0) | (1uL<<3))<<8); 9AFIO->EXTICR[0] |= (1uL<<2)<<12; 10AFIO->EXTICR[0] &= ~(((3uL<<0) | (1uL<<3))<<12); 11AFIO->EXTICR[1] |= (1uL<<2)<<0; 12AFIO->EXTICR[1] &= ~(((3uL<<0) | (1uL<<3))<<0); 13 这里,第7~8行将PE2口(KEY2)用作外部中断EXTI2输入,第9~10行将PE3口(KEY1)作为外部中断EXTI3输入,第11~12行将PE4口(KEY0)作为外部中断EXTI4输入。参考表43可知,将PE2口用作外部中断EXTI2输入,即将EXTI2[3:0]设置为4; 同理,将PE3口用作外部中断EXTI3输入,即将EXTI3[3:0]设置为4,将PE4口用作外部中断EXTI4输入,即将EXTI4[3:0]设置为4。由于EXTI2[3:0]位于寄存器AFIO_EXTICR1(即第7行的AFIO>EXTICR[0])的第[11:8]位,所以第7~8行中的配置字中出现了“<<8”; 同理,可理解第9~12行的配置字的含义。 14EXTI->IMR |= (7uL<<2); 15EXTI->FTSR |= (7uL<<2); 16 第14行打开外部中断EXTI2、EXTI3和EXTI4,第15行配置这3个外部中断为下降沿触发。 17NVIC_EnableIRQ(EXTI2_IRQn); 18NVIC_EnableIRQ(EXTI3_IRQn); 19NVIC_EnableIRQ(EXTI4_IRQn); 20NVIC_SetPriority(EXTI2_IRQn,5); 21NVIC_SetPriority(EXTI3_IRQn,6); 22NVIC_SetPriority(EXTI4_IRQn,7); 23} 24 第17~19行调用CMSIS库函数NVIC_EnableIRQ开放中断EXTI2、EXTI3和EXTI4。结合第14行可知,外部中断的开放需要两步,首先要配置EXTI模块中的EXTI_IMR寄存器使外部中断的线路有效,然后,还要开放外部中断对应的NVIC中断。第20~22行依次配置NVIC中断EXTI2、EXTI3和EXTI4的优先级号为5、6和7。 因此,第5~23行的函数EXTIKeyInit实现的作用如下: (1) 将外部按键对应的引脚配置为中断功能。 (2) 开放EXTI模块中的这些外部输入中断。 (3) 配置这些外部输入中断均为下降沿触发类型。 (4) 通过NVIC中断控制器开放这些NVIC中断。 (5) 配置这些外部输入中断的优先级,共有16级,优先级号取值范围为0~15。 25void EXTI2_IRQHandler() 26{ 27LED(0,LED_ON); 28EXTI->PR = (1uL<<2); 29NVIC_ClearPendingIRQ(EXTI2_IRQn); 30} 31 第25~31行为外部中断EXTI2的中断服务函数,函数名必须为EXTI2_IRQHandler(参考2.6节),当按键KEY2被按下后,将进入该函数执行。第27行点亮LED0灯,第28行清除EXTI2中断标志位,第29行清除NVIC寄存器中EXTI2中断对应的标志位。 32void EXTI3_IRQHandler() 33{ 34LED(0,LED_OFF); 35EXTI->PR = (1uL<<3); 36NVIC_ClearPendingIRQ(EXTI3_IRQn); 37} 38 第32~38行为外部中断EXTI3的中断服务函数,函数名必须为EXTI3_IRQHandler,当按键KEY1被按下后,将进入该函数执行。第34行熄灭LED0灯,第35行清除EXTI3中断标志位,第36行清除NVIC寄存器中EXTI3中断对应的标志位。 39void EXTI4_IRQHandler() 40{ 41BEEP(); 42EXTI->PR = (1uL<<4); 43NVIC_ClearPendingIRQ(EXTI4_IRQn); 44} 第39~44行为外部中断EXTI4的中断服务函数,函数名必须为EXTI4_IRQHandler,当按键KEY0被按下后,将进入该函数执行。第41行调用BEEP函数,如果蜂鸣器原来是关闭的,则打开蜂鸣器; 否则,关闭蜂鸣器。第42行清除EXTI4中断标志位,第43行清除NVIC寄存器中EXTI4中断对应的标志位。 程序段512文件exti.h 1//Filename: exti.h 2 3#ifndef_EXTI_H 4#define_EXTI_H 5 6void EXTIKeyInit(void); 7 8#endif 文件exti.h中声明了函数EXTIKeyInit,该函数定义在exti.c中,用于初始化外部输入中断。注意,文件exti.c中的3个中断服务函数EXTI2_IRQHandler、EXTI3_IRQHandler 和EXTI4_IRQHandler均无须声明,因为这些中断服务函数不是由主函数或其他函数调用执行的,而是由硬件的中断系统被触发相应的中断后自动调用的。 工程03的工作流程如图54所示。 图54工程03的工作流程 由图54可知,工程03运行到主函数main后,执行BSPInit函数初始化LED灯、按键、蜂鸣器和外部中断等外设,然后进行无限循环体,执行LED1灯的循环闪烁功能。工程03中有3个中断服务函数,当按键KEY02被按下时,执行EXTI2_IRQHandler函数,点亮LED0灯; 当按键KEY1被按下时,执行EXTI3_IRQHandler函数,熄灭LED0灯; 当按键KEY0被按下时,执行EXTI4_IRQHandler函数,使蜂鸣器切换工作状态。 视频讲解 5.3.2库函数类型工程实例 本节讨论的工程与5.3.1节的工程03实现的功能完全相同,这里使用库函数方式进行工程设计。在工程02的基础上,新建“工程04”,保存在目录“D:\STM32F103ZET6工程\工程04”下,此时的工程04与工程02完全相同,需要做的修改如下: (1) 修改文件main.c和includes.h。 (2) 新建文件bsp.c、bsp.h、key.c、key.h、beep.c、beep.h、exti.c和exti.h,新建的文件均保存在目录“D:\STM32F103ZET6工程\工程04\BSP”下。 (3) 将bsp.c、key.c、beep.c和exti.c文件添加到工程管理器的“BSP”分组下。 (4) 将位于目录“D:\STM32F103ZET6工程\工程04\STM32F10x_FWLib\src”下的库文件stm32f10x_exti.c添加到工程管理器的“LIB”分组下。 建设好的工程04如图55所示。 图55工程04工作窗口 工程04中的文件vartypes.h、led.c和led.h与工程02中的同名文件源代码相同; 工程04中的文件main.c、includes.h、bsp.c、bsp.h、exti.h、key.h、beep.h与工程03中的同名文件的内容相同。下面介绍其余文件的内容,即beep.c、key.c和exti.c文件的内容,如程序段513~程序段515所示。 程序段513文件beep.c 1//Filename: beep.c 2 3#include "includes.h" 4 5void BEEPInit(void) 6{ 7GPIO_InitTypeDef g; 8RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); 9 10g.GPIO_Pin = GPIO_Pin_8; 11g.GPIO_Mode = GPIO_Mode_Out_PP; 12g.GPIO_Speed = GPIO_Speed_10MHz; 13GPIO_Init(GPIOB, &g); 14 15GPIO_ResetBits(GPIOB,GPIO_Pin_8); 16} 17 第5~16行为蜂鸣器的初始化函数。蜂鸣器的控制端为PB8口,第8行调用库函数RCC_APB2PeriphClockCmd打开PB口的时钟源; 第10~12行为配置PB8口的属性为推挽输出且工作频率为10MHz,第13行调用库函数GPIO_Init初始化PB8口。 18void BEEP(void) 19{ 20Int08U v; 21v=GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_8); 22if(v==1) 23GPIO_ResetBits(GPIOB,GPIO_Pin_8); 24else 25GPIO_SetBits(GPIOB,GPIO_Pin_8); 26} 第18~26行为BEEP函数。第21行调用库函数GPIO_ReadOutputDataBit读出PB8口的输出状态,如果为高电平(第22行为真),则第23行调用GPIO_ResetBits函数清零PB8口,即关闭蜂鸣器; 如果为低电平(第24行的情况),则第25行调用GPIO_SetBits函数置位PB8口,即打开蜂鸣器。因此,调用一次BEEP函数,就使得蜂鸣器切换一次工作状态。 程序段514文件key.c 1//Filename: key.c 2 3#include "includes.h" 4 5void KEYInit(void) 6{ 7GPIO_InitTypeDef g; 8RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO, ENABLE); 9 10g.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4; 11g.GPIO_Mode = GPIO_Mode_IPU; 12g.GPIO_Speed = GPIO_Speed_10MHz; 13GPIO_Init(GPIOE, &g); 14 15GPIO_SetBits(GPIOE,GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4); 16} 第5~16行为KEYInit函数。按键KEY0~KEY2占用PE4、PE3和PE2口,同时需要开启AFIO功能,所以,第8行调用库函数RCC_APB2PeriphClockCmd开启PE口和AFIO口的时钟源。第10~13行初始化PE2~PE4为上拉有效的输入口,且工作频率为10MHz。第15行使PE2~PE4口输出高电平,相当于为PE2~PE4提供强上拉功能。 程序段515文件exti.c 1//Filename: exti.c 2 3#include "includes.h" 4 5void EXTIKeyInit(void) 6{ 7EXTI_InitTypeDefe; 8 9GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2); //PE2为EXTI2 10GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3); //PE3为 EXTI3 11GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4); //PE4为 EXTI4 12 13e.EXTI_Line = EXTI_Line2 | EXTI_Line3 | EXTI_Line4; 14e.EXTI_Mode = EXTI_Mode_Interrupt; 15e.EXTI_Trigger = EXTI_Trigger_Falling; 16e.EXTI_LineCmd = ENABLE; 17EXTI_Init(&e); 18 19NVIC_EnableIRQ(EXTI2_IRQn);//开放外部中断EXTI2,EXTI3,EXTI4 20NVIC_EnableIRQ(EXTI3_IRQn); 21NVIC_EnableIRQ(EXTI4_IRQn); 22NVIC_SetPriority(EXTI2_IRQn,5); 23NVIC_SetPriority(EXTI3_IRQn,6); 24NVIC_SetPriority(EXTI4_IRQn,7); 25} 26 第5~25行为外部输入中断的初始化函数EXTIKeyInit。第9~11行调用库函数GPIO_EXTILineConfig依次将PE2、PE3和PE4配置为外部中断EXTI2、EXTI3和EXTI4。第13~17行配置外部输入中断EXTI2、EXTI3和EXTI4工作在中断模式,且为下降沿触发方式。第19~21行为调用CMSIS库函数NVIC_EnableIRQ开放外部中断EXTI2、EXTI3和EXTI4,第22~24行调用CMSIS库函数NVIC_SetPriority配置外部中断EXTI2、EXTI3和EXTI4的优先级号依次为5、6和7。 27void EXTI2_IRQHandler() 28{ 29LED(0,LED_ON); 30EXTI_ClearFlag(EXTI_Line2); 31NVIC_ClearPendingIRQ(EXTI2_IRQn); 32} 33 34void EXTI3_IRQHandler() 35{ 36LED(0,LED_OFF); 37EXTI_ClearFlag(EXTI_Line3); 38NVIC_ClearPendingIRQ(EXTI3_IRQn); 39} 40 41void EXTI4_IRQHandler() 42{ 43BEEP(); 44EXTI_ClearFlag(EXTI_Line4); 45NVIC_ClearPendingIRQ(EXTI4_IRQn); 46} 对比程序段511,这里的3个中断服务函数中,清除中断标志使用了库函数EXTI_ClearFlag,如第30、37和44行所示,依次清零EXTI2、EXTI3和EXTI4的中断请求标志。 在上述库函数方式的工程程序中,新出现了结构体类型EXIT_InitTypeDef和新的库函数GPIO_EXTILineConfig、EXTI_Init和EXTI_ClearFlag,它们均被定义在stm32f10x_exti.c或stm32f10x_exti.h中,因此需要将stm32f10x_exti.c添加到工程管理器的“LIB”分组中。关于这些新结构体类型和新库函数的具体描述请参考STM32F10x库函数手册,或直接从头文件stm32f10x_exti.h中查看。 5.4本章小结◆ 本章介绍了嵌套向量中断控制器NVIC的工作原理和GPIO口作为外部中断的程序设计方法,然后以按键控制为例,讨论了下降沿触发中断的方法,并给出了寄存器类型和库函数类型的工程程序。外部中断是STM32F103ZET6微控制器响应外部异步事件的唯一方式,中断的处理能力也是反映STM32F103ZET6的性能和灵活性的重要指标。建议读者在学习本章内容后,仔细阅读库函数手册和文件stm32f10x_exti.c与stm32f10x_exti.h,充分掌握新出现的库函数的用法,并设计下降沿触发中断的工程程序。第6章介绍定时器时还将继续使用NVIC中断。 习题 1. 阐述与中断控制相关的操作及其CMSIS库函数。 2. 结合本章内容,说明GPIO外部输入中断的响应处理方法。 3. 编写寄存器类型工程,实现对按键的中断输入响应,用LED灯状态反映按键的按下或弹开。 4. 编写库函数类型工程,实现对按键的中断输入响应,用LED灯状态反映按键的按下或弹开。 5. 说明将PD12配置为外部中断输入EXTI12的方法。 6. 简要描述中断优先级的配置方法。