一、中断系统
中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
二、STM32中断
68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设
使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级
三、NVIC(嵌套中断向量控制器)基本结构
作用:统一分配中断优先级和管理中断
四、NVIC优先级分组
NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级
抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队
五、EXTI外部中断
5.1 外部中断基本知识
①EXTI(Extern Interrupt)外部中断
②EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
③支持的触发方式:上升沿/下降沿/双边沿/软件触发
④支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断
⑤通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
⑥触发响应方式:中断响应/事件响应中断响应:引脚电平变化触发中断
事件响应:不触发中断,触发其他外设操作
5.2 外部中断(EXTI)基本结构
5.2.1开发步骤:
①配置RCC,把涉及到的外设时钟都打开
②配置GPIO,选择端口为输入模式
③配置AFIO,选择用的某一路GPIO,连接到后面的EXTI
④配置EXTI,选择边沿触发方式、触发响应方式(中断响应/事件响应)
⑤配置NVIC,给中断选择合适的优先级
5.3 AFIO复用IO口
AFIO主要用于引脚复用功能的选择和重定义
在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择
5.4 EXTI内部框图
六、AFIO库函数
void GPIO_AFIODeInit(void);//复位AFIO
//配置AFIO事件输出功能
void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
void GPIO_EventOutputCmd(FunctionalState NewState);/*下面两个重要*/
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);//引脚重映射,第一个参数:重映射的方式;第二个参数:新的状态
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);//配置AFIO的数据选择器,本节中断用到的函数
void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);//以太网相关
AFIO中断选择函数详解
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
第一个参数:GPIO_PortSourceGPIOx where x can be (A..G)
第二个参数:GPIO_PinSourcex where x can be (0..15)
七、EXTI库函数
void EXTI_DeInit(void);//清除所有EXTI配置,复位
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);//结构体方式初始化EXTI
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);//给结构体变量赋默认值
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);//软件触发外部中断/*主函数查看和清除标志位*/
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);//获取指定标志位是否被置1
void EXTI_ClearFlag(uint32_t EXTI_Line);//对置1的标志位清除/*中断函数查看和清除标志位*/
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
八、NVIC库函数
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);//中断分组的方式,整个芯片只能用一种,最好放在主函数中
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);//结构体方式初始化NVIC/*下面两个不常用*/
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);//设置中断向量表
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);//系统低功耗配置
九、中断函数
每个通道对应一个中断函数,在启动文件中可查看
开发技巧:
在中断函数中,先进行中断标志位的判断,确保是我们想要的中断源触发的函数;然后再使用清除标志位函数清除,否则会一直执行中断函数,在中断函数里卡死。
以上函数具体使用请参考”第十章、实验“
十、实验
10.1对射式红外传感器计次
main.c
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "CountSensor.h" int main(void) { OLED_Init(); CountSensor_Init(); OLED_ShowString(1,1 ,"Count:"); while (1) { OLED_ShowNum(1,7,CountSensor_Get(),5); } }
CountSensor.c
#include "stm32f10x.h" // Device header uint16_t CountSensor_Count; void CountSensor_Init(void) { /*一、开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启APB2时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO时钟 /*二、GPIO配置*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_InitStructure); /*三、AFIO配置*/ GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14); /*四、EXTI第14个线路配置为中断模式,下降沿触发,开启中断*/ EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line = EXTI_Line14;//EXTI线 EXTI_InitStructure.EXTI_LineCmd = ENABLE;//开启或关闭EXTI EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断模式或者事件模式 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//指定触发信号的有效边沿 EXTI_Init(&EXTI_InitStructure); /*五、配置NVIC*/ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//优先级分组 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//选择中断通道 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能或失能 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级 NVIC_Init(&NVIC_InitStructure); } uint16_t CountSensor_Get(void) { return CountSensor_Count; } void EXTI15_10_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line14) == SET) //判断标志位,是否是EXTI_Line14触发的中断 { CountSensor_Count++; EXTI_ClearITPendingBit(EXTI_Line14);//清除标志位 } }
CountSensor.h
#ifndef __COUNTSENSOR_H #define __COUNTSENSOR_H void CountSensor_Init(void); uint16_t CountSensor_Get(void); #endif
10.2旋转编码器计次
mian.c
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Encoder.h" int16_t Num; int main(void) { OLED_Init(); Encoder_Init(); OLED_ShowString(1,1,"Num:"); while (1) { Num += Encoder_Get(); OLED_ShowSignedNum(1,5,Num,5); } }
Encoder.c
#include "stm32f10x.h" // Device header int16_t Encoder_Count;//无符号变量 /*初始化操作*/ void Encoder_Init(void) { /*一、开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启APB2时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO时钟 /*二、GPIO配置*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_InitStructure); /*三、AFIO配置*/ GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1); /*四、EXTI第0/1个线路配置为中断模式,下降沿触发,开启中断*/ EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStructure); /*五、配置NVIC*/ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//优先级分组 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级 NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;//响应优先级 NVIC_Init(&NVIC_InitStructure); } int16_t Encoder_Get(void) { int16_t Temp; Temp = Encoder_Count; Encoder_Count = 0; return Temp; } /*正转中断函数*/ void EXTI1_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line1) == SET) //B相下降沿(中断) { if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) == 0) //A相低电平 { Encoder_Count ++; } EXTI_ClearITPendingBit(EXTI_Line1); } } /*反转中断函数*/ void EXTI0_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line0) == SET) //A相下降沿(中断) { if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0) //B相低电平 { Encoder_Count --; } EXTI_ClearITPendingBit(EXTI_Line0); } }
Encoder.h
#ifndef __ENCODER_H #define __ENCODER_H void Encoder_Init(void); int16_t Encoder_Get(void); #endif
十一、中断编程建议
①在中断函数中,不要执行耗时过长的代码,中断函数要简短快速,不要执行Delay函数,防止主程序受到严重阻塞
②不要在主函数和中断函数中调用相同的函数或者操作同一个硬件