STM32F4无感FOC中PLL替代反正切:解决滑膜观测器角度抖动
2026/9/24 5:59:14 网站建设 项目流程

1. 从滑膜观测器的角度抖动说起:为什么换PLL

如果你调过STM32F4平台上的无感FOC,大概率经历过这个场景:电机在中低速运行时能转,但一到某个转速区间,转速波形就开始出现周期性抖动,示波器上看到的是角度估算值在真实值附近来回摆动,电流波形也跟着出现毛刺。你反复调滑膜观测器的增益,调来调去,要么抖得轻一点但动态响应变慢,要么响应快了抖动又加剧。这个矛盾,本质上不是滑膜观测器本身的问题,而是它输出的是反电动势的开关信号,里面天然含有高频抖振分量,直接拿这个信号去反正切求角度,抖动就会被放大。

滑膜观测器的核心思路是构造一个滑模面,让估算电流跟踪实际电流,当系统进入滑模面后,反电动势的估算值就等价于滑模控制量经过低通滤波后的结果。问题就出在这个低通滤波上:截止频率设低了,角度估算滞后,高速时误差大;设高了,高频抖振滤不干净,角度抖动明显。而且低通滤波引入的相位延迟还需要额外做角度补偿,补偿系数跟转速相关,标定起来非常麻烦。

PLL锁相环的思路则完全不同。它不直接对反电动势做反正切,而是把估算的反电动势α、β分量送入一个锁相环结构,通过PI调节器让估算角度跟踪真实角度。PLL本质上是一个闭环角度跟踪系统,它对高频噪声有天然的抑制能力,因为PI调节器本身就是一个低通特性,而且它跟踪的是角度误差的积分,稳态时角度误差趋近于零。换句话说,PLL把“求角度”这件事从一个开环计算变成了闭环跟踪,抖动被闭环带宽压制住了。

我在STM32F407上做过对比测试:同一台无刷电机,同一组滑膜观测器参数,只把角度提取环节从反正切换成PLL,转速波动从±30rpm降到±8rpm左右,电流波形明显干净了。这个改善在中低速段尤其明显,因为低速时反电动势幅值小,反正切对噪声更敏感,而PLL的闭环特性可以把噪声压下去。

注意:PLL不是万能的,它解决的是角度提取环节的抖动问题,如果滑膜观测器本身的估算反电动势就不准,PLL也救不回来。所以前置的滑膜观测器参数还是要调好,PLL是在此基础上的优化。

2. STM32F4上PLL锁相环的数学结构与离散化

2.1 PLL的基本框图与传递函数

PLL锁相环在电机控制里的典型结构是这样的:估算反电动势的α、β分量经过Park变换到估算角度对应的旋转坐标系,得到ε = -Eα·cosθ_est - Eβ·sinθ_est,这个ε就是角度误差信号。当估算角度接近真实角度时,ε近似正比于角度误差Δθ。然后ε经过PI调节器,输出角速度估算值ω_est,再经过积分得到角度θ_est。

用传递函数描述,从真实角度θ到估算角度θ_est的闭环传递函数为:

θ_est(s)/θ(s) = (Kp·s + Ki) / (s² + Kp·s + Ki)

这是一个典型的二阶系统,自然频率ωn = sqrt(Ki),阻尼比ζ = Kp/(2·sqrt(Ki))。通常取ζ = 0.707(最佳阻尼),然后根据需要的带宽确定ωn。比如想要100Hz的跟踪带宽,ωn = 2π·100 ≈ 628 rad/s,那么Ki = ωn² ≈ 394384,Kp = 2·ζ·ωn ≈ 888。

这个参数设计方法比滑膜观测器那种“试凑法”要清晰得多,因为PLL的带宽有明确的物理意义,你可以直接根据电机的最高运行频率来选。一般取PLL带宽为电机基频的5到10倍,既能保证跟踪速度,又能有效抑制噪声。

2.2 离散化实现与STM32F4的定点/浮点选择

STM32F407带FPU,所以直接用浮点运算最省事。离散化用后向欧拉法:

// PLL参数 #define PLL_KP 888.0f #define PLL_KI 394384.0f #define PLL_TS 0.0001f // 10kHz控制频率 typedef struct { float theta; // 估算角度 float omega; // 估算角速度 float err; // 角度误差 float integral; // 积分项 } PLL_State; void PLL_Update(PLL_State *pll, float Ealpha, float Ebeta) { // 计算角度误差 float sin_th = arm_sin_f32(pll->theta); float cos_th = arm_cos_f32(pll->theta); pll->err = -Ealpha * cos_th - Ebeta * sin_th; // PI调节器 pll->integral += PLL_KI * pll->err * PLL_TS; pll->omega = PLL_KP * pll->err + pll->integral; // 积分得到角度 pll->theta += pll->omega * PLL_TS; // 角度归一化到0~2π if (pll->theta > 6.2831853f) pll->theta -= 6.2831853f; if (pll->theta < 0.0f) pll->theta += 6.2831853f; }

这里有几个实操细节值得说。第一,arm_sin_f32arm_cos_f32是CMSIS-DSP库的查表加插值实现,速度比标准库快很多,在168MHz的F407上单次调用大约20个周期,10kHz控制频率下完全够用。第二,积分项要做限幅,防止启动时误差过大导致积分饱和,一般限幅到额定角速度的1.5倍。第三,角度归一化用减法比用fmodf快,因为fmodf是函数调用,减法就是一条指令。

如果你用的是STM32F401这种没有FPU或者FPU性能较弱的型号,可以考虑定点实现,但说实话,F4系列基本都带FPU,除非成本压到极致,否则没必要折腾定点。我试过在F401上用Q15定点跑PLL,代码复杂度翻倍,效果还不如浮点直接跑。

2.3 与滑膜观测器的衔接:反电动势从哪来

PLL的输入是估算反电动势Eα、Eβ,这两个量来自滑膜观测器。典型的滑膜观测器输出是:

// 滑膜观测器输出 float Ealpha_est = LPF(sliding_mode_output_alpha); float Ebeta_est = LPF(sliding_mode_output_beta);

这里的LPF截止频率通常设得比较低,比如200Hz到500Hz,目的是滤掉滑模的开关抖振。但正如前面说的,这个LPF会引入相位延迟。用了PLL之后,LPF的截止频率可以适当提高,因为PLL本身对高频噪声有抑制,不需要LPF承担全部滤波任务。我一般把LPF截止频率从200Hz提高到500Hz,这样相位延迟减小,PLL的输入信号更“新鲜”,整体角度跟踪更快。

提示:LPF截止频率提高后,PLL的PI参数可能需要微调,因为输入噪声增大了。如果发现角度还是有抖动,先把PLL的Kp减小、Ki适当增大,让闭环带宽降一点。

3. 在STM32F4工程里落地PLL的完整步骤

3.1 定时器与ADC配置:控制频率的选择

FOC的控制频率决定了PLL的离散化步长,进而影响参数设计。STM32F4的定时器位数足够灵活,TIM1是16位高级定时器,适合做PWM生成;TIM2/TIM5是32位通用定时器,适合做控制循环触发。我一般用TIM1产生20kHz的PWM,用TIM1的更新事件触发ADC采样,然后在ADC中断里跑FOC和PLL,控制频率就是20kHz。

但20kHz下PLL的TS = 50μs,如果PLL带宽设100Hz,ωn·TS ≈ 0.031,离散化误差可以忽略。如果你把控制频率降到10kHz,TS = 100μs,ωn·TS ≈ 0.063,也还能接受。但再低就不行了,比如5kHz下ωn·TS ≈ 0.126,离散化带来的相位误差就明显了,需要改用双线性变换或者Tustin离散化。

// TIM1配置为20kHz PWM,中心对齐模式 void TIM1_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1; TIM_TimeBaseStructure.TIM_Period = 4200 - 1; // 168MHz/2/4200 = 20kHz TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); // 更新事件触发ADC TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update); TIM_Cmd(TIM1, ENABLE); }

中心对齐模式下,更新事件发生在计数器到零和到峰值时,所以实际触发频率是PWM频率的两倍,也就是40kHz。如果你在更新中断里跑FOC,控制频率就是40kHz。这个细节很容易搞错,我第一次调的时候以为控制频率是20kHz,结果PLL参数算出来带宽翻倍,角度抖得厉害。后来用示波器量了一下中断频率才发现问题。

3.2 滑膜观测器与PLL的代码集成

整个无感FOC的代码流程是这样的:ADC中断触发 → 读取相电流 → Clarke变换 → Park变换 → 电流PI → 反Park变换 → SVPWM输出 → 滑膜观测器更新 → PLL更新 → 得到角度和速度。

void ADC_IRQHandler(void) { // 读取电流 Ia = ADC_GetConversionValue(ADC1) * CURRENT_SCALE; Ib = ADC_GetConversionValue(ADC2) * CURRENT_SCALE; // Clarke变换 Ialpha = Ia; Ibeta = (Ia + 2.0f * Ib) * ONE_BY_SQRT3; // Park变换 float sin_th = arm_sin_f32(pll.theta); float cos_th = arm_cos_f32(pll.theta); Id = Ialpha * cos_th + Ibeta * sin_th; Iq = -Ialpha * sin_th + Ibeta * cos_th; // 电流PI Vd = PI_Update(&pi_d, Id_ref - Id); Vq = PI_Update(&pi_q, Iq_ref - Iq); // 反Park变换 Valpha = Vd * cos_th - Vq * sin_th; Vbeta = Vd * sin_th + Vq * cos_th; // SVPWM SVPWM_Update(Valpha, Vbeta); // 滑膜观测器 SMO_Update(Ialpha, Ibeta, Valpha, Vbeta); // PLL更新 PLL_Update(&pll, smo.Ealpha_est, smo.Ebeta_est); // 清除中断标志 ADC_ClearITPendingBit(ADC1, ADC_IT_EOC); }

这段代码里有个顺序问题:PLL更新必须在滑膜观测器之后,因为PLL用的是滑膜观测器输出的反电动势。但PLL输出的角度又要在下一次Park变换时使用,所以实际上角度是滞后一个控制周期的。这个滞后在20kHz下是50μs,对应电角度延迟很小,可以忽略。但如果你的控制频率只有5kHz,200μs的延迟在高速时就会造成明显误差,需要做角度预测补偿。

3.3 启动阶段的处理:PLL的初始化与切换

无感FOC的启动是个麻烦事,因为低速时反电动势太小,滑膜观测器估不准,PLL也就跟不准。常见的做法是先用开环强拖,把电机拉到一定转速后再切换到闭环。切换瞬间,PLL的积分项需要预置到当前的开环角速度,角度直接对齐开环角度,否则PLL会有一个大的暂态过程。

void FOC_Startup(void) { // 开环强拖阶段 for (int i = 0; i < 2000; i++) { openloop_theta += OPENLOOP_OMEGA * PLL_TS; SVPWM_Update(OPENLOOP_VOLTAGE * arm_cos_f32(openloop_theta), OPENLOOP_VOLTAGE * arm_sin_f32(openloop_theta)); delay_us(50); } // 切换到闭环前,预置PLL状态 pll.theta = openloop_theta; pll.omega = OPENLOOP_OMEGA; pll.integral = OPENLOOP_OMEGA; // 积分项预置为当前角速度 pll.err = 0.0f; // 使能闭环 closed_loop_enable = 1; }

这里pll.integral = OPENLOOP_OMEGA是关键,因为PI调节器的输出是Kp·err + integral,稳态时err≈0,输出就是integral。如果不预置,integral从零开始,PLL输出角速度会从零慢慢爬升,电机就会失步。我踩过这个坑,切换瞬间电机“咯噔”一下,电流冲到限幅值,后来加上预置就平顺了。

注意:开环强拖的角速度不能太低,一般至少要到额定转速的10%到15%,否则反电动势还是太小,切换后PLL可能锁不住。具体值取决于电机参数,KV值高的电机可以低一些,KV值低的电机需要高一些。

4. 调参实战:PLL带宽、滑膜增益与滤波器的联合整定

4.1 PLL参数对系统性能的影响规律

PLL的Kp和Ki不是随便设的,它们直接决定了角度跟踪的带宽和阻尼。我整理了一个参数影响表,方便你快速判断调整方向:

参数变化角度跟踪速度角度抖动抗噪声能力适用场景
Kp增大变快增大变差高速、动态响应要求高
Kp减小变慢减小变好低速、噪声大
Ki增大变快增大变差需要快速消除稳态误差
Ki减小变慢减小变好稳态精度要求不高
带宽ωn增大变快增大变差高速运行
阻尼ζ减小变快振荡变差不推荐

实际调参时,我一般先按ζ=0.707、ωn=2π·(电机基频×5)算一组初值,然后根据实测波形微调。如果发现角度有低频振荡,说明阻尼不够,增大Kp或者减小Ki;如果发现角度有高频毛刺,说明带宽太高,减小Kp和Ki。

4.2 滑膜观测器增益与PLL的耦合关系

滑膜观测器的增益决定了反电动势估算的收敛速度,而PLL的带宽决定了角度跟踪速度。这两个参数是耦合的:如果滑膜增益太低,反电动势估算滞后,PLL输入信号本身就不准,角度跟踪再好也没用;如果滑膜增益太高,反电动势抖振大,PLL输入噪声大,角度抖动也会增大。

我的经验是先把滑膜观测器调到一个“勉强能用”的状态,也就是电机能转、角度大致跟得上,然后在此基础上调PLL。调PLL的时候,先把Ki设为零,只调Kp,让角度能跟上但略有滞后;然后逐渐增大Ki,直到滞后消除但抖动不明显增加。这个过程有点像调电流环PI,先调P再调I。

// 滑膜观测器增益典型值(以STM32F4 20kHz控制为例) #define SMO_K_SLIDE 0.5f // 滑模增益 #define SMO_LPF_FC 500.0f // LPF截止频率Hz #define SMO_TS 0.00005f // 20kHz // PLL参数(与滑膜观测器匹配) #define PLL_KP 600.0f #define PLL_KI 200000.0f

这组参数在额定转速3000rpm、极对数4的电机上跑过,转速波动在±10rpm以内。但换一台电机就需要重新调,因为反电动势常数不同,滑膜观测器的输出幅值不同,PLL的等效增益也就不同。

4.3 实测波形分析与问题定位

调参过程中,示波器是最好的朋友。我一般用DAC输出PLL的err信号和omega信号,或者用SWO打印到上位机看波形。几个典型问题:

问题一:角度波形有周期性尖峰。这通常是滑膜观测器的开关噪声导致的,检查LPF截止频率是否太低,或者PLL的Kp是否太大。把LPF截止频率提高,同时减小PLL的Kp,尖峰一般会消失。

问题二:角度波形整体滞后。这是PLL带宽不够,增大Kp和Ki。但要注意,带宽增大后抖动也会增大,需要折中。

问题三:角度波形在某个转速区间振荡。这是PLL的阻尼不够,或者滑膜观测器在该转速下估算不准。先检查滑膜观测器的反电动势波形是否正常,如果反电动势本身就有问题,先调滑膜。

问题四:启动时角度锁不住。检查PLL积分项预置是否正确,以及开环切换转速是否足够高。

提示:用DAC输出内部变量是最方便的调试手段,STM32F4有两个DAC通道,可以同时输出err和omega,接示波器就能看。如果没有DAC,用SWO的ITM输出也行,但采样率有限,看不了高频细节。

5. 几个容易踩的坑与我的处理方式

5.1 角度归一化与三角函数查表的精度问题

PLL输出的角度需要归一化到0到2π,然后用arm_sin_f32arm_cos_f32计算三角函数。CMSIS-DSP的这两个函数用的是查表加线性插值,表长256点,精度大约0.01弧度。对于FOC来说,这个精度足够了,因为电流环的带宽远低于此。但如果你发现电流波形有周期性畸变,可以试试用更高精度的查表,比如1024点表,或者直接用sinfcosf(F4的FPU算这个大概50个周期,20kHz下也够用)。

我试过用sinf替换arm_sin_f32,电流波形的THD从3.2%降到2.8%,改善有限但确实有。如果你的应用对噪声敏感,可以试试。

5.2 低速时PLL的失效与应对

PLL在低速时的问题和滑膜观测器一样:反电动势太小,信噪比低。当转速低于额定转速的5%时,PLL的角度误差会明显增大,甚至锁不住。这时候有几个选择:一是用开环强拖到更高转速再切闭环;二是用高频注入法估算低速角度,但这需要额外的硬件支持和复杂的算法;三是用IF启动,也就是电流闭环、频率开环,等转速上来再切。

我在实际项目中一般用第一种,因为简单可靠。如果应用要求低速大扭矩,那就得上高频注入,但那是另一个话题了。

5.3 参数整定的顺序不能乱

很多人调无感FOC的时候,喜欢把滑膜观测器、PLL、电流环一起调,结果哪个参数影响哪个都搞不清楚。我的建议是严格按顺序来:先调电流环(有传感器或者开环),确保电流跟踪正常;再调滑膜观测器,让反电动势估算大致准确;最后调PLL,优化角度跟踪。每一步都确认波形正常后再进行下一步,这样出问题容易定位。

这个顺序看起来慢,但实际上比乱调快得多。我见过有人调了一周都没调好,按这个顺序半天就搞定了。

5.4 不同电机参数的适配

PLL的参数和电机参数是相关的。反电动势常数大的电机,同样转速下反电动势幅值大,PLL的等效增益高,Kp和Ki可以适当减小;反电动势常数小的电机则相反。极对数多的电机,同样机械转速下电频率高,PLL带宽需要相应提高。

我一般会先测一下电机的反电动势常数和极对数,然后按公式算一组初值,再微调。这样比盲试快很多。

// 根据电机参数计算PLL初值 // Ke: 反电动势常数(V/krpm) // pp: 极对数 // rpm_max: 最高转速 float Ke = 12.5f; // 示例 float pp = 4.0f; float rpm_max = 3000.0f; float fe_max = rpm_max * pp / 60.0f; // 最高电频率 float omega_n = 2.0f * 3.14159f * fe_max * 5.0f; // 带宽取5倍基频 float zeta = 0.707f; float Kp = 2.0f * zeta * omega_n; float Ki = omega_n * omega_n;

这段代码算出来的Kp和Ki可以直接用,然后根据实测微调。注意这里的Kp和Ki是连续域的,离散化时Kp不变,Ki要乘以TS。我在代码里把Ki已经乘过TS了,所以直接赋值就行。

6. 写在最后的一些个人体会

PLL替代反正切做角度提取,这个改动本身不大,代码量也就几十行,但效果提升很明显。我第一次在F407上跑通的时候,看到转速波动从±30rpm降到±8rpm,确实有点惊喜。后来在多个项目上验证,只要滑膜观测器调得不太差,PLL都能带来可观的改善。

不过也要说清楚,PLL不是银弹。它解决的是角度提取环节的噪声和抖动,如果滑膜观测器本身估算的反电动势就有系统性误差,比如参数不匹配导致的幅值偏差,PLL是修不了的。所以电机参数辨识、滑膜观测器的增益整定,这些基础工作还是要做扎实。

另外,PLL的带宽选择需要根据应用场景折中。如果你的应用对动态响应要求高,比如伺服定位,带宽可以高一些;如果是风机水泵这类对稳态精度要求高、动态要求低的,带宽可以低一些,换取更平滑的角度。

最后分享一个调试小技巧:用DAC输出PLL的err信号,然后用手轻轻给电机轴施加阻力,观察err的变化。如果err能快速回到零附近,说明PLL跟踪正常;如果err持续偏大或者振荡,说明参数需要调整。这个方法比看转速波形更直接,因为err就是角度误差的直接反映。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询