STM32学习笔记(五)EXTI外部中断(上)
在学习 GPIO 输入时,程序需要在主循环中反复读取引脚电平,才能判断按键是否按下。这种不断查询设备状态的方式称为轮询。轮询容易理解,但当程序需要同时处理多个外设时,CPU 会把大量时间花在重复查询上。
中断提供了另一种处理思路:主程序照常执行,外设在事件发生时主动通知 CPU。CPU 暂停当前任务,处理完事件后再返回主程序。本篇将从中断的基本概念开始,依次介绍 NVIC、EXTI 和旋转编码器,为后续使用外部中断读取旋转编码器做好准备。
一、中断系统
1. 什么是中断
中断是指 CPU 正在执行主程序时,某个事件满足了中断触发条件,CPU 暂停当前程序,转而处理这个事件。处理完成后,CPU 会回到主程序被暂停的位置,继续执行后面的代码。
可以把中断理解成"写作业时有人敲门":
- 正在完成的作业相当于主程序;
- 敲门声相当于中断请求;
- 产生敲门声的人相当于中断源;
- 起身开门相当于执行中断服务程序;
- 开完门后回到原来的位置继续写作业,相当于中断返回。
中断并不会让主程序彻底结束,只是让主程序暂时停下来。中断服务程序执行完成后,CPU 仍会回到原来的主程序中继续工作。
2. 为什么需要中断
如果不使用中断,CPU 通常需要在主程序中反复查询某个事件是否发生,这种方式称为轮询。例如,为了判断按键是否按下,CPU 需要不断读取按键引脚的电平状态。即使按键一直没有动作,CPU 也要重复检查。
使用中断后,CPU 平时可以执行其他任务,等到按键、定时器或串口等外设真正产生事件时,再由外设主动发出请求。这就像在家等待快递时,不需要一直站在门口查看快递员是否到达,只要听到门铃后再去开门即可。
与轮询相比,中断的主要优点有:
- 减少无意义的重复查询,提高 CPU 的利用率;
- 能够更加及时地响应突发事件;
- 便于同时管理多个外设和多个任务;
- 在合适的低功耗模式下,可以由中断事件唤醒 CPU。
不过,中断并不是轮询的完全替代品。对于变化很慢、实时性要求不高的状态,轮询可能更加简单;对于需要快速响应、发生时间不确定的事件,中断通常更加合适。
3. 中断源与中断服务程序
能够向 CPU 提出中断请求的事件或设备称为中断源。STM32 中常见的中断源包括:
- 外部引脚出现上升沿或下降沿;
- 定时器计数达到设定值;
- 串口接收到一帧数据;
- ADC 完成一次模数转换;
- DMA 完成一次数据传输;
- RTC 闹钟到达设定时间。
不同中断源通常拥有各自对应的中断服务程序。中断服务程序也叫中断处理函数,主要负责完成事件发生后必须立即处理的工作。
例如,串口接收中断发生后,中断服务程序可以读取刚刚收到的数据;定时器中断发生后,中断服务程序可以更新计数值;外部按键中断发生后,中断服务程序可以记录按键已经按下。
主程序和中断服务程序的分工可以理解为"日常工作"和"紧急处理":主程序负责持续完成系统的常规任务,中断服务程序只负责处理突然到来的重要事件。
4. 普通中断的执行流程
一次普通中断的执行过程如下:
- CPU 正在执行主程序;
- 某个中断源满足触发条件,并提出中断请求;
- CPU 执行完当前正在处理的指令;
- CPU 保存主程序当前的执行位置和必要的运行状态;
- CPU 根据中断向量找到对应的中断服务程序;
- CPU 跳转到中断服务程序并处理本次事件;
- 中断服务程序执行完成,CPU 恢复之前保存的运行状态;
- CPU 返回主程序被暂停的位置,继续执行后面的代码。
整个过程可以概括为:
text
主程序运行
↓
中断事件发生
↓
保存当前位置和运行状态
↓
执行中断服务程序
↓
恢复原来的运行状态
↓
返回主程序继续运行
从主程序的角度来看,中断就像在正常执行过程中临时插入了一小段程序。插入的中断服务程序执行完成后,主程序仍然会从原来的位置继续运行。
5. 中断向量表
STM32 中存在多个中断源,CPU 必须知道每个中断发生后应该跳转到哪个处理函数。中断向量表就是一张记录中断入口地址的表格,其中保存了各个异常和中断服务程序的入口地址。
可以把中断向量表理解成一本"紧急联系电话簿":不同中断源对应不同联系人,中断发生后,CPU 根据中断号查找相应地址,然后进入正确的中断服务程序。
工程中的启动文件不仅负责完成复位后的初始启动,也定义了中断向量表以及默认的中断处理函数。因此,编写中断程序时,中断服务函数的名称必须与启动文件中规定的名称一致,否则中断发生后 CPU 无法进入我们编写的函数。
6. 中断中的"断点"
中断发生时,CPU 需要记住主程序被暂停的位置。中断服务程序执行完成后,CPU 根据保存的位置返回主程序,否则就不知道应该从哪里继续执行。
这里所说的"断点"并不是 Keil 调试过程中手动设置的程序断点。两者虽然名称相同,但作用不同:
- 中断流程中的断点,是 CPU 为了在中断结束后返回主程序而保存的执行位置;
- Keil 中的调试断点,是开发者主动设置的暂停位置,用于观察变量、寄存器和程序运行状态。
更准确地说,中断发生时,CPU 会自动保存返回地址和部分现场信息。开发者一般不需要手动记录这些内容,但需要保证中断服务程序能够正常结束并执行中断返回。
7. 中断优先级
多个中断源可能在相同或接近的时间提出中断请求,但 CPU 在同一时刻只能执行一段程序,因此需要根据中断优先级决定先处理哪一个中断。
可以把 CPU 想象成只有一个窗口的服务台,而不同的中断请求是同时前来办理业务的人。服务台一次只能接待一个人,因此需要根据事情的紧急程度安排顺序。优先级较高的中断先得到响应,优先级较低的中断则需要等待。
例如,系统中可能同时出现定时器中断、串口接收中断和外部按键中断。此时,中断控制器会按照预先设置的优先级进行判断,让更加紧急的中断优先得到处理。
中断优先级不应随意设置。在实际项目中,应综合考虑事件的重要程度、允许等待的时间、触发频率和中断服务程序的执行时长,再为不同中断分配合适的优先级。
8. 中断嵌套
中断嵌套是指 CPU 正在执行某个中断服务程序时,又出现了优先级更高的中断请求。此时,CPU 会暂停当前的中断服务程序,转而执行优先级更高的中断服务程序。
可以把中断嵌套理解成处理任务时发生了"更紧急的插队":工作人员正在处理普通故障,突然又发生了必须立即处理的紧急故障。工作人员会先记录普通故障处理到了哪里,然后转去处理紧急故障;紧急故障解决后,再回来继续处理原来的普通故障。
假设 CPU 正在执行中断服务程序 A,此时优先级更高的中断 B 到来,执行过程如下:
text
主程序运行
↓
中断 A 发生
↓
执行中断服务程序 A
↓
更高优先级的中断 B 发生
↓
暂停中断服务程序 A
↓
执行中断服务程序 B
↓
中断 B 处理完成
↓
返回中断服务程序 A
↓
中断 A 处理完成
↓
返回主程序
这个过程形成了三层执行关系:主程序被中断 A 暂停,中断 A 又被中断 B 暂停。CPU 会按照"后暂停的先恢复"的顺序逐层返回,也就是先从中断 B 返回中断 A,再从中断 A 返回主程序。
9. 中断服务程序的编写原则
中断能够提高系统的响应速度,但中断服务程序执行期间,主程序和低优先级中断可能处于等待状态,因此中断服务程序通常应尽量简短。
编写中断服务程序时,应注意以下几点:
- 只完成必须立即处理的工作;
- 及时读取或保存容易丢失的数据;
- 处理完成后及时清除中断标志位;
- 尽量不要在中断中执行长时间延时;
- 避免在中断中进行耗时的显示刷新和复杂运算;
- 需要完成复杂任务时,可以设置标志位并交给主程序处理。
可以把中断服务程序理解成急救过程:先快速稳定现场、记录关键信息,再把耗时的检查和后续处理交给主程序。这样既能及时响应事件,也能避免 CPU 长时间停留在中断中。
中断嵌套层数过多或高优先级中断触发过于频繁,可能导致低优先级中断长时间得不到执行,也会增加栈空间的占用。因此,在实际工程中应合理设置中断优先级,并控制中断服务程序的执行时间。
二、NVIC中断优先级管理
1. STM32 的中断通道
STM32 内部拥有多个可屏蔽中断通道,中断源包括外部中断 EXTI、定时器 TIM、模数转换器 ADC、串口 USART、串行外设接口 SPI、I2C 和实时时钟 RTC 等。
"可屏蔽中断"是指可以通过程序控制是否允许响应的中断。当某个中断通道被使能时,相应事件发生后可以向 CPU 提出中断请求;当该通道被关闭时,即使事件已经发生,CPU 也不会进入对应的中断服务程序。
可以把这些中断通道想象成连接到 CPU 办公室的多部电话。定时器、串口和外部引脚等外设都有自己的"电话号码",事件发生时会通过对应线路发出请求。某个通道被关闭,就相当于这部电话被设置成静音,外设仍可能产生事件,但 CPU 不会被它打断。
STM32F10x 系列最多可以包含 68 个可屏蔽中断通道,但不同型号实际拥有的中断通道数量并不完全相同,具体应以芯片的数据手册、启动文件和中断向量表为准。
2. NVIC 的作用
NVIC 的全称是嵌套向量中断控制器,位于 Cortex-M3 内核中,负责统一管理各个中断通道。外设产生中断请求后,会先把请求交给 NVIC,再由 NVIC 根据中断是否使能、优先级高低和 CPU 当前状态进行判断。
可以把 NVIC 理解为医院的"分诊台":不同外设产生的中断请求就像同时到来的病人,NVIC 会根据事先设定的优先级判断谁更紧急,再安排作为"医生"的 CPU 处理相应事件。CPU 负责执行任务,至于哪个任务先执行、哪个任务可以插队,则由 NVIC 负责裁决。
NVIC 主要完成以下工作:
- 使能或关闭指定的中断通道;
- 设置各个中断通道的优先级;
- 判断多个中断请求的响应顺序;
- 管理中断嵌套,使高优先级中断能够打断低优先级中断。
NVIC 的基本关系可以概括为:
text
EXTI、TIM、ADC、USART 等中断源
↓
NVIC
使能控制、优先级裁决
↓
CPU
执行对应中断服务程序
3. 中断优先级的表示
STM32F1 使用 4 个有效位表示外设中断优先级,因此一共可以形成 16 种优先级组合。
需要特别注意:**优先级数值越小,实际优先级越高。**例如,优先级 0 高于优先级 1,优先级 1 又高于优先级 2。这与日常生活中"数字越大越重要"的习惯相反,初学时很容易混淆。
这 4 个优先级位可以被分成两部分:
- 高
n位表示抢占优先级; - 低
4-n位表示响应优先级,也叫子优先级。
其中,n 的大小由 NVIC 的优先级分组决定。
4. 抢占优先级
抢占优先级决定一个中断能否打断另一个正在执行的中断,也就是决定能否发生中断嵌套。
抢占优先级可以理解为"紧急通行证"。拥有更高级别通行证的中断不需要在门外等待,可以打断当前优先级较低的中断,让 CPU 先处理自己。
例如,中断 A 的抢占优先级为 2,中断 B 的抢占优先级为 1。由于数值越小优先级越高,因此中断 B 可以打断中断 A,形成中断嵌套。中断 B 处理完成后,CPU 再返回中断 A 被暂停的位置继续执行。
如果两个中断的抢占优先级相同,那么它们之间不能相互打断。即使其中一个中断的响应优先级更高,也只能等待当前中断执行完成。
5. 响应优先级
响应优先级决定多个等待处理的中断中,哪一个先被 CPU 响应。它只负责"排队顺序",不能决定中断嵌套,也不能单独用来打断正在执行的中断。
响应优先级更像候诊区里的"排队号码"。它只能决定几个正在等待的中断谁排在前面,却不能让后来者冲进诊室打断医生正在进行的诊疗。因此,抢占优先级管的是"能不能插队",响应优先级管的是"不能插队时谁排在前面"。
例如,中断 A 和中断 B 的抢占优先级都为 1,中断 A 的响应优先级为 0,中断 B 的响应优先级为 2。当两个中断都在等待时,中断 A 的响应优先级更高,因此 CPU 会先处理中断 A。
但是,如果中断 B 已经开始执行,此时中断 A 才到来,中断 A 不能依靠更高的响应优先级打断中断 B,只能等待中断 B 执行结束。
6. 优先级的判断顺序
当多个中断请求同时出现时,NVIC 会按照以下顺序判断:
- 首先比较抢占优先级,数值较小的中断优先级更高;
- 抢占优先级相同时,再比较响应优先级,数值较小的中断先执行;
- 抢占优先级和响应优先级都相同时,按照中断号决定先后顺序。
因此,抢占优先级的判断顺序高于响应优先级。不能因为一个中断的响应优先级更高,就忽略它在抢占优先级上的差距。
7. NVIC 优先级分组
NVIC 可以把 4 个优先级位按照不同方式分配给抢占优先级和响应优先级,共有 5 种分组方式:
| 分组方式 | 抢占优先级 | 响应优先级 |
|---|---|---|
| 分组 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 |
可以把这 4 位想象成 4 个固定座位,一部分分给抢占优先级,剩余部分分给响应优先级。座位总数不会变化,改变分组只是在重新划分座位:分给抢占优先级的位数越多,可以区分的"紧急程度"越多;分给响应优先级的位数越多,同一抢占等级下的"排队顺序"划分得越细。
以分组 2 为例,4 个优先级位被平均分成两部分:高 2 位表示抢占优先级,取值范围为 0~3;低 2 位表示响应优先级,取值范围同样为 0~3。因此,分组 2 可以设置 4 个抢占优先级,每个抢占优先级下面又可以设置 4 个响应优先级。
无论采用哪一种分组方式,抢占优先级和响应优先级占用的总位数始终为 4 位,所以总共仍然只有 16 种优先级组合。分组只是改变这 4 位的分配方式,并不会增加优先级的总数量。
8. 优先级判断示例
假设系统采用分组 2,并设置了三个中断:
| 中断 | 抢占优先级 | 响应优先级 |
|---|---|---|
| 中断 A | 1 |
0 |
| 中断 B | 0 |
3 |
| 中断 C | 1 |
1 |
如果中断 A 正在执行,中断 B 到来,由于中断 B 的抢占优先级 0 高于中断 A 的抢占优先级 1,所以中断 B 可以打断中断 A。
如果中断 A 正在执行,中断 C 到来,由于两者的抢占优先级相同,所以中断 C 不能打断中断 A,只能等待中断 A 执行完成。
如果中断 A 和中断 C 同时处于等待状态,由于两者的抢占优先级相同,NVIC 会继续比较响应优先级。中断 A 的响应优先级为 0,高于中断 C 的响应优先级 1,因此中断 A 先执行。
9. 优先级分组的注意事项
一个工程通常只选择一种 NVIC 优先级分组。分组确定后,工程中的各个中断都应按照同一套规则填写抢占优先级和响应优先级,不应在不同外设初始化过程中反复更改分组。
如果后面的程序采用分组 2,那么抢占优先级和响应优先级都只能填写 0~3。超出当前分组允许范围的数值无法按照预期生效,还可能使不同中断的实际优先级关系与程序设计不一致。
三、EXTI外部中断
1. EXTI 简介
EXTI 是外部中断/事件控制器,用于监测指定输入线上的信号变化。当某个 GPIO 引脚出现符合要求的电平边沿时,EXTI 会记录该请求,并向 NVIC 申请中断。经过 NVIC 的优先级裁决后,CPU 暂停主程序,转而执行相应的 EXTI 中断服务程序。
可以把 GPIO 引脚理解成门口的传感器,把 EXTI 理解成报警器。GPIO 负责感知门的状态,EXTI 负责判断这次变化是否满足报警条件;满足条件后,EXTI 再通知作为"调度员"的 NVIC,由 NVIC 决定 CPU 何时处理这次报警。
EXTI 检测的重点不是某个电平保持了多长时间,而是信号从一种状态变化到另一种状态的瞬间,也就是信号的边沿。
2. EXTI 的触发方式
EXTI 支持以下 4 种触发方式:
- 上升沿触发:信号从低电平变为高电平时产生请求;
- 下降沿触发:信号从高电平变为低电平时产生请求;
- 双边沿触发:上升沿和下降沿都可以产生请求;
- 软件触发:不依赖外部引脚变化,由程序主动产生一次 EXTI 请求。
上升沿和下降沿可以理解为电梯运行的方向:从低楼层到高楼层是上升沿,从高楼层到低楼层是下降沿。如果两个方向都需要检测,就使用双边沿触发。
实际选择哪一种触发方式,应根据电路的有效电平决定。例如,一个按键按下时将引脚由高电平拉低,按下动作对应下降沿;松开时引脚由低电平恢复为高电平,松开动作则对应上升沿。
3. EXTI 中断线
STM32F1 的 EXTI 包含 20 条输入线,其中前 16 条用于连接 GPIO 引脚:
EXTI0对应编号为 0 的 GPIO 引脚;EXTI1对应编号为 1 的 GPIO 引脚;- 依此类推,
EXTI15对应编号为 15 的 GPIO 引脚。
另外 4 条输入线连接芯片内部的特定事件,包括 PVD 输出、RTC 闹钟、USB 唤醒和以太网唤醒。具体型号实际具有哪些内部中断源,应以对应芯片的参考手册为准。
因此,"外部中断"并不表示所有请求都必须来自芯片外部。GPIO 信号通常来自芯片引脚,而 PVD、RTC 和 USB 等请求则来自芯片内部的其他模块。
4. 相同编号的 GPIO 共用一条 EXTI 线
STM32 的不同 GPIO 端口可能拥有编号相同的引脚,例如 PA0、PB0 和 PC0。这些引脚都可以作为外部中断输入,但它们共同连接到 EXTI0,因此同一时刻只能选择其中一个引脚作为 EXTI0 的输入。
同理:
PA1、PB1、PC1等编号为 1 的引脚共用EXTI1;PA2、PB2、PC2等编号为 2 的引脚共用EXTI2;- 其余编号依此类推。
可以把 EXTI0 想象成只有一个插孔的检测设备。虽然 PA0、PB0 和 PC0 都能接入这个插孔,但同一时间只能插入其中一根线。因此,可以同时使用 PA0 和 PB1 作为外部中断,却不能让 PA0 和 PB0 同时独立连接到 EXTI0。
5. AFIO 的作用
AFIO 是复用功能 IO 模块,主要完成两个任务:
- 选择外设的复用功能引脚并进行引脚重映射;
- 选择某条 EXTI 中断线具体连接到哪一个 GPIO 端口。
在外部中断中,AFIO 就像一个"多选一开关"。以 EXTI0 为例,它可以在 PA0、PB0、PC0 等引脚之间选择,但最终只能把其中一个引脚接到 EXTI0。
GPIOA、GPIOB、GPIOC 等端口的引脚经过 AFIO 选择后,被整理为 16 条 EXTI 输入线,再交给 EXTI 进行边沿检测和请求控制。PVD、RTC、USB 和以太网等内部事件不需要通过 GPIO 引脚,因此会直接连接到相应的 EXTI 线。
使用标准外设库配置 GPIO 外部中断时,需要先使能 AFIO 时钟。即使 GPIO 输入本身已经能够正常读取,如果没有使能并配置 AFIO,EXTI 也可能无法连接到目标 GPIO 端口。
6. EXTI 与 NVIC 的连接关系
GPIO 的 16 条 EXTI 线并不全部拥有独立的 NVIC 中断通道,它们按照以下方式连接:
| EXTI 中断线 | 对应的 NVIC 中断通道 |
|---|---|
EXTI0 |
EXTI0 |
EXTI1 |
EXTI1 |
EXTI2 |
EXTI2 |
EXTI3 |
EXTI3 |
EXTI4 |
EXTI4 |
EXTI5~EXTI9 |
EXTI9_5 |
EXTI10~EXTI15 |
EXTI15_10 |
EXTI0~EXTI4 各自拥有独立的 NVIC 中断通道,而 EXTI5~EXTI9 共用一个通道,EXTI10~EXTI15 也共用一个通道。
共用通道就像几户人家共用一个门铃。门铃响起后,只能知道这一组中有人按铃,不能直接判断具体是哪一家。因此,进入 EXTI9_5 或 EXTI15_10 的中断服务程序后,还需要检查挂起标志,确定究竟是哪一条 EXTI 线触发了中断。
7. 中断响应与事件响应
EXTI 支持中断响应和事件响应两种方式:
- 中断响应:EXTI 将请求发送给 NVIC,NVIC 再通知 CPU 执行中断服务程序;
- 事件响应:EXTI 产生一个事件脉冲,用于触发其他硬件动作,不要求 CPU 进入中断服务程序。
中断响应更像"打电话通知负责人",需要 CPU 停下当前工作并运行一段处理程序;事件响应则更像"按下自动设备的启动按钮",信号到达后由硬件完成后续动作,不一定需要 CPU 参与。
对于需要执行 C 语言处理逻辑的 GPIO 输入,通常使用中断响应;对于只需要产生硬件触发信号的场景,可以考虑事件响应。
8. EXTI 框图中的主要寄存器
EXTI 内部共有 20 条信号通道,因此相关寄存器通常使用 20 个有效位分别控制这些通道。EXTI 框图中包含以下重要寄存器:
- 上升沿触发选择寄存器
EXTI_RTSR:决定哪些输入线可以由上升沿触发; - 下降沿触发选择寄存器
EXTI_FTSR:决定哪些输入线可以由下降沿触发; - 软件中断事件寄存器
EXTI_SWIER:允许程序主动产生一次中断或事件请求; - 请求挂起寄存器
EXTI_PR:记录哪些 EXTI 线已经产生请求; - 中断屏蔽寄存器
EXTI_IMR:决定请求是否可以进入 NVIC; - 事件屏蔽寄存器
EXTI_EMR:决定请求是否可以进入事件脉冲发生器。
"屏蔽寄存器"可以理解成总线路上的开关。边沿检测电路即使发现了信号变化,如果对应的中断屏蔽位没有打开,请求也无法送到 NVIC;如果事件屏蔽位没有打开,也不会产生事件脉冲。
请求挂起寄存器则像一个"未处理消息列表"。某条 EXTI 线触发后,对应的挂起标志会被置位。中断服务程序需要检查这个标志,确认中断来源,并在处理完成后清除标志,避免 CPU 重复处理同一次请求。
需要注意,EXTI 的挂起标志通常通过"写 1"清除,而不是写 0。使用标准外设库函数时,库函数会完成对应的寄存器操作。
9. EXTI 的完整工作流程
以 GPIO 引脚触发中断为例,EXTI 的完整信号流程如下:
text
GPIO 引脚产生电平变化
↓
AFIO 选择对应的 GPIO 端口
↓
EXTI 检测上升沿或下降沿
↓
产生请求并设置挂起标志
↓
中断屏蔽寄存器判断是否放行
↓
请求发送到 NVIC
↓
NVIC 根据优先级进行裁决
↓
CPU 执行对应的中断服务程序
↓
程序处理事件并清除挂起标志
如果选择的是事件响应,请求通过事件屏蔽寄存器后会进入脉冲发生器,产生一个硬件事件脉冲,而不会按照普通中断流程进入 NVIC。
10. 配置 GPIO 外部中断的基本思路
使用 GPIO 触发 EXTI 中断时,通常需要依次完成以下工作:
- 使能目标 GPIO 和 AFIO 的时钟;
- 将目标 GPIO 配置为合适的输入模式;
- 通过 AFIO 将 GPIO 端口映射到对应的 EXTI 线;
- 设置 EXTI 线、触发边沿和中断响应方式;
- 在 NVIC 中使能对应中断通道并设置优先级;
- 编写名称正确的中断服务程序;
- 在中断服务程序中判断中断来源并清除挂起标志。
这套流程可以简化为"GPIO 感知信号、AFIO 选择线路、EXTI 检测边沿、NVIC 安排顺序、CPU 执行程序"。只要分清每个模块的职责,外部中断的配置就不再是一串孤立的函数调用。
四、旋转编码器
1. 旋转编码器简介
旋转编码器是一种将旋转动作转换为电信号的装置,可以用来测量旋转轴的位置、速度和旋转方向。转轴旋转时,编码器输出端会产生具有特定频率和相位关系的方波信号。单片机读取这些信号后,就可以判断转了多少、转得多快以及朝哪个方向旋转。
可以把旋转编码器理解成安装在转轴旁边的"记录员":转轴每转过一定角度,记录员就发出一次脉冲;单片机统计脉冲数量可以计算位置,观察脉冲出现的快慢可以计算速度,比较两路脉冲谁先到达则可以判断方向。
旋转编码器常用于音量旋钮、菜单选择器、电机测速、机器人位置检测和工业控制设备等场景。
2. A、B 两相输出信号
旋转编码器通常具有 A、B 两路输出信号。转轴旋转时,A 相和 B 相都会产生周期性的方波,但两路信号之间存在固定的相位差,这种输出方式称为正交输出。
"正交"可以简单理解为两名运动员按照相同速度绕操场跑步,但其中一人始终领先另一人四分之一圈。两人的速度相同,先后顺序却十分明确。旋转方向改变后,原本领先的信号会变成落后,因此单片机可以通过比较 A、B 两相的先后顺序判断方向。
两相信号的关系可以简化表示为:
text
A 相:____┌────┐____┌────┐____
│ │ │ │
└────┘ └────┘
B 相:______┌────┐____┌────┐__
│ │ │ │
└────┘ └────┘
上图中两路方波的频率相同,但边沿出现的时间不同。实际使用时,A 相领先代表顺时针还是逆时针,取决于编码器结构、接线方式和程序中的方向定义,不能脱离具体电路直接确定。
3. 如何判断旋转方向
判断旋转方向时,程序可以在 A 相发生边沿变化的瞬间读取 B 相电平,也可以在 B 相变化时读取 A 相电平。
例如,在 A 相出现下降沿时:
- 如果此时 B 相为高电平,可以将本次旋转记为一个方向;
- 如果此时 B 相为低电平,可以将本次旋转记为相反方向。
究竟把哪一种情况定义为顺时针,可以根据实际接线和使用习惯决定。如果程序判断出的方向与旋钮实际方向相反,可以交换 A、B 两路接线,也可以在程序中对方向判断结果取反。
使用 A、B 两路共同判断方向,比只检测一路信号更加可靠。只读取 A 相只能知道编码器发生了转动,却无法单独判断转动方向;加入 B 相后,程序才能利用两路信号的先后关系区分正转与反转。
4. 如何测量位置和速度
旋转编码器每转过一定角度,就会输出一定数量的脉冲。单片机可以通过累计脉冲数估算旋转位置:正向旋转时计数值增加,反向旋转时计数值减少。
如果在固定时间内统计脉冲数量,就可以估算旋转速度。相同时间内出现的脉冲越多,说明转轴旋转得越快;脉冲越少,说明转轴旋转得越慢。也可以测量相邻脉冲之间的时间间隔,间隔越短表示速度越快。
三种信息可以概括为:
| 需要测量的信息 | 判断依据 |
|---|---|
| 旋转位置 | 累计脉冲数量 |
| 旋转速度 | 单位时间内的脉冲数量或相邻脉冲的时间间隔 |
| 旋转方向 | A、B 两相信号的先后顺序 |
如果需要得到真实角度或转速,还需要知道编码器每转一圈产生多少个脉冲,再根据实际采用的计数方式进行换算。
5. 旋转编码器的常见类型
按照产生信号的方式,旋转编码器可以分为以下几类:
- 机械触点式:依靠内部金属触点的通断产生信号,结构简单、成本较低,但触点动作时容易产生机械抖动;
- 霍尔传感器式:利用磁场变化产生信号,没有机械触点,耐磨性较好;
- 光栅式:利用光源、码盘和光电传感器检测旋转位置,精度较高,常用于电机测速和位置控制。
初学实验中常见的是机械触点式旋转编码器。它的工作方式与机械按键相似,触点接通和断开时不会形成绝对干净的电平跳变,因此需要通过硬件滤波或软件消抖减少误触发。
6. 模块引脚连接
旋转编码器模块通常需要连接电源、地线和两路旋转信号。在当前连接方式中:
| 模块引脚 | 连接位置 | 作用 |
|---|---|---|
VCC |
3.3V |
为模块供电 |
GND |
GND |
模块公共地 |
A |
PB0 |
输出 A 相方波信号 |
B |
PB1 |
输出 B 相方波信号 |
C |
未使用 | 当前实验中不参与方向检测 |
单片机与编码器模块必须共地,否则 A、B 两路信号没有统一的电平参考,读取结果可能不稳定或完全错误。
不同旋转编码器模块对引脚字母的定义可能存在差异,接线前应查看模块原理图或说明资料,不能只根据引脚排列直接判断功能。
7. A、B 两路信号的硬件电路
机械旋转编码器内部包含 A、B 两组触点和一个公共端。公共端接地后,触点断开时,A、B 两路通过上拉电阻保持高电平;触点闭合时,相应信号被连接到地,变为低电平。随着转轴旋转,A、B 两组触点按照不同的先后顺序接通和断开,从而形成两路带有相位差的方波。
电路中的 R1 和 R2 是 10kΩ 上拉电阻,主要作用是防止触点断开时 A、B 信号悬空。可以把上拉电阻理解为一根较弱的"弹簧",在没有其他信号作用时,它会把引脚轻轻拉回高电平;触点接地后,信号又会被明确拉成低电平。
R3、C1 和 R4、C2 分别构成两组 RC 低通滤波电路:
- A 相经过
R3和C1滤波后送入单片机; - B 相经过
R4和C2滤波后送入单片机; R3、R4的阻值均为10kΩ;C1、C2的电容量均为0.1μF。
RC 滤波电路就像门口的缓冲器:非常短暂的抖动和尖峰信号会被削弱,而持续时间足够长的真实电平变化可以通过。按照图中的参数计算,其时间常数约为:
text
τ = R × C
= 10kΩ × 0.1μF
≈ 1ms
这组滤波电路可以在一定程度上减小机械触点抖动和高频干扰,但不能保证消除所有抖动。实际程序中仍应结合中断触发方式、状态判断或软件消抖,避免一次旋转被错误识别成多次脉冲。
8. 使用 EXTI 读取旋转编码器
A、B 两路信号分别接入 PB0 和 PB1 后,可以将其映射到 EXTI0 和 EXTI1。由于两个引脚编号不同,因此可以同时作为两条独立的外部中断线使用。
程序可以让 A、B 两路都在指定边沿产生中断:
- A 相触发中断时,读取 B 相电平并判断一个方向;
- B 相触发中断时,读取 A 相电平并判断相反方向;
- 根据判断结果让计数值增加或减少。
这种方法不需要主程序持续读取编码器电平。旋钮不转动时,CPU 可以执行其他任务;旋钮产生脉冲时,EXTI 再立即通知 CPU 处理。
信号处理流程可以概括为:
text
转动旋转编码器
↓
A、B 触点按照不同顺序通断
↓
上拉电阻产生明确的高、低电平
↓
RC 电路削弱抖动和高频干扰
↓
PB0、PB1 接收到两路正交方波
↓
EXTI0、EXTI1 检测信号边沿
↓
程序比较 A、B 两相电平
↓
判断方向并更新计数值
9. 旋转编码器使用注意事项
机械旋转编码器容易产生触点抖动。如果直接把每一次电平变化都当作有效脉冲,一次拨动可能被识别为多次旋转。硬件 RC 滤波可以削弱短促抖动,软件还可以通过状态机、时间间隔判断或完整相位序列判断进一步提高可靠性。
由于编码器连续转动时可能快速产生多个中断,中断服务程序应尽量简短,通常只读取另一相信号、判断方向、更新计数值并清除中断标志,不应在中断中执行长时间延时、复杂计算或 OLED 刷新。
如果出现方向判断相反、计数跳变或一次旋转产生多个计数等问题,应依次检查:
- A、B 两路接线是否正确;
- GPIO 与 AFIO 的 EXTI 映射是否正确;
- 上升沿、下降沿配置是否与程序判断逻辑一致;
- 中断挂起标志是否在处理后清除;
- 编码器是否存在明显的机械抖动;
- 中断服务程序是否执行时间过长。
从旋转编码器产生信号到 CPU 完成计数,完整的知识链可以概括为:编码器产生 A、B 两相方波,GPIO 接收电平,AFIO 选择 EXTI 输入线,EXTI 检测边沿并提出请求,NVIC 根据优先级进行裁决,CPU 最终执行中断服务程序并更新计数值。