4. 编码器与速度测量:增量式编码器原理、STM32编码器接口模式、M法/T法/M-T法测速

做电机控制,速度反馈是闭环的灵魂。没有准确的速度测量,PID调得再好也是瞎忙活。今天咱们就聊聊编码器——这个在电机控制里最常见的“眼睛”。

我个人习惯把编码器比作电机的“码表”。它不直接告诉你速度,而是告诉你“转了多少格”。你得自己算,才能知道跑多快。这“怎么算”就是M法、T法、M-T法的核心。

4.1 增量式编码器工作原理

增量式编码器,说白了就是一个带光栅的圆盘。圆盘转,光栅切光,传感器输出脉冲。你数脉冲个数,就知道转了多少角度。

它通常输出两路信号:A相和B相。这两路信号相位差90度。通过判断谁先跳变,就能知道电机是正转还是反转。

核心概念:增量式编码器只输出“变化量”,不输出“绝对位置”。断电再上电,位置就丢了。需要配合归零校准。

我遇到过不少新手,上来就问:“编码器分辨率怎么算?”其实很简单:
分辨率 = 360° / (线数 × 4)

为什么乘以4?因为A相和B相的上升沿和下降沿都能用,这叫“四倍频”。比如一个1000线的编码器,四倍频后一圈就是4000个脉冲,每个脉冲对应0.09°。

参数 说明 常见值
线数(PPR) 编码器每圈输出的脉冲数 500、1000、2000、4096
四倍频后 实际可用的计数脉冲 4 × PPR
Z相(零位) 每圈输出一个脉冲,用于归零 1个/圈

4.2 STM32编码器接口模式

STM32的定时器有个很聪明的功能——编码器接口模式。你不需要在中断里手动判断A相B相,硬件全帮你干了。

配置起来其实就三步:

  1. 选定时器:STM32的TIM1~TIM8、TIM2~TIM5都支持编码器模式。具体看数据手册。
  2. 配GPIO:把A相接TI1,B相接TI2。注意要复用功能。
  3. 设模式:TIM_SlaveMode_Encoder,再选TI1和TI2都计数(这样就是四倍频)。
// STM32编码器模式配置示例(基于HAL库)
TIM_Encoder_InitTypeDef sEncoderConfig = {0};
sEncoderConfig.EncoderMode = TIM_ENCODERMODE_TI12;  // TI1和TI2都计数
sEncoderConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sEncoderConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
sEncoderConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sEncoderConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sEncoderConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sEncoderConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sEncoderConfig.IC1Filter = 0;
sEncoderConfig.IC2Filter = 0;

HAL_TIM_Encoder_Init(&htim3, &sEncoderConfig);
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);

我的经验:编码器线缆长的时候,信号容易抖动。我习惯在ICFilter里设个2~4的滤波值,能有效滤掉毛刺。别设太大,否则会丢失高速脉冲。

配置好之后,定时器的CNT寄存器会自动加减。正转加1,反转减1。你只需要定时读CNT值,再清零,就能算出这段时间的脉冲增量。

4.3 M法、T法、M-T法测速

有了脉冲数,怎么算速度?这就引出了三种经典方法。我当年刚入行时也纠结过该用哪个,后来发现——没有最好的,只有最合适的。

先看一张对比图,心里有个底:

M法 / T法 / M-T法 测速对比 M法(测频法) 固定时间 ΔT 数脉冲个数 M 速度 = M / (ΔT × PPR×4) 高速准,低速不准 T法(测周法) 固定脉冲数 M 测时间 ΔT 速度 = M / (ΔT × PPR×4) 低速准,高速不准 M-T法 同步测 M 和 ΔT 用高频时钟填充 全速域精度高 实现稍复杂 选型建议 • 高速电机(>1000 RPM):M法,简单够用 • 低速电机(< 100 RPM):T法,精度更高 • 宽调速范围(1~10000 RPM):M-T法,全速域兼顾

4.4 三种方法的详细实现

M法(测频法)

M法是最直观的。你固定一个时间窗口,比如10ms,数这个窗口内来了多少个脉冲。脉冲多,说明转得快。

// M法测速示例
uint32_t pulse_count;
float speed_rpm;

void M_Method_Timer_IRQ(void) {
    // 每10ms进入一次
    pulse_count = __HAL_TIM_GET_COUNTER(&htim3);
    __HAL_TIM_SET_COUNTER(&htim3, 0);
    
    // 速度 = 脉冲数 / (时间 × 编码器分辨率)
    // 假设编码器1000线,四倍频后4000脉冲/圈
    speed_rpm = (float)pulse_count / (0.01f * 4000.0f) * 60.0f;
}

注意:M法在低速时误差很大。比如10ms内只来了1个脉冲,那速度就是1.5 RPM。但如果实际是1.4 RPM呢?你测不出来。这就是“量化误差”。

T法(测周法)

T法反过来。我不固定时间,而是等固定数量的脉冲来了,再算用了多少时间。比如等10个脉冲,用定时器量这段时间。

低速时,脉冲间隔长,时间测得很准。但高速时,10个脉冲一瞬间就过去了,时间分辨率不够,误差反而大。

// T法测速思路(伪代码)
uint32_t time_interval;  // 单位us
uint32_t pulse_target = 10;  // 等10个脉冲

void T_Method_Edge_IRQ(void) {
    static uint32_t last_time = 0;
    static uint32_t pulse_cnt = 0;
    
    pulse_cnt++;
    if(pulse_cnt >= pulse_target) {
        time_interval = current_time - last_time;
        last_time = current_time;
        pulse_cnt = 0;
        
        // 速度 = 脉冲数 / (时间 × 分辨率)
        speed_rpm = (float)pulse_target / (time_interval * 1e-6f * 4000.0f) * 60.0f;
    }
}

M-T法

M-T法是我个人最推荐的方法。它同时测脉冲数和时间,用高频时钟来填充时间间隔。不管高速低速,精度都很好。

具体做法:启动一个高频定时器(比如1MHz),同时开始数编码器脉冲。当编码器脉冲数达到预设值(比如10个)时,停掉高频定时器,读出计数值。这样既知道脉冲数,又知道精确时间。

避坑指南:我曾经在一个项目里直接用M法测低速,结果速度反馈抖得像心电图。换成M-T法后,波形平滑多了。如果你的电机需要从零速到高速平稳运行,别犹豫,上M-T法。

4.5 实际项目中的选型建议

说了这么多,到底怎么选?我总结了一个简单的决策表:

应用场景 推荐方法 理由
高速风机(>3000 RPM) M法 脉冲密集,量化误差小,实现简单
低速云台(< 10 RPM) T法 脉冲间隔长,时间测量精度高
伺服电机(0~3000 RPM) M-T法 全速域精度一致,适合闭环控制
低成本玩具 M法 代码量小,MCU资源占用少