共计 2418 个字符,预计需要花费 7 分钟才能阅读完成。
技术背景:EC11 编码器的工作原理
EC11 是一种机械式旋转编码器,通过内部机械结构产生两路相位差 90 度的正交信号(A 相和 B 相)。当旋钮转动时,两路信号会产生特定的跳变序列:

- 顺时针旋转时,A 相跳变领先 B 相
- 逆时针旋转时,B 相跳变领先 A 相
机械编码器由于物理接触特性,在跳变时会产生 5 -20ms 的抖动(bouncing),这是软件必须处理的关键问题。EC11 广泛应用于音量控制、参数调节等需要精确旋转输入的场合。
硬件设计
电路连接图
EC11 CH32V
-------------------
A 相 ------> GPIOA.0 (外部中断)
B 相 ------> GPIOA.1 (普通输入)
COM ------> GND
上拉电阻:A 相 --4.7kΩ-- 3.3V
B 相 --4.7kΩ-- 3.3V
PCB 布局注意事项
- 信号线尽量短(<5cm)
- 靠近 EC11 放置 0.1uF 去耦电容
- 避免与高频信号线平行走线
- 上拉电阻建议使用 1% 精度
软件实现
状态机消抖算法
// 状态定义
typedef enum {
STATE_IDLE,
STATE_A_RISING,
STATE_A_FALLING,
STATE_B_RISING,
STATE_B_FALLING
} EncoderState;
// 全局变量
volatile EncoderState g_state = STATE_IDLE;
volatile uint32_t g_counter = 0;
volatile uint32_t g_lastTime = 0;
// 消抖时间阈值(单位:ms)#define DEBOUNCE_TIME 15
// 中断处理函数
void EXTI0_IRQHandler(void) {if(EXTI_GetITStatus(EXTI_Line0) != RESET) {uint32_t currentTime = GetSystemTick();
// 消抖判断
if((currentTime - g_lastTime) < DEBOUNCE_TIME) {EXTI_ClearITPendingBit(EXTI_Line0);
return;
}
g_lastTime = currentTime;
// 读取 B 相状态
uint8_t bState = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1);
// 状态机处理
switch(g_state) {
case STATE_IDLE:
if(bState) g_counter++;
else g_counter--;
break;
// 其他状态处理...
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
方向判断逻辑
// 获取旋转方向
int8_t GetEncoderDirection(void) {
static int16_t lastCount = 0;
int8_t dir = 0;
if(g_counter > lastCount) dir = 1;
else if(g_counter < lastCount) dir = -1;
lastCount = g_counter;
return dir;
}
性能优化
消抖时间参数调整
| 消抖时间 (ms) | 响应速度 | 稳定性 |
|---|---|---|
| 5 | 快 | 较差 |
| 10-15 | 中等 | 好 |
| 20+ | 慢 | 极好 |
中断优先级设置
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 中等优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
避坑指南
硬件常见问题
- 未接上拉电阻导致信号浮动
- 电源噪声导致误触发(加 0.1uF 电容可解决)
- 信号线过长引入干扰
软件常见错误
- 中断处理函数中执行耗时操作
- 未做变量 volatile 声明导致值不同步
- 消抖时间设置不合理
进阶建议
支持按键检测
// 连接 EC11 的 SW 引脚到 GPIO
#define ENC_SW_PIN GPIO_Pin_2
bool IsEncoderPressed(void) {
static uint32_t lastPressTime = 0;
if(GPIO_ReadInputDataBit(GPIOA, ENC_SW_PIN) == 0) {if(GetSystemTick() - lastPressTime > 50) {lastPressTime = GetSystemTick();
return true;
}
}
return false;
}
多编码器管理
- 为每个编码器创建独立的结构体
- 使用指针传递实例数据
- 共享中断服务例程
typedef struct {
GPIO_TypeDef* gpioA;
uint16_t pinA;
GPIO_TypeDef* gpioB;
uint16_t pinB;
int32_t counter;
} EncoderInstance;
void HandleMultipleEncoders(EncoderInstance* enc) {// 通用处理逻辑}
验证实验
- 连接 LED 到 GPIO
- 每转动一格改变 LED 状态
- 通过串口打印计数值
while(1) {if(g_counter != lastDisplayCount) {LED_Toggle();
printf("Count: %d\n", g_counter);
lastDisplayCount = g_counter;
}
}
学习资源推荐
- EC11 数据手册
- CH32V 参考手册(GPIO 和 EXTI 章节)
- 《嵌入式系统实时概念》
- ARM Cortex- M 中断机制详解
通过本指南,你应该已经掌握了 EC11 旋转编码器在 CH32V 平台上的完整开发流程。实际开发时建议先用示波器观察信号质量,再逐步调试软件参数,最终实现稳定可靠的旋转输入功能。
正文完
发表至: 未分类
近两天内
