第5章〓按键与中断处理

本章将介绍嵌套向量中断控制器NVIC的工作原理,阐述STM32F103ZET6微控制器外部输入中断的工作原理,然后,以用户按键为例,详细解释NVIC中断的寄存器类型和库函数类型的程序设计方法。

本章的学习目标: 

了解NVIC中断响应方法; 

熟悉GPIO中断响应方法; 

熟练应用寄存器或库函数进行GPIO中断程序设计。

5.1NVIC中断工作原理◆

嵌套向量中断控制器NVIC相关的中断管理工作主要有开放中断、关闭中断、设置中断请求标志、读中断请求标志、清除中断请求标志和配置中断优先级等。嵌套向量中断控制器NVIC的寄存器有ISER0、ISER1、ICER0、ICER1、ISPR0、ISPR1、ICPR0、ICPR1、IABR0、IABR1、IPR0~IPR14和STIR,如表51所示。


表51NVIC寄存器




序号地址寄存器名称描述


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为例,介绍开放中断的方法。

根据表51,ISER0[0]~ISER0[31]对应中断号为0~31的NVIC中断,而ISER1[0]~ISER1[27]对应中断号为32~59的NVIC中断。由表25可知,外部中断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中断的相关操作,这里重点介绍开放中断、关闭中断、设置中断请求标志、读中断请求标志、清除中断请求标志、设置中断优先级和获取中断优先级的函数,如程序段51所示。

程序段51NVIC中断相关的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,各成员的位置与表51中各个寄存器的位置对应,再结合第18~20行可知,NVIC为指向首地址0xE000E100的结构体指针,这样(结合表51),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文件中,如程序段52所示。第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中断的优先级号。

程序段51中,第47~68行的代码需要访问中断优先级寄存器IPR0~IPR14,这些寄存器的结构如图51所示。



图51中断优先级配置寄存器


由图51可知,每个IPR寄存器用于设置4个NVIC中断的优先级,32位的IPR寄存器的4字节的低4位均无效,只有高4位有效,故可以设置的优先级号为0~15。根据图51,如果设置EXTI2中断的优先级号为10,则需要将IPR2的第[7:4]位域设为10。当两个中断具有不同的优先级号时,优先级号小的中断优先级高; 当两个中断具有相同的优先级号时,中断号小的中断优先级高。

可配置优先级的异常的优先级号由3个系统手柄优先级寄存器(SHPR1~SHPR3)设置,其地址依次为0xE000ED18、0xE000ED1C和0xE000ED20,如表52所示。


表52异常号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



程序段52自定义枚举类型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;


上述代码对应表25,由于IRQn_Type为指定了成员值的枚举类型,因此,可以用强制类型转换将IRQn_Type类型的变量转化为整型。例如,程序段51第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(见表43),可从GPIO口中选择16个引脚配置为16个外部中断的输入端,如图52所示。




图52外部中断输入端引脚配置方法


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用户按键中断实例◆

结合图311和图36可知,STM32F103ZET6微控制器的PE2、PE3和PE4分别与网络标号KEY2、KEY1和KEY0相连接; 结合图313和图33可知,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”分组下,建设好的工程如图53所示。




图53工程03工作窗口


修改后的main.c和includes.h文件如程序段53和程序段54所示,新创建的文件bsp.c、bsp.h、beep.c、beep.h、key.c、key.h、exti.c和exti.h分别如程序段55~程序段512所示。

程序段53文件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}


对比程序段47,这里的主程序文件main.c中,第9行调用BSPInit函数实现外设的初始化,该函数在程序段55中介绍; 在第11~17行的无限循环体中,实现LED1灯的循环闪烁功能。

程序段54文件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行所示。

程序段55文件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函数是总的系统初始化函数。

程序段56文件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中。

程序段57文件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原来输出高电平,则输出低电平(关闭蜂鸣器)。

程序段58文件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。

程序段59文件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口的时钟源(参考图37)。第10~11行配置PE2~PE4口为带上拉的输入口,第13行使PE2~PE4口均输出高电平,相当于为3个按键KEY2、KEY1和KEY0提供上拉电平。

程序段510文件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。

程序段511文件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输入。参考表43可知,将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中断对应的标志位。

程序段512文件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的工作流程如图54所示。




图54工程03的工作流程


由图54可知,工程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如图55所示。




图55工程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文件的内容,如程序段513~程序段515所示。

程序段513文件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函数,就使得蜂鸣器切换一次工作状态。

程序段514文件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提供强上拉功能。

程序段515文件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}


对比程序段511,这里的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. 简要描述中断优先级的配置方法。