文章目录
一、中断
1.1中断系统
- 中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
- 中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应
- 更加紧急的中断源中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
1.2中断执行流程
1.3STM32中断
- 68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设
- 使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级
二、NVIC基本结构
2.1 NVIC优先级分组
- NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级
- 抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队
三、EXTI简介
- EXTI(Extern Interrupt)外部中断
- EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
- 支持的触发方式:上升沿/下降沿/双边沿/软件触发
- 支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断
- 通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
- 触发响应方式:中断响应/事件响应
3.1EXTI基本结构
这张图展示了 STM32 微控制器中 EXTI(External Interrupt/Event Controller,外部中断 / 事件控制器)的基本结构,以下是对图中各部分的详细讲解:
-
左侧 GPIO 部分
GPIOA、GPIOB、GPIOC 等: 代表 STM32 芯片的通用输入 / 输出端口。每个 GPIO 端口包含 16 个引脚,可作为外部中断的输入源。例如,当外部有一个电平变化的信号连接到 GPIO 引脚时,就有可能触发相应的中断。这些端口可以灵活配置为输入、输出等不同模式,用于连接各种外部设备,像按键、传感器等,当这些外部设备状态改变时,能通过 GPIO 引脚传递信号给 EXTI。
-
中间 AFIO 部分
AFIO(Alternate - Function Input/Output,复用功能输入 / 输出) : ==AFIO 的主要作用是中断引脚选择。==由于 GPIO 引脚众多,AFIO 负责将特定的 GPIO 引脚与 EXTI 的中断线进行映射关联。比如,==你可以通过 AFIO 配置,指定 GPIOA 的某一个引脚对应 EXTI 的某一条中断线,==实现引脚功能的灵活分配,使得不同的 GPIO 引脚能根据需求触发相应的外部中断。
-
中间 EXTI 部分
EXTI(External Interrupt/Event Controller,外部中断 / 事件控制器) :
边沿检测及控制:==EXTI 的核心功能模块,负责检测输入信号的电平变化边沿,可配置为上升沿触发、下降沿触发或者双边沿触发。==当检测到符合配置条件的边沿变化时,就会触发相应的中断或事件。例如,若配置为下降沿触发,当连接到 EXTI 的引脚电平从高电平跳变到低电平时,就会产生中断请求。
中断线: 图中列出了多条中断线,如 EXTI0 - EXTI15 等,以及一些组合中断线(如 EXTI9_5、 EXTI15_1) 。不同的中断线可以连接不同的输入源(GPIO 引脚或其他外设),每个中断线对应一个独立的中断请求通道,可单独配置触发方式等参数。
连接其他外设:除了 GPIO 引脚,EXTI 还可以连接 PVD(Power - Voltage Detector,电源电压检测器 )、RTC(Real - Time Clock,实时时钟 )、USB、ETH(Ethernet,以太网 )等其他外设。当这些外设出现特定事件时(如 PVD 检测到电压异常、RTC 到达设定时间等 ),也能通过 EXTI 触发中断,通知 CPU 进行处理。 -
右侧 NVIC 部分
NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器) : STM32 芯片内部用于管理中断的核心部件。当 EXTI 检测到符合条件的中断事件并发出中断请求后,NVIC 会根据预先配置的中断优先级(包括抢占优先级和子优先级 ),决定是否响应这个中断请求。如果满足响应条件,NVIC 会暂停当前正在执行的程序,保存现场,然后跳转到对应的中断服务函数去执行,处理完中断事件后再恢复之前的程序执行。
3.2 AFIO复用IO口
- AFIO主要用于引脚复用功能的选择和重定义
- 在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择
3.3EXTI框图
四、旋转编码器
4.1简介
4.2硬件电路
五、练习
5.1对射式红外传感器计次
5.1.1接线图
5.1.2代码
main.c
#include "stm32f10x.h" // 包含STM32F10x系列设备的头文件
#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);
}
}
CountSensor.c
#include "stm32f10x.h" // 包含STM32F10x系列设备的标准外设库头文件
// 全局变量:传感器计数值
uint16_t CountSensor_Count;
/**
* @brief 初始化计数传感器(外部中断方式)
* @param 无
* @retval 无
* @note 配置PB14为上拉输入,下降沿触发中断
*/
void CountSensor_Init(void)
{
// 使能GPIOB和AFIO外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// 配置PB14为上拉输入模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输入模式下速度设置无效
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 将PB14连接到外部中断线14
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
// 配置外部中断
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Line = EXTI_Line14; // 选择中断线14
EXTI_InitStruct.EXTI_LineCmd = ENABLE; // 使能中断线
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
EXTI_Init(&EXTI_InitStruct);
// 配置NVIC中断优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 抢占优先级2位,子优先级2位
// 配置NVIC中断
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn; // 中断线10-15共用一个中断向量
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级1
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; // 子优先级1
NVIC_Init(&NVIC_InitStruct);
}
/**
* @brief 获取当前计数值
* @param 无
* @retval uint16_t: 当前计数值
*/
uint16_t CountSensor_Get(void)
{
return CountSensor_Count;
}
/**
- @brief 外部中断10-15服务函数
- @param 无
- @retval 无
*/
void EXTI15_10_IRQHandler(void)
{
// 检查是否是LINE14中断触发
if(EXTI_GetITStatus(EXTI_Line14) == SET)
{
// 计数值加1
CountSensor_Count++;
// 清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
- 时钟使能:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); 使能 GPIOB 端口的时钟,因为要使用 GPIOB 的引脚 PB14,所以需要先使能其时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); 使能 AFIO(复用功能输入 / 输出)时钟,AFIO 用于将 GPIO 引脚映射到外部中断线等功能,这里配置 PB14 引脚连接外部中断线需要用到 AFIO 功能,所以也要使能其时钟。
- GPIO 配置:
定义GPIO_InitTypeDef结构体变量GPIO_InitStructure ,并配置其成员。
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 将 PB14 配置为上拉输入模式,上拉输入模式下,引脚默认被拉高到高电平,当外部信号拉低引脚时可以检测到电平变化,适合用于连接传感器等需要检测电平下降的场景。
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 虽然设置了速度为 50MHz,但在输入模式下速度设置实际上是无效的。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; 指定要配置的引脚为 PB14。
GPIO_Init(GPIOB, &GPIO_InitStructure); 调用GPIO_Init函数,根据上述配置初始化 GPIOB 端口的 PB14 引脚。
- 外部中断线映射:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14); 将 PB14 引脚连接到外部中断线 14,通过 AFIO 功能实现这种映射关系。
- 外部中断配置:
定义EXTI_InitTypeDef结构体变量EXTI_InitStruct ,并配置其成员。
EXTI_InitStruct.EXTI_Line = EXTI_Line14; 选择外部中断线 14,即指定这个配置是针对 PB14 引脚对应的中断线。
EXTI_InitStruct.EXTI_LineCmd = ENABLE; 使能外部中断线 14,只有使能后,这条中断线才能响应中断事件。
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; 设置为中断模式,即当满足条件时产生中断请求。
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; 设置触发方式为下降沿触发,意味着当 PB14 引脚出现从高电平到低电平的变化时,会触发中断。
EXTI_Init(&EXTI_InitStruct); 调用EXTI_Init函数,根据上述配置初始化外部中断。
NVIC 中断优先级分组配置:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 将 NVIC 中断优先级分组设置为 2,即抢占优先级占用 2 位,子优先级占用 2 位。这种分组方式决定了后续配置中断优先级时抢占优先级和子优先级的位数分配。
- NVIC 中断配置:
定义NVIC_InitTypeDef结构体变量NVIC_InitStruct ,并配置其成员。
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn; 因为外部中断线 10 - 15 共用一个中断向量,所以这里指定中断通道为EXTI15_10_IRQn 。
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; 使能这个中断通道,允许其产生中断请求。
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; 设置抢占优先级为 1,抢占优先级高的中断可以打断抢占优先级低的中断正在执行的中断服务函数。
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; 设置子优先级为 1,当抢占优先级相同时,子优先级高的中断优先得到处理。
NVIC_Init(&NVIC_InitStruct); 调用NVIC_Init函数,根据上述配置初始化 NVIC 中断。
5.1.3效果
原理:
电平检测与计次:对射式红外传感器由发射端和接收端组成 。无物体遮挡时,接收端输出高电平;当有物体遮挡红外线,接收端接收不到红外线,输出变为低电平。代码中配置 PB14 引脚为上拉输入、下降沿触发中断,当传感器输出电平从高变低(即物体遮挡红外线 )时,触发外部中断。在中断服务函数EXTI15_10_IRQHandler 中,计次变量CountSensor_Count 会自动加 1 ,实现对物体遮挡次数的计数。
计数值获取:
可通过调用CountSensor_Get函数获取当前的计数值。比如可将计数值通过串口打印输出到调试助手软件 ,或者在连接 OLED 显示屏等设备时,将计数值实时显示出来,方便直观观察计数结果。
结果:
每有一个物体遮挡一次对射式红外传感器,计数值就准确增加 1 ,计次结果与实际遮挡次数完全一致 。比如,拿一个物体在传感器前遮挡 10 次,最终获取到的计数值就是 10 。
5.2旋转编码器计次
5.2.1接线图
5.2.2代码
Encoder.c
#include "stm32f10x.h" // 包含STM32F10x系列微控制器的标准外设库头文件
uint16_t Encoder_Count; // 编码器计数值,用于记录编码器旋转步数
/**
* @brief 初始化编码器接口
* @param 无
* @retval 无
*/
void Encoder_Init(void)
{
// 使能GPIOB和AFIO外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// 配置PB0和PB1为上拉输入模式,用于连接编码器A/B相
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_0;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 将PB0和PB1连接到外部中断线
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
// 配置外部中断
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Line = EXTI_Line0 | EXTI_Line1; // 配置中断线0和1
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
EXTI_Init(&EXTI_InitStruct);
// 配置NVIC中断优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 配置EXTI0中断(对应PB0)
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
// 配置EXTI1中断(对应PB1)
NVIC_InitStruct.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2; // 子优先级低于EXTI0
NVIC_Init(&NVIC_InitStruct);
}
/**
* @brief 获取编码器计数值并清零
* @param 无
* @retval 当前计数值(获取后会清零)
*/
uint16_t Encoder_Get(void)
{
uint16_t Temp;
Temp = Encoder_Count; // 保存当前计数值
Encoder_Count = 0; // 清零计数值,准备下一次计数
return Temp; // 返回保存的计数值
}
/**
* @brief EXTI0中断服务函数(编码器A相触发)
* @param 无
* @retval 无
*/
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) == SET) // 检查中断线0是否触发
{
// 根据编码器B相电平判断旋转方向
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
Encoder_Count--; // B相为低,编码器逆时针旋转,计数值减1
}
EXTI_ClearITPendingBit(EXTI_Line0); // 清除中断标志位
}
}
/**
* @brief EXTI1中断服务函数(编码器B相触发)
* @param 无
* @retval 无
*/
void EXTI1_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line1) == SET) // 检查中断线1是否触发
{
// 根据编码器A相电平判断旋转方向
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
{
Encoder_Count++; // A相为低,编码器顺时针旋转,计数值加1
}
EXTI_ClearITPendingBit(EXTI_Line1); // 清除中断标志位
}
}
代码功能说明:
编码器接口初始化:
- 配置 PB0 和 PB1 为上拉输入模式,连接编码器 A/B 相
- 设置下降沿触发中断,用于检测编码器信号变化
- 配置中断优先级,确保编码器信号能被及时响应
计数值获取:
- Encoder_Get() 函数返回当前计数值并清零,适用于需要获取增量值的场景
- 例如:可用于计算单位时间内的旋转步数,从而得到转速
中断服务函数:
- EXTI0_IRQHandler():处理 PB0(编码器 A 相)的中断
- EXTI1_IRQHandler():处理 PB1(编码器 B 相)的中断
- 通过检测另一相的电平状态来判断旋转方向,实现正交解码
方向判断逻辑:
- 当 A 相出现下降沿时,如果 B 相为低电平 → 逆时针旋转
- 当 B 相出现下降沿时,如果 A 相为低电平 → 顺时针旋转
- 这种判断方法利用了正交编码器 A/B 两相的相位关系
5.2.3效果
- 编码器顺时针旋转,计数值增加
- 编码器逆时针旋转,计数值减小