4. 扇区判断:扇区划分原理、基于αβ分量的扇区判断算法、扇区判断的C代码实现

好,咱们接着聊SVPWM。前面我们把Clark变换讲清楚了,得到了αβ坐标系下的电压分量Uα和Uβ。那下一步是什么?

说白了,你得知道当前这个电压矢量落在哪个扇区里。为什么?因为不同的扇区,逆变器的开关管导通组合是不一样的。你连目标在哪个房间都不知道,怎么走路?

4.1 扇区划分原理

先说说扇区是怎么划分的。

三相逆变器有6个功率管,上桥臂三个,下桥臂三个。每个桥臂只能有一个管子导通,所以一共有2³=8种开关状态。其中6种是有效矢量,2种是零矢量(000和111)。

这6个有效矢量,在空间上把360°的平面均匀分成了6份。每份60°,这就是一个扇区。

关键点:扇区编号从I到VI,按逆时针方向排列。第I扇区在0°~60°之间,第II扇区在60°~120°之间,以此类推。

我刚开始学的时候,总记不住哪个扇区对应哪个角度范围。后来我习惯把扇区编号和基本电压矢量对应起来记:U1(100)对应0°,U2(110)对应60°,U3(010)对应120°...这样就好记多了。

下面这张图,是我用SVG画的扇区划分示意图,你可以直观感受一下:

α β U1(100) U2(110) U3(010) U4(011) U5(001) U6(101) I II III IV V VI 60° 120° 180° 240° 300° Uref 图4-1 扇区划分示意图(6个扇区,每个60°)

我的小技巧:在实际项目中,我习惯把扇区编号和三相的导通状态联系起来。比如第I扇区,A相上桥臂导通,B相下桥臂导通,C相下桥臂导通——这就是U1(100)。你多画几次这个六边形,自然就记住了。

4.2 基于αβ分量的扇区判断算法

好了,扇区划分清楚了。那怎么用程序判断呢?

你想想看,我们手头只有Uα和Uβ两个值。怎么从这两个值算出扇区号?

最直接的方法是用角度。先算出θ = arctan(Uβ / Uα),然后看θ落在哪个区间。但问题是,反正切函数计算量太大,在嵌入式MCU上跑起来很慢。我早期在一个项目里用过这种方法,结果PWM频率只能跑到5kHz,根本不够用。

后来我换了一种方法——用Uα和Uβ的符号和比例关系来判断。不需要三角函数,只需要加减法和比较运算。

具体怎么做?

先定义三个中间变量:

B1 = Uβ
B2 = (√3/2) * Uα - (1/2) * Uβ
B3 = -(√3/2) * Uα - (1/2) * Uβ

然后看这三个变量的符号:

  • 如果B1 > 0,记A=1,否则A=0
  • 如果B2 > 0,记B=1,否则B=0
  • 如果B3 > 0,记C=1,否则C=0

最后扇区号N = A + 2B + 4C。

嗯,这里要注意:算出来的N是1~6之间的数,分别对应扇区I~VI。但N的值和扇区编号不是一一对应的,需要查表转换。

N值 1 2 3 4 5 6
扇区号 II VI I IV III V

我曾经踩过的坑:一开始我直接用N值当扇区号,结果电机转起来一顿一顿的。查了半天才发现是扇区映射表搞错了。后来我学乖了,每次写完扇区判断代码,先用几个已知角度的手算验证一遍,确认映射关系正确再往下走。

4.3 扇区判断的C代码实现

理论讲完了,上代码。这是我在实际项目中用过的扇区判断函数,经过多次优化,计算量很小:

/**
 * @brief 扇区判断函数
 * @param Ualpha α轴电压分量(标幺值)
 * @param Ubeta  β轴电压分量(标幺值)
 * @return 扇区编号(1~6)
 */
uint8_t SVPWM_CalcSector(float Ualpha, float Ubeta)
{
    uint8_t sector = 0;
    float B1, B2, B3;
    uint8_t A = 0, B = 0, C = 0;
    
    /* 计算三个中间变量 */
    B1 = Ubeta;
    B2 = 0.8660254f * Ualpha - 0.5f * Ubeta;   /* √3/2 ≈ 0.8660254 */
    B3 = -0.8660254f * Ualpha - 0.5f * Ubeta;
    
    /* 判断符号 */
    if (B1 > 0.0f) A = 1;
    if (B2 > 0.0f) B = 1;
    if (B3 > 0.0f) C = 1;
    
    /* 计算N值 */
    uint8_t N = A + 2 * B + 4 * C;
    
    /* 查表映射到扇区号 */
    switch(N)
    {
        case 1: sector = 2; break;   /* N=1 → 扇区II */
        case 2: sector = 6; break;   /* N=2 → 扇区VI */
        case 3: sector = 1; break;   /* N=3 → 扇区I */
        case 4: sector = 4; break;   /* N=4 → 扇区IV */
        case 5: sector = 3; break;   /* N=5 → 扇区III */
        case 6: sector = 5; break;   /* N=6 → 扇区V */
        default: sector = 1; break;  /* 异常情况,默认扇区I */
    }
    
    return sector;
}

我个人的优化建议:如果你用的MCU不支持硬件浮点运算,可以把所有系数都转成Q15或Q24格式的定点数。我在一个Cortex-M0的项目里这么干过,扇区判断从原来的2.3μs降到了0.8μs,效果很明显。

这段代码的核心思想,其实就是用三个直线方程把平面分成6个区域。每个区域对应一个扇区。你想想看,这比算反正切快了多少倍?

另外,代码里我加了一个default分支。虽然正常情况下不会走到那里,但做嵌入式开发嘛,防呆设计不能少。万一ADC采出来的数据异常,至少不会让程序跑飞。

好了,扇区判断就讲到这里。下一节我们聊聊如何计算每个扇区里的作用时间,那才是SVPWM的精髓所在。


公众号:蓝海资料掘金营,微信deep3321