编码器 PLL 角度与速度观测器原理

一、为什么要使用 PLL 估算速度

在电机 FOC 控制中,编码器可以直接测量转子角度,但不能直接测量转子速度。

最简单的方法是使用相邻两次编码器角度作差:

速度 = (本次角度 - 上次角度)/采样周期

写成变量形式:

omega = (theta_now - theta_last) / Ts

其中:

  • theta_now:本次编码器角度
  • theta_last:上次编码器角度
  • Ts:采样周期
  • omega:计算得到的速度

这种方法虽然简单,但是编码器的量化误差、采样抖动和角度噪声都会被差分放大,导致计算出来的速度波动较大。

PLL 的思路不是直接对角度作差,而是在内部建立一个估算角度,让估算角度不断追踪编码器测量角度,同时得到比较平滑的估算速度。

二、PLL 中的变量

为了避免不同编辑器出现公式显示异常,本文采用以下变量名称:

  • theta_meas:编码器测量角度
  • theta_est:PLL 估算角度
  • omega_est:PLL 估算速度
  • error_theta:测量角度与估算角度之间的误差
  • k1:角度快速校正增益
  • k2:速度积分校正增益
  • Ts:PLL 执行周期

PLL 的核心思想可以概括为:

测量角度与估算角度作差

得到角度误差

k1 支路快速修正角度

k2 支路逐渐修正速度

速度积分得到估算角度

估算角度反馈,再次计算误差

三、首先计算角度误差

编码器测量角度与 PLL 估算角度之间的误差为:

cpp 复制代码
error_theta = wrap(theta_meas - theta_est)

其中 wrap() 表示角度环绕处理。

如果角度使用弧度,一般把角度误差限制在:

cpp 复制代码
error_theta = 2°

例如:

cpp 复制代码
theta_meas = 1°

theta_est = 359°

如果直接相减:

cpp 复制代码
error_theta = 1° - 359° = -358°

但两个角度实际只相差 2°。

经过角度环绕处理后:

cpp 复制代码
error_theta = 2°

因此,编码器 PLL 必须进行角度环绕处理,否则在角度经过 0° 或 360° 时,会产生一个非常大的错误角度差。

四、k1 支路:快速修正估算角度

k1 支路直接对当前角度误差进行比例放大:

cpp 复制代码
omega_corr = k1 × error_theta

其中 omega_corr 是角速度校正量。

这个校正量会与 PLL 当前的估算速度相加:

cpp 复制代码
theta_est 的变化速度 = omega_est + omega_corr

也就是:

cpp 复制代码
d(theta_est)/dt = omega_est + k1 × error_theta

然后对它进行积分,得到新的估算角度:

cpp 复制代码
theta_est = 积分[omega_est + k1 × error_theta]

k1 支路的作用

如果编码器测量角度在估算角度前面:

cpp 复制代码
error_theta > 0

那么:

cpp 复制代码
k1 × error_theta > 0

PLL 会增加估算角度的变化速度,让估算角度快速追赶编码器角度。

如果估算角度跑得太快:

cpp 复制代码
error_theta < 0

那么 k1 支路会降低估算角度的变化速度。

因此,k1 支路的作用是:

根据当前角度误差,立即产生一个角速度校正量,让估算角度快速跟随编码器角度。

k1 的单位

角度误差的单位是:rad。k1 乘以角度误差后,需要得到角速度:

cpp 复制代码
k1 × rad = rad/s

因此 k1 的单位是:k1 单位 = 1/s

也就是"每秒"。

例如:

cpp 复制代码
k1 = 100 1/s

error_theta = 0.1 rad

那么 k1 支路产生的角速度校正量为:

cpp 复制代码
omega_corr = 100 × 0.1

= 10 rad/s

意思是:

当前存在 0.1 rad 的角度误差时,k1 支路会额外产生 10 rad/s 的追赶速度。

五、k2 支路:逐渐修正估算速度

k2 支路同样来自角度误差:

cpp 复制代码
speed_change_rate = k2 × error_theta

然后经过积分,得到 PLL 的估算速度:

cpp 复制代码
omega_est = 积分[k2 × error_theta]

也可以写成:

cpp 复制代码
d(omega_est)/dt = k2 × error_theta

k2 支路的作用

如果:

cpp 复制代码
error_theta > 0

说明估算角度持续落后于编码器角度,PLL 会逐渐增加估算速度。

如果:

cpp 复制代码
error_theta < 0

说明估算角度跑得太快,PLL 会逐渐降低估算速度。

如果:

cpp 复制代码
error_theta = 0

速度不再被修正,估算速度保持当前值。

因此,k2 支路的作用是:

cpp 复制代码
k2 × error_theta

根据持续存在的角度误差,逐渐修正并保持 PLL 的估算速度。

六、为什么角度误差积分后可以得到速度

从真实物理关系来说,速度确实是角加速度积分得到的:

角速度 = 角加速度的积分

也就是:

cpp 复制代码
omega = 积分[alpha]

但是在 PLL 中:

并不是电机真实的角加速度,而是 PLL 根据角度误差构造出来的"估算速度变化率"。

可以将其理解为一种虚拟角加速度:

cpp 复制代码
虚拟角加速度 = k2 × error_theta

当估算角度持续落后时,PLL 认为当前估算速度太小,于是让估算速度不断增加。

当估算角度持续超前时,PLL 认为当前估算速度太大,于是让估算速度不断减小。

所以 PLL 不需要直接测量电机的真实角加速度,也能根据角度误差逐渐估算出速度。


七、k2 的单位

估算速度的单位是:rad/s

估算速度变化率的单位是:rad/s²

因为:

cpp 复制代码
k2 × error_theta = 估算速度变化率

所以:

cpp 复制代码
k2 × rad = rad/s²

最终得到:k2 单位 = 1/s²

因此:

  • k1 的单位是 1/s
  • k2 的单位是 1/s²

八、框图中的两个 1/s 是什么

PLL 框图中的 1/s 表示积分器。

第一个积分器的作用是:

cpp 复制代码
k2 × error_theta

↓ 积分

omega_est

也就是角度误差经过 k2 后积分,得到估算速度。

第二个积分器的作用是:

cpp 复制代码
omega_est + k1 × error_theta

↓ 积分

theta_est

也就是估算速度和角度校正量相加后,再积分得到估算角度。

需要区分下面两个概念:

  • k1 的单位为 1/s,表示"每秒"。
  • 框图中的 1/s 表示积分运算。

虽然它们都写成 1/s,但含义不同。


九、k1 和 k2 两条支路的区别

支路 输出量 主要作用 类似的控制作用
k1 支路 角速度校正量 快速减小当前角度误差 比例作用 P
k2 支路 估算速度变化率 修正并保持长期估算速度 积分作用 I

可以简单记忆为:

k1 负责让当前角度赶紧对齐,k2 负责把长期速度估准确。


十、PLL 的完整数学关系

PLL 的角度误差为:

cpp 复制代码
error_theta = wrap(theta_meas - theta_est)

估算速度的变化率为:

cpp 复制代码
d(omega_est)/dt = k2 × error_theta

估算角度的变化率为:

cpp 复制代码
d(theta_est)/dt = omega_est + k1 × error_theta

因此,PLL 的完整工作过程是:

cpp 复制代码
1. 读取编码器测量角度 theta_meas

2. 计算角度误差:

error_theta = wrap(theta_meas - theta_est)

3. k2 支路更新估算速度:

d(omega_est)/dt = k2 × error_theta

4. k1 支路产生角速度校正量:

omega_corr = k1 × error_theta

5. 估算速度与角速度校正量相加:

angle_rate = omega_est + omega_corr

6. 对 angle_rate 积分得到新的估算角度

7. 将估算角度反馈,继续与编码器角度比较

十一、PLL 参数的计算

PLL 是一个二阶系统,它的特征方程为:

cpp 复制代码
s² + k1 × s + k2

标准二阶系统的特征方程为:

cpp 复制代码
s² + 2 × zeta × wn × s + wn²

对比两者可以得到:

cpp 复制代码
k1 = 2 × zeta × wn
k2 = wn²

其中:

  • wn:PLL 的自然角频率,单位为 rad/s
  • zeta:阻尼比,没有单位
  • 常用阻尼比可以取 0.707

例如:

cpp 复制代码
zeta = 0.707
wn = 100 rad/s

则:

cpp 复制代码
k1 = 2 × 0.707 × 100
= 141.4 1/s

k2 = 100²
= 10000 1/s²

PLL 带宽越高:

  • 角度和速度响应越快
  • 动态延迟越小
  • 编码器噪声更容易进入速度估算

PLL 带宽越低:

  • 估算速度更加平滑
  • 编码器噪声更小
  • 速度响应更加滞后

因此,PLL 带宽并不是越高越好,需要在动态响应和速度噪声之间进行平衡。


十二、离散化后的计算过程

单片机不能直接执行连续积分,需要按照固定周期进行离散累加。

估算速度更新公式为:

cpp 复制代码
omega_est =
omega_est
+ k2 × error_theta × Ts

估算角度更新公式为:

cpp 复制代码
theta_est =
theta_est
+ (omega_est + k1 × error_theta) × Ts

其中 Ts 是 PLL 的真实执行周期。

例如 PLL 每 100 us 执行一次:

cpp 复制代码
Ts = 100 us
= 0.0001 s

如果执行频率为 20 kHz:

cpp 复制代码
Ts = 1 / 20000
= 0.00005 s

代码中的 Ts 必须与 PLL 的真实调用频率一致,否则 PLL 的实际带宽和计算出来的参数会不一致。


十三、C 语言实现

cpp 复制代码
typedef struct

{

float angle_est; /* 估算角度,rad */

float speed_est; /* 估算速度,rad/s */

float k1; /* 角度校正增益,1/s */

float k2; /* 速度校正增益,1/s^2 */

float Ts; /* PLL执行周期,s */

} EncoderPLL_t;

角度环绕函数:


static float WrapToPi(float angle)

{

const float PI = 3.14159265358979323846f;

const float TWO_PI = 6.28318530717958647692f;



while (angle >= PI)

{

angle -= TWO_PI;

}



while (angle < -PI)

{

angle += TWO_PI;

}


return angle;

}

PLL 更新函数:


void EncoderPLL_Update(EncoderPLL_t *pll, float angle_measured)

{

float angle_error;



/* 计算测量角度与估算角度之间的误差 */

angle_error =

WrapToPi(angle_measured - pll->angle_est);



/*

* k2支路:

* 角度误差经过k2后积分,更新估算速度

*/

pll->speed_est +=

pll->k2 * angle_error * pll->Ts;



/*

* k1支路:

* k1乘以角度误差得到角速度校正量

* 再与估算速度相加,积分得到估算角度

*/

pll->angle_est +=

(pll->speed_est

+ pll->k1 * angle_error)

* pll->Ts;



/* 将估算角度限制到 -π 到 π */

pll->angle_est =

WrapToPi(pll->angle_est);

}

十四、实际使用注意事项

1. 必须处理角度环绕

编码器角度经过 0° 和 360° 的交界处时,必须计算最短方向角度误差。

2. PLL 周期必须正确

代码中的 Ts 必须等于 PLL 的真实执行周期。

3. 启动时初始化估算角度

第一次读取到有效编码器角度时,建议执行:

cpp 复制代码
pll.angle_est = angle_measured;
pll.speed_est = 0.0f;

这样可以避免启动瞬间出现很大的角度误差和速度冲击。

4. 异常编码器数据不能更新 PLL

如果出现以下情况,不应该直接使用当前角度更新 PLL:

  • 编码器 CRC 校验失败
  • SPI 通信失败
  • 编码器角度跳变超过合理范围
  • 磁编码器数据无效
  • 编码器掉线

5. 机械角度和电角度不能混用

如果 PLL 输入的是机械角度,输出首先得到的是机械角速度。

电角速度需要通过极对数换算:

cpp 复制代码
电角速度 = 机械角速度 × 极对数

6. 带宽不能设置过高

PLL 带宽应该明显低于采样频率。

带宽过高会导致:

  • 速度噪声增大
  • 编码器量化误差被放大
  • 估算速度出现振荡
  • 离散系统稳定性下降

十五、总结

编码器 PLL 的本质是建立一个估算角度,并让它不断跟踪编码器测量角度。

核心关系为:

角度误差:

cpp 复制代码
error_theta = wrap(theta_meas - theta_est)

估算速度变化率:

cpp 复制代码
d(omega_est)/dt = k2 × error_theta

估算角度变化率:

cpp 复制代码
d(theta_est)/dt =

omega_est + k1 × error_theta

其中:

  • k1 支路根据当前角度误差,快速修正估算角度。
  • k2 支路根据持续存在的角度误差,逐渐修正估算速度。
  • 第一个积分器得到估算速度。
  • 第二个积分器得到估算角度。
  • 估算角度反馈后,继续与编码器测量角度比较。

一句话总结:

估算速度用于预测角度,角度误差用于修正估算角度和估算速度;k1 管快速跟随,k2 管长期速度。

相关推荐
AI风控技术指南1 小时前
大模型安全围栏厂商评估:以数美科技为例拆解能力、架构和 POC 指标
大数据·网络·人工智能
weixin_531670891 小时前
Interlude起来:久坐提醒软件为什么需要登录?有没有完全本地运行的Mac休息提醒软件?
人工智能·macos·swift
熊猫钓鱼>_>1 小时前
从“串数据“焦虑到 Space 自由,我用 Agent Bucket 智能体桶管游戏素材
开发语言·人工智能·游戏·agent·bucket·workbuddy·space
机器人猎头David1 小时前
机器视觉在机器人行业中有哪些实际应用?
人工智能·机器人·招聘·机器人猎头·猎头公司
2501_936960361 小时前
1.3 RAG检索增强生成实战:手把手教你落地AI技术
人工智能
skywalk81631 小时前
atomic chat量化居然比unsloth 更小更强
人工智能
元岳数字人小元2 小时前
极简运维体系,数字人一体机适配中小场景升级
运维·人工智能·开源·人机交互·交互·源代码管理
Nil2082 小时前
leetcode 146LRU缓存
算法·leetcode·缓存