零知IDE——STM32旋转编码器计数系统:原理可视化 + 精准计数实现

✔零知开源(零知IDE)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从 "配置环境" 转移到 "创意实现",极大降低了技术门槛。零知IDE编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知开源(零知IDE)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!

✔访问零知实验室,获取更多实战项目和教程资源吧!

www.lingzhilab.com

目录

一、系统硬件部分

[1.1 元件清单](#1.1 元件清单)

[1.2 接线方案表](#1.2 接线方案表)

[1.3 接线示意图](#1.3 接线示意图)

[1.4 实物连接图](#1.4 实物连接图)

二、安装与使用部分

三、核心代码讲解

[3.1 核心中断服务函数](#3.1 核心中断服务函数)

[3.2 波形显示优化算法](#3.2 波形显示优化算法)

[3.3 布局与状态显示系统](#3.3 布局与状态显示系统)

[3.4 按键消抖与系统复位](#3.4 按键消抖与系统复位)

[3.5 完整代码](#3.5 完整代码)

四、项目结果演示

[4.1 操作流程](#4.1 操作流程)

[4.2 视频演示](#4.2 视频演示)

五、旋转编码器技术讲解

[5.1 光电编码原理](#5.1 光电编码原理)

[5.2 A/B相信号产生](#5.2 A/B相信号产生)

[5.3 格雷码编码](#5.3 格雷码编码)

六、常见问题解答(FAQ)

Q1:编码器按键按下无法重置计数?

Q2:波形显示不清晰、相位差看不到、波形断裂?


项目概述

基于零知增强板(主控 STM32F407VET6) 打造了一套旋转编码器计数系统,不仅实现了编码器的精准计数与方向判断,还通过 ST7789 显示屏实时可视化展示 A/B 相脉冲波形、格雷码状态变化和 90° 相位差特征,同时配套串口调试、按键重置功能

项目难点及解决方案

问题描述:单引脚边沿检测导致每转多格 / 少格计数,硬件抖动引发误触发

**解决方案:**同时监听 CLK/DT 两相的所有边沿CHANGE变化,实现4 步格雷码完整循环计数一次,采用 volatile 关键字保护中断中修改的全局变量

一、系统硬件部分

1.1 元件清单

元件名称 规格型号 数量 备注
零知增强板 STM32F407VET6 1 主控制器,支持硬件SPI
旋转编码器 增量式 AB 相旋转编码器 1 带按键,5V/3.3V兼容
TFT显示屏 ST7789 240×320 1 SPI接口,3.5寸IPS屏
杜邦线 20cm 母对母 10 连接导线

1.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 电源地

ST7789 显示屏的 SDA(MOSI)、SCL(SCK)使用零知增强板的默认 SPI 引脚,无需额外定义,代码中采用 Adafruit 库的硬件 SPI 实现

1.3 接线示意图

TFT显示屏的背光引脚需要单独连接到3.3V,确保所有GND引脚共地

1.4 实物连接图

二、安装与使用部分

2.1 开源平台-输入"旋转编码器计数系统 "并搜索-代码下载自动打开

2.2 连接-验证-上传

2.3 调试-串口监视器

三、核心代码讲解

代码采用中断驱动 + 主循环刷新的架构,中断中完成编码器状态解码、计数、波形缓冲区更新,主循环中完成显示屏刷新、按键检测,中断与主循环通过标志位通信,避免中断中直接操作显示屏

3.1 核心中断服务函数

cpp 复制代码
void updateEncoder() {
    uint8_t MSB = digitalRead(CLK);  // A相状态
    uint8_t LSB = digitalRead(DT);   // B相状态
    
    // 组合当前格雷码状态(2位二进制)
    uint8_t encoded = (MSB << 1) | LSB;
    
    // 计算查找表索引:前2位为上次状态,后2位为当前状态
    uint8_t tableIndex = (lastEncoded << 2) | encoded;
    
    // 从16状态查找表获取方向(-1, 0, 1)
    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;  // 触发显示更新
        }
        // 累计步数在-3到3之间:未完成完整循环,不计数
    }
    
    lastEncoded = encoded;  // 保存当前状态供下次使用
}

中断函数中的全局变量进行volatile关键字修饰,告诉编译器不要对该变量进行优化,因为该变量可能在任何时刻被中断修改

3.2 波形显示优化算法

cpp 复制代码
void updateWaveform(uint8_t a, uint8_t b) {
  waveBufferA[waveIndex] = a;
  waveBufferB[waveIndex] = b;
  waveIndex = (waveIndex + 1) % WAVE_BUFFER_SIZE; // 循环索引,避免越界
}

void updateWaveformDisplay() {
    int pixelPerSample = 3;  // 时基拉伸:每个采样点3像素
    int displaySamples = min(WAVE_BUFFER_SIZE, (gridW - 4) / pixelPerSample);
    
    for (int i = 0; i < displaySamples; i++) {
        int idx = (waveIndex + WAVE_BUFFER_SIZE - displaySamples + i) % WAVE_BUFFER_SIZE;
        int x = gridX + 2 + i * pixelPerSample;
        
        // A相波形绘制(绿色)
        int yA = gridY_A + (waveBufferA[idx] ? 5 : gridH - 5);
        
        // 绘制水平线段(稳定电平)
        for (int px = 0; px < pixelPerSample; px++) {
            tft.drawPixel(x + px, yA, COLOR_SUCCESS);
        }
        
        // 绘制垂直线段(状态跳变)
        if (i > 0) {
            int prevIdx = (waveIndex + WAVE_BUFFER_SIZE - displaySamples + i - 1) % WAVE_BUFFER_SIZE;
            if (waveBufferA[idx] != waveBufferA[prevIdx]) {
                int yA_prev = gridY_A + (waveBufferA[prevIdx] ? 5 : gridH - 5);
                tft.drawLine(x, yA_prev, x, yA, COLOR_SUCCESS);
            }
        }
        
        // B相波形同理...
    }
}

时基拉伸与可视化,pixelPerSample=3使波形更易观察;垂直连接线清晰展示状态变化时刻,上下区域避免波形重叠混淆

3.3 布局与状态显示系统

cpp 复制代码
// 左侧面板 - 计数器和状态信息
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);     // 零:白色
    }
    
    // 居中显示计数值
    String countStr = String(counter);
    int16_t x1, y1;
    uint16_t w, h;
    tft.getTextBounds(countStr, 0, 0, &x1, &y1, &w, &h);
    int centerX = (LEFT_PANEL_W - w) / 2;
    tft.setCursor(centerX, 75);
    tft.println(countStr);
    
    // 方向指示(动态文本)
    tft.setTextSize(2);
    if (directionCW) {
        tft.setTextColor(COLOR_SUCCESS);
        tft.setCursor(25, 148);
        tft.println("CW >>");  // 顺时针
    } else {
        tft.setTextColor(COLOR_WARNING);
        tft.setCursor(25, 148);
        tft.println("<< CCW"); // 逆时针
    }
}

绿色=正数/顺时针,黄色=负数/逆时针,白色=中性

3.4 按键消抖与系统复位

cpp 复制代码
void checkButton() {
    int btnState = digitalRead(SW);
    
    if (btnState == LOW) {
        // 50ms消抖,平衡响应速度和稳定性
        if (millis() - lastButtonPress > 50) {
            counter = 0;  // 计数器清零
            
            // 清空波形缓冲区
            for (int i = 0; i < WAVE_BUFFER_SIZE; i++) {
                waveBufferA[i] = 0;
                waveBufferB[i] = 0;
            }
            waveIndex = 0;
            
            // 重绘界面
            drawMainUI();
            
            Serial.println("\n 按钮按下 - 计数器已重置 \n");
        }
        lastButtonPress = millis();
    }
}

状态保存记录上次按下时间,避免重复触发;计数器、波形、显示三部分同步清零

3.5 完整代码

cpp 复制代码
/**************************************************************************************
 * 文件: RotaryEncoder_Display_Fixed.ino
 * 作者:零知实验室(深圳市在芯间科技有限公司)
 * -^^- 零知实验室,让电子制作变得更简单! -^^-
 * 时间: 2026-1-30
 * 
 * 项目功能:
 * 旋转编码器工作原理交互系统,实时可视化展示A/B相脉冲波形、
 * 格雷码状态变化和方向判断逻辑,帮助理解增量式旋转编码器工作机制。
 ***************************************************************************************/

#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>

// ST7789显示屏引脚定义
#define TFT_CS   53
#define TFT_DC   9
#define TFT_RST  8
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

// 屏幕尺寸(横屏)
#define SCREEN_WIDTH  320
#define SCREEN_HEIGHT 240

// 旋转编码器引脚定义
#define CLK 2
#define DT 3
#define SW 4

// 颜色定义 - 现代简约配色
#define COLOR_BG        0x0841    // 深蓝背景
#define COLOR_PRIMARY   0x07FF    // 青色主题色
#define COLOR_ACCENT    0xFD20    // 橙色强调色
#define COLOR_SUCCESS   0x07E0    // 绿色
#define COLOR_WARNING   0xFFE0    // 黄色
#define COLOR_TEXT      0xFFFF    // 白色文字
#define COLOR_SECONDARY 0x8410    // 灰色次要文字
#define COLOR_PANEL     0x1082    // 面板背景
#define COLOR_GRID      0x2945    // 网格线

// 布局定义
#define LEFT_PANEL_X    0
#define LEFT_PANEL_W    160
#define RIGHT_PANEL_X   160
#define RIGHT_PANEL_W   160
#define DIVIDER_X       159

// 全局变量
volatile long counter = 0;
volatile bool encoderUpdated = false;
volatile bool directionCW = true;
unsigned long lastButtonPress = 0;

// 编码器状态变量
volatile uint8_t lastEncoded = 0;

// 波形显示缓冲区
#define WAVE_BUFFER_SIZE 100
uint8_t waveBufferA[WAVE_BUFFER_SIZE];
uint8_t waveBufferB[WAVE_BUFFER_SIZE];
uint8_t waveIndex = 0;

// 格雷码查找表(修正方向)
const int8_t encoderTable[] = {
   0, -1,  1,  0,
   1,  0,  0, -1,
  -1,  0,  0,  1,
   0,  1, -1,  0
};

void setup() {
  // 设置编码器引脚为输入模式
  pinMode(CLK, INPUT);
  pinMode(DT, INPUT);
  pinMode(SW, INPUT_PULLUP);

  // 初始化串口
  Serial.begin(115200);
  Serial.println("========================================");
  Serial.println("旋转编码器演示系统(零知实验室)");
  Serial.println("========================================");

  // 初始化显示屏
  tft.init(240, 320);
  tft.setRotation(3);  // 横屏显示
  tft.fillScreen(COLOR_BG);

  // 显示启动画面
  drawStartupScreen();
  delay(2000);

  // 绘制主界面
  drawMainUI();

  // 初始化波形缓冲区
  for (int i = 0; i < WAVE_BUFFER_SIZE; i++) {
    waveBufferA[i] = 0;
    waveBufferB[i] = 0;
  }

  // 读取编码器初始状态
  lastEncoded = (digitalRead(CLK) << 1) | digitalRead(DT);

  // 附加中断,监听所有边沿变化
  attachInterrupt(digitalPinToInterrupt(CLK), updateEncoder, CHANGE);
  attachInterrupt(digitalPinToInterrupt(DT), updateEncoder, CHANGE);

  Serial.println("系统初始化完成");
  Serial.println("开始监听编码器...\n");
}

void loop() {
  // 检查编码器是否有更新
  if (encoderUpdated) {
    encoderUpdated = false;
    updateDisplay();
  }

  // 检查按钮
  checkButton();

  delay(10);
}

// 中断服务函数,使用查找表快速解码,解决抖动和误判
void updateEncoder() {
  // 读取当前编码器状态
  uint8_t MSB = digitalRead(CLK);
  uint8_t LSB = digitalRead(DT);
  
  uint8_t encoded = (MSB << 1) | LSB;
  uint8_t tableIndex = (lastEncoded << 2) | encoded;
  
  // 从查找表获取方向
  int8_t direction = encoderTable[tableIndex];

  if (direction != 0) {
    // 使用静态变量累计步进,完整4步循环才计数
    // 累计不足4步,转一格不计数的问题;需要完整循环,正转反转误判的问题
    static int8_t accumulatedSteps = 0;
    accumulatedSteps += direction;
    
    // 波形实时更新(不管是否计数)
    updateWaveform(MSB, LSB);
    
    // 每累计4步(完整的格雷码循环)才计数一次
    if (accumulatedSteps >= 4) {
      counter++;
      directionCW = true;
      accumulatedSteps = 0;  // 重置累计
      encoderUpdated = true;
      
      // 串口调试输出
      Serial.print("方向: 顺时针 | 计数: ");
      Serial.print(counter);
      Serial.print(" | 格雷码: ");
      Serial.print(MSB);
      Serial.println(LSB);
      
    } else if (accumulatedSteps <= -4) {
      counter--;
      directionCW = false;
      accumulatedSteps = 0;  // 重置累计
      encoderUpdated = true;
      
      // 串口调试输出
      Serial.print("方向: 逆时针 | 计数: ");
      Serial.print(counter);
      Serial.print(" | 格雷码: ");
      Serial.print(MSB);
      Serial.println(LSB);
    }
    // 如果累计步数在-3到3之间,说明还没完成一个完整循环,不计数
  }
  
  lastEncoded = encoded;
}

// 更新波形缓冲区
void updateWaveform(uint8_t a, uint8_t b) {
  waveBufferA[waveIndex] = a;
  waveBufferB[waveIndex] = b;
  waveIndex = (waveIndex + 1) % WAVE_BUFFER_SIZE;
}

// 检查按钮(改进消抖)
void checkButton() {
  int btnState = digitalRead(SW);
  
  if (btnState == LOW) {
    // 消抖时间从200ms改为50ms,响应更快
    if (millis() - lastButtonPress > 50) {
      counter = 0;
      
      // 清空波形缓冲区
      for (int i = 0; i < WAVE_BUFFER_SIZE; i++) {
        waveBufferA[i] = 0;
        waveBufferB[i] = 0;
      }
      waveIndex = 0;
      
      drawMainUI();
      
      Serial.println("\n 按钮按下 - 计数器已重置 \n");
    }
    lastButtonPress = millis();
  }
}

// ==================== UI绘制函数 ====================

// 启动画面
void drawStartupScreen() {
  tft.fillScreen(COLOR_BG);
  
  // 标题
  tft.setTextSize(3);
  tft.setTextColor(COLOR_PRIMARY);
  tft.setCursor(20, 60);
  tft.println("Rotary Encoder");
  
  tft.setTextSize(2);
  tft.setTextColor(COLOR_ACCENT);
  tft.setCursor(30, 100);
  tft.println("Counting System");
  
  // 副标题 旋转编码器计数系统
  tft.setTextSize(1);
  tft.setTextColor(COLOR_SECONDARY);
  tft.setCursor(60, 140);
  tft.println("Interactive Tool");
  
  // 零知实验室标识
  tft.setTextColor(COLOR_TEXT);
  tft.setCursor(80, 200);
  tft.println("Lingzhi Lab");
}

// 绘制主界面
void drawMainUI() {
  tft.fillScreen(COLOR_BG);
  
  // 绘制中间分隔线
  tft.drawFastVLine(DIVIDER_X, 0, SCREEN_HEIGHT, COLOR_SECONDARY);
  tft.drawFastVLine(DIVIDER_X + 1, 0, SCREEN_HEIGHT, COLOR_SECONDARY);
  
  // 左侧面板 - 计数和状态显示
  drawLeftPanel();
  
  // 右侧面板 - 波形显示
  drawRightPanel();
}

// 绘制左侧面板(计数器和状态)
void drawLeftPanel() {
  // 顶部标题
  tft.fillRoundRect(5, 5, LEFT_PANEL_W - 10, 30, 6, COLOR_PANEL);
  tft.setTextSize(2);
  tft.setTextColor(COLOR_PRIMARY);
  tft.setCursor(15, 13);
  tft.println("ENCODER");
  
  // 计数值显示区
  tft.fillRoundRect(5, 45, LEFT_PANEL_W - 10, 70, 6, COLOR_PANEL);
  tft.setTextSize(1);
  tft.setTextColor(COLOR_SECONDARY);
  tft.setCursor(15, 53);
  tft.println("Counter Value:");
  
  tft.setTextSize(4);
  tft.setTextColor(COLOR_TEXT);
  tft.setCursor(40, 75);
  tft.println("0");
  
  // 方向显示区
  tft.fillRoundRect(5, 125, LEFT_PANEL_W - 10, 40, 6, COLOR_PANEL);
  tft.setTextSize(1);
  tft.setTextColor(COLOR_SECONDARY);
  tft.setCursor(15, 133);
  tft.println("Direction:");
  
  tft.setTextSize(2);
  tft.setTextColor(COLOR_TEXT);
  tft.setCursor(25, 148);
  tft.println("---");
  
  // 状态信息区
  tft.fillRoundRect(5, 175, LEFT_PANEL_W - 10, 60, 6, COLOR_PANEL);
  tft.setTextSize(1);
  tft.setTextColor(COLOR_ACCENT);
  tft.setCursor(15, 183);
  tft.println("Gray Code: 00");
  
  tft.setTextColor(COLOR_SECONDARY);
  tft.setCursor(15, 198);
  tft.println("Phase: 90deg");
  
  tft.setCursor(15, 213);
  tft.println("Press SW to Reset");
}

// 绘制右侧面板(波形显示)
void drawRightPanel() {
  // 顶部标题
  tft.fillRoundRect(RIGHT_PANEL_X + 5, 5, RIGHT_PANEL_W - 10, 30, 6, COLOR_PANEL);
  tft.setTextSize(2);
  tft.setTextColor(COLOR_PRIMARY);
  tft.setCursor(RIGHT_PANEL_X + 15, 13);
  tft.println("WAVEFORM");
  
  // 波形显示区域
  tft.fillRoundRect(RIGHT_PANEL_X + 5, 45, RIGHT_PANEL_W - 10, 190, 6, COLOR_PANEL);
  
  // A相标签
  tft.setTextSize(2);
  tft.setTextColor(COLOR_SUCCESS);
  tft.setCursor(RIGHT_PANEL_X + 15, 55);
  tft.println("A");
  
  // B相标签
  tft.setTextColor(COLOR_WARNING);
  tft.setCursor(RIGHT_PANEL_X + 15, 145);
  tft.println("B");
  
  // 绘制波形网格
  drawWaveformGrid();
  
  // 图例说明
  tft.setTextSize(1);
  tft.setTextColor(COLOR_SECONDARY);
  tft.setCursor(RIGHT_PANEL_X + 45, 60);
  tft.println("CLK (Phase A)");
  
  tft.setCursor(RIGHT_PANEL_X + 45, 150);
  tft.println("DT  (Phase B)");
}

// 绘制波形网格
void drawWaveformGrid() {
  int gridX = RIGHT_PANEL_X + 40;
  int gridY_A = 75;   // A相波形位置
  int gridY_B = 165;  // B相波形位置
  int gridW = 105;
  int gridH = 40;
  
  // A相波形区域
  tft.fillRect(gridX, gridY_A, gridW, gridH, COLOR_BG);
  tft.drawRect(gridX, gridY_A, gridW, gridH, COLOR_GRID);
  tft.drawLine(gridX, gridY_A + gridH/2, gridX + gridW, gridY_A + gridH/2, COLOR_GRID);
  
  // B相波形区域
  tft.fillRect(gridX, gridY_B, gridW, gridH, COLOR_BG);
  tft.drawRect(gridX, gridY_B, gridW, gridH, COLOR_GRID);
  tft.drawLine(gridX, gridY_B + gridH/2, gridX + gridW, gridY_B + gridH/2, COLOR_GRID);
}

// 更新显示内容
void updateDisplay() {
  // 更新计数值
  updateCounterDisplay();
  
  // 更新波形
  updateWaveformDisplay();
  
  // 更新方向
  updateDirectionDisplay();
  
  // 更新格雷码状态
  updateGrayCodeDisplay();
}

// 更新计数器显示
void updateCounterDisplay() {
  // 清除旧数值
  tft.fillRect(10, 70, LEFT_PANEL_W - 20, 40, COLOR_PANEL);
  
  // 显示新数值
  tft.setTextSize(4);
  
  // 根据数值正负改变颜色
  if (counter > 0) {
    tft.setTextColor(COLOR_SUCCESS);
  } else if (counter < 0) {
    tft.setTextColor(COLOR_WARNING);
  } else {
    tft.setTextColor(COLOR_TEXT);
  }
  
  // 居中显示
  String countStr = String(counter);
  int16_t x1, y1;
  uint16_t w, h;
  tft.getTextBounds(countStr, 0, 0, &x1, &y1, &w, &h);
  int centerX = (LEFT_PANEL_W - w) / 2;
  tft.setCursor(centerX, 75);
  tft.println(countStr);
}

// 优化波形显示,拉伸时基,更清晰展示相位差
void updateWaveformDisplay() {
  int gridX = RIGHT_PANEL_X + 40;
  int gridY_A = 75;
  int gridY_B = 165;
  int gridW = 105;
  int gridH = 40;
  
  // 清除波形区域
  tft.fillRect(gridX + 1, gridY_A + 1, gridW - 2, gridH - 2, COLOR_BG);
  tft.fillRect(gridX + 1, gridY_B + 1, gridW - 2, gridH - 2, COLOR_BG);
  
  // 绘制中线
  tft.drawLine(gridX, gridY_A + gridH/2, gridX + gridW, gridY_A + gridH/2, COLOR_GRID);
  tft.drawLine(gridX, gridY_B + gridH/2, gridX + gridW, gridY_B + gridH/2, COLOR_GRID);
  
  // 绘制波形 - 每个样本占3个像素,拉伸显示
  int pixelPerSample = 3;
  int displaySamples = min(WAVE_BUFFER_SIZE, (gridW - 4) / pixelPerSample);
  
  for (int i = 0; i < displaySamples; i++) {
    int idx = (waveIndex + WAVE_BUFFER_SIZE - displaySamples + i) % WAVE_BUFFER_SIZE;
    
    int x = gridX + 2 + i * pixelPerSample;
    
    // A相波形(绿色)- 绘制方块状波形
    int yA = gridY_A + (waveBufferA[idx] ? 5 : gridH - 5);
    
    // 绘制水平线段(拉伸显示)
    for (int px = 0; px < pixelPerSample; px++) {
      tft.drawPixel(x + px, yA, COLOR_SUCCESS);
    }
    
    // 绘制垂直连接线(状态变化时)
    if (i > 0) {
      int prevIdx = (waveIndex + WAVE_BUFFER_SIZE - displaySamples + i - 1) % WAVE_BUFFER_SIZE;
      if (waveBufferA[idx] != waveBufferA[prevIdx]) {
        int yA_prev = gridY_A + (waveBufferA[prevIdx] ? 5 : gridH - 5);
        tft.drawLine(x, yA_prev, x, yA, COLOR_SUCCESS);
      }
    }
    
    // B相波形(黄色)- 同样处理
    int yB = gridY_B + (waveBufferB[idx] ? 5 : gridH - 5);
    
    for (int px = 0; px < pixelPerSample; px++) {
      tft.drawPixel(x + px, yB, COLOR_WARNING);
    }
    
    if (i > 0) {
      int prevIdx = (waveIndex + WAVE_BUFFER_SIZE - displaySamples + i - 1) % WAVE_BUFFER_SIZE;
      if (waveBufferB[idx] != waveBufferB[prevIdx]) {
        int yB_prev = gridY_B + (waveBufferB[prevIdx] ? 5 : gridH - 5);
        tft.drawLine(x, yB_prev, x, yB, COLOR_WARNING);
      }
    }
  }
}

// 更新方向显示
void updateDirectionDisplay() {
  // 清除旧内容
  tft.fillRect(10, 143, LEFT_PANEL_W - 20, 20, COLOR_PANEL);
  
  tft.setTextSize(2);
  
  if (directionCW) {
    tft.setTextColor(COLOR_SUCCESS);
    tft.setCursor(25, 148);
    tft.println("CW >>");
  } else {
    tft.setTextColor(COLOR_WARNING);
    tft.setCursor(25, 148);
    tft.println("<< CCW");
  }
}

// 更新格雷码显示
void updateGrayCodeDisplay() {
  // 清除旧内容
  tft.fillRect(10, 180, LEFT_PANEL_W - 20, 50, COLOR_PANEL);
  
  tft.setTextSize(1);
  
  // 显示格雷码
  tft.setTextColor(COLOR_ACCENT);
  tft.setCursor(15, 183);
  tft.print("Gray Code: ");
  
  uint8_t clkState = digitalRead(CLK);
  uint8_t dtState = digitalRead(DT);
  
  tft.print(clkState);
  tft.print(dtState);
  
  // 显示相位关系
  tft.setTextColor(COLOR_SECONDARY);
  tft.setCursor(15, 198);
  tft.print("Phase Shift: 90");
  tft.print((char)247);  // 度数符号
  
  // 提示信息
  tft.setCursor(15, 213);
  tft.println("Press SW to Reset");
}

/******************************************************************************
 * 深圳市在芯间科技有限公司
 * 淘宝店铺:在芯间科技零知板
 * 店铺网址:https://shop533070398.taobao.com
 * 版权说明:
 *  1.本代码的版权归【深圳市在芯间科技有限公司】所有,仅限个人非商业性学习使用。
 *  2.严禁将本代码或其衍生版本用于任何商业用途。
 *  3.任何商业用途均需事先获得【深圳市在芯间科技有限公司】的书面授权。
******************************************************************************/

系统整体流程图

展示中断、主循环、UI 刷新、按键检测的逻辑关系

差分测量模式原理与实现

cpp 复制代码
// 差分测量通过比较相邻状态实现
uint8_t encoded = (digitalRead(CLK) << 1) | digitalRead(DT);
uint8_t tableIndex = (lastEncoded << 2) | encoded;
int8_t direction = encoderTable[tableIndex];

// 查找表定义状态转移方向
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
};

将A/B相电平组合为2位格雷码,当前状态与上次状态组成4位索引,查表得到方向值,累计4个有效方向变化确认一次计数

差分测量原理图

每个棘爪格对应完整的4状态循环

四、项目结果演示

4.1 操作流程

根据上述接线方案表完成硬件接线、烧录代码到零知增强板,驱动系统实现完整的功能

①系统启动

零知增强板通过 USB 供电,系统上电后首先显示启动画面,自动进入分屏主 UI,左侧显示计数、右侧显示 A/B 相波形网格

②正常操作

顺时针旋转编码器,计数器增加,方向显示"CW >>",A相波形超前B相90°;逆时针旋转编码器,计数器减少,方向显示"<< CCW",B相波形超前A相90°;观察波形区域,清晰看到两相脉冲的相位差和状态变化

③系统复位

按下编码器中间按键,计数器归零,波形清空,界面刷新

4.2 视频演示

零知增强板旋转编码器计数系统

首先展示系统启动过程,然后演示顺时针和逆时针旋转时计数器的变化、方向指示的切换、以及波形显示的实时更新。特别展示了A/B相脉冲波形的90°相位差特征,以及按下复位键后系统清空重置的过程。最后通过串口监视器展示实时的状态输出信息

五、旋转编码器技术讲解

旋转编码器是一种将旋转运动转换为电信号的传感器,增量式编码器通过输出两路相位差90°的方波信号来检测旋转方向和角度变化

5.1 光电编码原理

编码器内部有一个带有栅格的光码盘,红外发射管和接收管分别位于码盘两侧、旋转时,栅格交替遮挡光线,产生脉冲信号

5.2 A/B相信号产生

正交编码: 两个光电传感器安装位置相差1/4个栅格间距,产生相位差90°的A相和B相信号

正转:A 相脉冲的上升沿 / 下降沿超前B 相 90°, A 相先变化,B 相后变化

反转:B 相脉冲的上升沿 / 下降沿超前A 相 90°, B 相先变化,A 相后变化

5.3 格雷码编码

A/B相组合形成4种状态:00、01、11、10,相邻状态仅有 1 位发生变化

顺时针旋转时,A 引脚先于 B 引脚接地,格雷码按00→01→11→10→00的顺序循环

逆时针旋转时,B 引脚先于 A 引脚接地,格雷码按00→10→11→01→00的顺序循环

当A改变状态时:如果 B != A,则顺时针转动旋钮、如果 B = A,则逆时针转动旋钮

六、常见问题解答(FAQ)

Q1:编码器按键按下无法重置计数?

*A:进行排查:*代码已启用INPUT_PULLUP,外接上拉电阻会导致按键电平无法拉低;按键无反应,调整增大消抖时间;通过万用表测量按键按下 / 松开的通断

Q2:波形显示不清晰、相位差看不到、波形断裂?

*A:进行以下排查:*调整pixelPerSample,进一步拉伸时基;匀速旋转编码器实现连续波形、检查波形缓冲区WAVE_BUFFER_SIZE

项目资源整合

图形显示基础库: adafruit/Adafruit-GFX-Library

显示屏驱动库: adafruit/Adafruit-ST7735-Library

相关推荐
LS_learner2 小时前
ROS (Robot Operating System) 一个灵活的机器人软件框架及其适用平台
嵌入式硬件
兆龙电子单片机设计2 小时前
【STM32项目开源】STM32单片机厂房环境安全监测
stm32·单片机·安全·开源·毕业设计·电子信息
chengpei1472 小时前
Ender-3S加装3DTouch实现自动网床校准
单片机·嵌入式硬件·3d打印
松涛和鸣2 小时前
DAY69 Practical Guide to Linux Character Device Drivers
linux·服务器·arm开发·数据库·单片机·嵌入式硬件
bai5459362 小时前
STM32 CubeIDE 超声波测距
stm32·单片机·嵌入式硬件
robet_hua3 小时前
DSP28377D项目实战 (4) - 高速串口打印波形
单片机·嵌入式硬件
mftang3 小时前
STM32Cube IDE 详细介绍
ide·stm32·嵌入式硬件
克莱斯勒ya3 小时前
嵌入式编码器(Embedded Coder)
嵌入式硬件
码农三叔4 小时前
(9-2-02)电源管理与能源系统:能耗分析与功率管理(2)高峰功耗控制+ 电源分配架构
嵌入式硬件·机器人·人机交互·能源·人形机器人