简介:这份PDF笔记面向正在学习或准备实践FOC(磁场定向控制)的电机控制初学者与嵌入式开发者,系统整理了从理论推导到算法实现的完整脉络。内容围绕FOC核心思想展开,涵盖Clark变换与Park变换及其逆变换的数学推导、等幅值与恒功率变换系数的选择依据、SVPWM的扇区判断与矢量作用时间计算、过调制处理及马鞍波成因,并延伸至PID位置式与增量式算法、积分抗饱和方法以及磁链圆限制中MAX_MODULE与START_INDEX的确定。资源为1个PDF文件,压缩包约7.34MB,结构按知识点分节编排,便于按模块查阅与对照推导。目前已有1887人学习,适合希望打通FOC数学原理与工程实现之间衔接、补齐变换与调制细节的读者参考。
1. 从三条曲线到一张磁链圆:FOC 学习笔记到底该记什么
很多人第一次调 FOC,示波器上三根相电流像三条打架的蛇,转速一给就抖,电流环一上就啸叫。你翻遍教程,Clarke、Park、SVPWM、PID 每个词都认识,连起来就是跑不通。问题不在你笨,在于大部分资料把「数学推导」和「工程落地」切成了两段:推导只讲公式不讲为什么这么定义,代码只贴函数不讲参数从哪来。这份笔记想干的事,就是把 FOC 几大变换的数学推导、SVPWM 的扇区与作用时间、PID 与积分抗饱和、磁链圆限制串成一条线,让你从「知道有这几个模块」走到「自己能推、能写、能调」。适合刚上手电机控制的学生、转行做电控的嵌入式工程师,以及调了半天电流环还是发烫的从业者。下面按我实际踩过的顺序讲,不按教科书目录讲。
2. 三大变换的数学推导:从三相到两相到底在转什么
2.1 Clarke 变换的等幅值约定与系数 2/3
Clarke 变换的目标是把三相 120 度对称的量压到两相正交坐标。设三相为 $i_a, i_b, i_c$,且满足 $i_a + i_b + i_c = 0$,定义:
$$ i_\alpha = \frac{2}{3}\left(i_a - \frac{1}{2}i_b - \frac{1}{2}i_c\right) $$
$$ i_\beta = \frac{2}{3}\cdot\frac{\sqrt{3}}{2}(i_b - i_c) = \frac{1}{\sqrt{3}}(i_a + 2i_b) $$
这里系数取 $2/3$ 是等幅值约定:变换后 $\alpha\beta$ 矢量的模等于相电流幅值。另一种常见约定是等功率(系数 $\sqrt{2/3}$),两者只差一个比例,但混用会让你的 PID 参数整定全乱。我一般统一用等幅值,因为后面 SVPWM 的电压矢量模长直接对应相电压幅值,物理直觉清楚。
代码落地时不用每次算三角函数,直接写死系数:
import numpy as np def clarke_amplitude(ia, ib, ic): # 等幅值 Clarke,输入三相瞬时电流 # 前提:ia + ib + ic ≈ 0,硬件采样要保证 i_alpha = (2.0/3.0) * (ia - 0.5*ib - 0.5*ic) i_beta = (1.0/np.sqrt(3)) * (ia + 2.0*ib) return i_alpha, i_beta逻辑说明:第二行用 $i_a + 2i_b$ 是因为代入 $i_c = -i_a - i_b$ 化简后得到,比直接写 $i_b - i_c$ 少一次减法。参数上唯一要盯的是采样三相之和是否接近零,如果传感器有偏置,$i_\alpha$ 会带直流分量,Park 之后就是转速频率的波动,表现为转矩脉动。
2.2 Park 变换与角度对齐:θ 从哪来、错了会怎样
Park 变换把静止的 $\alpha\beta$ 转到随转子旋转的 $dq$:
$$ i_d = i_\alpha \cos\theta + i_\beta \sin\theta $$
$$ i_q = -i_\alpha \sin\theta + i_\beta \cos\theta $$
$\theta$ 是转子电角度。表贴式永磁同步电机常用 $i_d = 0$ 控制,所以 $i_q$ 就是转矩电流。角度对齐是 FOC 最容易被忽略的一步:如果 $\theta$ 有固定偏差,$i_d$ 和 $i_q$ 会互相串,你给 $i_q$ 指令,实际有一部分变成了励磁,电机发热但不出力。
def park(ialpha, ibeta, theta_elec): # theta_elec 为电角度,单位 rad cos_t = np.cos(theta_elec) sin_t = np.sin(theta_elec) id_ = ialpha * cos_t + ibeta * sin_t iq_ = -ialpha * sin_t + ibeta * cos_t return id_, iq_参数说明:$\theta$ 必须用电角度,等于机械角乘以极对数。常见翻车是编码器零点和电机 d 轴没对齐,上电先做一次对齐:给 d 轴通一个小电流,把转子吸到已知位置,再把编码器读数记为零点。这一步不做,后面所有推导都白搭。
2.3 反 Park 与逆 Clarke:把电压指令送回三相
电流环输出 $v_d, v_q$,要变回三相占空比,先反 Park:
$$ v_\alpha = v_d \cos\theta - v_q \sin\theta $$
$$ v_\beta = v_d \sin\theta + v_q \cos\theta $$
再逆 Clarke 得到三相电压,或者直接送 SVPWM。逆 Clarke 等幅值形式:
$$ v_a = v_\alpha $$
$$ v_b = -\frac{1}{2}v_\alpha + \frac{\sqrt{3}}{2}v_\beta $$
$$ v_c = -\frac{1}{2}v_\alpha - \frac{\sqrt{3}}{2}v_\beta $$
def inv_park(vd, vq, theta_elec): cos_t = np.cos(theta_elec) sin_t = np.sin(theta_elec) valpha = vd * cos_t - vq * sin_t vbeta = vd * sin_t + vq * cos_t return valpha, vbeta def inv_clarke(valpha, vbeta): va = valpha vb = -0.5*valpha + (np.sqrt(3)/2)*vbeta vc = -0.5*valpha - (np.sqrt(3)/2)*vbeta return va, vb, vc这三组变换合起来就是 FOC 的骨架。推导不难,难在每一步的系数约定和角度定义要前后一致。我习惯在代码里把「等幅值」写进注释,换人接手也不会混。
3. SVPWM 的过程:六个扇区、七段式与作用时间怎么算
3.1 电压矢量与扇区判断
三相逆变器有 8 种开关状态,对应 6 个非零矢量和 2 个零矢量。把 $v_\alpha, v_\beta$ 送进来,先判断落在哪个 60 度扇区。常用判据:
$$ A = v_\beta $$
$$ B = \frac{\sqrt{3}}{2}v_\alpha - \frac{1}{2}v_\beta $$
$$ C = -\frac{\sqrt{3}}{2}v_\alpha - \frac{1}{2}v_\beta $$
根据 $A, B, C$ 的正负组合得到扇区号。这一步用整数运算就能做,不用查表。
def svpwm_sector(valpha, vbeta): # 返回扇区 1~6 A = vbeta B = (np.sqrt(3)*valpha - vbeta) / 2.0 C = (-np.sqrt(3)*valpha - vbeta) / 2.0 # 符号函数构造扇区索引 N = (A > 0) + 2*(B > 0) + 4*(C > 0) # N 与扇区映射,常见做法用查表 sector_map = {3:1, 1:2, 5:3, 4:4, 6:5, 2:6} return sector_map[N]逻辑说明:$N$ 是三位二进制编码,映射表因 $A,B,C$ 定义顺序不同会有差异,一定要拿已知矢量验证,比如 $v_\alpha>0, v_\beta=0$ 应该落在扇区 1。我见过有人直接抄表不验证,结果电机转起来像步进电机。
3.2 相邻矢量作用时间与七段式对称
以扇区 1 为例,相邻矢量是 $V_1$ 和 $V_2$,作用时间:
$$ T_1 = \frac{\sqrt{3} T_s}{U_{dc}} \left(v_\alpha - \frac{v_\beta}{\sqrt{3}}\right) $$
$$ T_2 = \frac{\sqrt{3} T_s}{U_{dc}} \cdot \frac{2 v_\beta}{\sqrt{3}} $$
其中 $T_s$ 是 PWM 周期,$U_{dc}$ 是母线电压。零矢量时间 $T_0 = T_s - T_1 - T_2$,平均分给 $V_0$ 和 $V_7$ 构成七段式,对称输出谐波小。
def svpwm_times(valpha, vbeta, udc, ts): sector = svpwm_sector(valpha, vbeta) # 以扇区1为例,其他扇区做角度旋转 if sector == 1: t1 = (np.sqrt(3)*ts/udc) * (valpha - vbeta/np.sqrt(3)) t2 = (np.sqrt(3)*ts/udc) * (2*vbeta/np.sqrt(3)) # 其余扇区省略,实际用旋转或查表 t0 = ts - t1 - t2 return t1, t2, t0参数说明:$U_{dc}$ 要实测,不能拿标称值,母线跌落会让实际输出电压偏小。$T_s$ 一般取 10kHz~20kHz,太高开关损耗大,太低电流纹波大。$T_1+T_2$ 超过 $T_s$ 说明电压指令超出六边形内切圆,必须做限幅,这就是下一章的磁链圆限制。
3.3 从作用时间到比较寄存器
七段式把 $T_0/2, T_1, T_2, T_0/2$ 对称排布,得到三个比较值 $T_a, T_b, T_c$,再换算成定时器 CCR。不同扇区的排布不同,工程上常用「先算基础值再按扇区重排」的写法,减少重复代码。写完后用示波器看相电压,应该是马鞍波,如果看到明显平顶,多半是限幅没做对。
4. PID 与积分抗饱和:电流环为什么一给大指令就失控
4.1 电流环 PI 的参数从电机参数来
电流环被控对象近似一阶,$G(s) = \frac{1}{Ls + R}$。用零极点对消,令 $K_p/K_i = L/R$,闭环带宽由 $K_p$ 决定。常见做法:
$$ K_p = \omega_c L $$
$$ K_i = \omega_c R $$
$\omega_c$ 取电流环带宽,一般 1kHz~2kHz。这样整定比盲试快得多。
def current_pi_gains(L, R, wc): # L 单位 H,R 单位 ohm,wc 单位 rad/s kp = wc * L ki = wc * R return kp, ki参数说明:$L$ 和 $R$ 用万用表和 LCR 表实测,别信手册典型值。带宽不能超过开关频率的 1/10,否则采样和 PWM 延迟会让相位裕度不够,表现为高频啸叫。
4.2 积分抗饱和的三种写法
电压指令超出母线能输出的范围时,积分器还在累加,退饱和要很久,这就是积分饱和。现象是给一个大阶跃,电流冲上去后回落很慢,甚至振荡。三种常见做法:
| 方法 | 思路 | 适用 |
|---|---|---|
| 钳位法 | 积分项单独限幅 | 简单,效果一般 |
| 反算法 | 用饱和误差回灌积分器 | 退饱和快 |
| 条件积分 | 饱和时停止积分 | 实现最简 |
我一般用反算法,退饱和最干脆:
class PIController: def __init__(self, kp, ki, out_min, out_max): self.kp, self.ki = kp, ki self.out_min, self.out_max = out_min, out_max self.integral = 0.0 def update(self, error, dt): p_term = self.kp * error # 先算未限幅输出 unsat = p_term + self.integral + self.ki * error * dt # 限幅 sat = max(self.out_min, min(self.out_max, unsat)) # 反算:把饱和误差回灌,kaw 取 1/ki 或经验值 kaw = 1.0 / self.ki if self.ki != 0 else 0.0 self.integral += self.ki * error * dt + kaw * (sat - unsat) return sat逻辑说明:kaw是反算增益,取 $1/K_i$ 是常见起点,实际调试时看退饱和速度微调。注意dt要稳定,用定时器中断里实测的周期,不要用理论值。
4.3 速度环与电流环的带宽分离
速度环带宽一般取电流环的 1/5~1/10,否则两个环互相耦合,表现为低速爬行或高速抖动。速度环输出是 $i_q$ 指令,所以速度环的限幅就是最大转矩电流。调试顺序永远是先电流环、再速度环,电流环没调好就上速度环,等于在沙地上盖楼。
5. 磁链圆限制与避坑:电压矢量超出六边形之后
5.1 磁链圆限制的几何含义
SVPWM 能输出的最大不失真电压矢量是六边形内切圆,半径 $U_{dc}/\sqrt{3}$。如果 $v_\alpha, v_\beta$ 的模超过这个值,$T_1+T_2 > T_s$,必须限幅。常见做法是等比例缩放:
$$ k = \frac{U_{max}}{\sqrt{v_\alpha^2 + v_\beta^2}} $$
当 $k < 1$ 时,$v_\alpha, v_\beta$ 同乘 $k$。这样保持电压矢量方向不变,只压模长,转矩响应会变软但不会失控。
def flux_circle_limit(valpha, vbeta, udc): umax = udc / np.sqrt(3) mag = np.hypot(valpha, vbeta) if mag > umax: scale = umax / mag return valpha*scale, vbeta*scale return valpha, vbeta参数说明:$U_{dc}$ 用实时采样值,母线波动时限制跟着变。有些方案用六边形直接限幅,输出电压略高但谐波大,看应用取舍。
5.2 避坑记录:五个我实际踩过的坑
现象一:上电电机啸叫,电流波形毛刺。原因:PWM 死区没补偿,低压时死区占比大。解决:加死区补偿,或者提高母线电压测试。
现象二:给定转速后电机反转。原因:编码器计数方向或相序接反。解决:交换任意两相线,或软件里取反角度。
现象三:电流环一上就振荡。原因:带宽设太高,超过开关频率 1/10。解决:降 $K_p$,先保证稳定再提带宽。
现象四:大指令下退饱和慢。原因:积分器没有抗饱和。解决:按 4.2 加反算,kaw从 $1/K_i$ 起调。
现象五:高速时电流波形失真。原因:电压矢量超出内切圆没限幅。解决:加 5.1 的磁链圆限制,并确认 $U_{dc}$ 采样准确。
提示:每次改参数只改一个,改完记录波形。FOC 调试是排除法,不是玄学。
6. 进阶技巧:用 Python 仿真验证你的推导再上硬件
硬件调试成本高,烧管、炸电容都是钱。我习惯先在 Python 里把整套 FOC 跑一遍,验证变换系数、扇区判断、限幅逻辑,再移植到 MCU。下面是一个最小仿真框架,跑一个电流环阶跃,看 $i_q$ 跟踪。
import numpy as np import matplotlib.pyplot as plt # 电机参数 R, L = 0.5, 0.001 wc = 2*np.pi*1000 kp, ki = wc*L, wc*R ts = 1e-4 # 10kHz udc = 24.0 pi = PIController(kp, ki, -udc/np.sqrt(3), udc/np.sqrt(3)) iq_ref = 1.0 iq = 0.0 log = [] for k in range(2000): err = iq_ref - iq vq = pi.update(err, ts) # 简化:只跑 q 轴,d 轴为 0 diq = (vq - R*iq) / L * ts iq += diq log.append(iq) plt.plot(log) plt.xlabel('step') plt.ylabel('iq (A)') plt.title('current loop step response') plt.grid(True) plt.show()逻辑说明:这里忽略了反电动势和交叉耦合,只验证 PI 和限幅逻辑。实际移植时把 Clarke、Park、SVPWM 按第 2、3 章补上。参数上ts要和 MCU 中断周期一致,udc用实测值。看阶跃响应有没有超调、退饱和快不快,调好了再上硬件,能省很多返工。
我自己的习惯是:任何新电机,先仿真跑通,再拿低压小电流上电,最后才加满载。这套流程帮我省过至少两次炸管。希望帮到你。
本文还有配套的精品资源,点击获取