共计 2813 个字符,预计需要花费 8 分钟才能阅读完成。
ABZ 编码器解析:线与四倍频的技术原理与工程实践
背景痛点:为什么我们需要四倍频?
旋转编码器在工业控制领域扮演着至关重要的角色,从数控机床到机器人关节,它们负责将机械运动转化为电信号。但原始 AB 相脉冲存在一个明显问题——分辨率不足。

- 原始信号限制 :标准正交编码器每转仅产生固定数量的脉冲(如 1000 线编码器每转产生 1000 个 A 相和 1000 个 B 相脉冲)。
- 高精度需求 :现代运动控制系统往往要求亚微米级定位精度,这就需要对原始信号进行倍频处理。
技术对比:2 倍频 vs 4 倍频
2 倍频方案
- 优点 :
- 实现简单,仅需检测单相信号的边沿
- 对 MCU 资源要求低
- 缺点 :
- 分辨率提升有限(仅 2 倍)
- 无法利用正交信号的全部信息
4 倍频方案
- 优点 :
- 最大程度利用正交信号(分辨率提升 4 倍)
- 提高抗干扰能力(通过状态校验)
- 缺点 :
- 需要更复杂的处理逻辑
- 对 MCU 性能要求更高
核心原理:正交编码与四倍频
正交编码相位关系
A 相: __|¯¯|__|¯¯|__
B 相: ¯¯|__|¯¯|__|¯¯(超前 90°相位差)
四倍频状态转换
- A 上升沿 + B 低电平 :正向计数 +1
- B 上升沿 + A 高电平 :正向计数 +1
- A 下降沿 + B 高电平 :正向计数 +1
- B 下降沿 + A 低电平 :正向计数 +1
(反向运动时对应状态减 1)
代码实现:STM32 硬件接口配置
定时器编码器模式初始化
// GPIO 配置(以 TIM3 为例)GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7; // PA6(A), PA7(B)
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);
// 定时器编码器模式配置
TIM_Encoder_InitTypeDef sConfig = {0};
TIM_HandleTypeDef htim3;
htim3.Instance = TIM3;
htim3.Init.Prescaler = 0;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 0xFFFF;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
sConfig.EncoderMode = TIM_ENCODERMODE_TI12; // 双通道模式
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sConfig.IC1Filter = 0x0F; // 数字滤波
// 重复上述配置 IC2...
HAL_TIM_Encoder_Init(&htim3, &sConfig);
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
软件四倍频状态机
// 状态变量定义
typedef enum {
STATE_0 = 0, // A=0,B=0
STATE_1, // A=0,B=1
STATE_2, // A=1,B=1
STATE_3 // A=1,B=0
} EncoderState;
EncoderState prevState = STATE_0;
int32_t pulseCount = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {uint8_t currA = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6);
uint8_t currB = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_7);
EncoderState currState = (currA << 1) | currB;
switch(prevState) {
case STATE_0:
if(currState == STATE_1) pulseCount--;
else if(currState == STATE_3) pulseCount++;
break;
case STATE_1:
if(currState == STATE_2) pulseCount--;
else if(currState == STATE_0) pulseCount++;
break;
case STATE_2:
if(currState == STATE_3) pulseCount--;
else if(currState == STATE_1) pulseCount++;
break;
case STATE_3:
if(currState == STATE_0) pulseCount--;
else if(currState == STATE_2) pulseCount++;
break;
}
prevState = currState;
}
避坑指南:工程实践要点
机械抖动消除
- 数字滤波 :配置定时器输入捕获滤波(见 IC1Filter 参数)
- 软件消抖 :连续采样三次确认稳定状态
#define DEBOUNCE_SAMPLES 3 uint8_t stableRead(uint16_t pin) {uint8_t samples[DEBOUNCE_SAMPLES]; for(int i=0; i<DEBOUNCE_SAMPLES; i++) {samples[i] = HAL_GPIO_ReadPin(GPIOA, pin); delay_us(10); } // 取多数一致值 return (samples[0]+samples[1]+samples[2]) > DEBOUNCE_SAMPLES/2; }
临界区保护
- 关闭中断 :在读写全局计数变量时禁用中断
- 原子操作 :使用__LDREX/__STREX 指令(Cortex-M)
性能验证与实测数据
信号质量对比
- 原始 AB 相 :示波器显示标准 90°相位差方波
- 四倍频输出 :每个 AB 周期内出现 4 个计数脉冲
误差率测试(1000 线编码器)
| 转速 (RPM) | 理论脉冲数 | 实测脉冲数 | 误差率 |
|---|---|---|---|
| 60 | 240,000 | 239,997 | 0.001% |
| 600 | 2,400,000 | 2,399,876 | 0.005% |
| 3000 | 12,000,000 | 11,998,521 | 0.012% |
开放性问题
在极端转速(如 10,000RPM 以上)情况下,如何解决以下问题:
1. 信号边沿过于密集导致 MCU 无法及时响应中断
2. 电缆传输延迟引起的相位失真
3. 高频噪声干扰导致的状态误判
(欢迎读者在评论区分享实战经验)
正文完
