06FOC 总体流程
采样、变换、电流环、SVPWM 闭环链路
本模块主旨
FOC = 每个 PWM 周期跑一遍的 7 段纯函数流水线;时序预算 + 解析整定 + 解耦前馈三件事不掉链,电机调试就只剩选参数。学完应能回答
- 能背出每个 PWM 周期里依次发生的 7 件事(ADC 注入采样 → Clarke → Park → PI → 反 Park → SVPWM → 写 CCR)
- 能解释为什么 PI 要放在 dq 域而不是 abc 域(直流量 vs 交流量,零稳态误差)
- 能定位电流环 4 类典型故障:振荡(Kp/延迟)、Id-Iq 串扰(Δθ)、高速发软(缺解耦)、撞限不收(抗饱和)
- 能判断哪些代码必须放在 ADC ISR 里、哪些必须放到 1 kHz 低频任务里(按时间预算 < 10 μs)
- 理解 ADC 注入触发点必须放在 PWM 中点的硬件原因(电流处于纹波谷 + 桥臂稳态 000/111)
- 能用 Kp = ω_bw·L、Ki = ω_bw·R 算出 PI 起点,并解释为什么实测取 0.5-0.7 倍解析值
- 理解 ADC 采样 + CCR 预装载产生的 1.5 个 PWM 周期延迟为什么把电流环带宽锁死在 PWM/10~PWM/8
总览
FOC 不是数学难,是工程链路长。把每段都看成一个可独立验证的纯函数,整个系统才稳。零基础速入
类比
FOC 就像给三相交流电机装了一台"翻译机"。普通办法直接调三相电压,电机看到的是不停旋转的复杂波形,没法精确控转矩。FOC 把复杂的三相变成两个稳定的直流量(Id 控磁链、Iq 控转矩),就像把摇晃的船舱地板换成静止的——你站在静止的地板上调阀门容易得多。核心概念
FOC 不是某个单一公式,而是一条**每个 PWM 周期跑一遍**的流水线:采样 → Clarke → Park → 电流 PI → 反 Park → SVPWM → 输出。每个环节只做一件事,错了能定位。为什么要学
- 不会 FOC,PMSM 调起来就是开环加点小聪明,转矩控不准、效率低、动态差。
- FOC 是新能源汽车驱动、伺服、机器人关节的工业标准。学会它你就能读懂 90% 的电机控制代码。
- 搞清楚流水线每一段后,遇到"电机抖动""启动失败"等问题能精准定位(采样?角度?PI?SVPWM?硬件?)
第一手操作
点击顶部"电流环响应"标签,把右边 Iq 阶跃指令拖到 5A,看 Iq(绿)追指令(虚线)的过程。然后把"角度误差 Δθ"从 0 拖到 15°,看 Iq 阶跃时 Id(蓝)也被拉起来——这就是著名的"dq 串扰"。学习目标
- 串起采样、Clarke、Park、电流环、反 Park、SVPWM 和反馈角度
- 理解 FOC 流水线每段的输入输出
- 能定位电流环超调 / 振荡 / 串扰的根因
核心概念
- FOC 每个 PWM 周期做一次"小闭环":采电流 → 算坐标 → 跑 PI → 算电压矢量 → 更新 PWM → 等待下一次反馈。
- 角度反馈可来自编码器或观测器;角度质量直接决定 dq 解耦质量。
- PI 在 dq 域工作(直流量),比直接控三相 AC 简单得多。
- 电流环带宽 ω_bw ≈ Kp / L,Ki / Kp = R / L 是临界阻尼起点。
公式
FOC 流水线
abc → Clarke → Park → PI(Id, Iq) → inv-Park → SVPWM → 桥臂
每一段都是纯函数,输入输出明确,便于移植到 C / STM32 / MATLAB。
dq PMSM 模型
vd = R·id + Ld·did/dt - ω·Lq·iq; vq = R·iq + Lq·diq/dt + ω·(Ld·id + ψf)
dq 之间存在交叉耦合项 ω·L·iq 和 ω·L·id;速度越高耦合越强,需要在 PI 输出加解耦前馈。
电流环典型整定
Kp = ω_bw × L, Ki = ω_bw × R
想要 1 kHz 带宽,Kp = 6283 × 0.0012 ≈ 7.5(实际 PWM 16k 时取 1-3 较稳)。Ki/Kp = R/L 让零点抵消极点。
工程意义
- 流水线拆开后,"电机抖动"可以精确定位:采样错?角度错?PI 振荡?SVPWM 饱和?硬件直通?
- 电流环带宽决定速度环最大可用带宽。一般电流环 1-5 kHz,速度环 50-500 Hz,差 10× 才稳。
- 角度误差 Δθ 直接体现在 Iq 阶跃时 Id 跳一下:这是判断编码器零位是否对齐的实战手段。
STM32 移植指南
- PWM 中心对齐 16-20kHz,ADC 采样点放在 PWM 中点(电流纹波最小处),中断里完整跑完 FOC 链。
- TIM1/TIM8 + ADC1/ADC2 用 InjectedConv + DMA 双采,CPU 只在中断尾部处理(< 5μs)。
- 所有算法(Clarke/Park/PI/SVPWM)写成纯函数,主循环传引用,方便单元测试和移植。
常见误区
- 在 UI 或业务层写控制算法,无法移植和单元测试。
- 中断里加 printf / HAL_Delay / 浮点除法,PWM 周期超时。
- 采样点选在 PWM 边沿附近(噪声大)。
- PI 没做抗积分饱和,输出撞限幅后释放出大超调。
- 没在中断尾部用 __DSB() 确保 CCR 写入生效。
调试方法
- 按段冻结调试:先 ADC 校零(ia+ib+ic ≈ 0)→ 再开环转电压矢量看转子能否拖动 → 再上电流环 → 最后上速度环。
- 在 RTT/串口每个 PWM 周期记录 ia/ib/ic/iα/iβ/id/iq/vd/vq/duty 和 sector,跟波形发生器/示波器对照。
- 电流环超调先怀疑 Kp 太大或采样延迟过多;稳态误差先看 Ki 和限幅。
建议实验
- 把"角度误差 Δθ"从 0 拖到 ±15°,看 Iq 阶跃时 Id 出现峰值——这是 dq 串扰的物理表现。
- 把"电频率 ω"从 0 拉到 200Hz,看 Iq 上升过程中带"晃动"——这是交叉耦合 ω·L·iq 进入 vd 的结果。
- 把"采样延迟"从 1 拉到 4,相同 Kp 下振荡明显加重——延迟等效相位裕度损失。
- 加载"过激振荡"预设,再把 Kp 减半,对比超调减小。
STM32 C 代码骨架
/* ============================================
* foc.c — FOC 中断主循环(运行在 PWM 16kHz ISR 内)
* 适配 STM32G4 (Cortex-M4F + FPU),单次执行 < 6 μs
* ============================================ */
#include "foc.h"
static pi_state_t g_pi_d = { .integral = 0 };
static pi_state_t g_pi_q = { .integral = 0 };
void TIM1_UP_TIM16_IRQHandler(void) {
if (TIM1->SR & TIM_SR_UIF) {
TIM1->SR = ~TIM_SR_UIF;
/* 1. 采样三相电流(ADC 注入序列已经触发) */
float ia = adc_to_amps(ADC1->JDR1, g_offset.ia);
float ib = adc_to_amps(ADC1->JDR2, g_offset.ib);
float ic = -ia - ib; // 三相和为零,省一路 ADC
/* 2. 读电角度(编码器/观测器) */
float theta_e = encoder_to_theta_e(
ENCODER->CNT, g_param.encoder_zero, g_param.pole_pairs);
float sin_t, cos_t;
sincosf(theta_e, &sin_t, &cos_t);
/* 3. Clarke:abc → αβ */
float i_alpha = ia;
float i_beta = ONE_OVER_SQRT3 * (ia + 2.0f * ib);
/* 4. Park:αβ → dq(同步旋转坐标) */
float i_d = cos_t * i_alpha + sin_t * i_beta;
float i_q = -sin_t * i_alpha + cos_t * i_beta;
/* 5. 电流 PI(在 dq 域,控直流量) */
float v_d = pi_step(&g_pi_d, g_ref.id - i_d, g_param.kp, g_param.ki, DT);
float v_q = pi_step(&g_pi_q, g_ref.iq - i_q, g_param.kp, g_param.ki, DT);
/* 5b. 解耦前馈(高速时建议加,低速可省) */
v_d -= 2.0f * M_PI * g_state.elec_freq * g_param.lq * i_q;
v_q += 2.0f * M_PI * g_state.elec_freq * (g_param.ld * i_d + g_param.psi_f);
/* 5c. 圆形限幅(SVPWM 线性区 ≈ Udc/√3) */
float v_lim = g_state.udc * ONE_OVER_SQRT3 * 0.95f;
float v_mag = sqrtf(v_d*v_d + v_q*v_q);
if (v_mag > v_lim) { v_d *= v_lim/v_mag; v_q *= v_lim/v_mag; }
/* 6. 反 Park:dq → αβ */
float v_alpha = cos_t * v_d - sin_t * v_q;
float v_beta = sin_t * v_d + cos_t * v_q;
/* 7. SVPWM:αβ → 三相占空比 */
svpwm_t sv = svpwm_calc(v_alpha, v_beta, g_state.udc);
/* 8. 写入 CCR(下一个 PWM 周期生效) */
TIM1->CCR1 = (uint16_t)(sv.duty_a * TIM1->ARR);
TIM1->CCR2 = (uint16_t)(sv.duty_b * TIM1->ARR);
TIM1->CCR3 = (uint16_t)(sv.duty_c * TIM1->ARR);
__DSB();
}
}下一步
- 深入 SVPWM 模块,看反 Park 的 Uα/Uβ 怎么变成 6 个开关状态加零矢量。
随堂题
FOC 在 dq 坐标里做 PI,相比直接在 abc 做控制最大的好处是什么?
- 计算量少
- 稳态时控制对象是直流量,PI 可以严格消除稳态误差 (正确)
- 不需要传感器
- 硬件成本低
提示:abc 三相是 AC 量,标准 PI 跟踪 AC 会有稳态相位/幅值误差;dq 是 DC 量,PI 可以做到零稳态误差。
电流环 Kp 增大到使 Iq 出现 30% 超调和振荡,最可能的原因?
- Ki 太小
- 电压限幅太大
- Kp 已经超过电流环带宽 / 采样延迟匹配 (正确)
- 电机参数错
提示:电流环带宽 ω_bw ≈ Kp/L。Kp 太大让 ω_bw 高于 1/(2π·延迟),相位裕度耗尽就开始振荡。
同时给 Iq 阶跃指令但 Id 也被拉起来一个峰值,最可能的原因?
- Kp 不够
- 编码器零位没对齐 / 角度有误差 Δθ (正确)
- PWM 频率太低
- ADC 采样错
提示:Iq 投影到错误旋转的 d 轴上就会"溢"到 Id 上。试改"角度误差 Δθ"看现象。
高速运行时电流环响应变慢、有晃动,加什么能改善?
- 加大 Kp
- 加大 Ki
- 加 dq 解耦前馈(vd 减 ω·Lq·iq、vq 加 ω·(Ld·id + ψf)) (正确)
- 降低 PWM 频率
提示:高速时 ω·L 项让 d/q 互相干扰,靠 PI 反馈修复慢。前馈直接抵消这部分耦合。
FOC 中断里出现下面哪个最危险?
- 浮点乘加
- sincosf 调用
- printf 或 HAL_Delay (正确)
- CCR 写入
提示:printf/Delay 阻塞数百 μs 到 ms 级,PWM 周期 60μs 完全装不下,会立刻丢拍。
相关公式索引
FOC 总采样-生效延迟
t_delay ≈ 1.5·Ts (ADC 中点 + CCR 预装载); 电流环带宽上限 ≈ 1/(2π·1.5·Ts)