09-编码器接口.md 8.9 KB


title: 编码器接口 tags: [STM32, TIM, 编码器, 正交解码]

created: 2026-07-06

TIM — 编码器接口

原理

PPT 参考图 -- 编码器接口简介

详细工作原理

正交信号原理:增量式编码器输出 A、B 两路方波信号,相位差 90°(正交)。正转时 A 相信号超前 B 相 90°,反转时 B 相超前 A 相 90°。每个完整的正交周期内,A 和 B 各产生一个上升沿和一个下降沿,共 4 个边沿。

硬件解码过程:STM32 通用定时器的编码器接口自动对 TI1(A 相)和 TI2(B 相)的边沿进行检测。编码器控制逻辑根据两相信号的相位关系自动判断旋转方向,并相应地递增或递减 CNT:

  • TIM_EncoderMode_TI1:仅在 TI1(A 相)的上升沿和下降沿各计一次,×2 倍频
  • TIM_EncoderMode_TI2:仅在 TI2(B 相)的边沿计数,×2 倍频
  • TIM_EncoderMode_TI12:TI1 和 TI2 的上升沿和下降沿各计一次,×4 倍频

×4 模式详解:一个完整正交周期(A 一个方波、B 一个方波)产生 4 个边沿——A↑、A↓、B↑、B↓。每个边沿 CNT 都加/减 1,因此编码器每物理周期计 4 次,分辨率是编码器本体线数的 4 倍。例如 400 线的编码器在 ×4 模式下每圈产生 1600 个计数。

与外部中断方案对比

  • 外部中断方式:A 相连接到 EXTI 引脚,在中断中读取 B 相电平判断方向。每个边沿都需要 CPU 响应中断、保护现场、执行判断代码,高频时(>10kHz)CPU 负载极高且容易丢步
  • 硬件编码器接口:边沿检测、方向判断、加减计数全部由硬件自动完成,不占用 CPU 资源,可稳定处理 >100kHz 的编码器信号,且不会丢步

CNT 值的有符号解释:反转时 CNT 递减到 0 后回绕到 65535(int16_t 解释为 -1),这是正常的硬件行为。将 TIM_GetCounter() 的返回值强制转换为 int16_t 即可得到有符号的计数值。

正交编码器

正交编码器输出两路相位差 90° 的脉冲信号:

A相 ──┬───┬───┬───┬───
      │   │   │   │
      └───┴───┴───┴───

B相 ────┬───┬───┬───┬───
      │   │   │   │   │
      └───┴───┴───┴───┘

正转时 A 领先 B 90°:
A: ──┬──┬──┬──
     │  │  │  │
     └──┴──┴──┘
B: ──┴──┬──┬──┬─
        │  │  │  │
        └──┴──┴──┘

反转时 B 领先 A 90°:
A: ──┴──┬──┬──┬─
        │  │  │  │
        └──┴──┴──┘
B: ──┬──┬──┬──
     │  │  │  │
     └──┴──┴──┘

编码器模式

PPT 参考图 -- 编码器基本结构与实例

STM32 定时器自带编码器接口,自动解码正交信号:

模式 计数方式 倍频 描述
TIM_EncoderMode_TI1 仅在 TI1 边沿计数 ×2 只根据 A 相计数
TIM_EncoderMode_TI2 仅在 TI2 边沿计数 ×2 只根据 B 相计数
TIM_EncoderMode_TI12 TI1+TI2 双边沿计数 ×4 最高分辨率

计数值的正负由两相信号相位差自动判断:

正转 → 计数器递增
反转 → 计数器递减
停止 → 计数器不变

定时器内部结构:

A相(TI1) ──→ 边沿检测 ──→ 编码器控制逻辑 ──→ CNT(递增/递减)
B相(TI2) ──→ 边沿检测 ──→                    ↑
                                        编码器模式配置

API 表格

核心函数

函数 描述
TIM_EncoderInterfaceConfig(TIMx, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising) 配置编码器接口模式
TIM_GetCounter(TIMx) 读取当前计数值(返回 uint16_t
TIM_SetCounter(TIMx, uint16_t) 设置计数器值
TIM_Cmd(TIMx, ENABLE) 使能定时器

参数说明

TIM_EncoderInterfaceConfig 参数:

void TIM_EncoderInterfaceConfig(
    TIM_TypeDef* TIMx,
    uint16_t TIM_EncoderMode,  // TIM_EncoderMode_TI1/TI2/TI12
    uint16_t TIM_IC1Polarity,  // TIM_ICPolarity_Rising/Falling
    uint16_t TIM_IC2Polarity
);

TIM_EncoderMode 可选项

倍频 计数更新条件
TIM_EncoderMode_TI1 ×2 TI1 上升沿+下降沿
TIM_EncoderMode_TI2 ×2 TI2 上升沿+下降沿
TIM_EncoderMode_TI12 ×4 TI1+TI2 上升沿+下降沿

代码示例

编码器读取位置 (TIM3_CH1=PA6, TIM3_CH2=PA7)

#include "stm32f10x.h"

void Encoder_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;  // 浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 编码器模式—定时器时基无需PSC分频
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_TimeBaseStructure.TIM_Prescaler     = 0;         // 不分频
    TIM_TimeBaseStructure.TIM_Period        = 65535;     // 最大值
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode   = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

    // 配置编码器接口: TI1+TI2 ×4 模式
    TIM_EncoderInterfaceConfig(TIM3,
        TIM_EncoderMode_TI12,
        TIM_ICPolarity_Rising,  // TI1 不反相
        TIM_ICPolarity_Rising   // TI2 不反相
    );

    TIM_Cmd(TIM3, ENABLE);
}

int16_t Encoder_GetCount(void)
{
    // 转 int16_t 才能体现正负
    return (int16_t)TIM_GetCounter(TIM3);
}

int main(void)
{
    Encoder_Init();
    while (1) {
        int16_t pos = Encoder_GetCount();
        // 此处可用串口打印 pos
        // 正转 → pos 正数递增
        // 反转 → pos 负数递减
        Delay_ms(10);
    }
}

清零并读取角度

// 编码器归零
void Encoder_Reset(void)
{
    TIM_SetCounter(TIM3, 0);
}

// 读取并自动清零(用于周期性采样)
int16_t Encoder_GetCountAndReset(void)
{
    int16_t val = (int16_t)TIM_GetCounter(TIM3);
    TIM_SetCounter(TIM3, 0);
    return val;
}

外部中断与编码器对比

// 外部中断方式(不推荐):
//   分别配置 A/B 相为外部中断,在中断中判断另一相电平
//   缺点:占用 CPU,高频时丢步

// 编码器接口方式(推荐):
//   硬件自动加减,不占用 CPU,不丢步
//   缺点:需要占用定时器通道

常见坑点

  1. 读出值要转 int16_tTIM_GetCounter() 返回 uint16_t(0–65535),编码器可能递减到负数。强制转为 int16_t 才能正确体现正反转

  2. PSC 必须为 0:编码器模式下 PSC 必须为 0(不分频),因为计数器靠外部脉冲计数,不能用内部时钟分频。设 PSC>0 会导致完全不计数或计数异常

  3. 最大值 65535 → -1 回绕:反转时计数器从 0 递减到 65535,int16_t 解释为 -1。这是正常的,不要当成错误

  4. TIM_ICPolarity 含义不同:编码器模式下,极性参数 TIM_ICPolarity_Falling 的含义变为反相输入信号,而不是捕获边沿。设为 Falling 相当于交换 A/B 相

  5. GPIO 必须为浮空或上拉输入:不要配成输出模式。编码器模块输出的是开漏/推挽信号,通常用浮空输入

  6. 和外部中断的区别

    • 外部中断:需要 CPU 参与每条边沿,高频(>10kHz)必定丢步
    • 编码器接口:硬件自动处理,可应对 >100kHz 编码器信号
  7. 单次最大计数范围:16位定时器 ±32767(int16_t)。如果需要更大范围,在代码中扩展为 int32_t:

    volatile int32_t encoder_pos = 0;
    volatile int16_t last_cnt = 0;
    
    void Encoder_Poll(void)
    {
    int16_t cur = (int16_t)TIM_GetCounter(TIM3);
    int16_t diff = cur - last_cnt;
    last_cnt = cur;
    encoder_pos += diff; // 自动处理溢出回绕
    }
    
  8. 多个编码器:STM32F103C8T6 只有一个定时器支持编码器模式(TIM3)。TIM2 不支持编码器模式(仅 TIM3/4 有编码器功能)

纠正:TIM2、TIM3、TIM4、TIM5 都支持编码器模式(STM32F1 通用定时器均支持)。高级定时器 TIM1/TIM8 不支持。