创作背景
最近在写FOC,涉及到了一个FOC逃不掉的知识点:SVPWM。网上有很多SVPWM的实现,直接拿来用就可以了,为了更好的理解SVPWM,还是记录一下SVPWM的推导过程。
什么是SVPWM
关于什么是SVPWM,说不清楚,把他理解为使用PWM脉宽调制技术去控制驱动器的上下桥臂开关,让驱动器产生3相正弦波(单相为马鞍波)电压就可以了。

图1 SVPWM波形
为什么要使用SVPWM
为什么要使用SVPWM,让我们看一个FOC中的场景。假如我们知道转子的位置,然后通过PID控制得出了想要输出的Valpha和Vbeta,那么,我们就知道了电压的合成矢量大小和方向了。

图2 Valpha和Vbeta合成电压矢量(红色线)
知道了我们合成矢量电压的大小和方向,我们就要做一件事情,去控制驱动器的半桥开关,让驱动器的逆变器输出的电压矢量等效于上面这个合成电压矢量。

图3 逆变器
由图3和我们在控制逆变器时候的正常操作可知,逆变器某一相,比如U相有2中状态,上管打开,下管打开,假设上管打开为1,下管打开为0,那么UVW3相就有8种状态:000,001,010,011,100,101,110,111。
假如,U相上管打开,VW相下管打开,U相为正,电压矢量方向指向U相的正方向,VW同理,则可以有如下的关系。由于000和111没有电压差,所以是0矢量,位于下图圆心。

图4 编码图(6个矢量的方向均匀分布在360°)
如此,图2中的电压矢量,放到图4中,变成如下所示,可以看出是100矢量和110矢量的合成。

图5 合成矢量的编码表示
公式推导
根据前面的合成原理,我们知道了前面的合成的矢量关系:
V = V100 + V110 + V000 + V111 ----------------------------------------------(公式1)
这里有2个疑问:为什么加上000和111矢量?如果矢量不在110和100区域呢?
扇区计算
加上000和111这2个0矢量,是因为我们使用PWM控制时候,一定存在0和1,所以,为了方便计算,我们一般使用中心对齐模式,那么就一定存在000和111这2个矢量。

图6 中心对齐模式
疑问2,矢量不在110和100区域,我们需要知道矢量在哪个区域,然后用相邻两个标准矢量相加,为此,我们标注一下矢量的扇区,方便计算,如下图。

图7 扇区分布
扇区的计算,我们看一些极限情况,假如合成矢量刚好落在扇区的边缘,会出现什么情况(仅仅对110上的边界位置进行推导,见下面推导部分):
100上:Vα > 0, Vβ = 0
110上:Vα = Vβ / √ 3
010上:Vα = -Vβ / √ 3
011上:Vα < 0, Vβ = 0
001上:Vα = Vβ / √ 3
101上:Vα = -Vβ / √ 3
那么,假如:Vβ 大于0,以110矢量为分界线,确定矢量在1 2 3扇区之一,
假如:Vα > Vβ / √ 3,以110矢量为分界线,确定矢量一定在 1 5 6 扇区之一
假如:Vα > -Vβ / √ 3,以010矢量为分界线,确定矢量一定在 1 2 6 扇区之一
再大胆一点,我们把上面这个假如作为参考,然后用表格看看各个扇区的情况:
表1 扇区编码
如果直接对扇区进行8421编码,第一第二第三列不等式左边大于右边,分别为100 010 001进行编码,得出编码1,编码1存在111 和000,而且跳变很大,看起来不大聪明,可以放弃。参考st mcsdk的svpwm,我们按编码2进行编码,分别为第一列左边大于右边,第二列右边大于左边,第三列左边大于右边,按编码2进行编码。于是我们有下面的关系式:
假设扇区编码为Sector,那么,假如Vβ > 0,Sector_code += 1;
假如 Vα - Vβ / √ 3 > 0,Sector_code += 2;
假如 -Vβ / √ 3 - Vα > 0,Sector_code += 4;
得出的Sector_code 再从表1查看对应是哪个扇区,比如计算出来是5,对应扇区3,计算出来是6,对应扇区5。
边界位置推导
仅仅对合成矢量刚好落在110上进行推导,其余类似,由下图可知:
Vα / cos(60°) = Vβ / sin(60°)
展开:Vα * 2 = Vβ * 2 / √ 3
化简:Vα = Vβ / √ 3

图8 边界计算
占空比计算
知道了扇区的位置,我们仅仅完成了第一步,知道是哪两个向量相加,第二步,我们需要通过设置UVW三相的占空比,让他输出这个向量的等效电压。
同样,我们假设这个矢量落在了第一扇区,如下图:

图9 矢量分解
把矢量部分放大,并添加辅助线

图10 矢量分解二
矢量ov是我们要合成的矢量,这个矢量可以由Vα+Vβ合成,也可以由oa+ob合成。如果用Vα+Vβ合成,则和UVW的半桥开关扯不上关系,我们需要尽量把变量向UVW半桥的开关上靠拢,所以,这里,我们用oa+ob合成。
分别计算oa和ob:
oa = Vα - Vβ * tan(30°) = Vα - Vβ * (√ 3) / 3
ob = Vβ / cos(30°) = 2 *Vβ / √ 3
我们知道了oa和ob的值,那么我们只需要控制100和110开启的占空比,就可以得到等效电压了。现在我们需要知道100的最大电压是多少,然后通过比较oa和100的最大电压,我们就知道pwm的占空比是多少了。100状态下,U开启,VW关闭,电机3相线圈的等效电路如下图:

图11 最大电压分压
可以看出来,U相的最大分压是2/3*VCC,这是什么意思呢,比如,VCC = 3V,假如我们通过计算得到U相的电压应该设置为1V,那么,我们需要设置U相的占空比是多少呢?假如占空比为100%,那么U相的电压为2V,那我们需要1V的电压,占空比,只能设置为50%,而不是33.3%,参考最大值为2V而不是电源电压的3V。
再回到前面的oa和ob的电压值,我们已经通过Vα和Vβ得到了oa和ob的值了,然后我们知道了每一相的最大电压为2/3*Vdc(Vdc为母线电压),那么oa和ob所在的驱动器开关占空比就可以得到了:
Poa = oa / (2/3*Vdc) = 3/2*oa/Vdc = (3*Vα / 2 - √ 3 * Vβ / 2) / Vdc
Pob = ob/(2/3*Vdc) = 2 *Vβ / √ 3 * 3 / 2 / Vdc = √ 3 * Vβ / Vdc
于是,oa所在的100开启时间Toa = Poa * Ts
ob所在的110开启时间Tob = Pob * Ts
其中Poa为高电平占空比,Ts为PWM周期。我们使用7段式SVPWM,如下图所示,每一段所用的占空比

图12 占空比
上图可以看出,我们有效的部分为100和110这2部分(其余部分要么电压同时为0,要么同时为Vdc,没有电压差,无法形成电流驱动电机),为了简单,我们还需要做一个处理,上面存在2个0矢量,000和111,为了简化计算,我们让000和111这两个0矢量均分无效的占空比,则T000 = (Ts - T100 - T110) / 2,T111 = T000;
那么U相的开启时间Tu = T100 + T110 + T111
V相的开启时间Tv = T110 + T111
W相的开启时间Tw = T111
于是,我们就得到了UVW三相的驱动半桥的开启时间了(为啥,因为上面的T100 = Toa,T110 = Tob,所有的量都是已知了) 。
把前面计算的100和110开启时间带入UVW开启时间,可以得到UVW的开启时间:
Tu = Ts * ((3*Vα / 2 - √ 3 * Vβ / 2) / Vdc + √ 3 * Vβ / Vdc)+ T111
Tv = Ts * (√ 3 * Vβ / Vdc) + T111
Tw = T111
到此,我们已经完成了第一扇区的UVW占空比的推导过程,同理,我们对23456扇区进行推导:
第二扇区:
T110 = Ts * (3/2 * Vα + √ 3 * Vβ / 2) / Vdc
T010 = Ts * ( √ 3 * Vβ / 2 - 3/2 * Vα ) / Vdc
第三扇区:
T010 = Ts * √ 3 * Vβ / Vdc
T011 = Ts * (-3/2 * Vα - √ 3 * Vβ / 2) / Vdc
第四扇区:
T011 = Ts * (-3/2 * Vα + √ 3 * Vβ / 2) / Vdc
T001 = Ts * (-√ 3 * Vβ) / Vdc
第五扇区:
T001 = Ts * (-3/2 * Vα - √ 3 * Vβ / 2) / Vdc
T101 = Ts * (3/2 * Vα - √ 3 * Vβ / 2) / Vdc
第六扇区:
T101 = Ts * (-√ 3 * Vβ ) / Vdc
T100 = Ts * (3/2 * Vα + √ 3 * Vβ / 2) / Vdc
有了这些时间,我们就可以计算UVW在各个扇区的占空比了。
公式简化
前面通过硬算,已经知道了各个开关在不同扇区的占空比了,可以直接用这个进行计算。但是我们仔细观测,里面有很多相似的计算,为了让计算式通用,我们可以合并一些计算。比如扇区计算:
假设扇区编码为Sector,那么,假如Vβ > 0,Sector_code += 1;
假如 Vα - Vβ / √ 3 > 0,Sector_code += 2;
假如 -Vβ / √ 3 - Vα > 0,Sector_code += 4;
这里有一个Vβ / √ 3,而后面计算占空比,都是√ 3 * Vβ,那我们不计算Vβ / √ 3,直接对扇区计算的不等式两边同时乘以√ 3,不等式一样成立,变成了:
假如Vβ > 0,Sector_code += 1;
假如 √ 3 * Vα - Vβ > 0,Sector_code += 2;
假如 -Vβ - √ 3 * Vα > 0,Sector_code += 4;
得出来的Sector_code 再通过查表查看在哪个扇区。
再看占空比计算(第六扇区为例):
T101 = Ts * (-√ 3 * Vβ) / Vdc
T100 = Ts * (3/2 * Vα + √ 3 * Vβ / 2) / Vdc
里面有相同的常量:Ts,Vdc,√ 3,我们把这个量提出来,这样又可以化简公式:
假设Ts * √ 3 / Vdc = K
T101 = K * (-Vβ)
T100 = K * (√ 3 / 2 * Vα + Vβ / 2)
很好,这里的计算式和扇区计算有点相似,貌似扇区计算那里还可以再除以2,这样就更统一了,go,继续对扇区计算进行改造:
假如Vβ > 0,Sector_code += 1;
假如 √ 3 / 2* Vα - Vβ / 2 > 0,Sector_code += 2;
假如 -Vβ / 2 - √ 3 / 2 * Vα > 0,Sector_code += 4;
好,很好,非常好,如此,我们仅仅需要计算Vβ / 2 和 √ 3 / 2 * Vα这两个乘除法运算,其余就是加减运算了,非常符合单片机尽量减少乘除运算的特性。
公式整理
有了前面的推导,我们已经完成了大部分工作了,现在,我们对所有的公式进行整理,直接给出每个扇区的UVW开关的占空比值,方便直接赋值给定时器输出正确的PWM。
扇区公式
扇区计算,计算Vβ / 2、√ 3 / 2* Vα ,然后通过比较判断确定矢量在哪个扇区。
我们定义几个变量:
U1 = Vβ
U2 = √ 3 / 2* Vα - Vβ / 2
U3 = -Vβ / 2 - √ 3 / 2* Vα
如此,我们仅仅需要对U1 U2 U3与0进行比较,然后就可以知道扇区的位置了。
UVW占空比计算
把刚才定义的U1 U2 U3和前面定义的K值代入UVW占空比计算,我们得到新的占空比计算公式:
(K = Ts * √ 3 / Vdc )
我们再定义一个变量T0,表示0矢量的占空比。
扇区1:
T100 = (3*Vα / 2 - √ 3 * Vβ / 2) / Vdc * Ts = K * (√ 3 * Vα / 2 - Vβ / 2) = K * U2
T110 = Ts * √ 3 * Vβ / Vdc = K * U1
T0 = Ts - T100 - T110 = Ts - K * U2 - K * U1
Tw = T0 / 2
Tv = T0 / 2 + T110 = Tw + K * U1
Tu = T100 + Tv = Tw + K * U2 + K * U1

扇区1
扇区2:
T110 = Ts * (3/2 * Vα + √ 3 * Vβ / 2) / Vdc = K * (√ 3 * Vα / 2 + Vβ / 2) = -K * U3
T010 = Ts * ( √ 3 * Vβ / 2 - 3/2 * Vα ) / Vdc = K * (Vβ / 2 - √ 3 * Vα / 2) = -K * U2
T0 = Ts - T110 - T010 = Ts + K * U3 + K * U2
Tw = T0 / 2
Tu = T0 / 2 + T110 = Tw - K * U3
Tv = T010 + Tu = Tw - K * U2 - K * U3

扇区2
扇区3:
T010 = Ts * √ 3 * Vβ / Vdc = K * U1
T011 = Ts * (-3/2 * Vα - √ 3 * Vβ / 2) / Vdc = K * U3
T0 = Ts - T010 - T011 = Ts - K * U1 - K * U3
Tu = T0 / 2
Tw = Tu + T011 = Tu + K * U3
Tv = Tw + T010 = Tu + K * U3 + K * U1

扇区3
扇区4:
T011 = Ts * (-3/2 * Vα + √ 3 * Vβ / 2) / Vdc = -K * U2
T001 = Ts * (-√ 3 * Vβ) / Vdc = -K * U1
T0 = Ts - T011 - T001 = Ts + K * U2 + K * U1
Tu = T0 / 2
Tv = Tu + T011 = Tu - K * U2
Tw = Tv + T001 = Tu - K * U2 - K * U1

扇区4
扇区5:
T001 = Ts * (-3/2 * Vα - √ 3 * Vβ / 2) / Vdc = K * U3
T101 = Ts * (3/2 * Vα - √ 3 * Vβ / 2) / Vdc = K * U2
T0 = Ts - T001 - T101 = Ts - K * U3 - K * U2
Tv = T0 / 2
Tu = Tv + T101 = Tv + K * U2
Tw = Tu + T001 = Tv + K * U2 + K * U3

扇区5
扇区6:
T101 = Ts * (-√ 3 * Vβ ) / Vdc = -K * U1
T100 = Ts * (3/2 * Vα + √ 3 * Vβ / 2) / Vdc = -K * U3
T0 = Ts - T100 - T100 = Ts + K * U1 + K * U3
Tv = T0 / 2
Tw = Tv + T101 = Tv - K * U1
Tu = Tw + T100 = Tv - K * U1 - K * U3

扇区6
代码编写:
ok,比较耗时的公式推导终于结束了,开始轻松愉快的代码编写。
代码编写就不做说明了,直接lou!
void my_svpwm(PWMC_Handle_t * pHandle, alpha_beta_t Valfa_beta)
{
int32_t wUAlpha = Valfa_beta.alpha * SQRT3 >>16;
int32_t wUBeta = Valfa_beta.beta;
uint32_t ts, udc, k, ta, tb, tc;
int32_t tx,ty, t0;
uint8_t a, b, c, n, sector;
int32_t u1 = wUBeta;
int32_t u2 = (-wUBeta/2 + wUAlpha);
int32_t u3 = (-wUBeta/2 - wUAlpha);
if (u1 > 0)
a = 1;
else
a = 0;
if (u2 > 0)
b = 1;
else
b = 0;
if (u3 > 0)
c = 1;
else
c = 0;
n = 4 * c + 2 * b + a;
// 根据N的公式判断扇区
udc = pHandle->uDC; // 电压
ts = pHandle->period; // PWM的载波周期
k = SQRT3 * ts / udc;
// k = SQRT3 * ts / udc;
// sector = 0
if (n == 3)
{
sector = 1;
tx = k * u1;
ty = k * u2;
}
else if (n == 1)
{
sector = 2;
tx = -k * u3;
ty = -k * u2;
}
else if (n == 5)
{
sector = 3;
tx = k * u3;
ty = k * u1;
}
else if (n == 4)
{
sector = 4;
tx = -k * u2;
ty = -k * u1;
}
else if (n == 6)
{
sector = 5;
tx = k * u2;
ty = k * u3;
}
else if (n == 2)
{
sector = 6;
tx = -k * u1;
ty = -k * u3;
}
else
{
sector = 0;
tx = 0;
ty = 0;
}
// 过调制算法
tx = tx >> 15;
ty = ty >> 15;
if (tx + ty > ts)
{
tx = ts * tx / (tx + ty);
ty = ts * ty / (tx + ty);
}
// 零矢量总作用时间的二分之一
t0 = (ts - tx - ty) / 2;
ta = t0;
tb = ta + tx;
tc = tb + ty;
// print("tx: %d ty: %d" % (tx, ty))
// 根据不同扇区进行赋值
if (sector == 1)
{
pHandle->CntPhA = tc;
pHandle->CntPhB = tb;
pHandle->CntPhC = ta;
}
else if (sector == 2)
{
pHandle->CntPhA = tb;
pHandle->CntPhB = tc;
pHandle->CntPhC = ta;
}
else if (sector == 3)
{
pHandle->CntPhA = ta;
pHandle->CntPhB = tc;
pHandle->CntPhC = tb;
}
else if (sector == 4)
{
pHandle->CntPhA = ta;
pHandle->CntPhB = tb;
pHandle->CntPhC = tc;
}
else if (sector == 5)
{
pHandle->CntPhA = tb;
pHandle->CntPhB = ta;
pHandle->CntPhC = tc;
}
else if (sector == 6)
{
pHandle->CntPhA = tc;
pHandle->CntPhB = ta;
pHandle->CntPhC = tb;
}
}
串口输出:
while (1)
{
/* USER CODE END WHILE */
angle += 1000;
trig = Trig_Functions(angle);
repark_conversion(v_dq, &v_alpha_beta, trig);
my_svpwm(&pwmc, v_alpha_beta);
printf("%d %d %d %d %d\r\n", pwmc.CntPhA, pwmc.CntPhB, pwmc.CntPhC, 0, 0);
HAL_Delay(2);
/* USER CODE BEGIN 3 */
}
用serialplot查看波形

代码编写仅仅验证SVPWM的波形是否正确,极限位置是否正确,随机抽取几个矢量,看输出是否正确,代码的验证不使用电机验证,因为直接使用电机验证,涉及到编码器或者无感FOC,我们用最直观的方式来验证:串口输出。
通过上面的波形曲线,看出来这应该是一个svpwm的波形,需要注意,上面这个矢量定义是比如U相电压为2V,VW电压为0V,电压矢量方向为指向U相的方向的2V电压(电流流入U相)。如果使用采样电阻采样低端电流,比如U相采样出来的ADC显示电流为1A,则这个电流的方向为从U相流出的电流,和前面SVPWM的矢量方向是反的,电流需要加上负号。当然,为了简单,肯定是直接拷贝ST MCSDK的SVPWM的代码了,因为不用思考,只需要验证就可以了。
另外SVPWM还有一个限制,就是设置的alpha和beta的电压有个幅值限制,所以一般还需要加一个限幅的方法判断是否超过幅值。SVPWM据说比同样产生正弦波电压的SPWM效率高,我没有验证过,因为我相信应该是这样的,空了再验证。

6563

被折叠的 条评论
为什么被折叠?



