STM32 入门笔记 04:旋转编码器与双 EXTI 中断
这是 STM32 入门笔记专栏的第 04 篇。在会了 EXTI 之后,再用它做点「有意思」的事——旋转编码器:拧一下旋钮,单片机就能分辨出是「顺时针」还是「逆时针」转了一格。
一、什么是旋转编码器
旋转编码器是一种「拧一格、输出两相脉冲」的机电元件,常见的是 EC11 系列(淘宝几块钱一只)。它有 3 个引脚:A 相、B 相、GND(有的型号中间还多一个 C 接 VCC,按键按下才接通的「中心轴按下」功能)。
波形长相(理想情况下):
1 | A 相: _____|‾‾‾‾|_____ 上升 → 下降 |
旋转方向不同,A、B 相谁先变化也不同:
| 旋转方向 | A 边沿时 B 的状态 |
|---|---|
| 顺时针 | A 上升时 B = 0;A 下降时 B = 1 |
| 逆时针 | A 上升时 B = 1;A 下降时 B = 0 |
推论:只要在 A 的某个边沿(比如下降沿)触发中断,进去读 B 现在的电平,就能判断方向。
二、接线约定
- A 相 → PA0(EXTI0,独立 IRQ,方便调试)
- B 相 → PB0(EXTI0,不行——PA0 已经占了 EXTI0)→ 改成 PA1(EXTI1)
- GND → GND
旋转编码器内部一般没有上拉,所以 GPIO 配 上拉输入(GPIO_Mode_IPU),让默认电平稳定为高。
三、初始化:双 EXTI
A、B 两根线都要触发中断,但 PA0 和 PA1 是两条不同的 EXTI 线(Line0 和 Line1),所以可以分别用独立的 IRQ:
1 |
|
四、中断里怎么判断方向
诀窍:在 A 相触发时,读 B 相的电平。
- A 下降沿时 B = 0 → 顺时针 →
Encoder_Count-- - A 下降沿时 B = 1 → 逆时针 →
Encoder_Count++
为什么选 A 而不是 B?因为「编码器厂商约定」是 A 相先变,跟「按钮触发」一样属于惯用手。其实反过来也可以,只是方向要取反。
下面给出修正后的代码——原文这段有几处容易踩坑的地方,我都改过来了:
1 | /* A 相(PA0)触发:下降沿时读 B(PA1)判断方向 */ |
主循环里读计数:
1 | int16_t Encoder_Get(void) |
五、原文常见 bug 与修正对照
下面把原文代码里几处错的地方列出来对照——
| 行号 | 原文 | 问题 | 修正 |
|---|---|---|---|
EXTI0_IRQHandler 里 EXTI_GetITStatus(EXTI_Line14) |
错把 Line14 写进 EXTI0 的中断 | EXTI0 应该是 Line0 | 改成 EXTI_GetITStatus(EXTI_Line0) |
EXTI1_IRQHandler 里 EXTI_ClearITPendingBit(EXTI_Line0) |
中断函数和清标志的 Line 对不上 | 应该在 EXTI1 里清 Line1 | 改成 EXTI_ClearITPendingBit(EXTI_Line1) |
Encoder_Count^=1 |
用 XOR 实现「计数」 | ^= 1 是 0↔1 翻转,不是 ±1 |
改成 Encoder_Count++ / -- |
NVIC_Pin = GPIO_Pin_14(错误变量名) |
误把 GPIO 字段名写到 NVIC 结构体 | 编译都过不了 | 删掉 |
| 没消抖 | 机械触点会反复抖动 | 中断函数里短时间反复触发 | 加 2 ms 简易消抖 |
上面这些 bug 看上去是「抄漏了变量名」或「忘了改模板」,但只要有一个,模块就完全不能正常工作——所以写完之后一定要用万用表 / 示波器实际验证一下。
六、整体数据流
1 | ┌─────────────┐ |
七、易错点清单
- EXTI_Line 与中断函数对不上:EXTI0 的 IRQ 函数里
if (EXTI_GetITStatus(EXTI_Line0)),清EXTI_ClearITPendingBit(EXTI_Line0)——其它几条 EXTI 同样规律。 - 忘了清中断标志:导致中断反复触发,CPU 被打断无法返回主循环,看起来像「死机」。
- 共用 IRQ 函数里没分
if:EXTI9_5_IRQHandler里Line5和Line7都会进,必须各自分if (EXTI_GetITStatus(...))。 - 机械抖动没消:机械触点会有 1~5 ms 抖动,转一格往往多算 2~4 次。消抖是必须的。
- PA0 / PB0 不能同时用 EXTI0:EXTI 线是按 pin 编号共享的(PA0 / PB0 / ... / PG0 共用 Line0)。需要双 EXTI 就用 PA0 / PA1 这种组合。
- GPIO 没配上拉:旋转编码器机械开关是开漏输出,没有外部上拉就读到浮空电平,要么常低要么常高,要么疯狂跳变。
- 旋转方向反了:把判断里
== 0和== 1互换即可,不用改硬件。 - NVIC 优先级开太高:旋转编码器中断频繁(每转一格触发 4 次),别给它开抢占优先级 0,否则会打断更重要的事。
八、小结
旋转编码器把「EXTI0 / EXTI1 + 方向判定 + 消抖」三件套串起来,是 EXTI 系列练习最好的综合题:
| 要点 | 怎么做 |
|---|---|
| 触发边沿 | 双边沿触发(EXTI_Trigger_Rising_Falling) |
| 方向判定 | A 触发时读 B;B 触发时读 A |
| 消抖 | 2 ms 内忽略二次中断,或硬件加 RC 滤波 |
| 清中断 | 进入中断处理完必须 ClearITPendingBit |
到这一篇结束,「按键 / 中断 / 旋转编码器」这条线就串通了——下次拿到任何机械输入元件(限位开关、碰撞开关、震动传感器),套路都是:
GPIO 上拉输入 → EXTI 边沿触发 → NVIC 中断服务 → 主循环读取 / 处理。