文章目录
- 中断系统
- NVIC简介
- NVIC基本结构
- NVIC优先级分组
- EXTI外部中断
- EXIT基本结构
- AFIO复用IO口
- EXTI内部框图
- AFIO / EXTI / NVIC 相关函数
- AFIO相关函数
- EXTI相关函数
- NVIC相关函数
- 旋转编码器简介
- 对射式红外传感器计次
- 接线图
- CountSensor(传感器)驱动程序封装
- main.c 源程序
- 旋转编码器计次
- 接线图
- Encoder驱动程序封装
- main.c 源程序
中断系统
中断的定义:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
中断执行流程
NVIC简介
在STM32中,NVIC(Nested Vectored Interrupt Controller)是一个中断控制器,负责管理和处理微控制器的中断
NVIC是一个内核外设,是CPU的小助手
它允许多个中断源以优先级方式响应,使得系统能够快速响应高优先级的中断请求,NVIC支持嵌套中断,即高优先级的中断可以打断低优先级的中断
NVIC的主要功能包括:
- 中断向量表:存储所有中断的服务程序地址
- 中断优先级管理:允许为不同中断分配不同的优先级,以控制中断的响应顺序
- 中断使能和禁用:可以启用或禁用特定的中断
- 中断触发方式:支持多种中断触发方式,如上升沿、下降沿等
通过NVIC,STM32能够高效地处理实时事件,提高系统的响应速度和性能
一个形象的比喻:CPU是急诊室的医生,中断源 是需要就诊的病人,NVIC就是医生助手,负责安排病人就诊顺序,如果没有小助手那么医生就既需要看病又需要给病人排队,降低了看病效率,但是有小助手负责根据病人的优先程度排队,医生就只需要负责看病即可
NVIC基本结构
STM32有68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设
使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级
NVIC优先级分组
- NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级
- 抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队
分组方式 | 抢占优先级 | 响应优先级 |
---|---|---|
分组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 |
EXTI外部中断
EXTI(External Interrupt/Event Controller)是STM32微控制器中的一个模块,用于处理外部中断和事件,它允许外部信号(如按钮、传感器等)触发中断,从而实现对外部事件的快速响应
- EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
- 支持的触发方式:上升沿 / 下降沿 / 双边沿 / 软件触发
- 支持的GPIO口:所有 GPIO 口,但相同的 Pin 不能同时触发中断(eg. PA1 和 PB1 不能同时触发中断)
- 通道数:16 个 GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
- 触发响应方式:中断响应 / 事件响应
EXIT基本结构
【AFIO说明】
之前介绍过,EXTI模块只有16个GPIO的通道,但是每个GPIO外设都有16个引脚,所以在这里会有一个AFIO中断引脚选择的电路模块
AFIO就是一个数据选择器,它可以在前面的GPIO外设的16个引脚里选择一个连接到后面的EXTI通道里,这就是 “所有的GPIO 口都能触发中断
但是相同的Pin不能同时触发中断” 的原因
AFIO复用IO口
“复用”指的是将同一个输入/输出(I/O)引脚用于多种不同的功能或用途
- AFIO主要用于引脚复用功能的选择和重定义(也就是数据选择器的作用)
- 在STM32中,AFIO主要完成两个任务
- 复用功能引脚重映射
- 中断引脚选择
EXTI内部框图
AFIO / EXTI / NVIC 相关函数
AFIO相关函数
//AFIO 没有专门的库函数文件,它的库函数和 GPIO 在一个文件中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的数据选择器,选择想要的中断引脚(这个函数虽然是GPIO开头,但是里面操作的AFIO寄存器)第一个参数:GPIO_Portsource是选择某个GPIO外设作为外部中断源第二个参数:GPIO_Pinsource是指定要配置的外部中断线
*/void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);
//和以太网有关(我们的芯片没有以太网外设,所以用不到)
EXTI相关函数
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);
void EXTI_ClearFlag(uint32_t EXTI_Line);ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
//用来获取EXTI的中断标志位状态,如果EXTI线有中断发生函数返回“SET”否则返回“RESET”void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
//用于清除中断标志位,以便下一次中断能够触发(每次中断程序结束后都应该清除一下中断标志位)
NVIC相关函数
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
//用于中断分组,参数是中断分组的方式
//(在配置中断之前,要先指定一下中断分组)void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
//根据结构体里面指定的参数初始化NVICvoid NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);
//用于设置中断向量表void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);
//系统低功耗配置
旋转编码器简介
旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向
类型:机械触点式/霍尔传感器式/光栅式
对射式红外传感器计次
接线图
CountSensor(传感器)驱动程序封装
CountSensor.c
#include "stm32f10x.h" // Device headeruint16_t CountSensor_Count;void CountSensor_Init(void){ //Step1:开启始时钟 //RCC开启始时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //开启 AFIO 时钟,AFIO 也是 APB2 的外设,RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //EXTI 和 NVIC 两个外设的时钟一直是打开的,不需要再开启时钟了//Step2:配置GPIOGPIO_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);//Step3:配置AFIO//配置AFIO外部中断引脚选择GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//执行完后AFIO的第14个数据选择器就拨好了//这样PB14号引脚的电平信号就可以顺利通过AFIO进入到EXTI电路了//Step4:配置EXTIEXTI_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);//Step5:配置NVICNVIC_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);
}//中断函数
//在STM32中,中断函数的名字都是固定的,每个中断通道都对应一个中断函数
//中断函数的名字可以参考启动文件
void EXTI15_10_IRQHandler(void){//由于这个函数 EXTI10 到 EXTI15 都能进,所以要先判断一下是不是我们想要的 EXTI14if (EXTI_GetITStatus(EXTI_Line14) == SET){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0){CountSensor_Count++;} EXTI_ClearITPendingBit(EXTI_Line14);}
}uint16_t CountSensor_Get(void){return CountSensor_Count;
}
CountSensor.h
#ifndef __COUNT_SENSOR_H
#define __COUNT_SENSOR_Hvoid CountSensor_Init(void);
uint16_t CountSensor_Get(void);#endif
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);}
}
旋转编码器计次
接线图
Encoder驱动程序封装
Encoder.c
#include "stm32f10x.h" // Device headerint16_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;
}//在A相下降沿和B相低电平时才判断为反转,Encoder_Count--
void EXTI0_IRQHandler(void){if(EXTI_GetITStatus(EXTI_Line0) == SET){if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0){Encoder_Count--;}EXTI_ClearITPendingBit(EXTI_Line0);}}//在B相下降沿和A相低电平时才判断为正转,Encoder_Count++
void EXTI1_IRQHandler(void){if(EXTI_GetITStatus(EXTI_Line1) == SET){if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0){Encoder_Count++;}EXTI_ClearITPendingBit(EXTI_Line1);}
}
Encoder.h
#ifndef __ENCODER_H
#define __ENCODER_Hvoid Encoder_Init(void);
int16_t Encoder_Get(void);#endif
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_ShowSignedNum(1, 5, Num, 5);}
}
STM32 专栏文章均参考 《STM32入门教程-2023版 细致讲解 中文字幕》教程视频