本文介绍如何使用STM32标准外设库设置并使用中断,单片机中断系统的目的是为了让MCU对内部或外部的突发事件及时地作出响应,并执行相应的程序,最简单直观的为外部中断,可以用作检测按键的按下和弹起,本例程设置GPIOA的Pin0作为外部中断。
本文适合对单片机及C语言有一定基础的开发人员阅读,MCU使用STM32F103VE系列。
中断分为两部分,初始化和处理。
1. 初始化
初始化分为三步,包括设置优先级分组、结构体初始化和初始化库函数调用。
1.1. 优先级分组
共5组,一般选择NVIC_PriorityGroup_2,即2位抢占优先级、2位子优先级。
调用库函数
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
1.2. 结构体初始化
typedef struct { uint8_t NVIC_IRQChannel; uint8_t NVIC_IRQChannelPreemptionPriority; uint8_t NVIC_IRQChannelSubPriority; FunctionalState NVIC_IRQChannelCmd; } NVIC_InitTypeDef;
- 中断源:不同的中断设置不同的中断源,具体的成员配置可参考 stm32f10x.h 头文件里面的 IRQn_Type 结构体定义,这个结构体包含了所有的中断源。
/* 配置中断源:外部中断0 */ NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
- 抢占优先级,根据中断的优先级程度设置不同的优先级,优先级高的中断设置高的优先级,数值越小优先级越高,优先级分组为NVIC_PriorityGroup_2时,取值范围为0-3,优先级从高到低分别为0、1、2、3。
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
- 子优先级,根据中断的优先级程度设置不同的优先级,优先级高的中断设置高的优先级,数值越小优先级越高,优先级分组为NVIC_PriorityGroup_2时,取值范围为0-3,优先级从高到低分别为0、1、2、3。
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
- 使能
直接设置为ENABLE即可。
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
1.3. 库函数
调用库函数:
NVIC_Init(&NVIC_InitStructure);
2. 处理
2.1. 中断服务函数
可以把中断服务函数统一写在 stm32f10x_it.c 这个库文件中,也可以写在其他文件中,中断服务函数的函数名必须跟启动文件(startup_stm32f10x_hd.s)里面预先设置的一样,比如外部中断0的中断服务函数名称为EXTI0_IRQHandler ()。
void EXTI0_IRQHandler(void)
2.2. 中断处理
中断服务函数进入之后首先判定中断标志位是否置位,如果未置位则直接退出,如果已置位则执行相应的处理,退出中断之前需要清除中断标志位。有些操作如读串口数据会自动清除中断读数据寄存器非空标志位,因此无需手动清除。
外部中断分为两部分,初始化和处理。
1. 外部中断初始化项目
分三部分:GPIO、通用中断、EXTI(External interrupt/event controller)
1.1. GPIO:时钟、端口、引脚、输入模式
- 时钟:需要同时使能GPIO时钟
- 引脚:需要选择指定的引脚
- 输入模式:一般选择浮空输入
1.2. 通用中断:优先级分组、中断源、优先级、使能
- 优先级分组:设定合适的优先级分组
- 中断源:选择指定的EXTI中断源
- 优先级:设定合适的优先级
- 使能:调用库函数即可
1.3. EXTI:时钟、信号源、线选、EXTI模式、触发类型、使能
结构体:
typedef struct { uint32_t EXTI_Line; EXTIMode_TypeDef EXTI_Mode; EXTITrigger_TypeDef EXTI_Trigger; FunctionalState EXTI_LineCmd; }EXTI_InitTypeDef;
- 时钟:需要使能AFIO时钟
/*开启AFIO时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
- 信号源:需要配置指定引脚的信号源
直接调用库函数GPIO_EXTILineConfig()即可
/* 配置信号源 */ GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
- 线选:初始化需要配置的线
/* EXTI中断线选择Line0 */ EXTI_InitStructure.EXTI_Line = EXTI_Line0;
- EXTI模式:一般选择中断模式
/* EXTI为中断模式 */ EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
- 触发类型:可选上升沿或者下降沿
/* 上升沿中断 */ EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
- 使能:
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
- 初始化库函数
EXTI_Init(&EXTI_InitStructure);
2. 处理
2.1. 中断服务函数
外部中断0的中断服务函数名称为EXTI0_IRQHandler ()。
void EXTI0_IRQHandler(void);
2.2. 中断处理
中断服务函数中调用EXTI_GetITStatus()函数判定中断标志位状态以确定中断是否发生,调用EXTI_ClearITPendingBit()函数清除中断标志位。
if(EXTI_GetITStatus(EXTI_Line0) != RESET) { EXTI_ClearITPendingBit(EXTI_Line0); }
完整代码(仅自己编写的部分)
1 void GPIO_Config(void) 2 { 3 /*定义一个GPIO_InitTypeDef类型的结构体*/ 4 GPIO_InitTypeDef GPIO_InitStructure; 5 6 /*开启指定端口的GPIO外设时钟*/ 7 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); 8 9 /*选择要控制的GPIO引脚*/ 10 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; 11 12 /*设置引脚模式为浮空输入*/ 13 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 14 15 /*调用库函数,初始化GPIO*/ 16 GPIO_Init(GPIOA, &GPIO_InitStructure); 17 } 18 19 void NVIC_Config(void) 20 { 21 NVIC_InitTypeDef NVIC_InitStructure; 22 23 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 24 25 /* 配置中断源:外部中断0 */ 26 NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; 27 /* 配置抢占优先级 */ 28 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; 29 /* 配置子优先级 */ 30 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; 31 /* 使能中断通道 */ 32 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 33 34 NVIC_Init(&NVIC_InitStructure); 35 } 36 37 void EXTI0_Config(void) 38 { 39 EXTI_InitTypeDef EXTI_InitStructure; 40 41 /*开启AFIO时钟*/ 42 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); 43 44 /*配置信号源*/ 45 GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); 46 47 /* EXTI中断线选择Line0 */ 48 EXTI_InitStructure.EXTI_Line = EXTI_Line0; 49 /* EXTI为中断模式 */ 50 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; 51 /* 上升沿中断 */ 52 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; 53 /* 使能中断 */ 54 EXTI_InitStructure.EXTI_LineCmd = ENABLE; 55 56 EXTI_Init(&EXTI_InitStructure); 57 } 58 59 void EXTI0_init(void) 60 { 61 GPIO_Config(); 62 NVIC_Config(); 63 EXTI0_Config(); 64 } 65 66 //中断服务函数,待完善 67 void EXTI0_IRQHandler(void) 68 { 69 if(EXTI_GetITStatus(EXTI_Line0) != RESET) 70 { 71 EXTI_ClearITPendingBit(EXTI_Line0); 72 } 73 } 74 75 76 int main(void) 77 { 78 // 外部中断0中断初始化 79 EXTI0_init(); 80 81 while (1) 82 { 83 } 84 }
仿真结果
程序编译成功后,点击开始仿真,打开菜单栏Peripherals→General Purpose I/O→GPIOA,如果中断触发类型为上升沿,那么触发条件为指定端口先低电平,再设置为高电平;反之如果触发类型为下降沿,那么触发条件为指定端口先高电平,再设置为低电平。
可在中断服务函数内发生中断后设置断点,程序开始运行,当满足中断触发条件后,程序在断点处暂停,表明中断成功。