4、EXTI外部中断
一、中断
中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先相应更加紧急的中断源
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
中断响应:申请中断,让CPU执行中断函数
事件响应:触发一个事件,则中断信号不会通向CPU,而是通向其他外设的操作,例如触发ADC转换、触发DMA等(stm32对外部中断增加的额外的功能)(也就是不会触发中断,而是触发外设操作)
使用中断系统可以加大的提高程序的运行效率,如果没有中断系统,为了防止外部中断被忽略或者串口数据被覆盖,主程序就只能不断地查询是否有这些事件发生。
比如如果没有定时器中断,主程序只能使用delay函数,才能实现定时的功能。
有了中断系统后,主程序就可以放心执行其他事情,有中断的时候再去处理。
对于STM32来说,想要获取的信号是外部驱动的很快的突发信号,但是又不需要一直进行检测。
不需要经常使用、不需要一直检测的模块:
按键(注意抖动问题),不推荐,可以使用定时器中断
旋转编码器
红外遥控接收器
二、AFIO
AFIO(Alternate Function Input Output)是用于配置微控制器引脚的外设。在配置 EXTI 时,您可以选择将特定的 GPIO 引脚连接到 EXTI 通道。这允许您灵活地选择要触发中断的引脚。
AFIO主要用于引脚复用功能的选择和重定义
在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择
三、EXIT外部中断
EXTI可以监测指定 GPIO 口的电平信号,当其指定的 GPIO 口产生电平变化时, EXTI 将立即向 NVIC 发出中断申请,经过 NVIC 裁决后即可中断 CPU 主程序,使 CPU 执行 EXTI 对应的中断程序
- 支持的触发方式:上升沿 / 下降沿 / 双边沿 / 软件触发
- 支持的 GPIO 口:所有 GPIO 口,但相同的 Pin (例如 PA1 、 PB1 、 PC1 等 )不能 同时触发中断
- 通道数: 16个GPIO_Pin ,外加 PVD 输出、 RTC 闹钟、 USB 唤醒、以太网 唤醒(一共 20 个)
- 触发响应方式:中断响应 / 事件响应
1、抢占优先级和响应优先级:
- 抢占优先级用于确定中断的重要性,较高的抢占优先级意味着中断可以中断当前正在执行的较低优先级中断。
- -响应优先级用于确定多个中断同时触发时的执行顺序。响应优先级高的中断会先执行,而响应优先级相同的中断会按照中断号顺序排队执行。
2、中断触发方式:
- 上升沿触发:中断在引脚电平由低到高变化时触发。
- 下降沿触发:中断在引脚电平由高到低变化时触发。
- 双边沿触发:中断在引脚电平由低到高或由高到低变化时触发。
- 软件触发:通过执行一句特定的代码来触发中断,通常使用类似于
NVIC_SetPendingIRQ()
的函数。
3、触发响应方式:
- 中断响应:中断触发后,中断服务程序执行,通常用于需要立即响应的事件。
- 事件响应:事件触发后,不会引发中断,而是触发其他外设操作,如启动ADC转换或启动DMA传输。
4、注意事项:
- 进入中断函数时,务必检查中断标志位以确定中断的原因。
- 中断服务函数应该执行简短和高效的代码,以尽快释放中断,并允许更高优先级的中断执行。
- 在中断服务函数退出时,通常需要清除中断标志位,以允许下一次中断触发
四、 NVIC优先级分组
NVIC的名字叫做嵌套中断向量控制器
NVIC用于统一管理中断和分配中断优先级,属于内核外设,是CPU的小助手,可以让CPU专注于运算。相当于医院里的叫号系统(给病人进行排号,CPU相当于医生,只负责看病)。
优先级:抢占优先级、响应优先级。
插队:响应优先级
不等上个病人看完就直接进去:抢占优先级
NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级(插队)
抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队
如果中断不多,很难导致中断冲突时,可以随意选择优先级分组,那个都行。
五、 外部中断程序设计步骤
涉及到的外设:RCC、GPIO、AFIO、EXTI、NVIC
1、如何设置外部中断 (EXTI)
打开RCC时钟: 在使用任何外设之前,需要确保相应的外设时钟已打开。在这种情况下,您打开了RCC (Reset and Clock Control) 时钟,它是控制各种外设时钟的关键。没有时钟,外设无法正常工作。
配置GPIO: 在使用外部中断之前,需要选择一个GPIO端口,并将其配置为输入模式。输入模式的配置可以是浮空(不连接到任何电平)、上拉(连接到高电平)或下拉(连接到低电平),具体取决于应用的要求。
- 配置AFIO: AFIO (Alternate Function I/O) 控制着GPIO引脚的复用功能。在这一步中,您选择了使用的GPIO并将其连接到后面的EXTI模块,以允许GPIO引脚触发外部中断。
- 配置EXTI: 外部中断线 (EXTI) 控制着外部中断的触发和响应方式。您需要选择外部中断触发的方式,例如上升沿、下降沿或双边沿触发。通常,中断触发方式应根据应用的需求来选择。此外,您还可以选择中断响应方式,一般选择中断响应以触发中断处理函数。
- 配置NVIC: NVIC (Nested Vectored Interrupt Controller) 用于配置中断优先级和中断的全局控制。在这一步中,您可以为外部中断选择一个合适的优先级,以确定它在中断处理器中的相对重要性。抢占优先级和响应优先级的选择可以确保在多个中断同时发生时,系统可以正确地处理它们。
2、GPIO中断输入输出函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
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);
void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);
|
3、配置AFIO外部中断引脚选择
这段代码是用于配置外部中断线映射的函数。外部中断线映射允许将特定的GPIO引脚映射到外部中断线上,从而使这些引脚能够触发外部中断。以下是对该函数的注释和解释:
调用这个函数,就可以配置AFIO的数据选择器,来选择我们想要的中断引脚
1 2 3 4 5 6 7
|
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
|
GPIO_PortSource
参数用于指定外部中断线映射的源GPIO端口。这是一个数字,表示GPIO端口,例如,如果要映射的引脚属于GPIOA,则该参数为 GPIO_PortSource = GPIO_PortSourceGPIOA
。
GPIO_PinSource
参数用于指定外部中断线映射的源GPIO引脚。这是一个数字,表示要映射的GPIO引脚的位置,例如,如果要映射的引脚是GPIOA的第2号引脚,则该参数为 GPIO_PinSource = GPIO_PinSource2
。
通过调用此函数,您可以配置外部中断线映射,以便将特定GPIO引脚与外部中断相关联。这使得在满足外部中断触发条件时,可以触发相应的中断处理程序。在配置外部中断时,通常需要使用此函数来设置引脚到外部中断线的映射关系。
4、配置EXTI库函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
void EXTI_DeInit(void);
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
void EXTI_ClearFlag(uint32_t EXTI_Line);
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
|
5、配置NVIC
以下是对这些函数的代码注释解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);
|
这些函数用于配置和管理中断控制器 (NVIC) 和系统滴答定时器 (SysTick)。以下是它们的简要解释:
NVIC_PriorityGroupConfig
用于配置中断优先级分组,这是为了确定中断的优先级管理方式。
NVIC_Init
用于初始化中断控制器的寄存器,配置中断的优先级和其他设置。
NVIC_SetVectorTable
用于配置向量表的基地址和偏移量,这是中断处理程序的入口。
NVIC_SystemLPConfig
用于配置系统低功耗模式,以降低系统功耗。
SysTick_CLKSourceConfig
用于配置 SysTick 定时器的时钟源,以控制其工作时钟。
这些函数通常在初始化阶段用于配置系统的中断和定时器设置,以满足应用程序的需求。确保查阅您使用的STM32系列的参考手册和文档,以获取详细的配置信息和示例代码。
六、示例程序
1、红外传感器中断计次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| uint16_t CountSensor_Count;
void CountSensor_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); 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); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14); EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line = EXTI_Line14; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStructure); 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) { if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0) { CountSensor_Count++; } EXTI_ClearITPendingBit(EXTI_Line14); } }
|
实现逻辑:
这段代码初始化了一个外部传感器计数功能。具体注释如下:
CountSensor_Count
是用来存储传感器计数值的变量。
CountSensor_Init
函数用于初始化外部传感器。它执行以下步骤:
- 使能
GPIOB
和 AFIO
时钟。
- 配置
GPIOB
引脚14为上拉输入模式,这个引脚用于连接传感器。
- 配置外部中断线,将
GPIOB
引脚14与外部中断线14关联起来。
- 配置外部中断线14的触发方式为下降沿触发,表示当引脚电平从高变为低时触发中断。
- 配置中断分组为优先级组2,允许设置中断的抢占优先级和响应优先级。
- 配置外部中断的中断处理函数和中断优先级。
CountSensor_Get
函数用于获取传感器的计数值,并返回该值。
EXTI15_10_IRQHandler
是外部中断的中断处理函数。当外部中断14触发时(引脚电平下降),该函数会被调用。在函数内部,它会检查引脚的电平,如果电平为低(确保不是抖动),则增加 CountSensor_Count
计数值。最后,它会清除中断标志位,以表示已经处理了中断。
2、旋转编码器计次
这段代码是用于处理编码器的输入信号,并实时计算编码器的计数值。下面是对这段代码的详细注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| #include "stm32f10x.h"
int16_t Encoder_Count;
void Encoder_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
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);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
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_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 EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) == SET) { if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) { if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) { Encoder_Count--; } } EXTI_ClearITPendingBit(EXTI_Line0); } }
void EXTI1_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line1) == SET) { if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) { if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) { Encoder_Count++; } } EXTI_ClearITPendingBit(EXTI_Line1); } }
|
实现逻辑:
这段代码设置了两个外部中断线,用于处理编码器的输入信号。在外部中断触发时,会根据按钮的状态(旋转编码器的状态为双向滞后九十度的电平信号,通过判断高低电平分辨正负)递增或递减编码器的计数值。外部中断0用于逆时针旋转,而外部中断1用于顺时针旋转。每次中断触发后,计数值会被清零,以便进行下一轮计数。这个示例展示了如何使用外部中断来处理实际硬件输入,并在中断服务函数中更新相关的计数值。