4、编码器与PWM同步:编码器位置读取与PWM载波对齐、Z信号硬件触发采样、基于定时器的同步方案
做FOC控制,最头疼的问题之一就是“采样噪声”。
你想想看,电流采样和位置读取如果不同步,算出来的角度和电流值就是“错位”的。轻则电流波形毛刺多,重则电机跑着跑着就抖起来。我早期做伺服驱动时,就吃过这个亏——明明PI参数调得挺好,一上负载就啸叫,查了三天才发现是编码器读取时刻没对齐PWM载波。
这一章,咱们就把“同步”这件事彻底讲透。
4.1 为什么需要同步?——采样错位的代价
先看一个典型场景:PWM在载波峰值处更新占空比,同时触发ADC采样电流。如果编码器位置是在PWM周期的任意时刻读取的,那位置和电流之间就存在一个随机的时间差。
这个时间差会导致什么?
- 角度误差:电机在高速旋转时,几微秒的延迟可能对应好几度的电角度偏差。
- 电流重构失真:Clark/Park变换用的角度和实际电流采样时刻不匹配,算出来的Id/Iq就是错的。
- 转矩脉动:最终反映在电机输出上,就是转矩波动,甚至速度不稳。
核心原则:电流采样时刻和位置读取时刻必须严格对齐到同一个PWM事件上。要么都在载波峰值,要么都在载波谷值。
4.2 编码器位置读取与PWM载波对齐
对齐方式主要有两种:软件对齐和硬件对齐。
4.2.1 软件对齐——靠中断优先级抢时间
最简单粗暴的方法:在PWM中断服务函数里,先读编码器位置,再启动ADC采样。
// 伪代码示例:PWM中断中顺序执行
void PWM_IRQHandler(void) {
// 1. 先读编码器位置(SPI或正交解码)
encoder_position = read_encoder();
// 2. 再启动ADC采样
ADC_StartConversion();
// 3. 等待ADC完成,读取电流值
while(!ADC_IsComplete());
current_u = ADC_GetValue(0);
current_v = ADC_GetValue(1);
// 4. 执行FOC计算
FOC_Calculate(encoder_position, current_u, current_v);
}
这种方式的优点是简单,缺点是时间确定性差。如果编码器读取耗时较长(比如SPI通信),就会挤占后续FOC计算的时间,甚至导致PWM周期溢出。
我曾经在一个项目里用软件对齐,编码器是SSI接口,时钟5MHz,读一次要20μs。PWM周期才50μs,结果FOC计算时间不够,只能降速运行。后来果断换了硬件同步方案。
4.2.2 硬件对齐——让外设自己干活
硬件对齐的核心思想:利用定时器的触发输出功能,在PWM载波到达特定相位时,自动触发编码器位置锁存和ADC采样。
具体做法:
- 配置PWM定时器的某个比较事件(比如计数器等于周期值的一半)作为触发源。
- 该触发信号连接到编码器接口的锁存输入(Latch Input)和ADC的触发输入。
- 硬件自动完成位置锁存和采样,CPU只需要在中断里读取结果。
我的习惯:在STM32上,我会用TIM1的TRGO信号触发TIM2的编码器模式锁存,同时触发ADC注入组采样。这样CPU几乎零开销,而且时间抖动小于一个时钟周期。
4.3 Z信号硬件触发采样——零位校准的黄金时刻
Z信号(索引脉冲)每转一圈出现一次,用于绝对零位校准。但Z信号和PWM载波之间没有天然的同步关系。
如果Z信号到来时,正好处于PWM周期的中间,而你的采样点却在载波峰值,那这次Z信号的位置信息就会被“错过”或“延迟”一个周期。
4.3.1 硬件触发方案
最可靠的做法:将Z信号接入定时器的外部触发输入,配置为硬件触发锁存。
- 当Z信号上升沿到来时,硬件立即锁存当前编码器计数值。
- 同时产生一个中断,通知CPU读取锁存值。
- 这个锁存动作与PWM载波完全无关,保证了零位时刻的绝对精度。
注意:Z信号通常只有1个脉冲宽度(比如1个编码器周期)。如果PWM中断优先级高于Z信号中断,Z信号可能被漏掉。我建议将Z信号中断设为最高优先级,或者使用硬件直接锁存,不依赖中断响应。
4.3.2 软件补偿方案
如果硬件不支持Z信号锁存,也可以用软件补偿:
- 在PWM中断中读取编码器位置。
- 同时记录Z信号是否在本次PWM周期内到来。
- 如果Z信号到来,根据PWM计数器的当前值和Z信号到来的时间差,反推出零位位置。
这种方法精度取决于时间差的测量精度,一般不建议用于高速场合。
4.4 基于定时器的同步方案——把同步做成“流水线”
对于高性能FOC(比如10kHz以上电流环),单靠一个PWM中断做所有事情已经不够了。我们需要把采样、计算、更新占空比做成一个流水线。
4.4.1 双定时器同步架构
我常用的方案:用两个定时器协同工作。
| 定时器 | 功能 | 频率/周期 |
|---|---|---|
| TIM1 | PWM生成 + 载波触发 | 20kHz (50μs) |
| TIM2 | 编码器计数 + 位置锁存 | 与TIM1同步 |
| TIM3 | ADC采样触发 + 计算完成标志 | 与TIM1同步 |
工作流程:
- TIM1在载波峰值时输出TRGO信号。
- TRGO信号同时触发TIM2锁存编码器位置,和TIM3启动ADC采样。
- ADC采样完成后,DMA自动将结果搬运到内存。
- TIM3在采样完成后产生中断,CPU执行FOC计算。
- 计算完成后,更新TIM1的比较值,准备下一个周期。
关键点:整个流水线中,位置锁存和ADC采样是硬件同步的,时间抖动小于10ns。CPU只在计算阶段介入,效率极高。
4.4.2 同步时序图
下面这张图展示了整个同步流程的时间关系:
4.5 避坑指南与实战经验
做了这么多年电机控制,我总结了几条关于同步的“血泪教训”:
- 不要相信“差不多”:位置和电流的采样时间差超过1μs,在高速下就会产生明显的角度误差。我见过有人用软件延时来“对齐”,结果不同温度下延时漂移,电机直接失步。
- Z信号一定要硬件处理:软件轮询Z信号的方式,在高速下几乎不可靠。我曾经在一个项目中用软件读Z信号,电机跑到3000rpm时,Z信号脉冲宽度只有几个微秒,中断响应稍微慢一点就丢了。
- 注意定时器同步的相位关系:如果PWM定时器和编码器定时器不同步,即使触发信号对齐了,实际锁存时刻也可能有偏差。我建议使用定时器的从模式,让编码器定时器完全跟随PWM定时器。
- DMA是好朋友:ADC采样结果用DMA搬运,可以避免CPU在采样过程中被中断打扰。我现在的项目里,所有采样数据都走DMA,CPU只在计算阶段介入。
我的推荐配置:对于20kHz电流环,我通常用TIM1做PWM,TIM2做编码器接口(从模式跟随TIM1),TIM3做ADC触发。三个定时器通过硬件触发链连接,CPU只负责FOC计算和通信。这样即使电流环跑到30kHz,CPU负载也不到40%。