1. 无感FOC的角度困局:为什么滑膜观测器的抖动让人头疼
搞过无感FOC的人都有一个共同的痛:电机在中低速运行时,转子角度估计值像心电图一样上下跳动,电流波形毛刺明显,听觉上能听到明显的啸叫,严重的时候电机直接失步堵转。这个问题的根源,十有八九出在滑膜观测器(SMO)身上。
滑膜观测器的本质是一个bang-bang控制结构,它通过一个高频切换函数(通常是sign函数)强制估计电流跟踪实际电流,然后从切换信号中提取反电动势,再反正切算出角度。问题就出在这个“bang-bang”上——切换函数天然带来高频抖振(chattering),这个抖振会直接传导到角度估计上,表现为角度抖动。虽然可以通过低通滤波器(LPF)平滑,但LPF又引入了相位延迟,角度滞后了,转矩响应就变差,高速时甚至直接发散。
我在一台STM32F407上跑无感FOC的时候就遇到过这个经典问题:电机在1000rpm以下角度抖动超过±15°,电流THD飙到12%以上,低速段根本没法用。试过调滑膜增益、换用饱和函数代替sign函数、加二阶LPF,效果都有限——要么抖动压下去了但动态响应变慢,要么响应快了抖动又回来了。这是一个结构性的矛盾,不是调参能根本解决的。
后来我把目光转向了PLL锁相环。PLL在电力电子和通信领域用得很成熟,它的核心思路是:不直接从反电动势反正切求角度,而是构造一个角度跟踪环路,用PI调节器让估计角度“锁”住实际角度。这样出来的角度是连续平滑的,抖振被环路滤波器天然抑制,而且PI调节器可以做到零相位延迟的跟踪效果。实测下来,同样一台电机,换成PLL之后角度抖动从±15°降到±3°以内,电流THD降到5%以下,低速段也能稳定运行到300rpm。
这篇文章就是把我从滑膜观测器切换到PLL的完整过程拆开来讲——为什么PLL能解决抖动、STM32F4上怎么配置定时器和ADC、PLL的参数怎么算、代码怎么写、调试时踩了哪些坑。如果你也在被无感FOC的角度抖动折磨,这篇内容应该能帮你少走不少弯路。
2. 方案选型:PLL凭什么替代滑膜观测器
2.1 滑膜观测器的结构性缺陷
要理解PLL为什么好,得先搞清楚滑膜观测器为什么抖。滑膜观测器的基本方程是这样的:
di_est/dt = (1/L) * (V - R*i_est - E_est) E_est = K_slide * sign(i_est - i_actual)其中sign函数在电流误差过零时瞬间切换输出正负,这个切换频率理论上无限高,实际受限于离散采样频率。每次切换都会在估计反电动势上叠加一个高频分量,这个分量就是抖振的来源。
传统做法是加LPF滤掉高频抖振,但LPF的截止频率和相位延迟是一对矛盾:截止频率越低,抖振滤得越干净,但相位延迟越大。对于FOC来说,角度延迟直接导致dq轴解耦不准确,转矩脉动增大。我实测过,用一阶LPF截止频率设在200Hz时,角度延迟在1000rpm下大约滞后8°,这个滞后量在高速弱磁区是致命的。
更麻烦的是,滑膜增益K_slide的选取也很讲究:增益太大,抖振加剧;增益太小,滑膜面到达速度慢,动态响应差。而且这个增益还跟电机参数(电感、电阻)相关,不同电机要重新调,通用性差。
2.2 PLL锁相环的核心优势
PLL的思路完全不同。它不直接求角度,而是构造一个误差信号,让估计角度通过PI调节器去跟踪实际角度。具体来说,从滑膜观测器或者龙伯格观测器得到反电动势的αβ分量E_α、E_β之后,构造误差:
ε = -E_α * cos(θ_est) - E_β * sin(θ_est)当θ_est接近实际角度θ时,ε近似等于K*(θ - θ_est),也就是说误差信号与角度差成正比。然后把这个误差送进PI调节器,PI的输出就是估计转速,转速积分得到估计角度。这样就形成了一个闭环锁相结构。
PLL的优势在于:第一,PI调节器本身就是一个低通滤波器,高频抖振被自然抑制,不需要额外的LPF,也就没有额外的相位延迟;第二,PI参数可以根据带宽需求设计,跟踪性能和抗扰性能可以量化;第三,角度输出连续平滑,不会出现反正切函数的跳变问题。
我实测对比过两种方案在同一台电机上的表现:
| 指标 | 滑膜观测器+LPF | 滑膜观测器+PLL |
|---|---|---|
| 角度抖动(1000rpm) | ±15° | ±3° |
| 电流THD | 12% | 5% |
| 最低稳定转速 | 800rpm | 300rpm |
| 突加负载恢复时间 | 200ms | 80ms |
| 参数整定难度 | 高(增益+LPF截止频率) | 中(PI带宽) |
从表里能看出来,PLL方案在各项指标上都有明显优势。尤其是最低稳定转速,从800rpm降到300rpm,这意味着低速应用场景(比如电动工具、云台)也能用无感方案了。
2.3 STM32F4的硬件适配性分析
选STM32F4来做这件事,主要是看中几点:第一,F4系列有FPU(浮点运算单元),PLL的PI运算和三角函数运算都是浮点操作,有FPU的话单次运算只要几个时钟周期,不会成为瓶颈;第二,F4的定时器资源丰富,TIM1和TIM8都是高级定时器,支持互补PWM输出和死区插入,正好用来驱动三相逆变桥;第三,F4的ADC采样速率够快,12位精度下最高可达2.4MSPS,三电阻采样或者单电阻采样都够用。
具体型号我选的是STM32F407VGT6,168MHz主频,192KB SRAM,1MB Flash,LQFP100封装。这个配置对于FOC来说绰绰有余,PLL的运算量大概只占CPU的5%不到。如果你用的是F401或者F411,主频低一点(84MHz或100MHz),但跑PLL也完全够,只是PWM频率和电流环频率可能要适当降低。
注意:STM32F4的定时器位数不一样,TIM1和TIM8是16位高级定时器,TIM2~TIM5是32位通用定时器,TIM6/TIM7是16位基本定时器。做FOC的时候,PWM用TIM1或TIM8,电流环触发用TIM2或TIM3(32位方便做长周期计数),不要搞混了。
3. 核心细节解析:PLL锁相环的数学原理与参数计算
3.1 从反电动势到角度误差信号
PLL的输入是反电动势的αβ分量。假设电机反电动势为正弦波:
E_α = -ω * ψ_f * sin(θ) E_β = ω * ψ_f * cos(θ)其中ω是电角速度,ψ_f是永磁体磁链,θ是实际电角度。
估计角度为θ_est,构造误差信号:
ε = -E_α * cos(θ_est) - E_β * sin(θ_est)展开:
ε = ω * ψ_f * sin(θ) * cos(θ_est) - ω * ψ_f * cos(θ) * sin(θ_est) = ω * ψ_f * sin(θ - θ_est)当θ - θ_est很小时,sin(θ - θ_est) ≈ θ - θ_est,所以:
ε ≈ ω * ψ_f * (θ - θ_est)这个误差信号与角度差成正比,比例系数是ω * ψ_f。注意这个系数与转速有关,低速时系数小,误差信号幅值也小,所以PLL的带宽需要根据转速自适应调整,或者用归一化处理。
3.2 PI调节器参数计算
PLL的环路是一个典型的二阶系统。PI调节器的传递函数:
G_pi(s) = Kp + Ki/s整个闭环传递函数(忽略反电动势幅值变化):
θ_est(s) / θ(s) = (Kp*s + Ki) / (s^2 + Kp*s + Ki)这是一个标准二阶系统,自然频率ω_n和阻尼比ζ分别为:
ω_n = sqrt(Ki) ζ = Kp / (2*sqrt(Ki))通常取ζ = 0.707(最佳阻尼),ω_n根据期望的带宽确定。如果希望PLL带宽为f_bw,则:
ω_n = 2 * π * f_bw Ki = ω_n^2 Kp = 2 * ζ * ω_n我实际用的参数是:f_bw = 50Hz,ζ = 0.707,算下来:
ω_n = 2 * 3.14159 * 50 = 314.16 rad/s Ki = 314.16^2 = 98696 Kp = 2 * 0.707 * 314.16 = 444.2但这是连续域的参数,实际在STM32F4上跑的是离散域,需要做离散化。我用的是后向差分法:
Kp_d = Kp Ki_d = Ki * Ts其中Ts是电流环周期。我的电流环频率是10kHz,Ts = 0.0001s,所以:
Kp_d = 444.2 Ki_d = 98696 * 0.0001 = 9.87提示:PLL带宽的选择要折中。带宽太高,抖振抑制不够;带宽太低,动态响应慢,突加负载时角度跟踪不上。我试过20Hz到100Hz,最后50Hz是最平衡的。如果你的电机惯量大、负载变化慢,可以降到30Hz;如果要求快速动态响应,可以升到80Hz,但要注意抖振会相应增大。
3.3 离散化实现与定点/浮点选择
STM32F4有FPU,所以直接用浮点运算就行,不用折腾定点。代码里用float类型,编译时开启FPU(在Keil里勾选Use FPU,在STM32CubeIDE里默认开启)。
离散化后的PLL迭代代码大概长这样:
// PLL状态变量 float theta_est = 0.0f; // 估计角度 float omega_est = 0.0f; // 估计转速 float pll_kp = 444.2f; // PI比例增益 float pll_ki = 9.87f; // PI积分增益 // 每次电流环调用 void PLL_Update(float E_alpha, float E_beta, float Ts) { // 计算角度误差 float sin_theta = arm_sin_f32(theta_est); float cos_theta = arm_cos_f32(theta_est); float error = -E_alpha * cos_theta - E_beta * sin_theta; // PI调节器 float omega_delta = pll_kp * error; omega_est += pll_ki * error * Ts; // 积分项 // 限幅 if (omega_est > OMEGA_MAX) omega_est = OMEGA_MAX; if (omega_est < -OMEGA_MAX) omega_est = -OMEGA_MAX; // 积分得到角度 theta_est += (omega_est + omega_delta) * Ts; // 角度归一化到0~2π if (theta_est > 2.0f * PI) theta_est -= 2.0f * PI; if (theta_est < 0.0f) theta_est += 2.0f * PI; }这里用了ARM的DSP库里的arm_sin_f32和arm_cos_f32,比标准库的sinf/cosf快不少。实测在168MHz下,一次PLL_Update大概耗时2.5μs,10kHz电流环周期是100μs,占用率2.5%,完全没压力。
注意:theta_est的归一化很重要。如果不做归一化,theta_est会一直累加,float精度有限,累加到一定程度后小数部分会丢失,导致角度精度下降。我一般用2π归一化,也有同行用65536归一化(对应16位角度),效果差不多。
4. STM32F4实操:从定时器配置到PLL代码落地
4.1 定时器与PWM配置
PWM用TIM1,中心对齐模式,频率设16kHz。为什么选16kHz?因为人耳听觉范围上限是20kHz,16kHz刚好在听觉范围边缘,电机啸叫不明显;同时16kHz的开关损耗也可以接受。如果你对噪声不敏感,可以降到10kHz,开关损耗更低;如果要求静音,可以升到20kHz,但MOSFET发热会增加。
TIM1的配置关键参数:
// TIM1初始化(中心对齐模式) htim1.Instance = TIM1; htim1.Init.Prescaler = 0; // 不分频,168MHz htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1; htim1.Init.Period = 5250; // 168MHz / (2 * 16kHz) = 5250 htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;Period的计算:中心对齐模式下,PWM频率 = 定时器时钟 / (2 * Period)。所以Period = 168000000 / (2 * 16000) = 5250。
死区时间设200ns,对应死区寄存器值 = 200ns * 168MHz = 33.6,取34。死区时间的选择取决于MOSFET驱动器的开关速度和MOSFET本身的开关时间,一般取100ns到500ns。我用的IR2101驱动器加IRF3205 MOSFET,200ns够用。
// 死区配置 sBreakDeadTimeConfig.DeadTime = 34; // 200ns sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; sBreakDeadTimeConfig.DeadTimeConfig = TIM_DEADTIME_DTG_0;4.2 ADC采样与电流重构
电流采样用三电阻方案,在PWM下桥臂导通时采样。具体来说,用TIM1的CH4触发ADC,触发点设在中心对齐模式的下溢事件,这时候下桥臂全部导通,采样电阻上的电压就是相电流。
ADC配置:
// ADC1初始化 hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; // 42MHz hadc1.Init.Resolution = ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode = ENABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T1_CC4; hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONV_EDGE_RISING; hadc1.Init.DMAContinuousRequests = ENABLE; hadc1.Init.NbrOfConversion = 2; // 采两相,第三相计算得出采样两相电流(比如Ia和Ib),Ic = -Ia - Ib。采样时机很关键:必须在PWM下溢点附近,这时候下桥臂导通,采样电阻上有电流。如果采样点偏了,采到的就是开关噪声,电流波形会乱七八糟。
提示:ADC采样到PLL更新之间要做Clarke变换和滑膜观测器更新。我的顺序是:ADC中断 -> 读取Ia/Ib -> Clarke变换 -> 滑膜观测器更新 -> PLL更新 -> Park变换 -> 电流PI -> 反Park -> SVPWM输出。整个链路在10kHz中断里跑完,实测耗时约8μs,余量充足。
4.3 滑膜观测器与PLL的衔接
PLL的输入是滑膜观测器输出的反电动势。滑膜观测器的离散化实现:
// 滑膜观测器状态 float I_alpha_est = 0.0f, I_beta_est = 0.0f; float E_alpha_est = 0.0f, E_beta_est = 0.0f; float slide_gain = 0.5f; // 滑膜增益 void SMO_Update(float I_alpha, float I_beta, float V_alpha, float V_beta, float Ts) { // 电流估计误差 float I_alpha_err = I_alpha_est - I_alpha; float I_beta_err = I_beta_est - I_beta; // 滑膜控制项(用饱和函数代替sign,减少抖振) float Z_alpha = slide_gain * sat(I_alpha_err, 0.1f); float Z_beta = slide_gain * sat(I_beta_err, 0.1f); // 电流估计更新 I_alpha_est += (V_alpha - R * I_alpha_est - E_alpha_est) * Ts / L; I_beta_est += (V_beta - R * I_beta_est - E_beta_est) * Ts / L; // 反电动势估计(低通滤波) E_alpha_est += (Z_alpha - E_alpha_est) * Ts * LPF_FC; E_beta_est += (Z_beta - E_beta_est) * Ts * LPF_FC; }这里用了饱和函数sat代替sign,饱和阈值0.1A。饱和函数比sign函数平滑,抖振本身就小一些,再配合PLL,效果更好。LPF_FC是低通滤波截止频率,我设的是500Hz,这个LPF只是为了提取反电动势,不参与角度计算,所以相位延迟不影响角度。
然后PLL的输入就是E_alpha_est和E_beta_est:
PLL_Update(E_alpha_est, E_beta_est, Ts);这样整个链路就通了:ADC -> Clarke -> SMO -> PLL -> Park -> PI -> SVPWM。
4.4 参数整定与调试步骤
参数整定我一般按这个顺序来:
第一步,先不跑PLL,用开环强拖的方式让电机转起来,确认PWM输出正常、电流采样正常、SVPWM扇区判断正确。这一步可以用示波器看相电流波形,应该是正弦波,如果波形畸变严重,先查硬件。
第二步,跑滑膜观测器,但不接PLL,直接把E_alpha和E_beta做反正切求角度,观察角度波形。这时候角度应该有明显抖动,这是正常的。如果角度完全乱跳,说明滑膜增益不对,调slide_gain直到角度大致跟踪实际角度。
第三步,接入PLL,先把PLL带宽设低一点(比如20Hz),观察角度是否平滑。如果角度平滑但跟踪不上(突加负载时角度偏差大),逐步提高带宽到50Hz。如果角度又开始抖,说明带宽太高了,降回去。
第四步,调电流环PI。电流环带宽一般设PLL带宽的5到10倍,我设的是500Hz。电流环PI参数可以用零极点对消法算:Kp = L * ω_c,Ki = R * ω_c,其中ω_c是电流环带宽。
注意:调试的时候一定要先低压(比如12V)小电流试,确认没问题再升压。我见过有人直接上48V,参数不对直接炸MOSFET的。另外,PLL的积分项一定要限幅,不然启动瞬间误差大,积分饱和了角度会飞。
5. 常见问题与排查技巧实录
5.1 角度抖动依然大怎么办
如果换了PLL之后角度还是抖,先查这几个地方:
第一,滑膜观测器的反电动势估计是否干净。用DAC输出E_alpha_est,示波器看波形。如果反电动势上叠加了大量高频噪声,说明滑膜增益太大或者LPF截止频率太高。把slide_gain降一半,LPF_FC降到200Hz试试。
第二,ADC采样时机是否准确。如果采样点不在下溢点,采到的电流里有开关噪声,这个噪声会一路传到PLL。用示波器同时看PWM下桥臂驱动和ADC触发信号,确认触发点在下桥臂导通中间。
第三,PLL带宽是否过高。把带宽从50Hz降到30Hz,如果抖动明显减小,说明就是带宽问题。但带宽降了动态响应会变慢,需要折中。
第四,电机参数是否准确。滑膜观测器依赖R和L,如果这两个参数偏差大,反电动势估计就不准。用LCR表实测电机相电阻和相电感,填到代码里。
5.2 低速时角度跟踪不上
低速时反电动势幅值小,PLL的误差信号也小,跟踪能力下降。解决办法有两个:一是用归一化PLL,把误差信号除以转速或者反电动势幅值,这样低速时误差信号不会被衰减;二是低速时切换到IF强拖模式,等转速上来了再切到PLL。
我用的是归一化PLL,代码里加一行:
float E_mag = sqrtf(E_alpha_est * E_alpha_est + E_beta_est * E_beta_est); if (E_mag > 0.01f) { error = error / E_mag; // 归一化 }这样低速时误差信号不会被幅值衰减,跟踪能力明显改善。实测最低稳定转速从800rpm降到了300rpm。
5.3 启动时角度飞掉
启动时电机静止,反电动势为零,PLL没有输入,角度是随机的。如果直接跑PLL,角度可能锁到错误的位置,导致启动失败。
我的做法是:启动前先做一次预定位,给d轴通一个固定电流(比如额定电流的30%),持续500ms,把转子拉到d轴对齐位置,这时候角度设为0。然后再切到PLL闭环。预定位电流不能太大,不然电机会发热;也不能太小,不然拉不动。30%额定电流是个比较安全的经验值。
5.4 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决措施 |
|---|---|---|---|
| 角度抖动大 | PLL带宽过高 | 降低带宽到30Hz | 折中带宽与动态响应 |
| 角度抖动大 | 滑膜增益过大 | 减小slide_gain | 配合饱和函数使用 |
| 角度抖动大 | ADC采样噪声 | 示波器看采样点 | 调整触发时机 |
| 低速跟踪不上 | 反电动势幅值小 | 检查E_mag | 归一化PLL或IF强拖 |
| 启动角度飞 | 初始角度随机 | 检查预定位 | 加预定位流程 |
| 高速发散 | PLL积分饱和 | 检查限幅 | 加积分限幅 |
| 电流波形畸变 | 死区时间不对 | 示波器看相电流 | 调整死区值 |
| 电机发热严重 | PWM频率过低 | 检查TIM1配置 | 提高到16kHz以上 |
提示:这张表是我调试过程中实际遇到的问题汇总,每个问题都踩过坑。尤其是ADC采样噪声那个,我一开始没注意,调了三天PLL参数都没用,后来用示波器一看采样点偏了2μs,正好采在开关瞬间,改过来之后角度立刻稳了。
5.5 独家避坑经验
第一个坑:STM32F4的FPU默认可能没开。在Keil里要在Target选项里勾选Use FPU,在STM32CubeIDE里要在项目属性里设置Float ABI为Hardware。如果FPU没开,浮点运算会走软件模拟,速度慢10倍以上,10kHz中断根本跑不完。
第二个坑:arm_sin_f32和arm_cos_f32的输入范围是0到2π,如果theta_est超出这个范围,输出会不对。所以每次更新完theta_est一定要归一化。我一开始忘了归一化,跑了半小时后角度突然跳变,查了半天才发现是float精度丢失。
第三个坑:PLL的PI输出是转速,但实际代码里我把它拆成了比例项和积分项分别处理。比例项直接加到角度增量上,积分项累加到omega_est上。这样做的原因是比例项只影响瞬态,积分项影响稳态,分开处理方便限幅和调试。
第四个坑:滑膜观测器的LPF截止频率不能太低。我试过设到100Hz,反电动势估计倒是干净了,但幅值衰减严重,PLL的误差信号太小,低速时根本锁不住。后来设到500Hz,幅值和噪声都平衡得不错。
6. 实测效果与性能对比
6.1 波形对比
用同一台电机(24V,额定转速3000rpm,额定电流5A),分别跑滑膜+LPF和滑膜+PLL,用DAC输出角度估计值,示波器抓波形。
滑膜+LPF方案:1000rpm时角度波形上有明显的高频毛刺,峰峰值大约±15°,电流波形THD实测12.3%。
滑膜+PLL方案:同样1000rpm,角度波形平滑,峰峰值±3°以内,电流THD降到4.8%。
低速段对比更明显:滑膜+LPF在800rpm以下就开始失步,PLL方案能稳定跑到300rpm,角度抖动仍然在±5°以内。
6.2 动态响应对比
突加50%额定负载,滑膜+LPF方案角度恢复时间约200ms,转速跌落约15%;PLL方案角度恢复时间约80ms,转速跌落约8%。PLL的动态响应明显更好,原因是PI调节器可以设计成最佳阻尼,而LPF的相位延迟会恶化动态性能。
6.3 CPU占用率
在STM32F407上,168MHz主频,10kHz电流环,PLL_Update耗时约2.5μs,SMO_Update耗时约3μs,整个FOC中断耗时约8μs,CPU占用率8%。加上串口通信、保护逻辑等,总占用率约15%。余量充足,还可以跑一些上层逻辑。
| 项目 | 滑膜+LPF | 滑膜+PLL |
|---|---|---|
| 角度抖动(1000rpm) | ±15° | ±3° |
| 电流THD | 12.3% | 4.8% |
| 最低稳定转速 | 800rpm | 300rpm |
| 突加负载恢复时间 | 200ms | 80ms |
| CPU占用率 | 7% | 8% |
| 参数整定难度 | 高 | 中 |
从表里能看出来,PLL方案在性能上全面优于滑膜+LPF,代价是CPU占用率高了1个百分点,这个代价完全可以接受。
6.4 不同转速下的表现
我测了从300rpm到3000rpm的全转速段,PLL方案在300rpm时角度抖动±5°,1000rpm时±3°,3000rpm时±2°。高速时抖动更小,因为反电动势幅值大,PLL的误差信号信噪比高。低速时抖动稍大,但通过归一化处理已经能满足大部分应用需求。
如果你需要更低速(比如100rpm以下),PLL可能就不够了,需要考虑高频注入或者IF强拖配合。但对于大多数风机、水泵、电动工具应用,300rpm的最低转速已经够用了。
7. 代码结构与工程组织建议
7.1 文件划分
我的工程里FOC相关代码分成这几个文件:
foc_config.h:电机参数、PLL参数、SMO参数的宏定义foc_math.c/h:Clarke、Park、反Park、SVPWM等数学变换foc_smo.c/h:滑膜观测器实现foc_pll.c/h:PLL锁相环实现foc_current.c/h:电流环PI控制foc_main.c/h:FOC主状态机,负责调度各模块
这样划分的好处是模块解耦,换电机只需要改foc_config.h里的参数,换观测器算法只需要改foc_smo.c和foc_pll.c,不影响其他部分。
7.2 中断优先级安排
STM32F4的中断优先级安排很关键。我的配置是:
- ADC中断(电流环):优先级0(最高)
- TIM1更新中断(PWM):优先级1
- 串口中断:优先级2
- 系统滴答:优先级3
ADC中断必须最高,因为电流环对时序要求最严格。如果ADC中断被其他中断打断,电流环周期就会抖动,影响PLL性能。
7.3 参数宏定义示例
// foc_config.h #define MOTOR_RS 0.5f // 相电阻,欧姆 #define MOTOR_LS 0.001f // 相电感,亨利 #define MOTOR_PSI_F 0.01f // 永磁体磁链,韦伯 #define MOTOR_POLE_PAIRS 7 // 极对数 #define PWM_FREQ 16000 // PWM频率,Hz #define CURRENT_LOOP_FREQ 10000 // 电流环频率,Hz #define PLL_BANDWIDTH 50.0f // PLL带宽,Hz #define PLL_DAMPING 0.707f // PLL阻尼比 #define SMO_SLIDE_GAIN 0.5f // 滑膜增益 #define SMO_LPF_FC 500.0f // 滑膜LPF截止频率,Hz这些参数里,MOTOR_RS、MOTOR_LS、MOTOR_PSI_F、MOTOR_POLE_PAIRS必须根据实际电机实测填写,不能随便估。其他参数可以按经验值先填,调试时再微调。
注意:MOTOR_PSI_F的准确测量比较麻烦,可以用反电动势法:用另一台电机拖动被测电机旋转,测开路反电动势幅值E,则ψ_f = E / ω,其中ω是电角速度。如果没有拖动条件,可以用额定电压和额定转速估算,但精度差一些。
8. 扩展思路:从PLL到更高级的观测器
PLL解决的是角度平滑问题,但它的输入还是滑膜观测器的反电动势。如果滑膜观测器本身在低速时反电动势估计不准,PLL也救不了。所以下一步可以考虑用龙伯格观测器或者扩展卡尔曼滤波(EKF)替代滑膜观测器,从源头提高反电动势估计精度。
龙伯格观测器的全阶形式可以同时估计电流和反电动势,没有滑膜观测器的抖振问题,低速性能更好。但龙伯格观测器对电机参数更敏感,R和L的偏差会直接影响观测精度。EKF则更进一步,可以同时估计转速和角度,但计算量大,STM32F4上跑EKF可能有点吃力,需要优化。
我目前正在试龙伯格+PLL的组合,初步结果看低速能到200rpm,角度抖动±4°,比滑膜+PLL稍好。等调稳定了再分享。
另外,PLL的参数也可以做成自适应:低速时降低带宽保证稳定,高速时提高带宽保证跟踪。这样全转速段都能有比较好的性能。实现方式是根据omega_est动态调整Kp和Ki,代码里加个分段函数就行。
这个方案后续还可以扩展到无感FOC的弱磁控制。弱磁区反电动势幅值下降,PLL的误差信号变小,需要配合电压反馈或者电流反馈做补偿。这部分我还在摸索,有进展了再写。
最后分享一个小技巧:调试PLL的时候,可以用串口把theta_est和实际角度(如果有编码器的话)同时输出到上位机,用Python画个对比图,一眼就能看出跟踪效果。没有编码器的话,可以用开环强拖时的角度作为参考,虽然不准,但至少能看出PLL有没有锁住。我用的串口波特率是921600,10kHz下每100个周期发一次数据,上位机用matplotlib实时显示,调试效率高很多。