stm32中断定义流程及应用
stm32中断定义流程及应用
文章目录
- stm32中断定义流程及应用
- 1.中断系统
- 2.中断执行流程
- 3.stm32中断
- 3.1内核中断
- 3.2外设中断
- 4.NVIC基本结构
- 5.NVIC优先级分组
- 6.EXTI简介
- 6.1EXTI基本结构及解析
- 8.AFIO函数作用及解析
- 9.EXTI函数作用及解析
- 9.NVIC函数作用及解析
- 10.对射式红外传感器计次(操作)
- 10.1接线图
- 10.2代码编写
- 10.2.1主函数代码main.c
- 10.2.2红外传感器函数定义CountSensor.c
- 10.2.3红外传感器函数声明CountSensor.h
- 10.3不计数的原因
- 11.旋转编码器计次(操作)
- 11.1线路图
- 11.2代码编写
- 11.2.1主程序main.c
- 11.2.2编码器函数定义Encoder.c
- 11.2.3编码器函数声明Encoder.h
- 12.总结
- 12.1中断实现步骤:
- 12.2中断注意事项
1.中断系统
中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行(程序运行——>处理中断——>程序继续运行,有了中断就可以不必一直查看是否有事件发生,大大增加了效率)
中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中端程序,转而去处理新的中断程序,处理完成后依次进行返回
2.中断执行流程
一般情况下,程序就是在主程序里不断循环执行,当中断条件满足时,主程序就会暂停,然后自动跳转到中断程序里运行,中断程序执行顺序执行完后,再返回主程序继续执行。(中断程序一般是再一个函数里,当中断来临时,由硬件自动调用这个函数,C语言中中断的概念)
3.stm32中断
68个可屏蔽中断通道(中断源),包含EXTI(外部中断)、TIM(定时器)、ADC(模数转换器)、USART(串口)、SPI(通信)、I2C(通信)、RTC(实时时钟)等多个外设(几乎所有的外设都有中断)
98个中断源,这个是F1系列最多的中断数量,对于一个具体的型号,可能没有这么多中断,具体以对应型号的数据手册为准
3.1内核中断
灰色部分为内核中断,分别为复位、NMI不可屏蔽中断、硬件失效、存储管理、总线错误、错误应用等等,一般不会用到,了解即可。
3.2外设中断
其中因为硬件的地址是固定的,所以跳到硬件的地址只是过渡,还需要编写程序,让地址从硬件的固定地址连接到函数提供的随机地址,中断地址的列表就称为中断向量表
4.NVIC基本结构
NVIC是一个内核设计,是CPU的小助手.
NVIC有很多个输入口,有多少个中断线路都可以接过来。一个外设可能会同时占用多个中断通道,NVIC只有一个输出口,NVIC根据每个中断的优先级分配中断的先后顺序,最后通过右边的一个输出口告诉CPu,该处理哪个中断。(医院的叫号系统,就是一个NVIC)
5.NVIC优先级分组
响应优先级和抢占优先级
- NVIC的中断优先级由优先级寄存器的4位(总的)(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位(4减n位)的响应优先级(0—>15从低到高)
- 抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号上表中优先级相比) 的排队
分组方式 | 抢占优先级 | 响应优先级 |
---|---|---|
分组0 | 0位,取值为0 | 4位,取值为0~15 |
分组1 | 1位,取值为0~1 | 3位,取值为0~7 |
分组2 | 2位,取值为0~3 | 2位,取值为0~3 |
分组3 | 3位,取值为0~7 | 1位,取值为0~1 |
分组4 | 4位,取值为0~15 | 0位,取值为0 |
决定是不是可以中断嵌套的优先级,就叫抢占优先级,抢占优先级高的,可以进行中断嵌套。任何时候都是优先级高的先响应
6.EXTI简介
- EXTI(Extern Interrupt)外部中断.
- EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
- **支持的触发方式:**上升沿/下降沿/双边沿/软件触发
- 支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断
- **通道数:**16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒(总共20个通道)
- 触发响应方式: 中断响应/事件响应
外部中断的功能:从低功耗模式的停止模式下唤醒STM32
支持的触发方式: **上升沿:**低电平变到高电平的瞬间触发中断
**下降沿:**高电平变到低电平的瞬间触发中断
**双边沿:**上升沿和下降沿都可以触发中断
**软件触发:**无关引脚,仅靠代码就能触发中断
**相同的Pin不能同时触发中断:**就是PA0和PB0不能同时用,只能选一个作为中断引脚,要选择不同Pin的引脚,比如PA6和PB9等不同Pin引脚的
**16个GPIO_Pin:**对应GPIO_Pin_0到GPIO_Pin_15(是外部中断的主要功能
**中断响应:**中断响应是正常的流程,引脚电平变化触发中断
**事件响应:**不会触发中断,而是触发别的外设操作,属于外设之间的联合工作
6.1EXTI基本结构及解析
AFIO就是一个数据选择器,主要完成复用功能引脚重映射(就是将最初的引脚功能表的默认复用功能转换为重定义功能就是使用AFIO来完成的)、中断引脚选择
它可以在前面3个GPIO外设的16个引脚里选择其中一个连接到EXTI的通道里。上方的PA0、PB0和PC0也只有一个会接到通道上。
8.AFIO函数作用及解析
void GPIO_AFIODeInit(void);
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);
- void GPIO_AFIODeInit(void);复位AFIO外设的,调用后AFIO外设的配置就会全部清除
- void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);这个函数用于锁定,GPIO配置的,调用这个函数,参数指定某个引脚,那这个引脚的配置就会被锁定,防止意外更改
- void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
- void GPIO_EventOutputCmd(FunctionalState NewState);这两个函数(这个和上面)是用来配置AFIO的事件输出功能
- 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);和以太网有关,本芯片与以太网无关所以用不到。
9.EXTI函数作用及解析
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);
- void EXTI_DeInit(void);调用它就可以把EXTI的配置全部清除,恢复成上电默认状态
- void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);调用该函数,可以根据结构体里的参数配置EXTI外设,初始化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);获取中断标志位是否被置1了(如果你想在中断函数里查看和清除标志位,就用下面这两个函数,本质上都是对状态寄存器的读写)
- void EXTI_ClearITPendingBit(uint32_t EXTI_Line)清除中断挂起标志位
9.NVIC函数作用及解析
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 NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);用来中断分组的,参数是中断分组的方式(pre-emption priority先占优先级,即抢占优先级;subpriority从占优先级,即响应优先级)
- 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.对射式红外传感器计次(操作)
10.1接线图
挡住中间对射式传感器中间查看是否亮灭来判断是否正常输出高低电平。
10.2代码编写
10.2.1主函数代码main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "KEY.h"
#include "OLED.h"
//#include "OLED_Font.h"
#include "CountSensor.h"
int main(void){
OLED_Init();
CountSensor_Init();
OLED_ShowString(1,1,"count:");
//uint16_t num = CountSensor_Get();
while(1){
OLED_ShowNum(1,7,CountSensor_Get(),6);
}
}
10.2.2红外传感器函数定义CountSensor.c
#include "stm32f10x.h" // Device header
uint16_t CountSensor_Count;
/*
第一步:配置RCC把涉及到的外设的时钟全部都打开
第二步:配置GPIO,选择我们的端口为输入模式
第三步:配置AFIO,选择我们用的这一路的GPIO,连接到后面的EXTI
第四步:配置EXTI,选择边沿触发方式,比如上升沿、下降沿或者双边沿,还有选择触发响应方式,可以选择中断响应和事件响应
第五步:配置NVIC,给我们这个中断选择一个合适的优先级
最后,通过NVIC,外部中断信号就能进入CPU了
*/
void CountSensor_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//开启AFIO的时钟,AFIO可以同上的APB2
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
//EXTI和NVIC两个外设的时钟一直打开所以不需要重复开启,NVIC是内核的外设都不需要开启时钟
GPIO_InitTypeDef GPIO_Structure;
//手册处有建议引脚的输出建议
GPIO_Structure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Structure.GPIO_Pin = GPIO_Pin_14;
GPIO_Structure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_Structure);
//AFIO配置完成,这样的PB14就可以通过AFIO进入后级EXTI电路了
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
EXTI_InitTypeDef EXTI_Structure;
//将EXTI的第14线路配置为中断模式,下降沿触发然后开启中断
EXTI_Structure.EXTI_Line = EXTI_Line14;
EXTI_Structure.EXTI_LineCmd = ENABLE;
EXTI_Structure.EXTI_Mode = EXTI_Mode_Interrupt;
//这里应该是EXTITrigger_TypeDef里面的参数
EXTI_Structure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_Structure);
//中断方式在主函数只需执行一次就好,如果是在模块内分组要确保模块内的要一样
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_Structure;
//指定通道,我们使用的是stm32_md,所以只需在这里面查找
NVIC_Structure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_Structure.NVIC_IRQChannelCmd = ENABLE;
//抢占优先级(我们选择的是2,4-2的所以两个优先级的范围都是(0~3)
NVIC_Structure.NVIC_IRQChannelPreemptionPriority = 1;
//响应优先级
NVIC_Structure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_Structure);
}
//中断函数无需声明,自动调用
//stm32跳转的中断函数的函数名是固定的,可以在启动文件(startup_stm32f10x_md.s)中查找,中断函数都是无参无返回值的
void EXTI15_10_IRQHandler(void){
//中断标志源判断,确保是我们想要的中断源触发的这个函数
if (EXTI_GetITStatus(EXTI_Line14)==SET){
//每次都要清除中断标识位,否则如果为1,它就会一直申请中断
CountSensor_Count++;
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
//用于返回次数
uint16_t CountSensor_Get(void){
return CountSensor_Count;
}
10.2.3红外传感器函数声明CountSensor.h
#ifndef __COUNT_SENSOR_H
#define __COUNT_SENSOR_H
void CountSensor_Init(void);
uint16_t CountSensor_Get(void);
#endif
10.3不计数的原因
- 面板板未按实,PB14的引脚与面包板接触不良
- 代码问题,仔细检测
11.旋转编码器计次(操作)
11.1线路图
11.2代码编写
11.2.1主程序main.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_ShowNum(1,5,num,6);
}
}
11.2.2编码器函数定义Encoder.c
#include "stm32f10x.h" // Device header
int16_t Encoder_Count;
void Encoder_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitTypeDef GPIO_Structure;
GPIO_Structure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Structure.GPIO_Pin = GPIO_Pin_0 |GPIO_Pin_1;
GPIO_Structure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_Structure);
//中断引脚选择(PB0和PB1)
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
//把指定的中断线改为EXTI_Line0或EXTI_Line1,这样0和1线路都初始化为中断模式,下降沿触发
EXTI_InitTypeDef EXTI_Structure;
EXTI_Structure.EXTI_Line = EXTI_Line0 | EXTI_Line1;
EXTI_Structure.EXTI_LineCmd = ENABLE;
EXTI_Structure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_Structure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_Structure);
//中断分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_Structure;
NVIC_Structure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_Structure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Structure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_Structure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_Structure);
NVIC_Structure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_Structure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Structure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_Structure.NVIC_IRQChannelSubPriority = 2;//把响应优先级设低一点
NVIC_Init(&NVIC_Structure);
}
//定义函数返回变量
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_1)==0){
//反转就减少(方向不是绝对的,是相对的)
Encoder_Count--;
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
void EXTI1_IRQHandler(void){
if(EXTI_GetITStatus(EXTI_Line1)==SET){
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0)==0){
Encoder_Count++;
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
11.2.3编码器函数声明Encoder.h
#ifndef __ENCODER_H
#define __ENCODER_H
void Encoder_Init(void);
int16_t Encoder_Get(void);
#endif
12.总结
12.1中断实现步骤:
第一步:配置RCC把涉及到的外设的时钟全部都打开
第二步:配置GPIO,选择我们的端口为输入模式
第三步:配置AFIO,选择我们用的这一路的GPIO,连接到后面的EXTI
第四步:配置EXTI,选择边沿触发方式,比如上升沿、下降沿或者双边沿,还有选择触发响应方式,可以选择中断响应和事件响应
第五步:配置NVIC,给我们这个中断选择一个合适的优先级
最后,通过NVIC,外部中断信号就能进入CPU了
12.2中断注意事项
- 中断函数就是要快速结束的,否则会影响主程序的运行
- 外部硬件没有进入中断时的现场保护,所以中断返回会出问题