GD32F407VE天空星开发板的旋转编码器EC12的实现
GD32F407VE天空星开发板的旋转编码器的实现
一、旋转编码器概述
1.1 什么是旋转编码器?
旋转编码器 (Rotary Encoder) 是一种机电元件,可以将旋转的机械位移转换成一系列的数字电信号。它本质上是一个"无限旋转的旋钮",为用户提供直观的交互体验。

1.2 主要应用场景
- 音量调节:音响系统、汽车音响的音量控制
- 菜单导航:设备设置界面的上下选择
- 参数设定:仪器仪表的精确数值调整
- 位置控制:电机转速、机械臂位置等精密控制
1.3 EC12旋转编码器特性
- 型号:EC12D1524403
- 引脚功能:
- A、B相:旋转检测
- D引脚:按下功能
- C、E引脚:未使用(根据原理图)
二、硬件设计与连接
2.1 引脚定义与接线
//////////////////// EC12 旋钮 ////////////////////
/* 接线说明
交互板 GD32F407A ===> PD11 (外部中断11)B ===> PD13 (普通输入)D ===> PD15 (外部中断15)3v3 ===> 3v3GND ===> GND
*/
2.2 硬件抽象层定义
//// 外部中断实现
#define EC12_A_RCU RCU_GPIOD
#define EC12_A_PIN GPIOD, GPIO_PIN_11
#define EC12_A_STATE() gpio_input_bit_get(EC12_A_PIN)
#define EC12_A_ON_TRIG() EXTI11_on_trig() // 外部中断11回调函数#define EC12_B_RCU RCU_GPIOD
#define EC12_B_PIN GPIOD, GPIO_PIN_13
#define EC12_B_STATE() gpio_input_bit_get(EC12_B_PIN)#define EC12_D_RCU RCU_GPIOD
#define EC12_D_PIN GPIOD, GPIO_PIN_15
#define EC12_D_STATE() gpio_input_bit_get(EC12_D_PIN)
#define EC12_D_ON_TRIG() EXTI15_on_trig() // 外部中断15回调函数
三、旋转检测原理
3.1 正交编码原理
旋转编码器采用正交编码技术,使用两个存在90度相位差的信号通道:
- A相 (Phase A) 和 B相 (Phase B)
- 通过两个信号的相位关系判断旋转方向

EC11是相反的结构,其他都是一样的
3.2 方向判断算法
根据A、B两相的相位关系,可以准确判断旋转方向:
/*
旋转方向判断逻辑:
- 如果 A是下降沿时,此时B为高电平,那么说明,旋钮是顺时针旋转(右转)。
- 如果 A是下降沿时,此时B为低电平,那么说明,旋钮是逆时针旋转(左转)。
- 如果 A是上升沿时,此时B为低电平,那么说明,旋钮是顺时针旋转(右转)。
- 如果 A是上升沿时,此时B为高电平,那么说明,旋钮是逆时针旋转(左转)。
*/
3.3 时钟类比理解
-
顺时针旋转 (Clockwise, CW) - 右转:
- 想象时钟指针从12点走向1点、2点
- A相上升沿时,B相为低电平(0)
-
逆时针旋转 (Counter-Clockwise, CCW) - 左转:
- 与时钟指针相反方向,从12点走向11点
- A相上升沿时,B相为高电平(1)
四、软件架构设计
4.1 头文件定义
#ifndef __BSP_EC12_H__
#define __BSP_EC12_H__#include "gpio_cfg.h"//// 中断实现
void EC12_init();// ======= 轻触按钮回调配置
#define EC12_USE_DOWN 1
#define EC12_USE_UP 1
void EC12_on_down();
void EC12_on_up();// ======= 旋转回调配置
#define EC12_USE_RIGHT 1
#define EC12_USE_LEFT 1
void EC12_on_turn_right(); // 顺时针旋转
void EC12_on_turn_left(); // 逆时针旋转#endif
4.2 核心实现代码
#include "bsp_ec12.h"void EC12_init() {printf("==EC12_init==\n");// B相配置为浮空输入GPIO_input(EC12_B_RCU, EC12_B_PIN, GPIO_PUPD_NONE);
}#define DOWN RESET
#define UP SET// 按键中断处理函数
void EC12_D_ON_TRIG() {static FlagStatus last_state = UP; // 上一次状态FlagStatus cur_state = EC12_D_STATE(); // 当前状态if (last_state != cur_state) {last_state = cur_state; // 保存状态if (cur_state == DOWN) {#if EC12_USE_DOWNEC12_on_down();#endif} else {#if EC12_USE_UPEC12_on_up();#endif}}
}// 旋转检测中断处理函数
void EC12_A_ON_TRIG() {static FlagStatus last_state = SET;FlagStatus cur_state = EC12_A_STATE();if (last_state != cur_state) {last_state = cur_state;FlagStatus b_state = EC12_B_STATE();if (cur_state == DOWN) { // 下降沿if (b_state == SET) { // 顺时针旋转#if EC12_USE_RIGHTEC12_on_turn_right();#endif} else { // 逆时针旋转#if EC12_USE_LEFTEC12_on_turn_left();#endif } } else { // 上升沿if (b_state == RESET) { // 顺时针旋转#if EC12_USE_RIGHTEC12_on_turn_right();#endif} else { // 逆时针旋转#if EC12_USE_LEFTEC12_on_turn_left();#endif }}}
}
五、外部中断配置
5.1 中断配置系统
// EXTI_config.h 配置
#define USE_EXTI_11 1 // A相旋转检测
#define USE_EXTI_15 1 // D相按键检测#if USE_EXTI_11#define EXTI11_RCU RCU_GPIOD#define EXTI11_PORT GPIOD#define EXTI11_PUPD GPIO_PUPD_PULLUP#define EXTI11_SOURCE_PORT EXTI_SOURCE_GPIOD#define EXTI11_TRIG_TYPE EXTI_TRIG_BOTH // 双边沿触发#define EXTI11_PRIORITY 2, 2
#endif#if USE_EXTI_15#define EXTI15_RCU RCU_GPIOD#define EXTI15_PORT GPIOD#define EXTI15_PUPD GPIO_PUPD_PULLUP#define EXTI15_SOURCE_PORT EXTI_SOURCE_GPIOD#define EXTI15_TRIG_TYPE EXTI_TRIG_BOTH // 双边沿触发#define EXTI15_PRIORITY 2, 2
#endif
5.2 通用中断配置函数
static void EXTI_config(rcu_periph_enum rcu, uint32_t port, uint32_t pin, uint32_t pull_up_down, uint8_t exti_port, uint8_t exti_pin,exti_line_enum linex, exti_trig_type_enum trig_type,uint8_t nvic_irq, uint8_t nvic_irq_pre_priority, uint8_t nvic_irq_sub_priority) {// 引脚初始化GPIO_input(rcu, port, pin, pull_up_down);// 系统配置时钟使能rcu_periph_clock_enable(RCU_SYSCFG);// 中断线连接配置syscfg_exti_line_config(exti_port, exti_pin);// EXTI初始化exti_init(linex, EXTI_INTERRUPT, trig_type);exti_interrupt_enable(linex);// NVIC中断配置nvic_irq_enable(nvic_irq, nvic_irq_pre_priority, nvic_irq_sub_priority);
}
六、应用层回调实现
6.1 基础回调函数
// ============= 用户回调函数,可移至业务模块
void EC12_on_down() { // 按下printf("ec12 down\n");// 实际应用中可执行菜单确认、功能切换等操作
}void EC12_on_up() { // 抬起printf("ec12 up\n");// 可执行释放状态处理
}void EC12_on_turn_right() { // 顺时针旋转printf("----顺时针旋转(右转)----\n");// 实际应用:音量增加、数值增大、菜单下翻等
}void EC12_on_turn_left() { // 逆时针旋转printf("----逆时针旋转(左转)----\n");// 实际应用:音量减小、数值减小、菜单上翻等
}
6.2 高级应用示例
// 音量控制应用
static int volume = 50;void EC12_on_turn_right_volume() {if(volume < 100) {volume += 2;printf("音量增加: %d%%\n", volume);// 更新显示或发送控制命令}
}void EC12_on_turn_left_volume() {if(volume > 0) {volume -= 2;printf("音量减少: %d%%\n", volume);// 更新显示或发送控制命令}
}void EC12_on_down_volume() {printf("静音切换\n");// 实现静音功能
}// 菜单导航应用
static int menu_index = 0;
static const char* menu_items[] = {"设置", "音乐", "视频", "系统"};void EC12_on_turn_right_menu() {menu_index = (menu_index + 1) % 4;printf("选中: %s\n", menu_items[menu_index]);
}void EC12_on_turn_left_menu() {menu_index = (menu_index - 1 + 4) % 4;printf("选中: %s\n", menu_items[menu_index]);
}void EC12_on_down_menu() {printf("进入: %s\n", menu_items[menu_index]);// 执行进入菜单操作
}
七、防抖与稳定性优化
7.1 软件防抖处理
// 增强版旋转检测,添加时间防抖
#define DEBOUNCE_DELAY_MS 5void EC12_A_ON_TRIG_enhanced() {static FlagStatus last_state = SET;static uint32_t last_time = 0;FlagStatus cur_state = EC12_A_STATE();uint32_t current_time = get_system_tick();// 时间防抖检查if((current_time - last_time) < DEBOUNCE_DELAY_MS) {return;}if (last_state != cur_state) {last_state = cur_state;last_time = current_time;// ... 原有的方向判断逻辑}
}
7.2 状态机实现
typedef enum {STATE_IDLE,STATE_ROTATING,STATE_BUTTON_PRESSED
} ec12_state_t;static ec12_state_t current_state = STATE_IDLE;void EC12_state_machine_handler() {switch(current_state) {case STATE_IDLE:// 等待事件break;case STATE_ROTATING:// 处理旋转事件break;case STATE_BUTTON_PRESSED:// 处理按钮事件break;}
}
八、系统集成与测试
8.1 完整初始化序列
void System_EC12_init() {// 初始化外部中断系统EXTI_init();// 初始化旋转编码器EC12_init();printf("旋转编码器系统初始化完成\n");printf("支持功能: 旋转检测、按键检测\n");
}void Application_main() {System_EC12_init();while(1) {// 主循环处理其他任务// 旋转编码器通过中断自动处理// 系统休眠或处理其他业务delay_ms(10);}
}
8.2 调试与测试
// 调试信息输出
void EC12_debug_info() {printf("A相状态: %d\n", EC12_A_STATE());printf("B相状态: %d\n", EC12_B_STATE());printf("D按键状态: %d\n", EC12_D_STATE());
}// 测试模式
void EC12_test_mode() {printf("进入旋转编码器测试模式\n");printf("旋转旋钮观察方向检测\n");printf("按下旋钮测试按键功能\n");
}
九、设计要点与最佳实践
9.1 硬件设计注意事项
- 上拉电阻:确保A、B、D引脚都有合适的上拉电阻
- 信号滤波:在噪声环境中添加硬件RC滤波器
- 布局优化:编码器尽量靠近MCU,减少信号线长度
- 电源稳定:确保3.3V电源干净稳定
9.2 软件优化建议
- 中断优先级:合理设置中断优先级,避免丢失脉冲
- 资源占用:中断处理函数尽量简洁,快速退出
- 状态保存:使用static变量保存历史状态
- 模块化设计:回调机制便于功能扩展
9.3 可靠性设计
// 错误检测与恢复
#define MAX_MISSED_PULSES 10static int error_count = 0;void EC12_error_handler() {error_count++;if(error_count > MAX_MISSED_PULSES) {printf("旋转编码器异常,尝试重新初始化\n");EC12_init();error_count = 0;}
}
十、总结
- 精确的方向检测:基于正交编码原理,准确识别顺时针/逆时针旋转
- 多功能集成:同时支持旋转检测和按键功能
- 高效的中断处理:双边沿触发,确保不丢失任何操作
- 灵活的架构:回调机制便于功能扩展和模块化设计
- 稳定性保障:软件防抖、错误恢复等可靠性设计
