3. 编码器接口与速度测量:正交编码器原理、STM32配置与速度滤波

做电机控制,速度反馈是闭环的灵魂。没有准确的速度测量,PID调得再好也是白搭。这一章我们就来啃下编码器这块硬骨头——从原理到STM32配置,再到速度计算和滤波,一条龙讲清楚。

3.1 正交编码器到底是个啥?

正交编码器,说白了就是两个相位差90°的方波信号。一个叫A相,一个叫B相。电机转一圈,A和B各输出N个脉冲。通过判断A和B的先后顺序,就能知道电机是正转还是反转。

我刚开始做项目时,总觉得编码器就是个计数器,后来发现没那么简单。你想想看,如果只有一路脉冲,你只能知道转了多少,但方向呢?不知道。正交编码器就是靠两路信号的相位关系来判方向的。

核心概念:

  • 分辨率:编码器每转一圈输出的脉冲数(PPR)。比如500线编码器,一圈500个脉冲。
  • 倍频:利用A、B的上升沿和下降沿,可以把分辨率翻4倍。500线变2000线。
  • Z相(零位):每圈一个脉冲,用于绝对位置校准。我一般只在需要回零时才用。

正交编码器的输出波形长这样:A相和B相相差90°电角度。正转时A领先B,反转时B领先A。就这么简单。

正交编码器输出波形 A相 B相 90° 正转:A领先B 反转:B领先A 利用A、B的上升沿和下降沿可实现4倍频

3.2 STM32编码器模式配置

STM32的定时器有个专门的编码器模式,用起来非常方便。你只需要把A相和B相接定时器的CH1和CH2,配置好模式,硬件自动帮你计数和判向。

我个人习惯用TIM2或TIM3来做编码器接口,因为这两个定时器在大部分STM32上都有,而且引脚位置比较顺手。

3.2.1 配置步骤

  1. 使能时钟:GPIO时钟和定时器时钟都要打开。
  2. 配置GPIO:A相和B相配置为复用推挽输出?不对,是浮空输入或上拉输入。这里容易搞错,我刚开始就栽过跟头。
  3. 配置定时器:设置编码器模式(TI1和TI2都计数),配置ARR(自动重装载值)。
  4. 使能定时器:调用TIM_Cmd()或HAL_TIM_Encoder_Start()。

我曾经踩过的坑:编码器模式下,定时器的预分频器(PSC)必须设为0。否则计数会丢步。还有,ARR要设得比编码器最大计数值大,不然会溢出。我一般设成0xFFFF,用16位模式。

3.2.2 代码示例(HAL库)

// 编码器初始化
void Encoder_Init(void)
{
    TIM_Encoder_InitTypeDef sEncoderConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    
    // 使能时钟
    __HAL_RCC_TIM3_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // GPIO配置:PA6(TIM3_CH1), PA7(TIM3_CH2)
    GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 定时器配置
    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 0;      // 必须为0
    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim3.Init.Period = 0xFFFF;    // 16位最大
    htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    HAL_TIM_Encoder_Init(&htim3, &sEncoderConfig);
    
    // 启动编码器模式
    HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
}

// 读取编码器计数值
int16_t Encoder_GetCount(void)
{
    return (int16_t)__HAL_TIM_GET_COUNTER(&htim3);
}

小技巧:用int16_t读取计数值,这样正反转自动带符号。正转增加,反转减少。省去手动判向的麻烦。

3.3 速度计算:M法、T法、M/T法

有了编码器计数值,怎么算速度?常用的有三种方法。我根据项目需求选,没有银弹。

方法 原理 适用场景 优缺点
M法(测频法) 固定时间内计脉冲数 高速(>100 RPM) 低速时误差大
T法(测周法) 测量两个脉冲的时间间隔 低速(<100 RPM) 高速时精度下降
M/T法 结合M法和T法 全速范围 实现稍复杂,精度最高

我做项目时,大部分情况用M法就够了。电机额定转速3000RPM,编码器500线,4倍频后每转2000个脉冲。采样周期1ms,算下来每个周期能捕获几十个脉冲,精度完全够用。

3.3.1 M法速度计算

// M法速度计算
// 输入:当前计数值,上次计数值,采样周期(ms),编码器线数,倍频数
float Speed_Calc_M(int16_t current_cnt, int16_t last_cnt, 
                   uint32_t period_ms, uint16_t ppr, uint8_t multiplier)
{
    int16_t delta = current_cnt - last_cnt;  // 脉冲增量
    float speed_rpm;
    
    // 速度 = (脉冲数 / (每转脉冲数)) / 时间(分钟)
    // 时间 = period_ms / 60000 (分钟)
    speed_rpm = (float)delta * 60000.0f / (float)(period_ms * ppr * multiplier);
    
    return speed_rpm;
}

注意:M法在低速时,如果采样周期内只捕获到1-2个脉冲,速度计算误差会很大。比如1ms内只抓到1个脉冲,算出来速度可能跳变50%。这时候要么加大采样周期,要么换T法。

3.4 速度滤波:让数据更平滑

算出来的速度直接给PID用?不行。编码器本身有机械抖动,加上电磁干扰,原始速度数据毛刺很多。我一般做两级滤波。

3.4.1 一阶低通滤波

最简单也最常用。说白了就是「这次的值 = 上次的值 × 系数 + 新测的值 × (1 - 系数)」。

// 一阶低通滤波
typedef struct {
    float last_value;
    float alpha;  // 滤波系数 0.0~1.0,越小滤波越强
} LowPassFilter_t;

float LowPass_Update(LowPassFilter_t *filter, float input)
{
    filter->last_value = filter->alpha * filter->last_value + 
                         (1.0f - filter->alpha) * input;
    return filter->last_value;
}

// 使用示例
LowPassFilter_t speed_filter = {0.0f, 0.8f};  // alpha=0.8,滤波较弱
float filtered_speed = LowPass_Update(&speed_filter, raw_speed);

我的经验:alpha值选0.7~0.9比较合适。太小了响应慢,太大了滤不掉毛刺。我一般先设0.8,看波形再调。如果电机机械振动大,就降到0.6。

3.4.2 滑动平均滤波

取最近N个速度值的平均。N越大越平滑,但延迟也越大。

// 滑动平均滤波
#define FILTER_SIZE 8

typedef struct {
    float buffer[FILTER_SIZE];
    uint8_t index;
    float sum;
} MovingAverage_t;

float MovingAverage_Update(MovingAverage_t *ma, float input)
{
    ma->sum -= ma->buffer[ma->index];
    ma->buffer[ma->index] = input;
    ma->sum += input;
    ma->index = (ma->index + 1) % FILTER_SIZE;
    return ma->sum / FILTER_SIZE;
}

滑动平均的优点是实现简单,缺点是有固定延迟。N=8时,延迟大约4个采样周期。如果你的控制周期是1ms,那延迟4ms,对大部分电机控制来说可以接受。

3.5 完整的速度测量流程

把上面这些串起来,一个完整的速度测量流程是这样的:

速度测量完整流程 编码器硬件(A/B相) STM32编码器模式计数 速度计算(M法/T法) 速度滤波(低通/滑动平均) 关键参数 采样周期:1ms 编码器线数:500 倍频:4倍

嗯,到这里编码器接口和速度测量就讲完了。实际项目中,我建议你先用示波器看编码器波形,确认A/B相没问题,再配置STM32。别一上来就写代码,硬件先调通,后面省心很多。