SVPWM推导及C语言实现

创作背景

        最近在写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效率高,我没有验证过,因为我相信应该是这样的,空了再验证。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值