STM32 代码例程

知识库
知识库文档
/firmware/传感器/360度旋转编码器模块/360度旋转编码器模块 — Arduino/STM32 代码例程.md

文档

360度旋转编码器模块 — 代码例程


例程一:Arduino 平台(轮询法 + 状态机消抖)

/*
 * 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 平台(中断法,响应更快)

/*
 * 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 库(定时器编码器模式)

/*
 * 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轮询 + 状态机,无定时器占用)

/*
 * 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)

"""
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

信息

路径
/firmware/传感器/360度旋转编码器模块/360度旋转编码器模块 — Arduino/STM32 代码例程.md
更新时间
2026/5/26