一、正交信号与测速原理

增量式编码器输出两路相位差 90° 的方波信号:A 相(Channel A)和 B 相(Channel B)。旋转方向不同,A 与 B 的相位关系也不同:正转时 A 领先 B 90°,反转时 B 领先 A 90°。STM32 定时器编码器接口通过检测两个信号的边沿组合,可以在每个完整周期内产生 2 倍或 4 倍频 的计数脉冲,从而获得更高的分辨率。

在 4 倍频模式下,定时器的计数方向由 A、B 相电平状态决定,硬件自动完成鉴相与计数,无需软件干预。要实现速度测量,核心思路是:

  • 配置定时器为编码器模式(Slave Mode: Encoder Mode 1/2/3)
  • 开启定时器更新中断或使用定时器溢出计数
  • 周期读取计数寄存器(TIMx->CNT),并换算为转速

二、STM32 定时器编码器模式原理

STM32 的通用定时器(TIM2~TIM5)和高级定时器(TIM1、TIM8)都支持编码器接口。其本质是将定时器的两个输入通道 TI1 和 TI2 作为编码器输入,内部通过一个异或门和边沿检测逻辑,将 A/B 相信号转换为计数脉冲和方向信号。

关键寄存器与配置项:

  • TIMx_CCMR1:设置通道输入捕获映射,选择 IC1F 滤波和分频
  • TIMx_SMCR:设置 Slave Mode 为 Encoder Mode(101、110、111 分别对应模式 1、2、3)
  • TIMx_CR1:设置计数器方向(由硬件自动控制)和使能计数
  • TIMx_ARR:设定自动重装值,用于决定计数范围(通常设为 65535 或自定义值)
  • TIMx_CNT:当前计数值,代表位置(或速度差)

模式选择:

  • Encoder Mode 1(SMCR=101):只在 TI1 边沿计数,TI2 决定方向
  • Encoder Mode 2(SMCR=110):只在 TI2 边沿计数,TI1 决定方向
  • Encoder Mode 3(SMCR=111):在 TI1 和 TI2 的双边沿计数(4倍频),最常用

三、CubeMX 配置步骤

本文以 STM32F103 系列为例,使用 TIM4 的通道1(PA6)和通道2(PA7)。

  1. 设置时钟:RCC 开启 HSE,主频配置为 72MHz。
  2. 配置定时器 TIM4
    • Combined Channels: Encoder Mode
    • Prescaler: 0(编码器输入不需要分频)
    • Counter Period: 65535
    • Input Filter: 0(可根据实际情况设为 10~20 用于滤波)
    • Encoder Mode: Encoder Mode TI1 and TI2(即 4 倍频)
  3. 使能中断:NVIC Setting 中勾选 TIM4 global interrupt(若需要计算溢出次数,必须开启)。
  4. 生成代码:在 main.c 中初始化 TIM4,并启动编码器模式。

四、完整代码示例

/* main.c 中相关代码 */
#include "tim.h"

volatile int16_t encoder_raw = 0;        // 当前计数值(有符号)
volatile int32_t encoder_total = 0;      // 累计位置数

void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef *htim) {
    // GPIO 初始化已在 CubeMX 生成,此处无需额外处理
}

void Start_Encoder(void) {
    HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);
    __HAL_TIM_ENABLE_IT(&htim4, TIM_IT_UPDATE);  // 使能更新中断
}

void TIM4_IRQHandler(void) {
    if (__HAL_TIM_GET_FLAG(&htim4, TIM_FLAG_UPDATE)) {
        __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE);
        // 根据方向调整累计值。计数器递增溢出时,CNT 回到 0;递减下溢时,CNT 回到 ARR
        if (__HAL_TIM_GET_COUNTER(&htim4) > 32768) {
            encoder_total -= (htim4.Init.Period + 1);
        } else {
            encoder_total += (htim4.Init.Period + 1);
        }
    }
}

float Get_Speed_By_Encoder(float dt) {
    // dt 为两次采样的时间间隔(秒)
    int16_t current_cnt = (int16_t)__HAL_TIM_GET_COUNTER(&htim4);
    int32_t delta = current_cnt - encoder_raw;
    encoder_raw = current_cnt;

    // 需要考虑定时器重装造成的跳变,但增量编码器在短间隔内不会超过 ARR/2
    if (delta > 32767)   delta -= 65536;
    else if (delta < -32768) delta += 65536;

    encoder_total += delta;

    // 换算线速度或 RPM,需知道编码器线数和减速比
    // 假设编码器每转 N 线,4倍频后有 4*N 个计数。则转速(rpm) = delta / (4*N) / dt * 60
    float rpm = (float)delta / (4.0f * 1000) / dt * 60.0f; // 示例:1000线编码器
    return rpm;
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM4_Init();
    Start_Encoder();
    while (1) {
        // 每隔固定时间(如10ms)调用一次
        float rpm = Get_Speed_By_Encoder(0.01f);
        // 转速可输出到串口或用于 PID 控制
        HAL_Delay(10);
    }
}

五、测速换算与注意事项

  • 计数范围:TIM4 是 16 位计数器,ARR 设为 65535,CNT 在 0~65535 间循环。读取时要强制转换为 int16_t 才能正确解析有方向的值。
  • 溢出处理:若采样周期较长,必须开启更新中断,并记录溢出次数。上述代码中通过判断 CNT 值在中断中的大小近似判断方向,但更稳妥的方法是利用 TIM_CR1 的 DIR 位或将计数器配置为自动重装到 0xFFFF 再配合方向寄存器。
  • 滤波:在电机运行中会有机械抖动或电磁干扰,建议在 CubeMX 中设置 Input Filter(建议 20~50),可有效避免误计数。
  • 4倍频与分辨率:4倍频计数获得最高分辨率,但计数频率也更高。若电机转速很快,需确认定时器时钟与计数器位数是否够用。对于 16 位定时器,若计数脉冲频率超过 65536 CPU 时钟(72MHz),会溢出丢失。此时可考虑降低分频或改用双倍频模式。
  • 接线注意:A、B 相必须接在定时器对应通道上,且要共地。OC 输出通常需要上拉电阻,推荐使用 10kΩ 上拉到 3.3V。
  • 代码兼容性:若使用 HAL 库,需要确保 HAL_TIM_Encoder_StartMX_TIM4_Init 之后调用,并注意 TIM_CHANNEL_ALL 的写法对不同型号芯片可能存在区别。

六、扩展:M 法/T 法测速对比

  • M 法(测频法):固定采样时间,计数脉冲个数。适用于中高速,测速精度高。
  • T 法(测周法):测量相邻脉冲间隔时间,适用于低速。
  • STM32 编码器模式天然支持 M 法,但也可以结合定时器输入捕获实现 T 法测速。在实际项目中,可设定长短两个采样周期,低速用 T 法,高速用 M 法,实现宽范围高精度测速。

七、总结

STM32 的硬件编码器接口将繁琐的正交解码工作交给定时器外设,开发者只需读取计数值并做简单换算,即可获得精确的位置与速度信息。通过合理配置滤波、溢出处理和采样周期,能够稳定应对各类电机测速场景。希望本文的配置思路与示例代码能帮助你快速上手。