旋转编码器
本节课来学习使用 MicroPython 控制旋转编码器。
实验原理
旋转编码器是一种位置传感器,它将旋钮的角位置(旋转)转换为数字信号输出,可用于确定旋钮的转动方向,被广泛应用于各种领域。旋转编码器听起来虽然很陌生,但实际上在我们生活中十分常用,比如鼠标滚轮、汽车音箱的旋钮。

之前,我们学过一个与其类似的元件 电位计,它不同于普通电位器,电位计一般只能旋转大约 3/4 圈,而旋转编码器可以无限旋转,并能精确测量相对位置变化。旋转编码器是电位器的现代数字等效物,并且用途更广泛。
在旋转编码器内部,有一个带有均匀间隔槽孔的圆盘,圆盘与 GND 相连,另外,编码器还有另外两个金属探针 A 和 B,这些引脚将帮助我们确定旋钮的转动方向。

当我们转动编码器的旋钮时,圆盘会随之一起转动。根据转动方向的不同,探针 A 和 B 会先后接触到 GND,并先后产生两个脉冲信号,这两个信号会存在一个 90° 的相位差,形成一种正交编码。当顺时针旋转旋钮时,A 引脚先于 B 引脚接地。当逆时针旋转旋钮时,B 引脚先于 A 引脚接地,具体工作原理可以参考以下两个动图:


通过监控每个引脚何时连接或断开接地,我们可以确定旋钮旋转的方向。这可以通过简单地观察 A 的状态改变时 B 的状态来完成。
当 A 改变状态时:
- 如果
B != A
,则旋钮为顺时针转动

- 如果
B = A
,则旋钮为逆时针转动

旋转编码器模块的引脚排列如下:

VCC
是正电源电压,通常在 3.3 至 5 伏之间。KEY
是按钮开关的输出(低电平有效)。当按下旋钮时,电压变低。S2
金属探针引脚,用于确定旋转量S1
金属探针引脚,用于确定旋转量。GND
是接地连接。
硬件电路设计
物料清单(BOM 表):
材料名称 | 数量 |
---|---|
旋转编码器模块 | 1 |
OLED 屏幕 | 1 |
杜邦线(跳线) | 若干 |
面包板 | 1 |
OLED 的 SCK(D0)接开发板 D18、SDA(D1)接 D5、RES 接 D15、DC 接 D2、CS 接 D4
旋转编码器的 S2 引脚接开发板 D26、S1接 D27,KEY 按键没有用到,可以不接。

软件程序设计
1. 计数器
第一个程序,我们来通过代码获取旋转编码器的转动方向,做一个计数器,顺时针旋转则加,逆时针旋转则减,代码如下:
#define A 26
#define B 27
// 定义 A 上一个引脚状态变量
int last_state_a;
// 定义计数器变量
int count = 0;
void setup() {
// 设置引脚模式
pinMode(A, INPUT);
pinMode(B, INPUT);
// 设置串口通信波特率
Serial.begin(9600);
// 记录上一个 a 引脚的状态
last_state_a = digitalRead(A);
}
void loop() {
// 获取当前 A 引脚电平
int current_state_a = digitalRead(A);
// 检测 A 引脚状态变化,只检测下降沿
if(!current_state_a && last_state_a) {
// 如果 AB 状态不同,则顺时针选装,反之则逆时针
if(digitalRead(B) != current_state_a) {
count++;
Serial.print("顺时针旋转, ");
}else {
count--;
Serial.print("逆时针旋转, ");
}
Serial.println(count);
}
// 更新上一个引脚电平状态
last_state_a = current_state_a;
}
2. 旋转编码器控制菜单
最后,我们使用旋转编码器与 OLED 屏幕实现一个控制菜单的实验,该实验与 SPI 驱动 OLED 液晶屏幕 中的菜单 UI 界面一致,可以参考之前的教程,代码如下:
#include <Arduino.h>
#include <U8g2lib.h>
#define A 26
#define B 27
// PlatformIO 中 自己编写的函数如果处于末尾,需要在文件顶部显式声明
void display_menu(int index);
U8G2_SSD1306_128X64_NONAME_2_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/18, /* data=*/5,
/* cs=*/4, /* dc=*/2, /* reset=*/15);
// 定义菜单列表
char *menu[4] = {"Item 1", "Item 2", "Item 3", "Item 4"};
// 定义当前选项
unsigned int order = 0;
// 定义 A 上一个引脚状态变量
int last_state_a;
void setup()
{
// 初始化 OLED 对象
u8g2.begin();
u8g2.setFont(u8g2_font_6x12_tr);
// 配置输入按键
pinMode(A, INPUT);
pinMode(B, INPUT);
// 记录上一个 a 引脚的状态
last_state_a = digitalRead(A);
// 初始化菜单
display_menu(order);
}
void loop()
{
// 获取当前 A 引脚电平
int current_state_a = digitalRead(A);
// 检测 A 引脚状态变化,只检测下降沿
if(!current_state_a && last_state_a) {
// 如果 AB 状态不同,则顺时针选装,反之则逆时针
if(digitalRead(B) != current_state_a) {
order = (order + 1) % 4;
}else {
order = (order - 1) % 4;
}
display_menu(order);
}
// 更新上一个引脚电平状态
last_state_a = current_state_a;
}
void display_menu(int index)
{
// 进入第一页
u8g2.firstPage();
do
{
// 绘制页面内容
u8g2.drawStr(0, 12, "Menu");
u8g2.drawHLine(0, 14, 128);
for (int i = 0; i < 4; i++)
{
if (i == index)
{
u8g2.drawStr(5, (i + 2) * 12 + 2, ">");
u8g2.drawStr(20, (i + 2) * 12 + 2, menu[i]);
}
else
{
u8g2.drawStr(5, (i + 2) * 12 + 2, menu[i]);
}
}
} while (u8g2.nextPage()); // 进入下一页,如果还有下一页则返回 True.
}