4. 扇区判断算法:基于反Park变换后的Uα、Uβ,快速判断目标电压矢量所在扇区
好,咱们接着往下走。上一章我们把反Park变换讲透了,得到了Uα和Uβ。现在问题来了——这两个正交分量摆在你面前,你怎么知道目标电压矢量落在哪个扇区?
说白了,SVPWM的核心就是“判断扇区 → 计算作用时间 → 生成驱动波形”。扇区判断是第一步,也是最快的一步。我个人习惯用一句话概括:不用算角度,只用看符号和比值。
4.1 为什么不用atan2?
你可能会想:有了Uα和Uβ,直接算个角度θ = atan2(Uβ, Uα),然后看θ落在0°~60°还是60°~120°不就行了?
嗯,理论上可以。但我在项目中踩过这个坑——atan2的运算量太大了。在低端MCU上,一次atan2可能要吃掉几十微秒,而你的PWM周期可能只有100μs。你想想看,光算个角度就占掉一半时间,剩下的时间还够算啥?
所以工业上几乎清一色用代数判断法,只涉及加减法和比较运算,几个时钟周期就搞定。
4.2 扇区判断的数学本质
我们先回顾一下三相电压空间。六个扇区,每个扇区60°,边界就是六个非零基本矢量:U1(100)、U2(110)、U3(010)、U4(011)、U5(001)、U6(101)。
判断目标矢量落在哪个扇区,本质上就是看它相对于这些边界的位置。我习惯用三个中间变量来搞定:
核心思路:定义三个参考量B1、B2、B3,它们的符号组合直接映射到扇区编号。
具体来说:
// 第一步:计算三个参考量
B1 = Uβ
B2 = (√3/2) * Uα - (1/2) * Uβ
B3 = -(√3/2) * Uα - (1/2) * Uβ
// 第二步:符号判断(大于0为1,否则为0)
S1 = (B1 > 0) ? 1 : 0
S2 = (B2 > 0) ? 1 : 0
S3 = (B3 > 0) ? 1 : 0
// 第三步:合成扇区编号
N = S1 + 2*S2 + 4*S3
这个N值就是扇区编号。但注意,不同文献里N和扇区的对应关系可能不一样。我一般用下面这张表:
| N | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| 扇区 | II | VI | I | IV | III | V |
为什么是这个映射?你可以自己画个矢量图推一下。我记得第一次推导时花了整整一个下午,但推通之后就觉得——嗯,其实挺巧妙的。
4.3 简化版判断逻辑
上面那个公式里带了√3/2和1/2,如果你不想用浮点运算,可以做个等价变换。我在实际项目中更常用下面这个版本:
// 用Uα和Uβ直接判断
// 定义辅助变量
float Ua = Uα;
float Ub = 0.5f * Uα + 0.866025f * Uβ; // 0.866025 ≈ √3/2
float Uc = -0.5f * Uα + 0.866025f * Uβ;
// 符号判断
uint8_t sector = 0;
if (Ub > 0) sector |= 0x01;
if (Uc > 0) sector |= 0x02;
if (Ua > 0) sector |= 0x04;
// 查表得到实际扇区
const uint8_t sector_map[8] = {0, 2, 6, 1, 4, 3, 5, 0};
sector = sector_map[sector];
我的小技巧:如果你用定点MCU,可以把0.866025和0.5都乘以2^15(32768)转成Q15格式。这样所有运算都是整数乘加,速度飞快。我曾经在STM32F103上测过,整个扇区判断只用了不到1μs。
4.4 扇区判断的物理意义
光会算还不够,你得理解它背后的几何意义。我画了张图帮你直观理解:
你看,六个基本矢量把平面分成六个60°扇区。B1、B2、B3其实就是三条边界线的法向量方向判断。每个B值的正负告诉你目标矢量在边界线的哪一侧。三个符号一组合,扇区就唯一确定了。
4.5 代码实现与避坑
下面给出一份完整的C语言实现,这是我经过多次迭代后的版本:
/**
* @brief 判断目标电压矢量所在扇区
* @param Ualpha α轴分量
* @param Ubeta β轴分量
* @return 扇区编号 (1~6)
*/
uint8_t svpwm_sector_detect(float Ualpha, float Ubeta)
{
// 定义三个参考量
float B1 = Ubeta;
float B2 = 0.866025f * Ualpha - 0.5f * Ubeta; // √3/2 ≈ 0.866025
float B3 = -0.866025f * Ualpha - 0.5f * Ubeta;
// 符号判断
uint8_t N = 0;
if (B1 > 0.0f) N |= 0x01;
if (B2 > 0.0f) N |= 0x02;
if (B3 > 0.0f) N |= 0x04;
// 查表映射
static const uint8_t sector_lut[8] = {0, 2, 6, 1, 4, 3, 5, 0};
return sector_lut[N];
}
我曾经踩过的坑:浮点比较的精度问题。当Uα和Uβ都很小(比如接近零速时),B1、B2、B3可能因为浮点误差而在0附近抖动,导致扇区频繁跳变。我的解决办法是加一个死区判断:如果|Bx| < 1e-6,就强制认为等于0。另外,如果你用定点数,注意乘法溢出——Q15格式下两个16位数相乘结果要小心处理。
4.6 扇区判断的工程优化
在实际伺服驱动中,扇区判断的执行频率就是PWM频率——通常是10kHz~20kHz。每次中断都要跑一遍。所以哪怕省几个指令周期都是有意义的。
我常用的优化手段:
- 查表代替条件分支:上面代码里已经用了。避免if-else链,直接用位运算+查表,CPU流水线不会被破坏。
- 定点化:把0.866025和0.5转成Q15格式,所有运算用整数乘加。我在TMS320F28035上试过,从浮点版改成定点版,耗时从2.1μs降到0.3μs。
- 预计算:如果Uα和Uβ来自电流环输出,变化不会太剧烈。你可以只在电流环更新时才重新算扇区,PWM比较值更新时直接用上次结果。
我的个人习惯:在调试阶段先用浮点版跑,确保算法正确。等所有功能调通后,再定点化做性能优化。这样定位问题快,不会出现“算法不对还是精度不够”的纠结。
4.7 小结
扇区判断其实就三行数学公式加一个查表。但你别小看它——它是整个SVPWM的“导航仪”。扇区判错了,后面的作用时间计算全错,电机要么抖要么啸叫。
嗯,这一节就到这。代码你拿去直接用,但建议你亲手推一遍B1、B2、B3的几何意义。推通了,你才算真正理解SVPWM。