# FOC_CTRL **Repository Path**: imhaozi/foc_ctrl ## Basic Information - **Project Name**: FOC_CTRL - **Description**: foc算法验证使用。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-03-22 - **Last Updated**: 2026-07-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # FOC_CTRL:控制原理和实现 # 1. FOC 简介 磁场定向控制(FOC,Field-Oriented Control)是一种让**交流电机像直流电机一样好控制**的智能算法。 它的核心思想很简单: 通过实时“看”转子位置,把三相交流电流“翻译”成两个直流量—— * 一个管磁场(d轴电流) * 一个管转矩(q轴电流) 从此,工程师可以像拧水龙头一样精准调节电机力量: ✅ 启动瞬间就有劲(0速满转矩) ✅ 加速减速又快又稳(毫秒级响应) ✅ 低速运行安静平滑(无抖动) # 2. FOC 原理 foc的核心控制流程如下图所示: ![foc_framework](./Docs/Images/foc_framework.png) 计算步骤为: 1. 通过采样,获得电机三条电机线上当前的电流:`Ia、Ib、Ic` 2. 对电流`Ia、Ib、Ic`进行**Clark变换**得到`Iα、Iβ` 3. 对`Iα、Iβ`进行**Park变换**得到`Iq、Id` 4. 将`Iq、Id`与设定的`Iq_ref、Id_ref`计算误差 5. 将计算的误差用**PID控制器**(上图中只有PI控制)计算控制电压`Uq、Ud` 6. 对`Uq、Ud`进行**反Park变换**得到`Uα、Uβ` 7. 根据`Uα、Uβ`矢量需求,输入SVPWM,计算得到需要给三项驱动器的PWM占空比 8. 将目标输出添加到控制电路 一直重复以上步骤。 ## 电流检测 为了控制电机按照目标速度或者状态运转,核心就是就是控制电机三个相的电流大小,使得三个相的电流分别按照我们预期的大小进行运转。 其中`Ia、Ib、Ic`作为基向量,方向已经是固定的了,我们控制其大小,就可以调整磁场合力的方向和大小,实现控制转子旋转的目的。 ![电流方向示意](./Docs/Images/电流方向示意.png) 如果电机按照均匀的速度旋转:`θ = 2*pi*t + pi`,也就是从180度的地方开始,每秒逆时针转动一圈。 则理想的电流大小为:`Ia = A*sin(2pi*t) `、`Ib = A*sin(2pi*t - 2/3*pi) `、`Ic = A*sin(2pi*t - 4/3*pi) `,即频率和角度相同,相位分别相差120°的正弦波。 则三者随时间变化的曲线如下图所示 ![理想电流角度关系](./Docs/Images/理想电流角度关系.png) > 另外,根据基尔**霍夫电流定律**,电流之和等于0,如果只采集两相电流,也可以轻松求得第三相的电流,因此有些控制板在硬件上会只进行两相电流的采集。 ## Clark变换 前面知道,控制电机的核心就是就是控制电机三个相差120°的相电流大小,来实现一个目标方向的电流矢量和。 而我们最容易理解的坐标系其实是二维直角坐标系,描述电机的角度也是在这个坐标系下描述的。比如我已知想要一个角度为45°,模为根号2的向量,我们很容易知道使用(1,0)、(0,1)两个向量就可以合成我们想要的向量。但是如何使用三个相差120°的向量来合成就不好描述了。 因此,我们可以将得到的三相电流,使用二维坐标系来描述,横轴叫`alpha`,纵轴叫`beta`,这个描述的变换过程就叫**Clark变换**。 ![Clark变换示意](./Docs/Images/Clark变换示意.png) 克拉克变换(Clark)基本形式如下: $$ I_\alpha = I_a - \cos(\frac{2\Pi}{3})I_b - \cos(\frac{2\Pi}{3})I_c \\ I_\beta = \sin(\frac{2\Pi}{3})I_b - \sin(\frac{2\Pi}{3})I_c \\ $$ 结合基尔霍夫电流定律,`Ia + Ib + Ic = 0`,则 $$ I_\alpha = \frac{3}{2}I_a\\ I_\beta = \frac{\sqrt 3}{2}(2I_b+I_a) $$ 而一般,会再给上面公式中的右侧再乘上一个系数`2/3`,称为Clark变换的**等幅值形式**,也就是最终的公式为: $$ I_\alpha = I_a\\ I_\beta = \frac{1}{\sqrt 3}(2I_b+I_a) $$ 这样,我们就可以将三个相差120°的电流矢量用两个相差90°的矢量来表示了,转换前后的效果如下图所示。 ![Clark电流变换](./Docs/Images/Clark电流变换.png) > 保留疑问:实际上采集到的电流是多少就是多少,为什么可以直接乘上2/3,不是把实际电流缩小了吗? ## Park变换 从前面的图中可以看出,Clark变换后的`iα、iβ`在静止坐标系中仍是**正弦交流量**(随转子旋转而周期变化),如果我们直接对这两个量进行PID控制,依旧是比较麻烦的。 在经过Clark的变换后,假设某时刻,转子的位置与`α`轴的夹角为`θ`。 ![Park变换示意](./Docs/Images/Park变换示意.png) 转子可以受到的力可以分解为两个方向,一个是沿着转子旋转切线方向的`Iq`向量、另一个是沿着半径向外的`Id`分量。 显然: * `Id`分量对于电机的旋转是没有什么作用的,因此实际上我们也不期望他有值,恒等于0是最好的了。 * `Iq`分量就是我们理想的电流矢量朝向,不管转子在哪个位置,都期望和转子角度保持垂直,指向期望的运动方向。 而且上述关系,**不管转子角度在哪个位置,目标分量`Id`、`Iq`相对于转子关系永远是保持不变的**。 这样我们就可以通过两个定值`Id`、`Iq`,来描述目标的电流矢量期望。这个描述的变换过程就叫**Park变换**。 那怎么样用`Iα、Iβ`来表示我们期望的`Iq、Id`呢,我们把坐标轴旋转`θ`度。帕克(Park)变换公式如下: $$ I_d = \cos(\theta)I_\alpha + \sin(\theta))I_\beta \\ I_q = -\sin(\theta)I_\alpha + \cos(\theta)I_\beta \\ $$ 这样,我们就可以将两个相差90°的电流矢量用两个标量来表示了,转换前后的效果如下图所示。 **这里`Iq`的大小最终会等于三相电流的幅值大小,也就是说,我们控制`Iq`的大小,实际上最终控制的是三相电流的幅值大小。** ![Park电流变换](./Docs/Images/Park电流变换.png) 其实到了这里,关于变换的部分就已经差不多完成了。我们发现,把三个正弦波经过两次这样的变换就能得到两个常量,那我们设定两个这样的常量,在赋值给电机电压时,根据电机转子的角度,逆向变换过去,这样不就只需要控制两个常量了吗。 这也就是开头说的控制步骤中,第4、5、6步的做法。 4. 将`Iq`、`Id`与设定的`Iq_ref`、`Id_ref`计算误差 5. 将计算的误差用**PID控制器**(一般这里只使用PI控制即可)计算控制电压`Uq、Ud` 6. 对`Uq、Ud`进行**反Park变换**得到`Uα、Uβ` 反Park变换比较简单,根据前面的Park公式倒推回去即可,变换公式为: $$ u_\alpha = \cos(\theta)U_d - \sin(\theta))U_q \\ u_\beta = \sin(\theta)U_d + \cos(\theta)U_q $$ 反变换回去后,得到的电流波形和上面是一模一样的。 > 注意,经过PID计算后,原有的电流向量变成了电压向量,因为电流是通过电压控制的,我们直接控制不了电流。 ## SVPWM 在反Park变换得到`Uα、Uβ`之后,需要将其计算为三相电压的值,并设置到驱动电路中。 这个解析的方法有多种不一样的操作,这里使用的是**七段式SVPWM** ### 七段式SVPWM #### 基础矢量 在我们已知目标`Uα、Uβ`之后,接下来就是需要计算怎么产生目标的三相电压来等效出`Uα、Uβ`。 首先,硬件原理其实已经限制了,如果记录三相桥电路的上桥臂打开记录为1,下桥臂打开记录为0,我们一共可以产生8种不同的电压,分别来看一下这八种状态可以产生的电压矢量方向。 则**有效矢量**有如下六种情况 * (A,B,C)=(1,0,0)也就是电流从A相流入,B、C相流出,则产生的电压矢量和如下图中的U4所示 * (A,B,C)=(1,1,0)也就是电流从A、B相流入,C相流出,则产生的电压矢量和如下图中的U6所示 * (A,B,C)=(0,1,0)也就是电流从B相流入,A、C相流出,则产生的电压矢量和如下图中的U2所示 * (A,B,C)=(0,1,1)也就是电流从B、C相流入,A相流出,则产生的电压矢量和如下图中的U3所示 * (A,B,C)=(0,0,1)也就是电流从C相流入,A、B相流出,则产生的电压矢量和如下图中的U1所示 * (A,B,C)=(1,0,1)也就是电流从A、C相流入,B相流出,则产生的电压矢量和如下图中的U5所示 另外还有两种特殊情况,也就是上桥臂全部打开或者下桥臂全部打开,称为**0矢量** * (A,B,C)=(0,0,0)没有电流流入流出,则产生的电压矢量U0,即不提供电压 * (A,B,C)=(1,1,1)没有电流流入流出,则产生的电压矢量U7,即不提供电压 ![SVPWM向量集合](./Docs/Images/SVPWM向量集合.png) --- #### 矢量最大值 插入一个问题,记录直流母线电压为`Vdc`,则这几个电压矢量的最大值是多少? 比如U4,当A相上桥臂打开,B、C下桥臂打开时,星型连接的电机,中性点电压是浮动的。 * A相上桥臂导通:Van = Vdc - Vn * B/C相下桥臂导通:Vbn = Vcn = 0 - Vn = -Vn * 三相无中线:Van + Vbn + Vcn = Vdc - Vn - Vn - Vn = 0 * 推得:Vn = Vdc / 3 因此 * Van = Vdc - Vn = 2 / 3 * Vdc * Vbn = -1 / 3 * Vdc * Vcn = -1 / 3 * Vdc 再带入带Clark变换中: * Valpha = Van = 2 / 3 * Vdc * Vbeta = 1 / 根号3 * (Van + 2 * Vbn) = 0 **结论:U1-U6这六个向量的最大值为`2 / 3 * Vdc`** --- #### 向量作用时长 既然,我们能提供的基础向量只有这几个,那么如果判断该使用哪几个向量来合成呢。 这里添加定义:使用六个基础向量,将目标矢量角度分为6个部分,称为**六个扇区**。 则一共需要下面两个步骤: 1. 需要先判断,目标的`Uα、Uβ`矢量和位于哪个扇区(哪两个向量之间) 2. 判断需要相邻的两个向量分别持续多长时间 **扇区1推导** 比如,如果目标的`Uα、Uβ`向量和处于第一扇区的时候,我们需要使用U4、U6来合成目标的电压向量。 * T:PWM周期(s) * T4、T6:U4、U6的持续时间(s),显然`T4+T6<=T`。 ![SVPMW矢量计算](./Docs/Images/SVPMW扇区1矢量计算.png) 而`U_alpha`、`U_beta`和`U4`、`U6`之间的关系如下 $$ U_\alpha = U_4 + U_6 * \cos(60°) \\ U_\beta = U_6 * \sin(60°) \\ $$ 而`U4`、`U6`的实际值的大小为 $$ U_4 = U_4max * \frac{T_4}{T_s} = \frac{2}{3} * Udc * \frac{T_4}{T_s}\\ U_6 = U_6max * \frac{T_4}{T_s} = \frac{2}{3} * Udc * \frac{T_6}{T_s}\\ $$ 带入到上面的公式中解得 $$ T_4 = \frac{T_s * √3}{Udc} * (√3 * U_\alpha / 2 - U_\beta / 2) \\ T_6 = \frac{T_s * √3}{Udc} * U_\beta \\ T_0 = T_7 = \frac{T_s - T_4 - T_6}{2} $$ **扇区2推导** $$ U_\alpha = U_6 * \cos(60°) - U_2 * \cos(60°) \\ U_\beta = U_6 * \sin(60°) + U_2 * \sin(60°) \\ $$ 同理,带入 $$ U_6 = U_6max * \frac{T_6}{T_s} = \frac{2}{3} * Udc * \frac{T_6}{T_s}\\ U_2 = U_2max * \frac{T_2}{T_s} = \frac{2}{3} * Udc * \frac{T_2}{T_s}\\ $$ 解得 $$ T_6 = \frac{T_s * √3}{Udc} * (√3 * U_\alpha / 2+ U_\beta / 2) \\ T_2 = \frac{T_s * √3}{Udc} * (-1) * (√3 * U_\alpha / 2 - U_\beta/2) \\ T_0 = T_7 = \frac{T_s - T_2 - T_6}{2} $$ **扇区3** $$ T_2 = \frac{T_s * √3}{Udc} * U_\beta \\ T_3 = \frac{T_s * √3}{Udc} * (-1) * (√3 * U_\alpha / 2 + U_\beta/2) \\ T_0 = T_7 = \frac{T_s - T_2 - T_3}{2} $$ **扇区4** $$ T_3 = \frac{T_s * √3}{Udc} * (-1) * (√3 * U_\alpha / 2 - U_\beta/2) \\ T_1 = \frac{T_s * √3}{Udc} * (-1) * U_\beta \\ T_0 = T_7 = \frac{T_s - T_3 - T_1}{2} $$ **扇区5** $$ T_1 = \frac{T_s * √3}{Udc} * (-1) * (√3 * U_\alpha / 2 + U_\beta / 2) \\ T_5 = \frac{T_s * √3}{Udc} * (√3 * U_\alpha / 2 - U_\beta/2) \\ T_0 = T_7 = \frac{T_s - T_1 - T_5}{2} $$ **扇区6** $$ T_5 = \frac{T_s * √3}{Udc} * (-1) * U_\beta \\ T_4 = \frac{T_s * √3}{Udc} * (√3 * U_\alpha / 2 + U_\beta/2) \\ T_0 = T_7 = \frac{T_s - T_5 - T_4}{2} $$ 就说明白了怎么在某个扇区内产生任意大小和方向的电压向量。 --- #### 三相占空比 最后一步:将矢量作用时间计算为占空比。 **扇区1推导** 当位于扇区1时,需要用向量U4、U6来合成目标向量,U4、U6需要作用的时长分别为T4、T6, 而U4=(1, 0, 0),U6=(1, 1, 0),另外还有0矢量需要分给U0=(0, 0, 0)和U7=(1, 1, 1) * U4和U6都需要A相导通,所以A相需要作用的时长就是T4+T6,再加上0向量需要均分给U0和U7,因此Ta=T4+T6+T7 * 只有U6需要B相导通,所以B相需要作用的时长就是T6,再加上0向量需要均分给U0和U7,因此Tb=T6+T7 * 都不需要C相导通,所以C相需要作用的时长只有0向量需要均分给U0和U7,因此Tc=T7 **其他扇区同理** 最终得到三相需要作用的时间如下 | 扇区 | 构成矢量 | 开关状态 | A相时间 | B相时间 | C相时间 | | ---- | -------- | ---------------- | -------- | -------- | -------- | | 1 | U4、U6 | (1,0,0)、(1,1,0) | T4+T6+T7 | T6+T7 | T7 | | 2 | U6、U2 | (1,1,0)、(0,1,0) | T6+T7 | T6+T2+T7 | T7 | | 3 | U2、U3 | (0,1,0)、(0,1,1) | T7 | T2+T3+T7 | T3+T7 | | 4 | U3、U1 | (0,1,1)、(0,0,1) | T7 | T3+T7 | T3+T1+T7 | | 5 | U1、U5 | (0,0,1)、(1,0,1) | T5+T7 | T7 | T1+T5+T7 | | 6 | U5、U4 | (1,0,1)、(1,0,0) | T5+T4+T7 | T7 | T5+T7 | 计算占空比就只需要用作用时间 除上PWM周期即可 * dutyA = Ta / Ts;pwm_ccr_A = dutyA * ARR; * dutyB = Tb / Ts;pwm_ccr_B = dutyB * ARR; * dutyC = Tc / Ts; pwm_ccr_C = dutyC * ARR; 最终按照上面步骤计算,最终输出的三个相的PWM定时器的比较值是这样的马鞍波。 ![马鞍波](./Docs/Images/马鞍波.png) > 上述推导过程也是正常实现的计算过程,但是在单片机中有部分可以优化计算量的地方,就在实现的地方看了。 > 以上只是七段式SVPWM的计算过程,也还有其他的办法也能最终得到相同的马鞍波,得到一样的控制效果,这里就没再研究了。 # 3. FOC 实现 从简单的开始慢慢完善实现,直到完成所有的功能。 ## 3.1 VF VF(Voltage/Frequency control),一种开环控制 * 硬件:无需电流检测、无需角度反馈 * 输入:一个固定的目标电流;一个按照目标速度变化的角度,假设角度就是照此进行变化的进行控制 * 现象:按照设置的固定转速旋转,电机容易发热 * 目的:可用于验证SVPWM是否可用 ![VF控制框图](./Docs/Images/VF控制框图.png) ## 3.2 VF_ENCODER 在VF控制的基础上,使用真实的编码器角度进行控制 * 硬件:需要传感器、无需电流检测 * 输入:一个固定的目标电流;传感器闭环读到的角度 * 现象:空载快速旋转,电机略有发热 * 目的:验证编码器数据 ![VF_ENCODER控制框图](./Docs/Images/VF_ENCODER控制框图.png)