比起51,32的中断类型更为丰富和细腻。
前置知识
STM32中断
STM32的中断有$16$个优先等级,可以设置抢占优先级和响应优先级。理论上有$68$种中段线路,具体取决于手册给出。
当中断来临时,由硬件自动调用相关函数。中断的类型既有内核的中断,例如复位、硬件失效等;也有外设的中断,如看门狗、PVD等等。
中断向量表由编译器给出。
当有多个外设同时触发中断时,中断机制会将这些触发排队或是嵌套。
NVIC
管理32中断的系统:NVIC(嵌套中断向量控制器)
统一分配中断优先级和管理中断。是内核的外设。
NVIC按优先级处理中断,告诉CPU当前应该处理哪个中断。
CPU不知道中断优先级——除非谁告诉了它。
NVIC既管理中断的“排队”,也负责中断的嵌套。
NVIC的中断优先级由优先级寄存器的$4$位$(0-15)$决定,这四位可以进行切分,分为高$n$位抢占优先级和低$4-n$位的响应优先级
优先级
抢占优先级和响应优先级相同的,按中断号进行排序
抢占优先级
抢占优先级高的可以中断嵌套
响应优先级
响应优先级高的可以优先排队
EXTI简介
EXTI(Extern Interrupt)外部中断可以监测制定GPIO口的电平信号,档期制定的GPIO口产生电平变化时,EXTI将立即向NVIC发出终端申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序。
- 支持的触发方式:上升沿/下降沿/双边沿/软件触发
- 支持的GPIO口:所有的GPIO口,但相同的GPIO_Pin不能同时触发中断。例如PA0和PB0不能同时选为中断引脚。
- 通道数:16个GPIO_Pin,PVD输出,RTC闹钟、USB唤醒、以太网唤醒
在省电模式的停止模式下,需要借助外部中断唤醒STM32
触发响应方式
中断响应/事件响应
如果选择事件响应,那么当引脚电平变化时,将不会触发中断,而是触发别的外设操作(例如由PVC触发ADC),属于外设之间的联合工作。
EXTI基本结构
GPIOn连接着AFIO(中断引脚选择)这个数据选择器,在各个Pin中选择一个连接到EXTI的通道上去。
再由EXTI边沿检测和控制电路上。
PVD、RTC、USB、ETH并接在EXTI边沿检测和控制电路上。
之后分为两种电路,一种连到NVIC上。外部中断的5~9和15~10会触发同一个中断函数~(ST偷工减料)~。
另有20条线路接到其它外设上,用来触发其它外设操作(事件响应)。
AFIO主要功能是引脚重映射、中断引脚选择等功能。
具体电路图如下:
AFIO相关的函数
在stm32f10x.h
中
- GPIO_AFIODeInit():清除AFIO有关的设置
- GPIO_PinLockConfig(GPIO_TypeDef*, uin16_t):没啥大用
- GPIO_EventOutputConfig(uint8_t, uint8_t):配置AFIO事件输出功能
- GPIO_EventOutputCmd(FunctionalState):配置AFIO事件输出功能
- GPIO_PinRemapConfig(uint32_t, FunctionalState):进行引脚重映射
- GPIO_EXTILineConfig(uint8_t, uint8_t):配置AFIO的数据选择器,选择需要的中断引脚
- GPIO_ETH_MediaInterfaceConfig(uint32_t):与以太网外设有关
在stm32f10x_exti.h
中
- EXTI_DeInit()
- EXTI_Init(EXTI_InitTypeDef*)
- EXTI_StructInit(EXTI_InitTypeDef*):获得配置信息结构体
- EXTI_GenerateSWInterrupt(uint32_t):软件触发外部中断,参数是一个指定的中断线
- EXTI_GetFlagStatus(uint32_t):获取指定的标志位
- EXTI_ClearFlag(uin32_t):清除标志位
- EXTI_GetITStatus(uint32_t):(在中断函数中)获取与中断有关的标志位
- EXTI_ClearITPendingBit():(在中断函数中)清除与中断有关的标志位
其中,外设_Init()
、外设_StructInit()
等在各种外设里都有类似的存在。
NVIC相关函数
在misc.h
中(怎么被发配到杂项里去了
)
- NVIC_PriorityGroupConfig(uint32_t):用于中断分组
- NVIC_Init(NVIC_InitTypeDef)
- NVIC_SetVectorTable(uint32_t, uin32_t)
- NVIC_SystemLPConfig(uint8_t, FunctionalState)
- SysTick_CLKSourceConfig()
中断分组方式整个芯片只能用一种,NVIC_PriorityGroupConfig()调用一次即可。
若多次调用,请保证所有的调用作出同样的分组。
一些实例
这里使用了 江科大自化协 的例子,通过OLED显示屏显示结果
对射式红外传感器计次
电路连接图如下
上代码!
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include "stm32f10x.h" #include "OLED.h" #include "CountSensor.h" #include "delay.h"
void main(void){ OLED_Init(); CounterSensor_Init(); while (1){ OLED_ShowNum(1, 1, CountSensor_Get()); } }
|
CounterSensor.c
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
| #include "stm32f10x.h"
uin16_t CounterSensor_Count = 0;
void CounterSensor_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_IRQChanneCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_Init(&NVIC_InitStructure); }
uin16_t CountSensor_Get(void){ return CounterSensor_Count; }
void EXTI15_10_IRQHandler(void){ if (EXTI_GetITStatus(EXTI_Line14) == SET){ CounterSensor_Count++; EXTI_ClearITPendingBit)EXTI_Line14); } }
|
旋转编码器计次
电路连接图如下
与 江科大自化协 的例子有所不同,我这里写的Encoder只写了个计数,并没有单位时间内计次功能。
上代码!
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include "stm32f10x.h" #include "OLED.h" #include "Encoder.h" #include "delay.h"
void main(void){ OLED_Init(); Encoder_Init(); while (1){ OLED_ShowNum(1, 1, Encoder_Get()); } }
|
Encoder.h
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
| #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_IRQChanneCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; NVIC_InitStructure.NVIC_IRQChanneCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; NVIC_Init(&NVIC_InitStructure); }
int16_t Encoder_Get(void){ return Encoder_Count; }
void EXTI0_IREHandler(void){ if (EXTI_GetITStatus(EXTI_Line0) == SET){ if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0){ Encoder_Count--; } EXTI_ClearITPendingBit(EXTI_Line0); } } void EXTI1_IREHandler(void){ if (EXTI_GetITStatus(EXTI_Line1) == SET){ if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0){ Encoder_Count++; } EXTI_ClearITPendingBit(EXTI_Line1); } }
|
杂记
可以考虑:在中断中对变量进行操作,当中断返回时,对中断变量进行显示和操作
最好不要在中断函数和主函数调用相同的函数或者操作同一个硬件