1. 项目概述:STM32旋转编码器计数系统
作为一名嵌入式开发工程师,我最近使用零知增强板(STM32F407VET6主控)完成了一个旋转编码器计数系统的开发。这个项目不仅实现了编码器的精准计数和方向判断,还通过ST7789显示屏实时可视化展示了A/B相脉冲波形、格雷码状态变化和90°相位差特征。对于想要学习嵌入式硬件开发的朋友来说,这是一个非常实用的练手项目。
旋转编码器在工业控制、人机交互等领域应用广泛,但初学者往往难以理解其工作原理。这个项目的独特之处在于:
- 硬件上采用零知增强板+增量式编码器+3.5寸IPS屏的组合
- 软件实现了中断驱动架构,通过查找表解决抖动问题
- 可视化界面动态显示计数、方向及波形变化
2. 系统硬件设计
2.1 核心元件选型
在这个项目中,我精心挑选了以下硬件组件:
| 元件名称 | 规格型号 | 数量 | 关键特性说明 |
|---|---|---|---|
| 零知增强板 | STM32F407VET6 | 1 | 主控制器,168MHz Cortex-M4内核 |
| 旋转编码器 | EC11增量式编码器 | 1 | 20脉冲/转,带按键功能 |
| TFT显示屏 | ST7789 240×320 | 1 | 3.5寸IPS屏,SPI接口 |
| 杜邦线 | 20cm母对母 | 10 | 用于开发板与外围器件连接 |
选择STM32F407VET6作为主控是因为:
- 丰富的外设资源(多个定时器、中断引脚)
- 足够的处理能力应对实时波形显示
- 零知增强板已经内置了必要的电源电路和调试接口
2.2 硬件连接方案
硬件连接是项目成功的基础,我设计了如下接线方案:
| 零知增强板引脚 | 连接元件 | 元件引脚 | 功能说明 |
|---|---|---|---|
| 53 | ST7789 TFT | CS | 片选信号,低电平有效 |
| 9 | ST7789 TFT | DC | 数据/命令选择线 |
| 8 | ST7789 TFT | RST | 复位信号,低电平复位 |
| 51 (SDA) | ST7789 TFT | SDA | SPI数据线 |
| 52 (SCL) | ST7789 TFT | SCL | SPI时钟线 |
| 3.3V | ST7789 TFT | VCC | 电源正极 |
| GND | ST7789 TFT | GND | 电源地 |
| 2 | 旋转编码器 | CLK | A相信号(配置为外部中断) |
| 3 | 旋转编码器 | DT | B相信号(配置为外部中断) |
| 4 | 旋转编码器 | SW | 按键信号(内部上拉) |
| 5V | 旋转编码器 | + | 电源正极 |
| GND | 旋转编码器 | GND | 电源地 |
实际接线时特别注意:
- 显示屏背光引脚需要单独连接到3.3V
- 所有GND引脚必须共地
- 编码器CLK和DT引脚必须连接到支持外部中断的GPIO
3. 软件架构设计
3.1 系统整体流程
项目采用中断驱动+主循环刷新的架构设计:
mermaid复制graph TD
A[系统初始化] --> B[配置GPIO和中断]
B --> C[初始化显示屏]
C --> D[绘制UI界面]
D --> E{主循环}
E --> F[检测编码器更新]
E --> G[检测按键按下]
F --> H[更新计数显示]
F --> I[更新波形显示]
G --> J[重置系统]
3.2 核心算法实现
3.2.1 格雷码解码算法
旋转编码器输出的A/B相信号组合形成格雷码,其状态转换遵循特定规律:
c复制const int8_t encoderTable[] = {
0, -1, 1, 0, // 上次=00,当前=00,01,10,11
1, 0, 0, -1, // 上次=01
-1, 0, 0, 1, // 上次=10
0, 1, -1, 0 // 上次=11
};
这个查找表的原理是:
- 将上一次和当前的AB相状态组合成4位索引
- 查表得到方向值(-1、0、1)
- 累计4个有效方向变化才确认一次完整计数
3.2.2 波形显示优化
为了实现清晰的波形显示,我设计了专门的波形缓冲区:
c复制#define WAVE_BUFFER_SIZE 100
uint8_t waveBufferA[WAVE_BUFFER_SIZE];
uint8_t waveBufferB[WAVE_BUFFER_SIZE];
uint8_t waveIndex = 0;
void updateWaveform(uint8_t a, uint8_t b) {
waveBufferA[waveIndex] = a;
waveBufferB[waveIndex] = b;
waveIndex = (waveIndex + 1) % WAVE_BUFFER_SIZE;
}
显示时采用时基拉伸技术,每个采样点显示为3个像素宽度,使波形更易观察:
c复制int pixelPerSample = 3; // 时基拉伸系数
for(int i=0; i<displaySamples; i++){
int x = gridX + 2 + i * pixelPerSample;
// 绘制水平线段
for(int px=0; px<pixelPerSample; px++){
tft.drawPixel(x + px, yA, COLOR_SUCCESS);
}
}
4. 关键代码解析
4.1 中断服务函数
中断函数负责实时检测编码器状态变化:
c复制void updateEncoder() {
uint8_t MSB = digitalRead(CLK); // A相状态
uint8_t LSB = digitalRead(DT); // B相状态
uint8_t encoded = (MSB << 1) | LSB; // 组合当前格雷码
uint8_t tableIndex = (lastEncoded << 2) | encoded;
int8_t direction = encoderTable[tableIndex];
if (direction != 0) {
static int8_t accumulatedSteps = 0;
accumulatedSteps += direction;
updateWaveform(MSB, LSB); // 更新波形缓冲区
// 完整4步循环确认机制
if (accumulatedSteps >= 4) {
counter++;
directionCW = true;
accumulatedSteps = 0;
encoderUpdated = true;
}
else if (accumulatedSteps <= -4) {
counter--;
directionCW = false;
accumulatedSteps = 0;
encoderUpdated = true;
}
}
lastEncoded = encoded;
}
关键点说明:
- 使用volatile修饰全局变量,防止编译器优化
- 采用4步确认机制,避免误触发
- 波形更新与计数逻辑分离,保证实时性
4.2 显示界面设计
显示界面分为左右两个面板:
c复制void drawMainUI() {
tft.fillScreen(COLOR_BG);
// 左侧面板 - 计数和状态
drawLeftPanel();
// 右侧面板 - 波形显示
drawRightPanel();
}
void drawLeftPanel() {
// 计数值显示
tft.setTextSize(4);
if(counter > 0) tft.setTextColor(COLOR_SUCCESS);
else if(counter < 0) tft.setTextColor(COLOR_WARNING);
else tft.setTextColor(COLOR_TEXT);
tft.setCursor(centerX, 75);
tft.println(counter);
// 方向指示
tft.setTextSize(2);
if(directionCW) {
tft.setTextColor(COLOR_SUCCESS);
tft.println("CW >>");
} else {
tft.setTextColor(COLOR_WARNING);
tft.println("<< CCW");
}
}
5. 旋转编码器工作原理
5.1 光电编码原理
旋转编码器内部结构包含:
- 带栅格的光码盘
- 红外发射管和接收管
- 机械旋转机构
当码盘旋转时,栅格交替遮挡光线,在接收端产生脉冲信号。增量式编码器通常有两路输出(A相和B相),相位差90°。
5.2 正交编码信号
A/B相信号的相位关系决定了旋转方向:
- 顺时针旋转:A相脉冲超前B相90°
- 逆时针旋转:B相脉冲超前A相90°
这种设计使得方向判断非常可靠,即使存在抖动也能准确识别。
5.3 格雷码特性
旋转编码器输出的格雷码具有以下特点:
- 相邻状态只有1位变化
- 循环顺序:00→01→11→10→00(顺时针)
- 抗干扰能力强,减少误判
6. 项目调试与优化
6.1 常见问题解决
在实际开发中,我遇到了几个典型问题:
Q1:编码器按键按下无法重置计数?
排查步骤:
- 检查按键引脚是否配置为INPUT_PULLUP
- 测量按键按下时的电压变化
- 调整消抖时间(项目中使用50ms)
Q2:波形显示不清晰?
优化方案:
- 增加时基拉伸系数(pixelPerSample=3)
- 优化波形缓冲区大小(WAVE_BUFFER_SIZE=100)
- 添加状态跳变的垂直线段显示
6.2 性能优化技巧
通过实践,我总结了几个提升系统性能的技巧:
-
中断优化:
- 只做必要的操作(状态检测和计数)
- 将显示更新放在主循环中
-
显示优化:
- 使用局部刷新代替全屏刷新
- 预计算显示位置,减少重复计算
-
资源管理:
- 合理设置波形缓冲区大小
- 使用查找表代替实时计算
7. 项目扩展思路
这个基础项目还有很大的扩展空间:
-
功能扩展:
- 添加速度检测功能
- 实现多圈计数
- 增加预设值功能
-
应用场景:
- 工业设备控制面板
- 音频设备音量控制
- 机器人关节位置检测
-
硬件改进:
- 改用更高精度的编码器
- 添加蓝牙/WiFi无线传输
- 设计专用PCB板
8. 开发心得
通过这个项目的开发,我深刻体会到:
-
硬件设计要点:
- 信号完整性至关重要
- 合理的接地能减少干扰
- 电源滤波不可忽视
-
软件设计经验:
- 中断服务函数要尽可能简短
- 全局变量必须妥善保护
- 状态机设计让逻辑更清晰
-
调试技巧:
- 善用串口打印调试信息
- 分段验证各个功能模块
- 保持耐心,逐步排查问题
这个项目完整展示了从硬件连接到软件实现的整个过程,希望能给嵌入式开发爱好者提供有价值的参考。对于想要复现这个项目的朋友,建议先从理解旋转编码器原理入手,再逐步实现各个功能模块。