介绍
360度旋转编码器模块,5引脚(GND/VCC/A/B/C),EC11型旋钮编码器,20脉冲/圈,AB双相正交输出,C为按键开关输出,适用于音量调节、菜单导航、步进电机手动控制等
360度旋转编码器模块,5引脚(GND/VCC/A/B/C),EC11型旋钮编码器,20脉冲/圈,AB双相正交输出,C为按键开关输出,适用于音量调节、菜单导航、步进电机手动控制等
| 参数 | 值 |
|---|---|
| 引脚 | GND, VCC, A(CLK), B(DT), C(SW) |
| 脉冲数 | 20脉冲/圈 (典型) |
| AB相位差 | 90°正交 |
| 工作电压 | 3.3V~5V DC |
| 按键寿命 | ≥10万次 |
| 按键行程 | 约0.5mm下压 |
| 旋转寿命 | ≥10万转 |
| 模块尺寸 | 20×20mm (不含旋钮帽) |
| 输出类型 | 数字脉冲 (AB), 低电平有效(SW) |
| 编码器类型 | 增量式EC11旋转编码器 |
# 360度旋转编码器模块 — 代码例程
---
## 例程一:Arduino 平台(轮询法 + 状态机消抖)
```cpp
/*
* 360°旋转编码器 EC11 — Arduino 轮询例程
* 接线:A→D2, B→D3, C→D4, VCC→5V, GND→GND
* 功能:串口打印方向(+/-)和计数值,按键按下打印 "Click!"
*/
#define PIN_A 2 // A相
#define PIN_B 3 // B相
#define PIN_BTN 4 // 按键
volatile int encoderCount = 0;
int lastCount = 0;
void setup() {
Serial.begin(115200);
pinMode(PIN_A, INPUT_PULLUP);
pinMode(PIN_B, INPUT_PULLUP);
pinMode(PIN_BTN, INPUT_PULLUP);
Serial.println("EC11 Encoder Demo Ready");
}
void loop() {
// --- 编码器轮询 ---
static uint8_t lastState = 0b00;
uint8_t a = digitalRead(PIN_A);
uint8_t b = digitalRead(PIN_B);
uint8_t state = (a << 1) | b;
if (state != lastState) {
// 状态变化,根据格雷码转换判断方向
// CW: 00→01→11→10→00 (每步+1)
// CCW: 00→10→11→01→00 (每步-1)
switch ((lastState << 2) | state) {
case 0b0001: // 00→01
case 0b0111: // 01→11
case 0b1110: // 11→10
case 0b1000: // 10→00
encoderCount++;
break;
case 0b0010: // 00→10
case 0b1011: // 10→11
case 0b1101: // 11→01
case 0b0100: // 01→00
encoderCount--;
break;
}
lastState = state;
if (encoderCount != lastCount) {
Serial.print("Encoder: ");
Serial.println(encoderCount);
lastCount = encoderCount;
}
}
// --- 按键检测(消抖) ---
static unsigned long lastBtnTime = 0;
static bool lastBtnState = HIGH;
bool btn = digitalRead(PIN_BTN);
if (btn != lastBtnState) {
lastBtnTime = millis();
lastBtnState = btn;
}
if ((millis() - lastBtnTime) > 30) { // 30ms消抖
if (btn == LOW && lastBtnState == LOW) {
Serial.println("Click!");
// 等待释放避免重复触发
while (digitalRead(PIN_BTN) == LOW);
lastBtnState = HIGH;
}
}
}
```
---
## 例程二:Arduino 平台(中断法,响应更快)
```cpp
/*
* 360°旋转编码器 EC11 — Arduino 中断例程
* 接线:A→D2 (INT0), B→D3, C→D4, VCC→5V, GND→GND
* 优点:不丢脉冲,适合实时性要求高的场景
*/
#define PIN_A 2 // 必须接中断引脚 (UNO: D2=INT0)
#define PIN_B 3
#define PIN_BTN 4
volatile int count = 0;
void setup() {
Serial.begin(115200);
pinMode(PIN_A, INPUT_PULLUP);
pinMode(PIN_B, INPUT_PULLUP);
pinMode(PIN_BTN, INPUT_PULLUP);
// 在A信号的CHANGE边沿触发中断
attachInterrupt(digitalPinToInterrupt(PIN_A), encoderISR, CHANGE);
Serial.println("EC11 Interrupt Demo Ready");
}
void loop() {
static int lastCount = 0;
if (count != lastCount) {
Serial.print("Count: ");
Serial.println(count);
lastCount = count;
}
// 按键轮询
if (digitalRead(PIN_BTN) == LOW) {
delay(20); // 消抖
if (digitalRead(PIN_BTN) == LOW) {
Serial.println("Click!");
while (digitalRead(PIN_BTN) == LOW); // 等释放
}
}
}
void encoderISR() {
// A变化时读B判断方向
if (digitalRead(PIN_A) == digitalRead(PIN_B)) {
count++; // 同相 → CW
} else {
count--; // 异相 → CCW
}
}
```
---
## 例程三:STM32 HAL 库(定时器编码器模式)
```c
/*
* STM32 定时器编码器模式 + 按键中断
* 使用 TIM2 的 CH1(PA0)/CH2(PA1) 作为编码器AB输入
* 按键接 PA2,外部中断下降沿触发
*
* CubeMX 配置提示:
* - TIM2 → Combined Channels → Encoder Mode
* - Counter Period: 65535 (最大)
* - PA0: TIM2_CH1, PA1: TIM2_CH2
* - PA2: GPIO_EXTI2, 下降沿触发
* - 开启 TIM2 和 EXTI2 中断
*/
#include "main.h"
TIM_HandleTypeDef htim2;
UART_HandleTypeDef huart2;
int32_t encoderCount = 0;
uint8_t buttonPressed = 0;
// 按键回调
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == GPIO_PIN_2) {
// 软件消抖(简单延时)
HAL_Delay(30);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) == GPIO_PIN_RESET) {
buttonPressed = 1;
}
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM2_Init();
MX_USART2_UART_Init();
// 启动编码器模式
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
printf("STM32 EC11 Encoder Demo Ready\r\n");
int32_t lastCount = 0;
while (1) {
// 读取编码器计数值(TIM2->CNT会自动根据方向增减)
encoderCount = (int32_t)__HAL_TIM_GET_COUNTER(&htim2);
if (encoderCount != lastCount) {
printf("Encoder: %ld\r\n", encoderCount);
lastCount = encoderCount;
}
// 按键处理
if (buttonPressed) {
buttonPressed = 0;
printf("Button Clicked!\r\n");
}
HAL_Delay(10);
}
}
```
---
## 例程四:STM32 HAL 库(GPIO轮询 + 状态机,无定时器占用)
```c
/*
* STM32 GPIO轮询编码器(适合定时器不够用的场景)
* A→PA0, B→PA1, BTN→PA2
* 放在1ms定时器中断或主循环中调用即可
*/
#include "main.h"
typedef struct {
GPIO_TypeDef* portA;
uint16_t pinA;
GPIO_TypeDef* portB;
uint16_t pinB;
uint8_t lastState; // 上次AB状态 (bit1=A, bit0=B)
int32_t count;
} EC11_Encoder;
EC11_Encoder encoder = {
.portA = GPIOA, .pinA = GPIO_PIN_0,
.portB = GPIOA, .pinB = GPIO_PIN_1,
.lastState = 0,
.count = 0
};
/*
* 调用此函数轮询编码器(建议每 1ms 调用一次)
* 返回:1=顺时针一步, -1=逆时针一步, 0=无变化
*/
int8_t EC11_Poll(EC11_Encoder* enc) {
uint8_t a = (HAL_GPIO_ReadPin(enc->portA, enc->pinA) == GPIO_PIN_SET) ? 1 : 0;
uint8_t b = (HAL_GPIO_ReadPin(enc->portB, enc->pinB) == GPIO_PIN_SET) ? 1 : 0;
uint8_t state = (a << 1) | b;
int8_t result = 0;
if (state != enc->lastState) {
// 格雷码状态转移判断
switch ((enc->lastState << 2) | state) {
case 0b0001: case 0b0111: case 0b1110: case 0b1000:
enc->count++;
result = 1;
break;
case 0b0010: case 0b1011: case 0b1101: case 0b0100:
enc->count--;
result = -1;
break;
default:
// 非法跳转(可能是抖动),忽略
break;
}
enc->lastState = state;
}
return result;
}
int32_t EC11_GetCount(const EC11_Encoder* enc) {
return enc->count;
}
void EC11_ResetCount(EC11_Encoder* enc) {
enc->count = 0;
}
// ============ 使用示例 ============
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
printf("EC11 Polling Demo Ready\r\n");
int32_t lastCount = 0;
while (1) {
EC11_Poll(&encoder);
int32_t cnt = EC11_GetCount(&encoder);
if (cnt != lastCount) {
printf("Count: %ld\r\n", cnt);
lastCount = cnt;
}
HAL_Delay(1); // 1ms 轮询周期
}
}
```
---
## 例程五:MicroPython (ESP32 / Raspberry Pi Pico)
```python
"""
360°旋转编码器 EC11 — MicroPython 例程
接线:A→GPIO14, B→GPIO15, BTN→GPIO16, VCC→3.3V, GND→GND
"""
from machine import Pin
import time
PIN_A = 14
PIN_B = 15
PIN_BTN = 16
a = Pin(PIN_A, Pin.IN, Pin.PULL_UP)
b = Pin(PIN_B, Pin.IN, Pin.PULL_UP)
btn = Pin(PIN_BTN, Pin.IN, Pin.PULL_UP)
count = 0
last_state = (a.value() << 1) | b.value()
print("EC11 MicroPython Demo Ready")
def handle_button(pin):
"""按键中断回调"""
time.sleep_ms(30) # 消抖
if btn.value() == 0:
print("Click!")
# 配置按键中断(下降沿)
btn.irq(trigger=Pin.IRQ_FALLING, handler=handle_button)
while True:
state = (a.value() << 1) | b.value()
if state != last_state:
transition = (last_state << 2) | state
if transition in (0b0001, 0b0111, 0b1110, 0b1000):
count += 1
elif transition in (0b0010, 0b1011, 0b1101, 0b0100):
count -= 1
last_state = state
print(f"Count: {count}")
time.sleep_ms(1)
```
---
## 常见问题排查
| 现象 | 可能原因 | 解决方法 |
|------|----------|----------|
| 旋转无反应 | 上拉电阻缺失/VCC未接 | 检查接线;确认模块板载上拉是否正常 |
| 方向时对时错 | 软件消抖不足 | 使用状态机法替代简单延时 |
| 转一圈计数不是20 | 不同批次脉冲数不同 | 实测后调整每圈脉冲系数 |
| 中断法频繁误触发 | 触点抖动引入噪声 | A/B引脚各对地加100nF电容 |
| 按键偶尔不响应 | 消抖时间不当 | 适当增加消抖延时至30~50ms |
暂无参考文献