引言
在高性能永磁同步电机(PMSM)的矢量控制(FOC)系统中,高质量的转子位置与转速反馈是实现精准磁场定向与电流解耦的基石。反馈信号的精度与平滑度,直接决定了驱动系统的稳态转矩脉动、动态跟随响应,以及电机在极低速下的运行表现。由于结构紧凑、性价比高且抗干扰能力强,增量式编码器早已成为中小功率伺服系统中获取位置信息的主流方案。
然而,如何从离散的增量脉冲中提取高质量的速度信号,一直是工程实践中的一大挑战。传统的速度解算主要依赖 M 法、T 法或 M/T 混合测速法。这类方案本质上都属于开环的离散脉冲差分计算:M 法在低速时存在严重的量化误差;T 法在高速时受限于高频时钟的分辨率;M/T 法虽取长补短,但由于其“位置求导”的底层逻辑未变,输出的速度信号往往伴随着明显的阶梯状跳变与高频噪声。在工程应用中,若直接对这种含噪信号使用常规的一阶低通滤波器(LPF)进行平滑,又不可避免地会引入显著的相位滞后,从而限制速度环的控制带宽并影响系统稳定性。
为了打破“信号平滑度与相位延迟”之间的博弈,基于锁相环(PLL, Phase Locked Loop)的闭环观测算法提供了一种更为优雅的解算思路。与传统的直接求导不同,PLL 测速的核心在于“相位跟踪”。它将编码器解析出的实际机械角度作为参考基准,通过鉴相环节计算出“观测角度”与“实际角度”的偏差,再交由 PI 环路进行动态滤波与校正,最终积分输出平滑的观测角度与观测转速。这种闭环机制不仅能大幅削弱离散脉冲引入的量化噪声,还能实现对转速的零延迟或低延迟跟踪,为伺服系统的稳定运行提供了极佳的反馈数据。
PLL是什么?在编码器测速中是如何工作的?
PLL,全称 Phase Locked Loop,中文叫锁相环。它的核心思想非常简单: 我自己内部生成一个“估计角度,然后不断和真实测得的角度
meas比较。如果估计角度落后了,就提高估计速度;如果估计角度超前了,就降低估计速度。最终让估计角度锁住输入角度,同时得到一个平滑的速度估计值。在电机测速场景里,PLL可以理解为一个二阶位置跟踪观测器。
编码器PLL测速的基本框图
其中:
- 输入:编码器测得的位置
- 输出 1:估计速度
ω̂ - 输出 2:估计角度
θ̂ 核心误差:
PI 控制器根据位置误差输出速度:
然后速度积分得到估计角度:
这就是典型的二阶 Type-II PLL。
为什么 PLL 比传统 M 法 / 差分测速更平滑?
传统差分测速本质是:
这个公式的物理意义是每间隔 Ts 时间读取一次编码器角度,用两次角度差值,除以采样间隔,得到这段时间内的平均角速度,并把该平均值当作当前时刻瞬时转速。但这本身存在一个问题:编码器的角度是量化的。
假设编码器一圈有CPR个计数,则一个脉冲对应的机械角度为:
差分测速时,一个采样周期内多一个脉冲或少一个脉冲,速度就会跳变:
举个例子:
- 编码器四倍频后:
= 10000 - 采样周期:
= 100 us
因此我们可以得到:
换算为RPM可以得到:
也就是说,传统差分测速在这个条件下,速度分辨率就是60 rpm 一级跳变。低速时会非常抖。
而 PLL 不直接对位置做差分,而是用一个二阶系统去“跟踪位置”,速度是内部观测器状态输出,因此能明显抑制量化噪声。
增量式编码器 PLL 测速离散化数学模型
编码器脉冲数到机械角度
我们做如下假设:
| 符号 | 含义 |
|---|---|
第k次采样时的累计脉冲数 | |
| 编码器一圈计数数,注意是四倍频后的计数 | |
| 采样周期 | |
| 电机极对数 | |
| 测得的机械角度 |
| 测得的电角度 |
我们首先计算机械角度:
由机械角度就可以得到电角度:
机械角速度和电角速度的关系为:
注意:如果你是做速度环控制,通常建议 PLL 直接工作在机械角度上,输出机械角速度,如果你是做无感 FOC 或电角度估计,则可以让 PLL 工作在电角度上。
连续域 PLL 数学模型
由PLL结构图我们可以得到在连续域下的数学结构方程
其中:
:位置误差,单位 rad
:积分器状态,单位 rad/s
:估计角速度,单位 rad/s
:估计角度,单位 rad
:比例增益
:积分增益
在实际的工程中由于无法在mcu中实现这个连续域的数学模型结构,因此我们需要将其进行离散化。
模型离散化公式
采用前向欧拉离散化,固定采样周期为:
第一步:位置测量: 通过读取到的计数值去计算当前实际的位置信息
第二步:位置误差:计算实际位置与估算位置的差值
如果使用累计脉冲数,即多圈连续角度:
如果角度是0 -周期量,则需要做角度环绕:
其中:表示将角度限制在
。
第三步:PI 积分状态更新
第四步:估计速度输出
第五步:估计角度更新
这就是 MCU 中最常用的编码器 PLL 离散实现形式。
PLL 的闭环传递函数
连续域下,PLL 开环传递函数为:
将s带入:
闭环角度跟踪传递函数:
因为:
而真实的速度:
所以速度估计相对于真实速度的闭环传递函数也是:
这个系统的特征方程为:
与标准二阶系统对比:
可以得到非常重要的整定关系:
其中:
:PLL 自然频率,单位 rad/s
:阻尼比,一般取 0.707 - 1.0
MATLAB / Simulink 仿真与频域分析
Simulink仿真分析
首先我们在Simulink中搭建测试环节,测试环节搭建分为四个模块,为了便于搭建每个模块均采样M-Funcation实现,在进行仿真之前我们需要确定几个前置条件,首先仿真模型我们设置为定步长,模拟MCU采样执行周期,其次我们设置CPR计数值为10000,这个CPR的含义代表我们在使用增量式编码器在MCU中电机旋转一圈对应的计数值。这四个模块分别为:
- 参考速度生成模块
- 电机角度加编码器量化模块
- 传统差分测速模拟模块
- 软件PLL锁相环测速模块
- 误差分析模块
接线来对每个模块进行实现和分析:
参考速度生成模块:
模块内部实现:
function [t, omega_true, rpm_true] = MF_SpeedProfile() %#codegen % 速度轨迹发生器 % % 输出: % t : 当前仿真时间,单位 s % omega_true : 真实机械角速度,单位 rad/s % rpm_true : 真实机械转速,单位 rpm Ts = 1e-4; % 采样周期 PI = 3.141592653589793; persistent k; if isempty(k) k = uint32(0); end t = double(k) * Ts; % ===================== 速度轨迹设计 ===================== % 0 ~ 0.05 s : 0 rpm % 0.05 ~ 0.15 s : 0 -> 1000 rpm 斜坡 % 0.15 ~ 0.35 s : 1000 rpm 恒速 % 0.35 ~ 0.45 s : 1000 -> 300 rpm 斜坡 % 0.45 ~ 0.60 s : 300 rpm 恒速 if t < 0.05 rpm_true = 0.0; elseif t < 0.15 rpm_true = 1000.0 * (t - 0.05) / 0.10; elseif t < 0.35 rpm_true = 1000.0; elseif t < 0.45 rpm_true = 1000.0 - 700.0 * (t - 0.35) / 0.10; else rpm_true = 300.0; end omega_true = rpm_true * 2.0 * PI / 60.0; k = k + 1; end这个模块比较简单,通过这模块我们可以生成给定的参考速度与参考角速度。
电机角度加编码器量化模块
模块内部实现:
function [theta_true, theta_meas, count, q_noise] = MF_EncoderModel(omega_true) %#codegen % 电机真实角度 + 增量式编码器量化模型 % % 输入: % omega_true : 真实机械角速度,rad/s % % 输出: % theta_true : 连续真实机械角度,rad % theta_meas : 编码器量化后的机械角度,rad % count : 编码器累计计数值 % q_noise : 位置量化误差,rad Ts = 1e-4; PI = 3.141592653589793; CPR = 10000.0; % 四倍频后每机械圈计数数 dtheta = 2.0 * PI / CPR; % 一个 count 对应角度 persistent theta; if isempty(theta) theta = 0.0; end % 真实角度积分 theta = theta + omega_true * Ts; theta_true = theta; % 编码器量化 % 这里模拟增量式编码器只能输出整数 count count = int32(round(theta_true / dtheta)); % 再由 count 还原成测量角度 theta_meas = double(count) * dtheta; % 量化误差 q_noise = theta_meas - theta_true; end这个模块模拟的是:
编码器量化:
其中:
传统差分测速模拟模块
模块内部实现:
function [omega_diff, rpm_diff] = MF_DiffSpeed(theta_meas) %#codegen % 传统差分测速 % % omega_diff[k] = (theta_meas[k] - theta_meas[k-1]) / Ts Ts = 1e-4; PI = 3.141592653589793; persistent theta_last; persistent init_flag; if isempty(init_flag) init_flag = false; theta_last = 0.0; end if init_flag == false omega_diff = 0.0; rpm_diff = 0.0; theta_last = theta_meas; init_flag = true; else omega_diff = (theta_meas - theta_last) / Ts; rpm_diff = omega_diff * 60.0 / (2.0 * PI); theta_last = theta_meas; end end这个模块就是传统测速:
它的缺陷:编码器输出位置是离散整数脉冲,只要计数值变化1个count,计算出的速度就会产生阶跃跳变,转速波形毛刺严重。
软件PLL锁相环测速模块
模块内部实现:
function [omega_pll, rpm_pll, theta_hat, theta_hat_mod, err_pll, omega_i] = MF_PLLSpeedObserver(count) %#codegen % 增量式编码器软件 PLL 速度观测器 % % 输入: % count : 编码器累计计数值 % % 输出: % omega_pll : PLL 估计机械角速度,rad/s % rpm_pll : PLL 估计机械转速,rpm % theta_hat : PLL 估计连续机械角度,rad % theta_hat_mod : PLL 估计单圈机械角度,0~2pi % err_pll : PLL 位置误差,rad % omega_i : PLL 积分器状态,rad/s Ts = 1e-4; PI = 3.141592653589793; TWO_PI = 6.283185307179586; CPR = 10000.0; dtheta = TWO_PI / CPR; % ===================== PLL 参数 ===================== Kp = 888.4420; Ki = 394784.1760; omega_limit = 1000.0; % rad/s integrator_limit = 1000.0; % rad/s persistent theta_hat_state; persistent omega_i_state; persistent init_flag; if isempty(init_flag) init_flag = false; theta_hat_state = 0.0; omega_i_state = 0.0; end % 由累计 count 得到测量角度 theta_meas = double(count) * dtheta; % 初始化:让 PLL 初始角度等于测量角度 if init_flag == false theta_hat_state = theta_meas; omega_i_state = 0.0; init_flag = true; end % ===================== 1. 计算位置误差 ===================== % 这里使用累计 count,所以 theta_meas 是多圈连续角度 % 不需要 wrapToPi err_pll = theta_meas - theta_hat_state; % ===================== 2. 积分器更新 ===================== % omega_i[k] = omega_i[k-1] + Ki * Ts * err[k] omega_i_state = omega_i_state + Ki * Ts * err_pll; % 积分器限幅 if omega_i_state > integrator_limit omega_i_state = integrator_limit; elseif omega_i_state < -integrator_limit omega_i_state = -integrator_limit; end % ===================== 3. PI 输出速度 ===================== % omega_pll[k] = omega_i[k] + Kp * err[k] omega_pll = omega_i_state + Kp * err_pll; % 速度限幅 if omega_pll > omega_limit omega_pll = omega_limit; elseif omega_pll < -omega_limit omega_pll = -omega_limit; end % ===================== 4. 速度积分得到估计角度 ===================== theta_hat_state = theta_hat_state + Ts * omega_pll; % 输出状态 theta_hat = theta_hat_state; omega_i = omega_i_state; % 单圈角度 0~2pi theta_hat_mod = mod(theta_hat_state, TWO_PI); % rad/s -> rpm rpm_pll = omega_pll * 60.0 / TWO_PI; end这个模块对应的离散 PLL 公式是:
具体的实现方式可以这么理解:将 MCU 读到的编码器脉冲计数值,换算为实际机械角度。计算当前实测角度与上一拍 PLL 输出估计角度的差值,作为环路相位跟踪误差。PI 调节器积分项离散迭代,比例项 + 积分项合并,输出 PLL 平滑后的估计角速度;比例环节快速抑制瞬时角度误差,积分环节消除稳态静差。角速度积分得到下一时刻估计角度完成闭环相位跟踪。
误差分析模块
模块内部实现:
function [err_diff_rpm, err_pll_rpm, rms_diff_rpm, rms_pll_rpm] = MF_ErrorAnalysis(t, omega_true, omega_diff, omega_pll) %#codegen % 速度误差和稳态 RMS 分析 % % 这里在 0.20s ~ 0.34s 之间统计 RMS % 因为这段时间速度为 1000 rpm 恒速,适合比较噪声 PI = 3.141592653589793; radps_to_rpm = 60.0 / (2.0 * PI); rpm_true = omega_true * radps_to_rpm; rpm_diff = omega_diff * radps_to_rpm; rpm_pll = omega_pll * radps_to_rpm; err_diff_rpm = rpm_diff - rpm_true; err_pll_rpm = rpm_pll - rpm_true; persistent sum_diff_sq; persistent sum_pll_sq; persistent n; if isempty(n) sum_diff_sq = 0.0; sum_pll_sq = 0.0; n = uint32(0); end % 只统计恒速区间 if t > 0.20 && t < 0.34 sum_diff_sq = sum_diff_sq + err_diff_rpm * err_diff_rpm; sum_pll_sq = sum_pll_sq + err_pll_rpm * err_pll_rpm; n = n + 1; end if n > 0 rms_diff_rpm = sqrt(sum_diff_sq / double(n)); rms_pll_rpm = sqrt(sum_pll_sq / double(n)); else rms_diff_rpm = 0.0; rms_pll_rpm = 0.0; end end这个模块用来定量比较传统差分测速RMS误差,PLL测速RMS误差。
整体仿真搭建
最终误差分析结果
这四组数据分别为:
- 传统差分测速误差
- PLL 测速误差
- 差分测速 RMS 误差
- PLL 测速 RMS 误差
从最终误差结果来看,传统差分测速受编码器量化影响比较明显,速度误差呈现周期性跳变。在当前参数下,差分测速的误差幅值大约在60rpm 范围内,RMS 误差约为28 rpm,这与理论上的 60 rpm 速度分辨率跳变基本一致。
相比之下,PLL 测速的误差明显更小,恒速段速度波动被有效抑制,RMS 误差约为1.5 rpm。说明 PLL 没有直接对量化角度做差分,而是通过位置闭环跟踪的方式对速度进行估计,因此能够显著降低编码器量化噪声对测速结果的影响。
在加速、减速切换点附近,PLL 误差会出现短暂尖峰,这是由于观测器带宽有限,速度变化时存在一定跟踪滞后,属于正常现象。总体来看,当前参数下 PLL 测速结果更加平滑,误差更小,适合作为电机速度环的反馈信号。
Matlab下对PLL进行频域分析
在前面的时域仿真中,已经可以直观看到:传统差分测速会把编码器的位置量化误差直接放大成速度抖动,而 PLL 速度观测器能够明显降低测速噪声。
但是,仅从时域波形中很难定量判断 PLL 的动态特性,例如:
- PLL 响应到底有多快;
- 参数 KpK_pKp、KiK_iKi 是否会导致振荡;
- PLL 对高频量化噪声的抑制能力如何;
- 当前参数对应的闭环带宽是多少。
因此,需要进一步从频域角度分析 PLL 观测器。
在前面的建模分析中 我们已经得到了PLL 速度估计相对于真实速度的闭环传递函数:
这个传递函数反映了 PLL 对真实速度变化的跟踪能力。低频段增益接近 1,说明 PLL 能够准确跟踪低频速度变化;高频段增益下降,说明 PLL 对高频扰动和量化噪声具有一定抑制作用。
PLL 的特征方程为:
与标准二阶系统:
对比可得:
因此,PLL 参数可以通过期望的自然频率和阻尼比直接设计。工程中通常先确定 PLL 带宽,再选取阻尼比,一般取:
在频域分析中,主要关注以下三个方面:
- 分析开环传递函数
的 Bode 图和相位裕度。相位裕度反映系统稳定。一般要求大于
- 分析闭环传递函数,传递函数可以反映 PLL 对真实速度的跟踪能力。其带宽越高,PLL 响应越快,但对编码器量化噪声也越敏感;带宽越低,速度估计越平滑,但动态响应会变慢。
- 第三,分析角度测量噪声到速度输出的传递函数。由于编码器量化噪声首先作用在位置测量值上,因此可以建立:
因此,下面的 MATLAB 脚本主要完成以下工作:
- 根据给定的 PLL 带宽和阻尼比计算 Kp、Ki;
- 建立 PLL 的开环传递函数 L(s);
- 建立 PLL 的闭环速度跟踪传递函数;
- 绘制开环 Bode 图并计算相位裕度;
- 绘制闭环 Bode 图并计算带宽;
- 分析编码器角度量化噪声到速度输出的频率响应;
- 对比连续域模型和离散化模型的频率特性。
脚本实现:
%% pll_freq_analysis.m % 软件 PLL 速度观测器频域分析 clear; clc; close all; %% ===================== 参数 ===================== fs = 10000; Ts = 1 / fs; CPR = 10000; dtheta = 2*pi / CPR; f_pll = 100; zeta = 0.707; wn = 2*pi*f_pll; Kp = 2*zeta*wn; Ki = wn^2; fprintf('========== PLL 参数 ==========\n'); fprintf('fs = %.1f Hz\n', fs); fprintf('Ts = %.8f s\n', Ts); fprintf('CPR = %.0f counts/rev\n', CPR); fprintf('dtheta = %.8e rad/count\n', dtheta); fprintf('f_pll = %.2f Hz\n', f_pll); fprintf('zeta = %.3f\n', zeta); fprintf('wn = %.4f rad/s\n', wn); fprintf('Kp = %.4f\n', Kp); fprintf('Ki = %.4f\n', Ki); fprintf('Kp*Ts = %.6f\n', Kp*Ts); fprintf('Ki*Ts = %.6f\n', Ki*Ts); %% ===================== 连续域传递函数 ===================== s = tf('s'); % 开环传递函数 % L(s) = (Kp + Ki/s) * 1/s L = (Kp*s + Ki) / s^2; % 闭环角度跟踪传递函数 % theta_hat / theta H_theta = feedback(L, 1); % 速度跟踪传递函数 % omega_hat / omega = theta_hat / theta H_omega = H_theta; % 角度测量噪声到速度输出 % omega_hat / theta_noise G_noise = s * H_theta; %% ===================== 开环裕度 ===================== figure; margin(L); grid on; title('PLL Open Loop: L(s)'); [GM, PM, Wcg, Wcp] = margin(L); fprintf('\n========== 开环稳定裕度 ==========\n'); fprintf('Phase Margin = %.2f deg\n', PM); fprintf('Gain crossover frequency = %.2f Hz\n', Wcp/(2*pi)); %% ===================== 闭环带宽 ===================== bw_rad = bandwidth(H_omega); bw_hz = bw_rad / (2*pi); fprintf('\n========== 闭环带宽 ==========\n'); fprintf('Closed-loop bandwidth = %.2f Hz\n', bw_hz); figure; bode(H_omega); grid on; title('Closed Loop: omega\_pll / omega\_true'); %% ===================== 噪声传递函数 ===================== figure; bode(G_noise); grid on; title('Noise Transfer: omega\_pll / theta\_noise'); %% ===================== 离散化对比 ===================== H_omega_z = c2d(H_omega, Ts, 'tustin'); figure; bode(H_omega, H_omega_z); grid on; legend('Continuous', 'Discrete Tustin'); title('Continuous vs Discrete Closed Loop'); %% ===================== 差分测速量化跳变 ===================== domega_diff = dtheta / Ts; drpm_diff = domega_diff * 60 / (2*pi); fprintf('\n========== 差分测速量化跳变 ==========\n'); fprintf('One count jump = %.4f rad/s\n', domega_diff); fprintf('One count jump = %.4f rpm\n', drpm_diff); %% ===================== PLL 对一个 count 误差的比例响应 ===================== domega_pll_p = Kp * dtheta; drpm_pll_p = domega_pll_p * 60 / (2*pi); fprintf('\n========== PLL 一个 count 误差的比例响应 ==========\n'); fprintf('Kp*dtheta = %.4f rad/s\n', domega_pll_p); fprintf('Kp*dtheta = %.4f rpm\n', drpm_pll_p);运行脚本后的输出
========== PLL 参数 ========== fs = 10000.0 Hz Ts = 0.00010000 s CPR = 10000 counts/rev dtheta = 6.28318531e-04 rad/count f_pll = 100.00 Hz zeta = 0.707 wn = 628.3185 rad/s Kp = 888.4424 Ki = 394784.1760 Kp*Ts = 0.088844 Ki*Ts = 39.478418 ========== 开环稳定裕度 ========== Phase Margin = 65.52 deg Gain crossover frequency = 155.36 Hz ========== 闭环带宽 ========== Closed-loop bandwidth = 205.56 Hz ========== 差分测速量化跳变 ========== One count jump = 6.2832 rad/s One count jump = 60.0000 rpm ========== PLL 一个 count 误差的比例响应 ========== Kp*dtheta = 0.5582 rad/s Kp*dtheta = 5.3307 rpm对仿真结果进行分析:
这个结果比较好。一般工程上相位裕度大于 45度就可以认为系统有足够稳定性。
差分测速的一个 count 跳变量为:,而 PLL 对一个 count 位置误差的比例响应为:
。也就是说,同样是一个编码器 count 的位置误差,差分测速会直接表现成60 rpm的速度跳变,而 PLL 比例项只产生大约5.33 rpm的速度修正,再加上积分环节的平滑作用,所以 PLL 速度输出会明显平滑很多。
开环bode图分析
从幅频曲线看,低频段增益很高,说明 PLL 对低频位置误差修正能力强,能够保证估计角度跟随编码器角度。
在高频段,幅值逐渐下降,说明高频误差不会被无限放大,这对抑制编码器量化噪声是有利的。
从相频曲线看,系统在增益交越频率附近的相位还没有接近 −180度,所以稳定裕度比较充足。
闭环bode图分析
从幅频曲线看,低频段增益接近:
说明低频速度变化可以被 PLL 准确跟踪。也就是说,对于缓慢变化的电机速度,PLL 输出基本不会有明显幅值误差。
在中频附近,幅值有一点轻微抬升,这是二阶系统的正常现象,与阻尼比有关。这个抬升不大,说明系统没有明显谐振问题。
高频段幅值开始下降,说明 PLL 不会跟随很高频的速度扰动或编码器量化噪声。这个特性正是 PLL 比传统差分测速更平滑的原因之一。
前面计算得到闭环带宽为:,也就是说,在大约 200 Hz 以内的速度变化,PLL 可以比较有效地跟踪;超过这个频率后,PLL 会逐渐衰减,速度输出会变得更平滑,但动态响应也会变慢。
和时域仿真的对应关系
前面时域仿真中可以看到:
- 差分测速误差:50 rpm
- PLL 测速误差:约 1.5 rpm RMS
这和频域结果是对应的。
原因是:
- PLL 低频增益接近 0 dB,所以能跟踪真实速度;
- PLL 高频增益下降,所以能抑制编码器量化噪声;
- 相位裕度较高,所以不会出现明显振荡。
因此,当前 PLL 参数在“响应速度”和“噪声抑制”之间取得了比较好的折中。
C代码实现与平台验证
代码实现:
typedef struct { float Ts; //速度环采样执行周期 float Count; //计数器的计数值 float Count_To_Rad; //计数值转化到角度值的系数 float Zeta; //阻尼比 float Omega_n; //自然频率 float Kp; //比例系数 float Ki; //积分系数 float Theta_Hat; //估计的机械角度 float Omega_m_Hat; //估计的机械角速度 float I_Port; //积分项 float Theta_Meas; //实际的机械角度 float Err; //当前角度误差 float Omega_m_Limit; //速度限幅 float I_Limit; //积分限幅 float Speed; //机械转速RPM; }Increment_Encoder_PLL_Get_Speed; //锁相环获取电机转速 Increment_Encoder_PLL_Get_Speed Bsp_Increment_Encoder_PLL //锁相环获取电机转速 void Bsp_PLL_Get_Motor_Speed(void) { /*将脉冲数转换为机械角度*/ Bsp_Increment_Encoder_PLL.Theta_Meas = (float) Bsp_Increment_Encoder_PLL.Count * Bsp_Increment_Encoder_PLL.Count_To_Rad; /*计算角度误差*/ Bsp_Increment_Encoder_PLL.Err = Bsp_Increment_Encoder_PLL.Theta_Meas - Bsp_Increment_Encoder_PLL.Theta_Hat; /*误差限幅*/ if(Bsp_Increment_Encoder_PLL.Err > PLL_PI) { Bsp_Increment_Encoder_PLL.Err -= PLL_TWO_PI; } else if(Bsp_Increment_Encoder_PLL.Err < -PLL_PI) { Bsp_Increment_Encoder_PLL.Err += PLL_TWO_PI; } /*计算锁相环积分项*/ Bsp_Increment_Encoder_PLL.I_Port += Bsp_Increment_Encoder_PLL.Ki * Bsp_Increment_Encoder_PLL.Ts * Bsp_Increment_Encoder_PLL.Err; /*积分限幅度*/ if(Bsp_Increment_Encoder_PLL.I_Port > Bsp_Increment_Encoder_PLL.I_Limit) { Bsp_Increment_Encoder_PLL.I_Port = Bsp_Increment_Encoder_PLL.I_Limit; } else if(Bsp_Increment_Encoder_PLL.I_Port < -Bsp_Increment_Encoder_PLL.I_Limit) { Bsp_Increment_Encoder_PLL.I_Port = -Bsp_Increment_Encoder_PLL.I_Limit; } /*计算PI输出 估算的角速度*/ Bsp_Increment_Encoder_PLL.Omega_m_Hat = Bsp_Increment_Encoder_PLL.I_Port + Bsp_Increment_Encoder_PLL.Kp * Bsp_Increment_Encoder_PLL.Err; /*角速度输出限幅*/ if(Bsp_Increment_Encoder_PLL.Omega_m_Hat > Bsp_Increment_Encoder_PLL.Omega_m_Limit) { Bsp_Increment_Encoder_PLL.Omega_m_Hat = Bsp_Increment_Encoder_PLL.Omega_m_Limit; } else if(Bsp_Increment_Encoder_PLL.Omega_m_Hat < -Bsp_Increment_Encoder_PLL.Omega_m_Limit) { Bsp_Increment_Encoder_PLL.Omega_m_Hat = -Bsp_Increment_Encoder_PLL.Omega_m_Limit; } /*转化机械转速*/ Bsp_Increment_Encoder_PLL.Speed = (Bsp_Increment_Encoder_PLL.Omega_m_Hat * 60.0f) / (2*PI); /*速度积分得到估算角度*/ Bsp_Increment_Encoder_PLL.Theta_Hat += Bsp_Increment_Encoder_PLL.Omega_m_Hat * Bsp_Increment_Encoder_PLL.Ts; /*估算角度限幅度*/ if(Bsp_Increment_Encoder_PLL.Theta_Hat >= PLL_TWO_PI) { Bsp_Increment_Encoder_PLL.Theta_Hat -= PLL_TWO_PI; } else if(Bsp_Increment_Encoder_PLL.Theta_Hat <= 0) { Bsp_Increment_Encoder_PLL.Theta_Hat += PLL_TWO_PI; } }平台验证:
本次验证采用永磁同步电机实验平台,编码器为1000线增量式编码器, MCU主控stm32g474vet6 ,通过配置定时器3为编码器模式来进行脉冲获取。采用4倍频后,分别进行M法测速和PLL锁相环法进行测速,设置阻尼比为0.707,自然频率为2*PI*100。
从 MCU 实测波形来看,红色为设定转速300 rpm,绿色为传统 M 法测速结果,黄色为 PLL 锁相环测速结果。可以看出,M 法测速波形存在明显的脉冲跳变和尖峰,这是由于增量式编码器在低速下单位采样周期内脉冲数较少,测速结果容易受到量化误差影响,导致速度反馈抖动较大。相比之下,PLL 测速结果明显更加平滑,黄色曲线能够围绕设定转速附近变化,没有出现 M 法那种剧烈的瞬时跳变。说明 PLL 通过“位置误差闭环 + PI 调节 + 速度积分”的方式,对编码器脉冲量化噪声起到了较好的抑制作用。不过从波形上看,PLL 输出仍然存在一定周期性波动,这可能与编码器分辨率、采样周期、PLL 带宽参数以及电机实际转速波动有关。当前 PLL 参数已经能够明显改善测速质量,但后续如果希望速度反馈更平滑,可以适当降低 PLL 带宽;如果希望动态响应更快,可以适当提高 PLL 带宽。
结束语
经过理论推导、Simulink 仿真验证以及最终的 MCU 实机部署,本次基于增量式编码器的软件 PLL 测速方案已基本落地。对比测试表明,软件 PLL 方案有效克服了传统 M 法在低速区间受量化误差影响而产生的剧烈抖动,使得速度反馈曲线具备了出色的平滑度。
需要指出的是,PLL 的本质是基于角度偏差的闭环跟踪器,而非单纯的低通滤波器。因此,核心参数 Kp 与 Ki (即系统的带宽与阻尼比)的匹配,依然是决定动态响应与抗噪能力的关键。本次工作不仅验证了该算法在工程实践中的可行性,也为后续速度环的精细调参及更高阶的伺服控制构筑了可靠的底层数据支持。