【STM32】江科大学习笔记——EXTI外部中断

该笔记基于B站江科大视频内容结合自己的理解进行编写,如有错误,请指正。视频结合笔记共同食用,效果更佳哦~



中断系统

B 站空降链接

中断,指在程序运行过程中,出现了特定的中断触发条件(也称为中断源,比如指定 GPIO 口发生电平跳变、定时器时间到了、串口接收到数据),事件紧急需要立即处理,使得 CPU 暂停当前正在运行的程序,转而去执行中断程序,执行完中断程序后再返回到上一个正在执行的程序暂停的位置继续执行。被中断的程序可以是主程序,也可以是中断程序。

在这里插入图片描述

中断优先级,指的是有多个中断源同时申请中断时,CPU 会根据中断源的轻重缓急进行裁决,优先响应更紧急的中断源。中断优先级可以在程序开发中根据需求自行设置。

中断嵌套,指的是当一个中断程序正在执行时,出现了更高优先级的中断源申请中断,CPU 会暂停当前的中断程序,转而执行新的中断程序,执行完再返回原先的中断程序继续执行。

在这里插入图片描述

中断系统,指的是管理和执行中断的逻辑结构。如果没有中断系统,为了防止紧急事件不被忽略,及时处理,只能在主程序中不断查询事件是否发生,占用不必要的资源,还会降低开发和执行效率。

int main(void)
{
	while(1)
	{
		// 主程序
	}
}

void EXTI0_IRQHandler(void)
{
	// 中断程序
}

带有中断程序的代码如上面所示,正常情况下,CPU 会在主程序中不断循环执行,当中断源申请中断成功后,主程序就会暂停,自动跳转到中断程序 EXTI0_IRPHandler 中执行,中断程序执行完后再返回主程序中执行。中断程序不需要主动调用,当发生中断时,硬件会自动调用。

STM32 中断

B 站空降链接

博主认为在视频中了解一下即可,不需要进行记录。

NVIC 基本结构

B 站空降链接

在这里插入图片描述
NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器)在 STM32 中负责统一分配中断优先级和管理中断。NVIC 是一个内核外设,由于 STM32 具有众多的中断通道,若直接连接到 CPU,会导致 CPU 框架设计困难,且当同时出现多个中断同时申请时或者中断产生拥堵,将给主要承担运算任务的 CPU 带来极大负担,基于这个需求,NVIC 应运而生。

NVIC 支持多种外设的中断输入通道,包括 EXTI、TIM、ADC、USART等。图中箭头线条上的斜杠和 n 表示该外设可能会同时占用多个中断通道。

NVIC 只有一个输出通道,NVIC 会根据每个中断的优先级分配中断的先后顺序,然后通过输出通道告知 CPU 应该执行哪个中断程序,CPU 无需关注中断执行顺序,只需专注于运算任务。

NVIC 优先级分组

B 站空降链接

为了处理不同形式的优先级,STM32 的 NVIC 可以对优先级进行分组。中断优先级分为抢占优先级和响应优先级。抢占优先级和响应优先级的区别可以通过看病的例子进行理解:

  • 响应优先级:当多位病人排队就诊时,若有更紧急患者需要插队,则等待当前就诊完成后开始就诊。
  • 抢占优先级:若出现极其紧急的情况,医生会立即中止当前诊疗,优先处理新患者,处理完新患者再继续处理上一个患者。这种情况也就是前文提到的中断嵌套。

NVIC 为每个中断通道都分配了一个 4 位的中断优先级寄存器(IPR),4 位二进制能表示的数值范围为 [ 0 , 15 ] [0, 15] [0,15],对应 16 个优先级,数值越小优先级越高。然而单纯的 16 个优先级只能解决 “谁的优先级更高” 的问题,无法区分响应优先级和抢占优先级,为了解决这个问题,就需要对这 16 个优先级进行分组。

中断优先级的分组方式是将中断优先级寄存器中的 4 位划分为:高 n 位表示抢占优先级,低 4-n 位表示响应优先级。

  • 抢占优先级高的可以中断嵌套。
  • 响应优先级高的可以优先排队。
  • 抢占优先级和响应优先级均相同时,按中断号排队。(中断号可查询手册中表格的优先级一列,数值小的优先响应)
分组方式抢占优先级(n 位)响应优先级(4-n 位)
分组 00 位,取值为 04 位,取值为 [ 0 , 15 ] [0, 15] [0,15]
分组 11 位,取值位 [ 0 , 1 ] [0, 1] [0,1]3 位,取值为 [ 0 , 7 ] [0, 7] [0,7]
分组 22 位,取值位 [ 0 , 3 ] [0, 3] [0,3]2 位,取值为 [ 0 , 3 ] [0, 3] [0,3]
分组 33 位,取值位 [ 0 , 7 ] [0, 7] [0,7]1 位,取值为 [ 0 , 1 ] [0, 1] [0,1]
分组 44 位,取值位 [ 0 , 15 ] [0, 15] [0,15]3 位,取值为 0

分组方式需要在程序中进行设置,设置好分组方式后再配置优先级,需要注意优先级的取值范围。

EXTI 简介

B 站空降链接

EXTI(Extern Interrupt,外部中断)是众多可产生中断的外设之一,能够监听指定 GPIO 口的电平变化,当检测到指定 GPIO 口电平信号变化时,EXTI 将立即向 NVIC 发送中断请求,经过 NVIC 裁决后即可中断 CPU 当前正在执行的程序,转而执行中断源对应的中断程序。

EXTI 支持的触发方式:

  • 上升沿触发(低电平到高电平)
  • 下降沿触发(高电平到低电平)
  • 双边沿触发(上升沿和下降沿都支持)
  • 软件触发(代码触发)

EXTI 支持所有的 GPIO 口,但不同端口相同编号的口不能同时触发中断,比如 PA1 和 PB1 不能同时触发中断。

EXTI 支持的通道:

  • 16 路 GPIO 通道
  • PVD 输出
  • RTC 闹钟
  • USB 唤醒
  • 以太网唤醒

EXTI 支持的触发响应方式:

  • 中断响应
  • 事件响应

EXTI 基本结构

B 站空降链接

在这里插入图片描述
根据上图分析,EXTI 的输入源分为 2 类:GPIO 引脚输入、外设事件输入。

首先在 GPIO 引脚输入方面,中断触发信号来自 GPIOA、GPIOB、GPIOC 等 GPIO 端口,虽然每个 GPIO 端口包含 16 个引脚,但 EXTI 只有 16 条中断线(EXTI0、EXTI1…EXTI15),无法与所有 GPIO 引脚一一对应。此时就需要有个角色负责从相同 Pin 号的 GPIO 引脚中选择一个与 EXTI 进行连接,而这个角色就是 AFIO。16 条 EXTI 线分别对应一个 Pin 号,比如 EXTI0 只接收 Pin 号为 0 的信号。AFIO 负责从相同 Pin 号的 GPIO 引脚中选择一个与 EXTI 进行连接,这也是为什么相同 Pin 不能同时触发中断的原因。

经过 AFIO 选择后的 16 个 GPIO 通道与 PVD、RTC、USB、ETH 这 4 个外设共同连接到 EXTI 边缘检测及控制电路上,形成了 EXTI 的 20 个输入信号。经过 EXTI 电路后输出的信号分为 2 种,一部分输出接到 NVIC 用于触发中断,需要注意的是,虽然存在 20 路输入信号,但可能 ST 公司觉得占用太多 NVIC 的通道资源,所以将 EXTI5 到 EXTI9 合并为一个通道 EXTI9_5,EXTI10 到 EXTI15 合并为一个通道 EXTI15_10,所以 EXTI5 到 EXTI9 会触发同一个中断函数,EXTI10 到 EXTI15 会触发同一个中断函数,编程时可以在中断函数中根据中断标志位判断是否是计划中的 GPIO 引脚触发中断。另一部分输出则连接到其他外设,共 20 路用于触发事件响应。

以上就是 EXTI 基本的工作流程。

AFIO 内部电路

B 站空降链接

AFIO(Alternate Function Input/Output)主要用于引脚复用功能的选择和重定义,在 STM32 中便是负责复用功能引脚重映射、中断引脚选择。

其中复用功能引脚重映射指的是将指定外设的信号线从默认引脚整体搬到备用引脚。比如定时器信号线 TIM2_CH4,通过查询引脚定义表可知其位于引脚 PA3 的默认复用功能中,同时还位于引脚 PB11 的重定义功能中,所以信号线 TIM2_CH4 的默认引脚为 PA3,备用引脚为 PB11。当 PA3 被占用而需使用 TIM2_CH4,可通过 AFIO 将该信号线重映射到空闲的 PB11。

AFIO 选择中断引脚结构图
上图是 AFIO 选择中断引脚的结构图。图中每个梯形符号代表一个数据选择器。以第一个引脚选择为例,PA0 到 PG0 通过数据选择器进行选择,该过程在程序中实现,最终只有一个引脚连接到 EXTI0。可以通过在 AFIO_EXTICR1 寄存器中配置 EXTI0[3:0] 即可决定选择哪个 GPIO 引脚作为输入源。

EXTI 内部电路

B 站空降链接

外部中断/事件控制器框图
从右下角的 20 根输入线开始,电平信号首先进入边沿检测电路,边缘检测通过配置上升沿触发选择寄存器和下降沿触发选择寄存器,选择上升沿触发、下降沿触发或双边沿触发,接着触发信号进入到或门的输入端。

或门
图中月牙形符号代表或门。它具有多个输入端和单个输出端。执行的是 “或” 的逻辑,只要任一输入为高电平 1,则输出就是高电平 1;仅当所有输入都为低电平 0 时,才会输出低电平 0。

与门
图中半圆状符号代表与门,同样具有多个输入端和单个输出端。任一输入端为低电平 0,则输出为低电平 0;仅当所有输入为高电平 1 才会输出高电平 1。

除硬件中断触发,软件中断事件寄存器也接到或门的输入端,只要硬件输入和软件输入任意一个为 1,则或门输出 1。

通过或门后,触发信号便分成 2 路,通往 NVIC 中断控制器则触发中断,通往脉冲发生器则触发事件。

触发中断首先会设置触发中断的外设通道对应的中断挂起寄存器,用于标记中断,程序中可以读取该寄存器判断是哪个通道触发的中断。若中断挂起寄存器置为 1,则与中断屏蔽寄存器一起输入与门,然后进入 NVIC 中断控制器。这里的中断屏蔽寄存器相当于一个开关,因为如果中断屏蔽寄存器输出 1,则请求挂起寄存器的输出直接进入 NVIC 中断控制器;如果中断屏蔽器输出 0,则无论请求挂起寄存器输出什么,与门都是输出 0,无法中断。

触发事件的路线首先也是有一个事件屏蔽寄存器进行开关控制,最后通过一个脉冲发生器进入其他外设。脉冲发生器就是提供一个电平脉冲,用来触发其他外设的动作。

旋转编码器

B 站空降链接

哪些设备适合使用外部中断?外部中断的优势是什么?

课程大概总结了使用外部中断模块的特性。对于 STM32 来说,想要获取的信号是外部驱动的突发信号,比如旋转编码器的输出信号,在静止状态下,STM32 无需响应;但当旋转发生时,会突然产生大量需要及时处理的脉冲波形。这类信号具有 3 个典型特征:

  • 突发性:由外部主动触发。
  • 被动性:STM32 只能被动接收。
  • 时效性:信号速度很快,不及时处理会丢失信号。

对于这种情况就可以考虑使用外部中断了。

同理,红外遥控接收头的输出信号也很适合使用外部中断。当接收到遥控指令时,接收头会输出持续时间极短的波形数据,必须快速捕获。

需要注意的是,虽然按键也属于外部触发事件,但通常不建议采用外部中断处理。主要原因有两点:

  • 抖动问题:外部中断难以有效处理按键抖动。
  • 检测时机:难以准确判断按键释放时刻。

对于按键而言,更推荐以下两种方案:

  • 在主循环中轮询检测,因其输出信号并不是转瞬即逝的,适用于实时性要求不高的场景。
  • 采用定时器中断检测,既能避免阻塞主程序,又能妥善处理抖动和松手检测问题。

说了这么多,再来介绍以下本章实验的核心模块——旋转编码器。

旋转编码器是一种用于精确测量旋转位置、速度和方向的传感器。当其旋转轴旋转时,编码器会输出与转速和转向对应的方波信号,通过分析这些信号的频率和相位差,即可准确计算出旋转轴的运动状态。

常见的旋转编码器主要分为三种类型:机械触点式、霍尔传感器式、光栅式。

下图是一个光栅式旋转编码器:

在这里插入图片描述
光栅式旋转编码器是一种结构简单的转速测量装置,其核心部件由对射式红外传感器和光栅编码盘组成。工作原理如下:当编码盘旋转时,红外传感器会周期性地被遮挡和透光,从而产生高低电平交替的方波信号。通过统计方波脉冲数量可以计算旋转角度,分析方波频率则可获得转速数据。这样就可以采用外部中断方式捕获方波边沿信号,实现位置和速度的精确测量。但受限于单路输出的设计,该模块无法通过波形相位判断旋转方向,因此仅适用于单向转速和位置检测场景。

下图是本次实验用到的机械触点式旋转编码器:

在这里插入图片描述
在这里插入图片描述
如图所示,金属触点分为左右两组,每组包含细、粗两种规格触点。两边细触点根据蓝线连接到引脚 C,粗触点根据黄线分别与引脚 A 和 B连接,这就是触点的连接方式。中间的圆形金属片作为按键使用,按压时导通,松开即断开。编码盘的结构设计与光栅式编码盘类似,只是金属触点替代了光栅。特别需要注意的是,金属触点的布局经过精确设计,可使两侧触点的通断状态形成 90° 相位差。

当正转时 A 相引脚输出的方波和 B 相引脚输出的方波相差 90° 的波形,如下图所示:
在这里插入图片描述
当反向旋转时,B 相会提前 90°,如下图所示:
在这里插入图片描述
这样,正转和反转就就能明显区分。这种具有 90° 相位差的波形称为正交波形,采用这种输出方式的编码器能够检测方向。由于机械触点式旋转编码器的结构特性,它不适合用于电机的高速运转,通常只适用于手动调节场景,比如音响的音量控制。
总的来说,单相输出和两相正交输出的主要区别就在于此。此外,还有一种编码器采用不同的方向检测方式:一个引脚输出方波信号表示转速,另一个引脚通过高低电平来指示旋转方向。
在这里插入图片描述
上图是霍尔传感器式的旋转编码器,其编码器直接安装在电机后方,编码器中心装有圆形磁铁,磁铁边上有两个稍大的黑色方块,它们就是霍尔传感器,当磁铁旋转时,霍尔传感器能够检测磁场变化并输出正交方波信号。
在这里插入图片描述
上图是独立的编码器元件,它的输入轴转动时,输出就会有波形,可以测速和方向,具体用法需查看对应手册。

机械触点式旋转编码器电路

B 站空降链接

在这里插入图片描述

在这里插入图片描述
左图展示了机械触点式旋转编码器的内部电路结构。矩形区域上方为旋转轴按钮的电路(本章实验无需使用),左下角和右下角分别设有编码器的两个触点。旋转轴旋转时,这两个触点以 90° 的相位差交替导通,由于这两个触点仅作为开关使用,需配合外围电路才能输出高低电平。

先看下左边的外围电路,R1 是一个 10K 的上拉电阻,默认在没旋转的情况下,触点断开,VCC 流经 R1 和 R3,从 A 输出高电平;当旋转时触点导通,A 经 R3 与中间的 GND 连接,A 口就输出低电平了。R3 是一个输出限流电阻,防止模块引脚电流过大,C1 是输出滤波电阻,可以防止一些输出信后抖动。右边外围电路与左边原理相同,不过多赘述。

右图是在使用该模块时的接线图,A 和 B 的引脚可以自行调整,但由于实验使用外部中断,所以 A 和 B 连接到 STM32 的引脚 Pin 号不要相同,C 口本次实验无需使用。

实验:对射式红外传感器计次

B 站空降链接

实验材料

  • STM32F103C8T6
  • 0.96 寸 4 脚OLED 显示屏
  • 对射式红外传感器
  • ST-LINK 仿真器
  • 面包板

在这里插入图片描述

按照上图进行连接,通电后用挡光片测试对射式红外传感器是否能正常工作。当挡光片置于对射式红外传感器中间时,指示灯应熄灭;移开挡光片时指示灯应重新亮起,由此可判单高低电平输出正常。

实验目标
统计物体通过对射式红外传感器的次数。

实验原理
当物体通过对射式红外传感器检测区域时,传感器的数字输出口(DO)会产生电平跳变信号。该信号将触发 STM32 微控制器的 PB14 引脚中断,在中断服务程序中对计数变量进行自增操作,随后在主程序循环中通过 OLED 显示屏实时更新显示该计数值。

核心代码

#include "stm32f10x.h"
#include "OLED.h"

uint16_t count = 0;

int main(void)
{
	OLED_Init();
	
	OLED_ShowString(1, 1, "Count:");
	
	/* 1.打开涉及到的外设的时钟,不打开时钟外设无法工作 */
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);    // 根据接线图,OLED 和对射式红外传感器都接在 GPIOB 端口,所以需要开启 GPIOB 外设的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);    // 开启 AFIO 外设的时钟
	
	// EXTI 和 NVIC 这两个外设的时钟是一直开启的,无需手动开启
	// EXTI 作为独立外设,按理应该需要手动开启时钟,但寄存器里没有 EXTI 时钟的控制位,可能是设计上的考虑,暂时不用考虑
	// NVIC 是内核外设,所以无需手动开启时钟
	
	/* 2.配置 GPIO,获取对射式红外传感器的电平输出信号 */
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;    // 对射式红外传感器的 DO 口连接 PB14 引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;    // GPIO 口作为 EXTI 输入时,可以选择浮空输入、上拉输入、下拉输入,这里选择上拉输入,默认输入高电平
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    // 不重要,选择最高速
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	/* 3.配置 AFIO,使电平信号能传输到 EXTI */
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);    // 该函数用于选择 GPIO 口作为 EXTI 输入线,虽然是 GPIO 开头,但实际操作的是 AFIO 的寄存器,执行该函数后 PB14 引脚的信号就可以通过 AFIO 与 EXTI14 中断线导通了
	
	/* 4.配置 EXTI,使中断信号能传输到 NVIC */
	
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line = EXTI_Line14;    // PB14 口对应中断线 EXTI_Line14
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;    // 指定中断线的新状态,要开启中断则设置 ENABLE
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;    // 选择中断触发模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;    // 选择下降沿触发
	EXTI_Init(&EXTI_InitStructure);
	
	/* 5.配置 NVIC */
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    // 一般来说中断不多,很难导致中断冲突,所以设置比较随意,这里选择分组 2,抢占优先级和响应优先级都比较平均。需要注意的是芯片只能选择一种分组方式,所以该函数一般整个工程只需要执行一次
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;    // 选择中断通道,由于选择了 EXTI14,而 EXTI10~EXTI15 都合并成 EXTI15_10_IRQn 这一个通道
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;    // 指定中断通道(即选择的 EXTI15_10_IRQn)是使能还是失能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;    // 指定抢占优先级,由于优先级分组选择了 NVIC_PriorityGroup_2,则两个优先级各占 2 位,则取值范围均为 [0, 3],由于本工程只有一个中断,所以设置随意
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;    // 指定响应优先级
	NVIC_Init(&NVIC_InitStructure);
	
	while(1)
	{
		/* 7.在 OLED 中显示通过对射式红外传感器的次数 */
		OLED_ShowNum(1, 7, count, 5);
	}
}

/**
  * 6.在 STM32 中,中断函数的名称都是固定的,每个中断通道对应一个中断函数
  */
void EXTI15_10_IRQHandler(void)
{
	// 一般中断函数开头都需要判断中断标志位,确保是想要的中断源触发的中断函数
	// 况且 EXTI10~EXTI15 这些通道都能触发这个中断函数,所以要判断中断信号是否是从 EXTI14 进来的
	if(EXTI_GetITStatus(EXTI_Line14) == SET)
	{
		/* 8.如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动 */
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
		{
			count++;
		}
		
		// 执行结束后一定要清除中断标志位,否则会一直申请中断,导致一直执行中断程序,进行死循环
		EXTI_ClearITPendingBit(EXTI_Line14);
	}
}

实验:旋转编码器计次

B 站空降链接

实验材料

  • STM32F103C8T6
  • 0.96 寸 4 脚OLED 显示屏
  • 旋转编码器
  • ST-LINK 仿真器
  • 面包板

在这里插入图片描述
根据上图完成模块之间的连接。

实验目标
实现旋转编码器顺时针旋转时自增,逆时针旋转时自减。

核心代码

#include "stm32f10x.h"
#include "OLED.h"

int16_t count = 0;    // 使用区分正负的整数类型进行计次,区分正反转

int main(void)
{
	OLED_Init();
	
	OLED_ShowString(1, 1, "Count:");
	
	/* 1.打开涉及到的外设的时钟,不打开时钟外设无法工作 */
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);    // 根据接线图,OLED 和旋转编码器都接在 GPIOB 端口,所以需要开启 GPIOB 外设的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);    // 开启 AFIO 外设的时钟
	
	// EXTI 和 NVIC 这两个外设的时钟是一直开启的,无需手动开启
	// EXTI 作为独立外设,按理应该需要手动开启时钟,但寄存器里没有 EXTI 时钟的控制位,可能是设计上的考虑,暂时不用考虑
	// NVIC 是内核外设,所以无需手动开启时钟
	
	/* 2.配置 GPIO,获取旋转编码器 A 相和 B 相的电平输出信号 */
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;    // 同时配置 PB0 口和 PB1 口
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;    // GPIO 口作为 EXTI 输入时,可以选择浮空输入、上拉输入、下拉输入,这里选择上拉输入,默认输入高电平
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    // 不重要,选择最高速
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	/* 3.配置 AFIO,使电平信号能传输到 EXTI */
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);    // 该函数用于选择 GPIO 口作为 EXTI 输入线,虽然是 GPIO 开头,但实际操作的是 AFIO 的寄存器,执行该函数后 PB0 引脚的信号就可以通过 AFIO 与 EXTI0 中断线导通了
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);    // 执行该函数后 PB1 引脚的信号就可以通过 AFIO 与 EXTI1 中断线导通了
	
	/**
      * 4.配置 EXTI,使中断信号能传输到 NVIC
	  * 
	  * 其实这里只配置一个 EXTI 通道也可以完成功能,因为对于输出正交波形的旋转编码器而言,
	  * 如果 A 相的下降沿触发中断,在中断函数中读取 B 相的电平,正转时 B 相是高电平,反转就是低电平,
	  * 这样就可以区分旋转方向。不过这样在操作上会存在瑕疵,因为 A 相电平跳变就触发了中断,
	  * 没有考虑到 B 相的电平是否发生跳变,所以在中断触发的时机上存在一些小瑕疵。
	  */
	
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;    // 同时配置中断线 EXTI_Line0 和 EXTI_Line1
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;    // 指定中断线的新状态,要开启中断则设置 ENABLE
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;    // 选择中断触发模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;    // 选择下降沿触发
	EXTI_Init(&EXTI_InitStructure);
	
	/* 5.配置 NVIC */
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    // 一般来说中断不多,很难导致中断冲突,所以设置比较随意,这里选择分组 2,抢占优先级和响应优先级都比较平均。需要注意的是芯片只能选择一种分组方式,所以该函数一般整个工程只需要执行一次
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;    // 选择中断通道
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;    // 指定中断通道是使能还是失能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;    // 指定抢占优先级,由于优先级分组选择了 NVIC_PriorityGroup_2,则两个优先级各占 2 位,则取值范围均为 [0, 3]
	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;    // B 相的响应优先级低一点,避免中断冲突
	NVIC_Init(&NVIC_InitStructure);
	
	while(1)
	{
		/* 8.在 OLED 中显示旋转编码器的次数 */
		OLED_ShowSignedNum(1, 7, count, 5);
	}
}

/**
  * 6.旋转编码器 A 相的中断函数
  */
void EXTI0_IRQHandler(void)
{
	// 一般中断函数开头都需要判断中断标志位,确保是想要的中断源触发的中断函数
	if(EXTI_GetITStatus(EXTI_Line0) == SET)
	{
		// 判断 B 相是否是低电平,从而判断是否是反转
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
		{
			count--;
		}
		
		// 执行结束后一定要清除中断标志位,否则会一直申请中断,导致一直执行中断程序,进行死循环
		EXTI_ClearITPendingBit(EXTI_Line0);
	}
}

/**
  * 7.旋转编码器 B 相的中断函数
  */
void EXTI1_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line1) == SET)
	{
		// 判断 A 相是否是低电平,从而判断是否是正转
		if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
		{
			count++;
		}
		
		EXTI_ClearITPendingBit(EXTI_Line1);
	}
}

注意事项

在中断函数中最好不要执行耗时时间过长的代码,要简短快速,因为中断是处理突发事件,如果程序为了处理突发事件一直在执行中断函数,主循环就会长期阻塞。

最好不要在中断函数和主函数调用相同的函数或操作同一个硬件,尤其是硬件相关的函数,比如 OLED 显示函数,如果在主函数中调用 OLED 显示函数,又在中断函数中调用,会直接导致 OLED 显示错乱甚至系统崩溃,其本质是两个执行流对同一硬件资源的无序抢占,中断破坏了硬件操作的原子性。为了避免这样的问题,最好不要在主函数或中断函数中操作可能产生冲突的硬件,在实现功能时可以在中断中操作变量或标志位,当中断结束时再对这个变量或标志位进行操作。

EXTI 库函数

B 站空降链接

提示:本节内容基于 stm32f10x_exti.h 和 stm32f10x_exti.c。

/**
  * @brief 清除 EXTI 配置,恢复成上电默认状态。
  */
void EXTI_DeInit(void);

/** 
  * @brief 根据传入结构体参数 [EXTI_InitStruct] 配置 EXTI 外设并进行初始化。 
  */
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);

/**
  * @brief 给传入结构体 [EXTI_InitStruct] 赋予默认值。
  */
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);

/**
  * @brief 用于软件触发中断。
  * @param  EXTI_Line: 指定中断线,在 EXTI_Line0 ~ EXTI_Line19 中选择。
  */
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);

/* 在外设运行中,会在状态寄存器中产生一些状态标志位,当程序想看这些标志位时就可以用以下函数。*/

/**
  * @brief 适合在主程序中获取指定中断线的事件标志位是否被置 1,即是否发生了外部事件或中断触发。
  * @retval 指定中断线的状态,[FlagStatus] 为枚举类型,值为 SET 或 RESET。
  */
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);

/**
  * @brief 适合在主程序中对清除指定中断线的事件标志位。
  */
void EXTI_ClearFlag(uint32_t EXTI_Line);

/**
  * @brief 适合在中断程序中获取指定中断线的中断标志位是否被置 1,从而判断当前中断是否由指定中断线触发。
  */
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);

/**
  * @brief 清除指定中断线的中断挂起标志位,同时会清除对应的事件标志位。只能在中断程序中处理完后调用,否则会重复触发中断。
  */
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);

NVIC 库函数

B 站空降链接

/**
  * @brief 设置中断优先级的分组,划分抢占优先级和响应优先级在优先级寄存器中的位数,即划分取值范围。
  */
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);

/**
  * @brief 初始化 NVIC。
  */
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);

/**
  * @brief 设置中断向量表。
  */
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);

/**
  * @brief 系统低功耗配置。
  */
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);

总结

以上就是视频中 5-1、5-2 所讲的关于 EXTI 外部中断的内容。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白日梦想家之练气期

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值