文章目录
- 中断系统
- 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);
//根据结构体里面指定的参数初始化NVIC
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);
//用于设置中断向量表
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);
//系统低功耗配置
旋转编码器简介
旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向
类型:机械触点式/霍尔传感器式/光栅式
对射式红外传感器计次
接线图
CountSensor(传感器)驱动程序封装
CountSensor.c
#include "stm32f10x.h" // Device header
uint16_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:配置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);
//Step3:配置AFIO
//配置AFIO外部中断引脚选择
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
//执行完后AFIO的第14个数据选择器就拨好了
//这样PB14号引脚的电平信号就可以顺利通过AFIO进入到EXTI电路了
//Step4:配置EXTI
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);
//Step5:配置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);
}
//中断函数
//在STM32中,中断函数的名字都是固定的,每个中断通道都对应一个中断函数
//中断函数的名字可以参考启动文件
void EXTI15_10_IRQHandler(void){
//由于这个函数 EXTI10 到 EXTI15 都能进,所以要先判断一下是不是我们想要的 EXTI14
if (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_H
void 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 header
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;
}
//在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_H
void 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版 细致讲解 中文字幕》教程视频