4. 编码器接口读取

任务目标

使用STM32定时器的编码器接口模式(Encoder Mode)连接增量式编码器,通过定时器硬件对A/B相脉冲进行正交解码,获取电机转子位置(累计脉冲数)与转速,并通过串口周期性输出。本案例以STM32F103系列为例,使用TIM4的CH1/CH2作为编码器输入。

硬件连接与依赖

  • 编码器A相 → PA6 (TIM4_CH1),B相 → PA7 (TIM4_CH2)
  • 编码器为推挽输出或开漏输出(需外部上拉),电压匹配3.3V
  • 依赖HAL库(stm32f1xx_hal_tim.h、stm32f1xx_hal_uart.h)
  • 串口:USART1(PA9/PA10),波特率115200

核心实现

定时器编码器模式利用硬件对A/B相信号进行正交解码,TIM4的计数器CNT在每对边沿自动增减,无需软件中断。位置通过读取CNT获得,转速通过定时采样CNT差值除以采样周期计算。以下代码完成初始化、位置/转速计算及串口输出。

// 编码器接口读取 - 基于STM32F103 HAL库
#include "stm32f1xx_hal.h"

TIM_HandleTypeDef htim4;
UART_HandleTypeDef huart1;

// 编码器参数
#define ENCODER_PPR      400   // 编码器物理分辨率(线/圈)
#define GEAR_RATIO       30    // 减速比(电机轴:输出轴)
#define SAMPLE_PERIOD_MS 100   // 转速采样周期(ms)
#define TIM4_PRESCALER   0     // 编码器模式不分频

// 位置与速度变量
volatile int32_t last_count = 0;
volatile int32_t position = 0;      // 累计脉冲数(带方向)
volatile float speed_rpm = 0.0f;    // 输出轴转速(RPM)

// 定时器编码器模式初始化
void Encoder_Init(void) {
    __HAL_RCC_TIM4_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_USART1_CLK_ENABLE();

    // GPIO: PA6=CH1, PA7=CH2
    GPIO_InitTypeDef gpio = {0};
    gpio.Pin = GPIO_PIN_6 | GPIO_PIN_7;
    gpio.Mode = GPIO_MODE_AF_PP;
    gpio.Pull = GPIO_PULLUP;      // 编码器输出为开漏时需上拉
    gpio.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &gpio);

    // TIM4 编码器模式配置
    htim4.Instance = TIM4;
    htim4.Init.Prescaler = TIM4_PRESCALER;
    htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim4.Init.Period = 0xFFFF;   // 16位自动重载
    htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Encoder_Init(&htim4, &(TIM_Encoder_InitTypeDef){
        .EncoderMode = TIM_ENCODERMODE_TI12,  // 双沿计数(4倍频)
        .IC1Polarity = TIM_ICPOLARITY_RISING,
        .IC2Polarity = TIM_ICPOLARITY_RISING
    });

    // 启动编码器接口
    HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);
    __HAL_TIM_SET_COUNTER(&htim4, 0);
}

// 串口初始化(115200-8-N-1)
void UART1_Init(void) {
    GPIO_InitTypeDef gpio = {0};
    gpio.Pin = GPIO_PIN_9 | GPIO_PIN_10;
    gpio.Mode = GPIO_MODE_AF_PP;
    gpio.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &gpio);

    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    HAL_UART_Init(&huart1);
}

// 读取当前累计位置(单位:脉冲数,带方向)
int32_t Encoder_GetPosition(void) {
    return (int32_t)__HAL_TIM_GET_COUNTER(&htim4);
}

// 计算转速:基于定时采样差值
void Encoder_UpdateSpeed(void) {
    int32_t current = Encoder_GetPosition();
    int32_t delta = current - last_count;   // 采样周期内脉冲增量
    last_count = current;

    // 转速计算:delta / (4*PPR) 为电机轴圈数,除以采样时间得RPM
    // 再除以减速比得到输出轴转速
    float motor_rpm = (float)delta * 60000.0f / (4.0f * ENCODER_PPR * SAMPLE_PERIOD_MS);
    speed_rpm = motor_rpm / GEAR_RATIO;
}

// 主循环示例
int main(void) {
    HAL_Init();
    UART1_Init();
    Encoder_Init();

    uint32_t last_tick = HAL_GetTick();
    char msg[64];

    while (1) {
        if (HAL_GetTick() - last_tick >= SAMPLE_PERIOD_MS) {
            last_tick = HAL_GetTick();
            Encoder_UpdateSpeed();
            position = Encoder_GetPosition();

            // 串口输出:位置(脉冲) 转速(RPM)
            int len = snprintf(msg, sizeof(msg), "Pos:%ld Speed:%.2f RPM\r\n",
                               (long)position, (double)speed_rpm);
            HAL_UART_Transmit(&huart1, (uint8_t*)msg, len, 100);
        }
    }
}

编译与运行

  1. 使用STM32CubeMX生成工程时,将TIM4的Combined Channels配置为Encoder Mode,并确认PA6/PA7已分配。
  2. 编译下载后,连接编码器A/B相至PA6/PA7,共地并确保供电匹配。
  3. 打开串口助手(115200-8-N-1),观察输出。

验证方法

  • 位置验证:手动缓慢旋转电机输出轴一整圈,串口输出的Pos值应变化 4 × PPR × GEAR_RATIO = 4×400×30 = 48000 个脉冲(正转增加,反转减少)。
  • 转速验证:用已知转速的电机驱动(或手摇匀速),观察Speed值是否在合理范围。例如输出轴1 RPM时,Speed应显示约1.0。
  • 方向验证:反转电机,Pos值递减,Speed显示负值。

若读数异常,检查编码器A/B相是否接反(交换PA6/PA7)、上拉电阻是否匹配、编码器供电电压是否在逻辑高电平范围内。