简介:这份PDF笔记面向正在学习或准备实现FOC的电机控制初学者与工程实践者,围绕磁场定向控制的核心链路,梳理数学推导、Simulink仿真与算法实现要点,适合用来补齐理论推导和代码落地之间的断层。压缩包仅含1个PDF文件,大小约7.34MB,便于集中阅读和随查随用。内容从FOC本质与超前角控制讲起,依次展开Clark、Park及逆变换的数学推导与程序实现,并覆盖SVPWM扇区判断、矢量作用时间、过调制与切换点确定,还讨论了PID离散化、位置式与增量式控制、参数整定、积分抗饱和,以及磁链圆限制中MAX_MODULE和START_INDEX的确定。已有1920人学习,可作为搭建仿真、排查调制与电流环问题的参考笔记。
1. 什么是FOC:让定子磁场永远比转子快半步
拧过电动车油门的人都体会过一种差别:有的车一拧就窜,松手又是一顿;有的车拧多少走多少,安静顺滑。差别很多时候不在电池和电机本身,而在控制器里到底跑没跑 FOC。FOC 的全称是磁场定向控制(Field-Oriented Control),一句话概括:让定子绕组产生的磁场始终比转子磁场超前 90 度,并且把定子电流拆成“只管出力”和“只管励磁”两路分开管。
打个比方,推一扇旋转门:正面顶住门板,门转得最省力;斜着推,大半力气都花在把门往侧面掰。电机也一样,转子磁极和定子磁场之间,90 度的夹角对应最大转矩。FOC 干的就是这件事——实时知道转子转到哪儿了,然后把定子磁场精确地摆到超前 90 度的位置上,多一分浪费、少一分没劲。搞懂它,你才能看懂为什么驱动器里要算那么多三角函数,也才能明白后面那几组“变换”到底在忙什么。
2. Clarke变换:三根线的电流,其实只有两个自由度
2.1 为什么必须先降维
三相电机的三组绕组在空间上互差 120 度,任何时刻三根线上的电流都满足一个硬约束:流入节点的电流之和等于流出,也就是Ia + Ib + Ic = 0。这意味着三个数字里只有两个是独立的,第三个永远可以被算出来。三脚凳有三条腿,可凳面在平面上的位置只需要两个坐标就能描述清楚——三相电流和这个是一个道理。
更麻烦的是,如果真去单独控制每一相,你会发现三相之间是互相拉扯的:改 A 相的占空比,B 相和 C 相的合成磁场跟着偏,闭环根本没法独立设计。Clarke 变换要做的事,就是把这三个互相拉扯的量,换成一个箭头的“长度”和“方向”:让 α 轴和 A 相绕组重合,β 轴超前 α 轴 90 度,那么三相电流合成的那个旋转磁场箭头,在这个 αβ 平面里就有了一组干净、正交、互不干扰的两维坐标。
需要留意的是,Clarke 变换只是把三轴降成两轴,αβ 平面里的箭头依然在以电角速度疯狂旋转,值还是正弦波,PID 拿它照样没辙。降维解决的是“耦合”,不解决“旋转”,旋转的问题要留给下一章的 Park 变换。
这一段推导的起点是基尔霍夫电流定律和三组绕组在空间上的投影关系。把 A、B、C 三相的瞬时值投影到 α 和 β 两个正交轴上,得到的就是那两个只差一个系数的等式——数学形式不复杂,难的是想明白“为什么可以这么投”。想象三束互成 120 度的探照灯打在同一面墙上,墙上那个光斑的位置,本质上就是三束光的合成结果,换成两个互相垂直的坐标去描述它,光斑一点没变,只是记账方式变了。
2.2 等幅值变换和恒功率变换,差在一个系数
同一个 Clarke 变换,那个变换系数 k 有两种常见取法,带来的效果完全不同:
| 变换类型 | 系数 k | 变换后的特点 | 常见使用场景 |
|---|---|---|---|
| 等幅值变换 | 2/3 | 变换前后幅值保持一致,αβ 坐标里的读数直接就是相电流的峰值 | FOC 主流选择,接 SVPWM 时不会顶到圆形边界 |
| 恒功率变换 | √(2/3) | 变换前后功率守恒,但矢量幅值被整体抬高一截 | 功率分析、理论推导、需要严格能量对应的地方 |
名字听起来玄学,其实就是一句话:等幅值管的是“箭头长度别变”,恒功率管的是“能量账别算错”。两者数学上等价,选哪个都能跑通,但工程上必须统一,不能前面用等幅值算电流、后面用恒功率算电压。
FOC 里普遍用等幅值,理由很实在:后面 SVPWM 要在一个圆里合成电压矢量,如果这里用了恒功率系数,矢量的幅值会整体偏大一截,很容易在还没到最大能力时就越过圆形边界,产生过调制,波形直接削顶。换句话说,等幅值是为了让后面的“圆”留够余量。
2.3 程序实现:几行代码背后的取舍
落到代码上,Clarke 变换短得让人意外:
typedef struct { float ia, ib, ic; } abc_t; typedef struct { float i_alpha, i_beta; } alphaBeta_t; alphaBeta_t Clark(abc_t input) { alphaBeta_t output; /* Iα = Ia */ output.i_alpha = input.ia; /* Iβ = (Ia + 2·Ib) / √3,0.577350269 就是 1/√3 */ output.i_beta = (input.ia + 2.0f * input.ib) * 0.577350269f; return output; }关键点有三个。第一,0.577350269f就是 1/√3,写成 0.5 或者 0.6 都会让后面的角度整体偏掉,这种错误在低速时几乎看不出来,一上高速就翻车。第二,代码里只读了ia和ib,ic根本没进参数——因为ic = -ia - ib,采样电路少一路运放就少一路成本,这也是很多驱动器只采两相电流的原因。第三,这个写法默认用的是等幅值系数 2/3,如果换成恒功率,i_beta前面的系数要跟着改。
反变换同样顺手:
abc_t Rev_Clark(alphaBeta_t input) { abc_t output; output.ia = input.i_alpha; output.ib = -0.5f * input.i_alpha + 0.866025403f * input.i_beta; /* √3/2 */ output.ic = -0.5f * input.i_alpha - 0.866025403f * input.i_beta; return output; }它一般用在需要把 αβ 系的量还原成三相的地方,比如某些电流重构或者调试观测。日常 FOC 主链路里,Clarke 只用正向那一次,反变换出场机会不多,但一旦要对着示波器核对三相波形,它就是那个能帮你省掉半小时的后悔药。
3. Park变换:把转着的交流“拍平”成直流
3.1 两行公式是怎么来的
Clarke 变换之后,αβ 平面里的电流矢量还在转。你坐在路边看一辆匀速行驶的车,它一直在动;可如果你开车跟它并排,从你车里看过去,它是静止的。Park 变换就是把自己“搬”到跟着转子一起转的坐标系里——d 轴对准转子磁极方向,q 轴超前 90 度——这时候再去观察定子电流,稳态下它就变成了两个不动的数。
公式就是把 αβ 矢量往 d、q 两个轴上做投影:
Id = Iα·cosθ + Iβ·sinθIq = -Iα·sinθ + Iβ·cosθ
这里的 θ 就是转子的电角度。为什么是这两个式子而不是别的?因为 d 轴方向在 αβ 平面里的单位向量恰好是(cosθ, sinθ),把一个矢量投影到某个方向,就是点乘那个方向的单位向量,剩下的分量自然落在垂直的 q 轴上。推导过程无非是三角函数展开和坐标旋转矩阵,结论记住这两行就够用。
这一步的意义被严重低估:它把“控制一个不停旋转的正弦量”变成了“控制两个基本不动的直流量”。PID 最怕的就是追着一个正弦目标跑——那一定会产生稳态误差和相位滞后;而当目标是直流量时,积分项才能真正把误差磨到零。整个 FOC 能跑起来,靠的就是这一次“拍平”。
3.2 逆变换为什么必须存在
PI 控制器在 dq 坐标系里算出来的结果,是两个电压指令Ud和Uq。可逆变桥不认识 d 轴 q 轴,它只认识三相开关;SVPWM 认识的也只有 αβ 平面里的电压矢量。所以必须再走一趟回头路,把Ud、Uq转回Uα、Uβ。
Park 变换的矩阵是一个正交矩阵,正交矩阵的逆等于它的转置,所以逆变换的形式特别对称:
Uα = Ud·cosθ - Uq·sinθUβ = Ud·sinθ + Uq·cosθ
对比正向公式,你会发现正弦项的符号反了。这不是巧合,而是旋转方向相反的直接体现。写代码的时候如果正变换和逆变换用了同一个 θ 却把符号抄错,表现出来的现象非常诡异:电机能转,但电流环一给指令就发散,或者空载正常、一加负载就抖。这类问题排查起来很费时间,建议在单元测试里就用固定的 θ(比如 0 度、90 度)代进去手工核对一遍。
3.3 电角度和定点数真正容易踩坑的地方
先说电角度本身。它存在的意义只有一个:告诉算法“转子现在朝哪儿”。所以它的零点定在 A 相轴线、d 轴、还是 q 轴,理论上都行,只要整个系统前后一致。取 A 相轴线和 d 轴的夹角是最常见的做法,因为这样投影时乘的系数正好是现成的三角函数值,Park 和逆 Park 都省事。
但有一个坑必须提前说清楚:机械角度和电角度不是一回事。电角度 = 极对数 × 机械角度。一台 7 对极的电机,转子机械上转一圈,电角度已经转了 7 圈。编码器读出来的是机械角,如果不乘极对数就直接喂给 Park 变换,电机大概率能转,但转矩脉动大得离谱,而且高速时电流环完全失控。
再看定点数。很多单片机上的三角函数是用查表实现的,返回的不是 -1 到 1 的浮点,而是 Q15 格式的定点数:
dq_t Park(alphaBeta_t input, int16_t theta) { dq_t output; Trig_Components sc = Trig_Functions(theta); /* 查表,返回 Q15 格式的 sin/cos */ float cos_da = (float)(sc.hCos * DIV_Q15); /* 32768 映射成 1.0 */ float sin_da = (float)(sc.hSin * DIV_Q15); output.id = input.i_alpha * cos_da + input.i_beta * sin_da; output.iq = -input.i_alpha * sin_da + input.i_beta * cos_da; return output; }DIV_Q15通常是1.0f / 32768.0f。这里最容易出问题的地方是漏乘这个系数,导致 sin/cos 变成几千倍的放大,然后 id、iq 瞬间爆掉,看起来像是电流采样坏了,其实是比例因子没归一化。另一种情况是角度单位搞错:查表函数要的是 0 到 65535 对应 0 到 2π 的整数角度,你传了个弧度进去,波形就会以一种说不清的频率乱跳。
4. SVPWM:用六个基本矢量拼出一个旋转的圆
4.1 平均值等效原理到底等效的是什么
三相逆变桥有六个开关管,每一相的上管和下管互补导通。三相结合起来,一共只有 8 种合法的开关状态:其中 6 种会让三相输出接到不同的母线电位上,在空间里形成 6 个长度相等、方向相隔 60 度的电压矢量;另外 2 种是三相同接上管或同接下管的“零矢量”,输出为零。
这 6 个非零矢量像六等分的披萨,把整个平面切成 6 个扇区。SVPWM 的核心思路是:在任意一个很短的时间窗口里,用扇区边界上的那两个非零矢量,加上零矢量,按不同比例混合,让它们的平均效果等于你想要的那个电压矢量。
打个比方,你想调出一种介于红和蓝之间的紫色,颜料只有纯红、纯蓝和透明三种。你可以用 30% 的时间和红颜料、20% 的时间用蓝颜料、剩下 50% 的时间什么都不涂,只要这个“混合周期”足够短,人眼看到的就是稳定的紫。SVPWM 里的 PWM 周期就是这个“足够短”,零矢量就是那个“什么都不涂”的透明。
这个原理听起来简单,但它解释了一件事:逆变桥本身只会输出 8 种离散状态,是不可能连续变化的;所有的连续电压,都是靠开关在时间上的平均“骗”出来的。理解了这一点,后面那些扇区判断、作用时间计算,就都只是“怎么把这个比例算准”的问题了。
4.2 扇区判断:三个不等式定位置
要混合,先得知道目标矢量落在哪个扇区。做法是把 αβ 分量代进三个线性表达式里,看它们的正负号:
Uref1 = UβUref2 = (√3/2)·Uα - (1/2)·UβUref3 = -(√3/2)·Uα - (1/2)·Uβ
分别判断这三个量是否大于零,得到三个比特 A、B、C,再令N = A + 2B + 4C,N 和扇区的对应关系是固定的:
| N | 3 | 1 | 5 | 4 | 6 | 2 |
|---|---|---|---|---|---|---|
| 扇区 | I | II | III | IV | V | VI |
代码大概长这样:
/* 返回 N 值,查表得到扇区号 */ int Sector_Cal(alphaBeta_t v) { int A = (v.i_beta > 0.0f) ? 1 : 0; int B = (1.7320508f * v.i_alpha - v.i_beta > 0.0f) ? 1 : 0; /* √3·Uα - Uβ */ int C = (-1.7320508f * v.i_alpha - v.i_beta > 0.0f) ? 1 : 0; return A + 2 * B + 4 * C; }三个系数里的1.7320508就是 √3,别写成 1.73,精度差一点在低速大电流时会导致扇区偶尔判错,表现为电流波形上冒出周期性的毛刺。另外判断条件必须严格用> 0而不是>= 0,横跨坐标轴的那一瞬间如果同时满足两个扇区,宁可让它走一边,否则切换点会出现一个采样周期的抖动。
4.3 作用时间、过调制与母线电压利用率
以扇区 I 为例,两个相邻非零矢量的作用时间可以从伏秒平衡直接解出来:
T6 = √3 · Ts · Uβ / UdcT4 = √3 · Ts · (√3/2·Uα - 1/2·Uβ) / UdcT0 = Ts - T4 - T6
其中Ts是 PWM 周期,Udc是母线电压。别的扇区形式类似,只是把 Uα、Uβ 换一下符号和组合,所以工程上通常写成统一形式,用扇区号去选符号。这里的调制系数定义为m = √3·|Uref| / Udc,要保证线性调制,m 不能超过 1,也就是说电压矢量的最大幅值是Udc/√3。
对比一下 SPWM:SPWM 的相电压峰值最多到Udc/2,而 SVPWM 能到Udc/√3 ≈ 0.577·Udc,两者之比是 2/√3 ≈ 1.1547,也就是常说的母线电压利用率高出约 15.47%。这多出来的部分不是凭空的,是它把三相调制波里那些“用不上的空隙”重新分配了。
当T4 + T6 > Ts时,说明要的电压超出了圆,进入过调制。处理方法有两种:一种是按比例缩放,令T4' = T4·Ts/(T4+T6),T6'同理,这样方向不变、幅值压回圆内;另一种是直接截断超出的部分。前者的波形畸变小,后者实现简单但谐波明显,实际调试时如果发现高速段电流 THD 突然变差,先看看过调制是怎么处理的。
注意:过调制处理一定不能只把时间截断到 Ts 就完事,还得保证 T4 和 T6 的相对比例不变,否则电压矢量的方向会偏,表现出来是高速时转矩突然掉一截。
4.4 马鞍波不是 bug:零序分量注入
用示波器看 SVPWM 的相电压波形,会发现它顶部是平的、两侧往下垂,形状像马鞍。很多人第一次看到会以为波形坏了,其实这正是它比 SPWM 更强的地方。
原因是这样的:电机真正感受到的是线电压,也就是两相之间的差。相电压里如果加进一个三相共有的分量(零序分量,最常见的是三次谐波),线电压在两两相减时这个共有的部分会被抵消掉,电机一点都感觉不到。但加了之后,每一相的峰值被削低了,同样的母线电压下就能塞进更大的基波幅值。SVPWM 这种“用相邻矢量和零矢量合成”的做法,在数学上恰好等价于在正弦调制波里注入了这种零序分量,所以它的相电压天然就是马鞍形。
换句话说,马鞍波不是妥协,是主动设计的结果。看着不好看的波形,换来的是母线电压利用率那 15% 的提升。判断一个 SVPWM 实现对不对,最简单的办法就是看相电压波形是不是稳定的马鞍形,如果变成纯正弦或者削顶的梯形,那基本可以确认扇区或者过调制那里出了岔子。
5. 容易搞混的三组概念:增量式PID、积分饱和与磁链圆限制
5.1 位置式 PID 和增量式 PID,差的不是写法是责任
易混点:很多人觉得这两种只是公式变形,实现哪个都行,效果一样。
为什么容易混:纸面上推一推,增量式确实是从位置式做差分推出来的,看起来只是“少存一个积分累加值”。但两者的输出含义根本不同:位置式 PID 输出的是控制量的绝对值(比如直接就是 PWM 占空比),增量式输出的是这一次比上一次“要多给多少”。
怎么区分:看执行机构。如果执行器本身有记忆(比如步进电机的步数、带积分环节的阀门),用增量式更自然,因为丢一帧指令影响不大;如果执行器每次都要一个绝对位置(比如 PWM 比较寄存器),位置式更直接,但必须自己处理积分累加和抗饱和。
/* 位置式:输出绝对量,积分项需要自己限幅 */ float Pos_PID(PID *p, float err) { p->integral += err; if (p->integral > p->i_max) p->integral = p->i_max; /* 这一行就是抗饱和 */ if (p->integral < p->i_min) p->integral = p->i_min; return <p> <a href="https://download.csdn.net/download/qq_28149763/87943594" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>