【从零开始实现stm32无刷电机FOC】【理论】【2/7 SVPWM数学模型】
朴人100
2025年03月18日 21:05
收录于文集
共5篇

上一节​,我们找到了一种控制线圈合成磁矢量的方法---SVPWM,但是仅停留在逻辑层面上。本节对SVPWM进行数学推导,给出最终的线圈控制函数。本节的目标为找到一个函数,输入目标线圈磁矢量,输出三相桥臂的pwm占空比。目标磁矢量可以有两种表示方式,一种是极坐标,一种是笛卡尔坐标(简单来说就是直角坐标系)。

极坐标形式:

(D_u%2C%20D_v%2C%20D_w)%5Cleftarrow%20svpwm(%5Ctheta%3A%E7%A3%81%E7%9F%A2%E9%87%8F%E6%96%B9%E5%90%91%2C%20s%3A%E7%A3%81%E7%9F%A2%E9%87%8F%E5%BC%BA%E5%BA%A6)

电机转子旋转靠的是切向受力,在极坐标形式下,磁矢量方向和磁矢量强度都会对切向分力有影响,存在耦合问题,以后难以进行电机控制,因此可以将磁矢量表达方式改为笛卡尔坐标,将磁矢量解耦为切向和直向。

笛卡尔坐标形式:

(D_u%2C%20D_v%2C%20D_w)%5Cleftarrow%20svpwm(d%3A%E7%A3%81%E7%9F%A2%E9%87%8F%E7%9B%B4%E5%90%91%E5%BC%BA%E5%BA%A6%2C%20q%3A%E7%A3%81%E7%9F%A2%E9%87%8F%E5%88%87%E5%90%91%E5%BC%BA%E5%BA%A6)

由于极坐标形式下的推导非常简单清晰,因此本节先按照极坐标形式进行推导,形成大致的概念,再按照笛卡尔坐标形式进行推导。

本节推导过程不考虑物理单位,将各类数值归一化,比如扇区6个基础矢量长度视为1,pwm周期视为1,磁矢量强度最大为1,以后在推导结果上再乘物理量即可。

线性调制区

回顾上节的SVPWM扇区图,在此将坐标系建立在扇区图中心,扇区图中有6个基础矢量:

从图中可以看出,线圈磁矢量可以落在在扇形范围内任意位置,但是有一个问题,线圈磁矢量强度最大值不恒定,意味着电机转一圈的力矩有波动。所以我们将线圈磁矢量的范围限制为扇形内切圆,人为降低一些线圈磁矢量能达到的最大强度,让电机转动时力矩可以保持恒定,这称为线性调制(非线性过调制不在本文范围内)。

扇区pwm计算

从扇区图可知,如果目标线圈磁矢量落在扇区1,需要用pwm混合情况1和情况2;如果目标线圈磁矢量落在扇区2,需要用pwm混合情况2和情况3。同理,每个扇区都需要独立计算(你可能会想,为什么不把扇区1和扇区2合并起来用情况1和情况3进行混合?实际上这样混合出来的磁矢量强度较低)。

这里要注意,此处的主角是【线圈磁矢量】,而不是转子磁矢量。

以扇区1的计算为例:

考虑到线性调制,线圈磁矢量最大强度为扇区内切圆半径,由于6个基础矢量长度为1,因此内切圆半径等于%5Cfrac%7B%5Csqrt%7B3%7D%7D%7B2%7D

以下计算过程先按照线圈磁矢量最大强度进行,最后将占空比乘上强度系数就可以实现强度控制。

根据正弦定理可得:

%5Cfrac%7Bt_1%7D%7Bsin%7B(60%5E%7B%5Ccirc%7D-%5Ctheta%7D)%7D%3D%5Cfrac%7Bt_2%7D%7Bsin%7B%5Ctheta%7D%7D%3D%5Cfrac%7B%5Cfrac%7B%5Csqrt%7B3%7D%7D%7B2%7D%7D%7Bsin%7B120%5E%7B%5Ccirc%7D%7D%7D

得到基础矢量的占空比t_1t_2t_1%20%3D%5Csin(60%5E%5Ccirc%20-%20%5Ctheta)%EF%BC%8Ct_2%20%3D%5Csin%20%5Ctheta%5Ctag%7B1-1%7D

注意,式(1-1)得到的占空比是基础矢量的在一个周期内的比例(权重),桥臂的占空比后续再计算。

其余扇区的几何形状与扇区1是相同的,比如扇区2的计算只需在式(1-1)的基础上将%5Ctheta减去60°。把扇区顺时针方向的基础矢量记号为m,逆时针方向的基础矢量记号为n,结果为:

总结:t_m%20%3D%5Csin(%E6%89%87%E5%8C%BA%E5%8F%B7*60%5E%5Ccirc%20-%20%5Ctheta)%EF%BC%8Ct_n%20%3D%20%5Csin%20(%5Ctheta-(%E6%89%87%E5%8C%BA%E5%8F%B7*60%5E%5Ccirc-60%5E%5Ccirc))

以上均是按照线圈磁矢量最大强度计算得到的结果,要控制磁矢量强度,再乘上强度系数s即可。

在此重复提醒,上面这张表有什么用?意思是目标线圈磁矢量落在哪个扇区,合成时就使用哪个扇区的基础矢量占空比结果。举例:目标线圈磁矢量角度是170°,强度是s,属于落在第3扇区,那么在一个周期内,给情况3分配s*%5Csin(180%5E%5Ccirc%20-%20%5Ctheta)比例,给情况4分配s*%5Csin%20(%5Ctheta-120%5E%5Ccirc)比例,剩下的比例分配给零矢量,即可得到目标线圈磁矢量。

桥臂pwm计算

上部分我们获得了基础矢量的比例,我们的最终目标还是桥臂mos管占空比。

在一个周期内,分配完基础矢量后,剩下时间t_0%3D1-t_m-t_n是分配给零矢量的。以扇区1为例,写成表达式:

%5Cleft%5B%5Cbegin%7Bmatrix%7DD_u%5C%5CD_v%5C%5CD_w%5C%5C%5Cend%7Bmatrix%7D%5Cright%5D%3Dt_1*%5Cleft%5B%5Cbegin%7Bmatrix%7D1%5C%5C0%5C%5C0%5C%5C%5Cend%7Bmatrix%7D%5Cright%5D%2Bt_2*%5Cleft%5B%5Cbegin%7Bmatrix%7D1%5C%5C1%5C%5C0%5C%5C%5Cend%7Bmatrix%7D%5Cright%5D%2Bt_0*%E9%9B%B6%E7%9F%A2%E9%87%8F%E6%A1%A5%E8%87%82

零矢量的选择:

111和000两种桥臂状态都可以产生零矢量,应该怎么安排呢?如果一个周期内零矢量由111和000【平分】,这称为七段式SVPWM。如果一个周期内零矢量只有一种,这称为五段式SVPWM。五段式SVPWM比七段式SVPWM电流谐波更大,但是mos管开关次数更少。五段式SVPWM有各种各样的矢量分配顺序和零矢量选择方法,难度较大。这里只选择七段式SVPWM。

不同扇区只需要更改不同的基础矢量即可,再乘上强度系数s,6个扇区的三相桥臂占空比为:

%5Cleft%5B%5Cbegin%7Bmatrix%7DD_u%5C%5CD_v%5C%5CD_w%5C%5C%5Cend%7Bmatrix%7D%5Cright%5D%3Ds*t_m*%5Cvec%7Bm%7D%2Bs*t_n*%5Cvec%7Bn%7D%2B%5Cfrac%7Bt_0%7D%7B2%7D*%5Cleft%5B%5Cbegin%7Bmatrix%7D1%5C%5C1%5C%5C1%5C%5C%5Cend%7Bmatrix%7D%5Cright%5D%5Ctag%7B1-2%7D

其中,t_0%3D1-t_m-t_ns是强度系数,%5Cvec%7Bm%7D是顺时针方向的基础矢量,%5Cvec%7Bn%7D是逆时针方向的基础矢量,t_m%5Cvec%7Bm%7D在周期内的比例,t_n%5Cvec%7Bn%7D在周期内的比例,以及:

至此,将表(1-1)代入式(1-2),我们得到了svpwm函数,输入为线圈磁矢量角度%5Ctheta和强度s,输出为D_u%2CD_v%2CD_w

纯c语言代码验证

将式(1-2)用C语言描述出来,这一定是你见过最简洁优雅的SVPWM函数:

代码块
clike
自动换行
复制代码
#include <stdio.h>

#include <math.h>

#define PI 3.14159265358979323846

#define deg_to_rad(a) (PI * (a) / 180)



typedef struct duty

{

  float d_u;

  float d_v;

  float d_w;

} duty_t;



/**

 * @brief 极坐标系下的svpwm

 *

 * @param theta 目标磁矢量角度

 * @param s 目标磁矢量强度

 * @return duty_t 三相桥臂占空比

 */

duty_t svpwm(float theta, float s)

{

  const float rad60 = deg_to_rad(60);

  const int v[6][3] = {{1, 0, 0}, {1, 1, 0}, {0, 1, 0}, {0, 1, 1}, {0, 0, 1}, {1, 0, 1}};

  int sector = 1 + theta / rad60;

  float t_m = s * sinf(sector * rad60 - theta);

  float t_n = s * sinf(theta - (sector * rad60 - rad60));

  float t_0 = 1 - t_m - t_n;



  duty_t duty;

  duty.d_u = t_m * v[sector - 1][0] + t_n * v[sector % 6][0] + t_0 / 2;

  duty.d_v = t_m * v[sector - 1][1] + t_n * v[sector % 6][1] + t_0 / 2;

  duty.d_w = t_m * v[sector - 1][2] + t_n * v[sector % 6][2] + t_0 / 2;

  return duty;

}



int main()

{

  for (float phi = 0; phi < 360; phi += 10)

  {

    // 这里我设置磁矢量与转子垂直,这样转子受力最大

    duty_t duty = svpwm(deg_to_rad(fmodf(phi + 90, 360)), 1);

    printf("%f,%f,%f,\r\n", duty.d_u, duty.d_v, duty.d_w);

  }

  return 0;

}
复制成功

将运行结果用折线图绘制出来,可以看到三相桥臂占空比结果,将占空比设置给单片机的pwm产生器就可以控制电机旋转了:

目标磁矢量为笛卡尔坐标系形式的推导

上述推导的目标磁矢量形式为极坐标形式,但是磁矢量方向和磁矢量强度都会对转子切向分力产生影响,存在耦合问题,可以把磁矢量分解到转子的直向和切向进行解耦合,方便以后对转子切向单独进行强度控制(电机旋转只关注转子切向强度就可以了)。

%5Calpha轴,%5Cbeta,d轴,q轴:

 %5Calpha轴和%5Cbeta轴就是svpwm扇区图的xy坐标系轴,以后用词将混用%5Calpha轴和x轴、%5Cbeta轴和y轴。

 d(direct,直接)轴与转子磁矢量平行,q(quadrature,正交)轴与转子磁矢量垂直。这两个轴很好理解,电机旋转靠的是转子切向受力(q轴),任何一个力都可以被分解到转子的直向和切向,也就是被分解到d轴和q轴。

按照笛卡尔坐标系形式,svpwm函数输入改为d、q轴的强度:

(D_u%2C%20D_v%2C%20D_w)%5Cleftarrow%20svpwm(d%2C%20q)

 在下面的推导之前,你可能会想,直接把磁矢量d、q数值转换到极坐标%5Cthetas,然后代入极坐标形式的svpwm函数不就好了,根本用不着下面复杂的推导?实际上,笛卡尔坐标转换到极坐标需要计算%5Carctan和变量开根号,计算耗时更大一些。如果觉得下面的推导太麻烦,而不介意计算耗时,这里提供直接的转换公式:%5Ctheta%3Datan2(q%2Cd)%2B%5Cphi_%7B%E8%BD%AC%E5%AD%90%E8%A7%92%E5%BA%A6%7D%EF%BC%8Cs%3D%5Csqrt%7Bd%5E2%2Bq%5E2%7D

 注意,转子角度%5Cphi可从电机编码器获取。

park变换:

在本小节的方法下,所有的矢量都在xy坐标系内,最好统一用xy坐标(%5Calpha轴和%5Cbeta轴)表示,因此d、q轴需要转换到%5Calpha轴和%5Cbeta轴。

从图中可以看出,d、q轴旋转%5Cphi角后与%5Calpha轴、%5Cbeta轴相等,写成表达式为:

%5Cleft%5B%5Cbegin%7Bmatrix%7D%5Calpha%5C%5C%5Cbeta%5C%5C%5Cend%7Bmatrix%7D%5Cright%5D%3D%5Cleft%5B%5Cbegin%7Bmatrix%7D%5Ccos%7B%5Cphi%7D%26-%5Csin%7B%5Cphi%7D%5C%5C%5Csin%7B%5Cphi%7D%26%5Ccos%7B%5Cphi%7D%5C%5C%5Cend%7Bmatrix%7D%5Cright%5D*%5Cleft%5B%5Cbegin%7Bmatrix%7Dd%5C%5Cq%5C%5C%5Cend%7Bmatrix%7D%5Cright%5D

上面这个表达式就是反park变换。正park变换就是%5Calpha轴、%5Cbeta轴转换到d、q轴,以后电流采样部分会用到。

扇区pwm:

到此,我们计算得到了线圈磁矢量的xy坐标,接下来就可以计算扇区pwm了。表(1-1)是我们已经计算过的,表(1-1)中的%5Ctheta是磁矢量角度,与极坐标形式不同的是,笛卡尔坐标系形式的磁矢量并未给定%5Ctheta,根据xy坐标的定义,已知的是%5Ccos%5Ctheta%3D%5Calpha%2C%5Csin%5Ctheta%3D%5Cbeta,因此对表(1-1)的结果进行展开,得到:

t_m%20%3D%5Csin(%E6%89%87%E5%8C%BA%E5%8F%B7*60%5E%5Ccirc)*%5Calpha%20-%20%5Ccos(%E6%89%87%E5%8C%BA%E5%8F%B7*60%5E%5Ccirc)*%5Cbeta%EF%BC%8Ct_n%20%3D%20%5Cbeta*%5Ccos(%E6%89%87%E5%8C%BA%E5%8F%B7*60%5E%5Ccirc-60%5E%5Ccirc)-%5Calpha*%5Csin(%E6%89%87%E5%8C%BA%E5%8F%B7*60%5E%5Ccirc-60%5E%5Ccirc)

扇区号计算:

极坐标形式的磁矢量是给定了目标磁矢量角度的,因此根据角度数值可以直接判别出磁矢量落在了哪个扇区,而笛卡尔坐标系形式的磁矢量只有给定的转子角度和d、q轴数值以及推导得出的x、y轴数值,并不能直接判断出落在了哪个扇区。

第一个想法就是参考编程常见的atan2(y,x)函数,直接根据得出的角度判断落在了哪个扇区。但是我们可以进行优化,不必计算atan这个耗时操作。

先对各个扇区现有参数进行整理:

仔细看上表数据,我们发现可以通过一个一个的条件逐渐将6个扇区分开,条件分叉后实际上形成了以下这棵二叉树:

可以看到每个叶子节点的路径总值都不一样,因此可以据此分类,形成表达式即为:

A%3Dbool(%5Cbeta%3E0)%2CB%3Dbool(%7C%5Cbeta%7C%3E%5Csqrt%7B3%7D*%7C%5Calpha%7C)%2CC%3Dbool(%5Calpha%3E0)

K%3D4*A%2B2*B%2BC,有如下映射关系:

至此,扇区号计算完成,没有用到耗时的atan函数,甚至连浮点数除法都没有用到。

桥臂pwm:

桥臂pwm我们已经在笛卡尔坐标形式的磁矢量中计算过了,由于笛卡尔坐标系下的磁矢量不给定强度s,将式(1-2)去掉强度s即为此处的桥臂pwm表达式。

纯c语言代码验证:

代码块
clike
自动换行
复制代码
#include <stdio.h>

#include <math.h>

#include <stdbool.h>

#define PI 3.14159265358979323846

#define SQRT3 1.73205080756887729353

#define deg_to_rad(a) (PI * (a) / 180)

typedef struct duty

{

  float d_u;

  float d_v;

  float d_w;

} duty_t;



/**

 * @brief 笛卡尔坐标系下的svpwm

 *

 * @param phi 转子角度

 * @param d d轴强度单位比例

 * @param q q轴强度单位比例

 * @return duty_t 三相桥臂占空比

 */

duty_t svpwm(float phi, float d, float q)

{

  const float rad60 = deg_to_rad(60);

  const int v[6][3] = {{1, 0, 0}, {1, 1, 0}, {0, 1, 0}, {0, 1, 1}, {0, 0, 1}, {1, 0, 1}};

  const int K_to_sector[] = {4, 6, 5, 5, 3, 1, 2, 2};

  float cos_phi = cosf(phi);

  float sin_phi = sinf(phi);

  float alpha = cos_phi * d - sin_phi * q;

  float beta = sin_phi * d + cos_phi * q;



  bool A = beta > 0;

  bool B = fabs(beta) > SQRT3 * fabs(alpha);

  bool C = alpha > 0;



  int K = 4 * A + 2 * B + C;

  int sector = K_to_sector[K];



  float t_m = sin(sector * rad60) * alpha - cos(sector * rad60) * beta;

  float t_n = beta * cos(sector * rad60 - rad60) - alpha * sin(sector * rad60 - rad60);

  float t_0 = 1 - t_m - t_n;



  duty_t duty;

  duty.d_u = t_m * v[sector - 1][0] + t_n * v[sector % 6][0] + t_0 / 2;

  duty.d_v = t_m * v[sector - 1][1] + t_n * v[sector % 6][1] + t_0 / 2;

  duty.d_w = t_m * v[sector - 1][2] + t_n * v[sector % 6][2] + t_0 / 2;

  return duty;

}



int main()

{

  for (float phi = 0; phi < 360; phi += 10)

  {

    duty_t duty = svpwm(deg_to_rad(phi), 0, 1);

    printf("%f,%f,%f,\r\n", duty.d_u, duty.d_v, duty.d_w);

  }

  return 0;

}
复制成功

可以看到与极坐标系的计算结果是一样的:

结束

从代码上可以看到,极坐标形式的svpwm计算量更小。如果后续只考虑电机开环控制,可选择极坐标形式的svpwm;如果后续考虑更高级的电机控制,需要将磁矢量解耦到转子的直轴(直向)和交轴(切向)。

经过本节的推导,我们得到了磁矢量的生成函数,也就是说我们可以随意控制电机转子的受力了,接下来就有能力对电机进行角度(位置)、速度、力矩控制了。