1. 从SPWM到SVPWM:为什么我们需要换一种思路调制?
搞电机控制,尤其是玩FOC(磁场定向控制)的朋友,绕不开的一个核心环节就是PWM调制。你可能听说过SPWM(正弦脉宽调制),它简单直观,就是把一个正弦波和三角载波比较,生成占空比按正弦规律变化的PWM波。在早期逆变器和一些对性能要求不高的场合,SPWM确实够用。但当你真正想把一个永磁同步电机(PMSM)或者无刷直流电机(BLDC)的性能榨干,追求更高的电压利用率、更低的谐波和更精准的矢量控制时,SPWM就显得有些力不从心了。
这里就引出了SVPWM(空间矢量脉宽调制)。我第一次接触SVPWM时,也被那一堆扇区、矢量、作用时间计算搞得头大。但后来想明白了,SVPWM本质上是一种更“聪明”的调制策略。它不再盯着三相独立的波形,而是把三相逆变器输出的八个基本电压矢量(六个有效矢量,两个零矢量)看作一个整体,在一个复平面(α-β坐标系)上玩拼图。我们的目标,就是用这八个基本矢量,去合成任意一个我们期望的电压空间矢量。这种思维方式,天然就和FOC中在α-β坐标系下计算出的电压指令Uα和Uβ完美对接。
最直观的好处有两个:一是直流母线电压利用率更高。理论上,SVPWM能比SPWM的电压利用率高出约15.5%。这意味着同样的电池电压,SVPWM能让电机转出更高的转速,或者输出更大的转矩,相当于把电源的“每一滴油”都榨干了。二是谐波特性更好,电流波形更正弦,电机运行起来噪音更小,效率也更高。所以,在追求高性能的FOC系统中,SVPWM几乎是标配。
2. SVPWM的核心原理:八个矢量与扇区拼图
要理解SVPWM,我们必须先忘掉U、V、W三相独立的视角,进入α-β坐标系的“上帝视角”。一个三相逆变桥,上下桥臂不能同时导通,所以每相的输出对直流母线负端(N)的电压只能是Vdc或0。这样,三相组合起来就有2^3 = 8种开关状态。每一种开关状态对应一个基本的电压空间矢量。
我们通常用[A, B, C]来表示上桥臂的导通状态,1代表导通,0代表关断。例如[1,0,0]表示A相上桥臂开,下桥臂关,此时A相输出Vdc,B、C相输出0。这八个矢量如下:
| 开关状态 [A, B, C] | 电压矢量符号 | α轴分量 (Uα) | β轴分量 (Uβ) |
|---|---|---|---|
| [0,0,0] | V0 | 0 | 0 |
| [1,1,1] | V7 | 0 | 0 |
| [1,0,0] | V1 | (2/3) * Vdc | 0 |
| [1,1,0] | V2 | (1/3) * Vdc | (√3/3) * Vdc |
| [0,1,0] | V3 | -(1/3) * Vdc | (√3/3) * Vdc |
| [0,1,1] | V4 | -(2/3) * Vdc | 0 |
| [0,0,1] | V5 | -(1/3) * Vdc | -(√3/3) * Vdc |
| [1,0,1] | V6 | (1/3) * Vdc | -(√3/3) * Vdc |
注意:这里的系数
2/3是等幅值变换的系数,也有用等功率变换系数的,但在SVPWM合成中,我们更关心矢量的相对比例,最终会归一化处理,所以系数选择不影响最终合成逻辑。
V0和V7是零矢量,它们的作用是“填充时间”,不产生有效的电机转矩。另外六个矢量V1到V6,幅值相等,相位依次相差60度,均匀地分布在复平面上,把整个平面分成了六个扇区(Sector I 到 VI)。
SVPWM要解决的问题是:给定一个目标电压矢量Uref(由FOC的电流环输出Uα, Uβ构成),它落在哪个扇区?然后,用这个扇区相邻的两个基本矢量(比如扇区I就用V1和V2)以及零矢量,按照“伏秒平衡”的原则,在一个PWM周期Ts内合成它。
“伏秒平衡”是核心:V1的作用时间T1乘以V1,加上V2的作用时间T2乘以V2,再加上零矢量的作用时间T0,其矢量和应该等于Uref * Ts。用公式表示就是:Uref * Ts = V1 * T1 + V2 * T2 + V0 * T0(或V7 * T0) 其中T1 + T2 + T0 = Ts。
接下来的任务,就是根据Uref的坐标(Uα, Uβ),计算出它所在的扇区,以及T1和T2。
3. 扇区判断与作用时间计算:一步步拆解
这是SVPWM算法里最需要厘清的部分,很多代码看起来复杂,但拆解后逻辑很清晰。我们假设Uα和Uβ是已知的(来自电流环PI控制器的输出)。
3.1 扇区判断:三个中间变量的妙用
我们定义三个中间变量A, B, C:
A = Uβ B = (√3 * Uα - Uβ) / 2 C = (-√3 * Uα - Uβ) / 2然后,再定义三个判断变量:
U1 = (A > 0) ? 1 : 0 U2 = (B > 0) ? 1 : 0 U3 = (C > 0) ? 1 : 0最后,扇区号N可以通过一个简单的公式得到:N = U1 + 2*U2 + 4*U3计算出的N值对应关系为:N=3-> 扇区 I,N=1-> 扇区 II,N=5-> 扇区 III,N=4-> 扇区 IV,N=6-> 扇区 V,N=2-> 扇区 VI。
这个方法的几何意义是,用A, B, C的正负来判断Uref矢量位于哪两条60度分隔线的哪一侧,从而唯一确定扇区。这是我实践下来最稳定、计算量也较小的方法,避免了复杂的反正切计算。
3.2 作用时间计算:扇区归一化处理
不同扇区的计算看似不同,但其实可以通过坐标变换,统一到第一扇区(I)来处理,这样代码会非常简洁。我们定义两个中间时间变量X和Y:
X = (√3 * Ts / Vdc) * Uβ Y = (√3 * Ts / Vdc) * ( (√3/2) * Uα + (1/2) * Uβ ) Z = (√3 * Ts / Vdc) * ( (-√3/2) * Uα + (1/2) * Uβ )注意,这里的Vdc是直流母线电压,Ts是PWM周期。X, Y, Z对应了某种投影关系。
然后,根据前面判断出的扇区N,我们按下面这个表来分配T1和T2:
| 扇区 | I | II | III | IV | V | VI |
|---|---|---|---|---|---|---|
T1 | -Z | Z | X | -X | -Y | Y |
T2 | X | -Y | Y | Z | -Z | -X |
注意:这里计算出的
T1和T2可能是负数,也可能加起来超过Ts。所以必须进行饱和处理:如果T1 + T2 > Ts,则需要按比例缩放:T1 = T1 * Ts / (T1+T2),T2 = T2 * Ts / (T1+T2)。缩放后,T0 = Ts - T1 - T2。
这个表需要死记硬背吗?完全不用。它的本质是,在任何扇区,我们总是选择该扇区对应的两个相邻基本矢量,并将目标矢量Uref分解到这两个矢量的方向上。上面的X, Y, Z和查表法,只是这种几何分解的一种快速计算实现。理解了这个,你甚至可以自己推导出每个扇区的公式。
4. 七段式与五段式:PWM波形生成的两种编排艺术
算出了T1,T2,T0,怎么把它们变成实际控制六个功率管(MOSFET/IGBT)的PWM信号呢?这里就有两种主流的编排方式:七段式和五段式(也叫中心对齐和边缘对齐)。
4.1 七段式SVPWM(中心对齐)
这是最常用、特性最好的一种方式。在一个PWM周期Ts内,开关状态变化七次,因此得名。它对称地安排两个有效矢量和一个零矢量的作用顺序,并且每个桥臂的PWM波形都是中心对称的。以扇区I为例,其开关序列为:V0(000) -> V1(100) -> V2(110) -> V7(111) -> V2(110) -> V1(100) -> V0(000)。
它的优点非常突出:
- 谐波特性最优:因为波形对称,谐波能量主要集中在开关频率的偶数倍附近,而电机绕组本身是感性负载,对高频谐波有很好的滤波作用,因此实际电流的纹波更小,正弦度更好。
- 开关损耗相对均衡:每个周期内,每个功率管只开关一次,总开关次数固定,便于散热设计。
- 与定时器中心对齐模式完美匹配:像STM32的高级定时器,其中心对齐计数模式(Up-Down)天生就是为了生成这种对称的PWM波形而设计的,配置起来非常方便。
具体如何将T1, T2, T0转化为比较寄存器的值呢?我们通常将零矢量时间T0平分两半,放在周期开头和结尾。那么,对于三相比较寄存器CCR_A,CCR_B,CCR_C(对应A、B、C三相),在扇区I的计算公式为:
T_CM1 = T0 / 4 T_CM2 = T_CM1 + T1 / 2 T_CM3 = T_CM2 + T2 / 2然后赋值(假设计数器从0向上计数到ARR再向下计数,ARR对应Ts/2):CCR_A = (T_CM1 + T_CM2 + T_CM3) / 2?等等,这里容易出错。更通用的方法是先计算各相高电平的切换点时间。对于七段式,我们需要计算三个时间点Ta, Tb, Tc。在扇区I:
Ta = (Ts - T1 - T2) / 4 // 第一个切换点 Tb = Ta + T1 / 2 // 第二个切换点 Tc = Tb + T2 / 2 // 第三个切换点然后,根据中心对齐模式,比较寄存器的值应设置为Tc(因为波形是从低到高再到低,Tc是第一个下降沿点)。但实际操作中,我们常直接计算占空比。一个更稳妥的实践是:定义T1, T2为对应矢量的半周期作用时间(因为中心对齐模式一个矢量会作用两次),那么:
CCR_A = (Ts - T1 - T2) / 2 CCR_B = CCR_A + T1 CCR_C = CCR_B + T2这个公式需要根据扇区进行变换。网上有很多现成的“扇区-比较值”对应表,我建议在初期直接采用,并充分测试。理解其对称性原理后,可以尝试自己推导。
4.2 五段式SVPWM(边缘对齐)
五段式在一个周期内开关状态变化五次。同样以扇区I为例,序列为:V1(100) -> V2(110) -> V7(111) -> V2(110) -> V1(100)。可以看到,它少用了两个零矢量V0。
它的特点是:
- 开关损耗更低:每个周期每个管子还是开关一次,但有些序列下开关次数分布可能不均。
- 计算和实现稍简单。
- 谐波特性略差于七段式:因为波形不对称。
- 可与定时器边缘对齐模式配合。
在大多数对电流波形质量要求高的FOC应用(如伺服、无人机电调)中,七段式是首选。它的电流纹波和噪音表现明显更好。五段式可能在一些对开关损耗极度敏感的超高频开关场合会考虑。
5. 代码实现与工程实践:从公式到寄存器
理论通了,最终要落地到代码。这里以STM32系列MCU的高级定时器(如TIM1, TIM8)为例,分享我的实现步骤和坑点。
5.1 基础配置
- 定时器模式:配置为中央对齐模式1(Up-Down counting)。ARR寄存器设置为PWM周期值(例如,20kHz开关频率,系统时钟72MHz,预分频PSC=0,则ARR = 72e6 / (20e3 * 2) - 1 = 1799)。
- PWM输出通道:配置三个通道(CH1, CH2, CH3)为PWM模式1或2,输出极性根据你的硬件驱动逻辑设定(是高电平有效还是低电平有效,这决定了互补输出的死区插入方式)。务必使能预装载寄存器。
- 死区时间:至关重要!必须根据你使用的MOSFET/IGBT驱动芯片的导通/关断时间,设置合适的死区时间(Dead Time),防止上下桥臂直通烧管。在STM32中通过
BDTR寄存器的DTG位设置。 - 刹车功能:连接硬件过流保护信号(如通过比较器)到定时器的刹车输入,确保故障时能快速关闭所有PWM输出。
- 触发ADC采样:利用定时器的更新事件(UEV)或比较事件(CCx)触发ADC,对相电流进行同步采样。这是实现高精度FOC的关键。通常选择在PWM周期中心点(计数器为0或ARR时)采样,此时电流纹波最小,采样值最准确。对于七段式,中心点就是零矢量作用的中点。
5.2 SVPWM算法函数
你需要实现一个函数,输入是Uα和Uβ(通常来自电流环PI控制器的输出,且经过了幅值限幅),输出是三个比较寄存器值CCR1, CCR2, CCR3。
步骤一:标幺化与限幅Uα, Uβ是电压值,我们需要将其转换为时间。首先进行标幺化处理,将其缩放到-Umax ~ Umax之间,其中Umax = Vdc / √3(这是SVPWM线性调制区的极限,即六边形内切圆半径)。如果Uref的幅值超过Umax,就需要进行限幅(Over Modulation,过调制,这里先不展开)。 一种常见的做法是:
Ualpha = Ualpha * K; // K是一个缩放系数,与Vdc和PWM周期相关 Ubeta = Ubeta * K;然后计算合成矢量幅值Umag = sqrt(Ualpha*Ualpha + Ubeta*Ubeta),如果Umag > Umax,则Ualpha = Ualpha * (Umax/Umag); Ubeta = Ubeta * (Umax/Umag);。
步骤二:扇区判断使用前面介绍的A, B, C和U1, U2, U3方法,计算出扇区号Sector(1~6)。
步骤三:计算X, Y, Z和时间T1, T2
T = PWM_Period; // 对应定时器ARR值,注意是半周期还是全周期,这里假设为半周期值 X = (SQRT3 * Ubeta) * T / Vdc; Y = ( (SQRT3/2) * Ualpha + (1/2) * Ubeta ) * SQRT3 * T / Vdc; Z = ( (-SQRT3/2) * Ualpha + (1/2) * Ubeta ) * SQRT3 * T / Vdc;根据扇区查表得到T1和T2。
步骤四:时间饱和处理
if((T1 + T2) > T) { T1 = T1 * T / (T1 + T2); T2 = T2 * T / (T1 + T2); } T0 = T - T1 - T2; // 零矢量总时间步骤五:计算比较值对于七段式中心对齐模式,我们需要计算三个时间点tA, tB, tC。不同的扇区,这三个点与T1, T2, T0的关系不同。网上有标准的变换公式。这里给出一种常见的计算方式,先计算:
uint32_t t1 = (uint32_t)(T1 / 2); uint32_t t2 = (uint32_t)(T2 / 2); uint32_t t0 = (uint32_t)(T0 / 2); // 因为对称,零矢量时间也平分然后,根据扇区,计算CCR值。例如,对于扇区I:
CCR1_Val = t0 + t1 + t2; CCR2_Val = t0 + t2; CCR3_Val = t0;对于扇区II:
CCR1_Val = t0 + t1; CCR2_Val = t0 + t1 + t2; CCR3_Val = t0;... 以此类推。你需要为6个扇区准备6组赋值语句。最后,将CCR1_Val, CCR2_Val, CCR3_Val写入对应的捕获比较寄存器。
关键经验:务必搞清楚你的
T,T1,T2的时间单位和定时器计数器的对应关系。是微秒?还是定时器计数值?我强烈建议在算法内部全部使用“定时器计数单位”进行计算,避免浮点运算。例如,Vdc可以用ADC采样的原始值表示,Ualpha/Ubeta也使用标幺化的整型数。整个SVPWM函数用定点数运算完成,速度极快。
5.3 过调制与弱磁:突破电压极限
当电机转速升高,反电动势增大,所需的Uref幅值可能超过SVPWM线性调制区的极限Umax。此时,单纯的限幅会导致指令失真,电流控制环路饱和。这时就需要引入过调制策略。
过调制的核心思想是:当Uref超出内切圆时,不再保持其方向不变地缩放到圆上,而是允许合成矢量的轨迹沿着六边形的边运动。这样,在部分周期内,实际输出的电压矢量幅值可以大于Umax,从而提高了直流母线电压的利用率。实现过调制算法会更复杂一些,需要修改T1, T2的计算规则,通常是通过一个过调制因子来调整。
当转速继续升高,即使过调制也无法提供足够电压时,就需要进入“弱磁”控制。弱磁的本质是通过注入负的d轴电流,来抵消永磁体产生的磁场,从而降低反电动势,使得在现有电压下能驱动电机达到更高转速。FOC MTPA(最大转矩每安培)和弱磁通常是结合使用的,先通过MTPA找到最优的id, iq组合,在高速区再切换到弱磁控制。这部分是FOC算法中比较高级的话题,需要精确的电机参数和观测器。
6. 调试与问题排查:让电机真正转起来
算法写完,下载到板子,给电,电机可能不转,或者振动、异响。别慌,按以下步骤排查:
先开环验证:在闭环FOC跑起来之前,一定要先验证SVPWM本身和驱动电路是否正确。写一个开环测试程序,让
Uref以一个恒定的幅值和缓慢递增的角度旋转(例如,每秒转一圈)。用示波器测量电机三相线间的电压(最好用差分探头),应该能看到一个幅值恒定、频率缓慢增加的三相正弦波(实际上是PWM调制后的马鞍波)。同时观察相电流,应该是平滑的正弦波。如果波形严重畸变或电流很大,检查死区时间是否足够,MOSFET驱动是否正常。检查扇区切换:在开环旋转时,可以在代码中打印当前的扇区号。它应该按1->2->3->4->5->6->1...的顺序平滑切换。如果出现跳变或不连续,检查扇区判断算法和
Uα, Uβ的计算。电流采样与对齐:这是闭环能否稳定的重中之重。确保ADC的采样时刻在PWM周期的中心点(七段式的零矢量中点)。用示波器同时抓取一相PWM的高电平信号和该相电流采样ADC的触发信号,确认触发点在PWM波形的中心。电流采样的硬件电路也要校准,包括运放偏移、增益,以及ADC本身的偏移。可以在电机静止时,读取三相电流ADC值,理论上应该接近零点,如果有较大偏移,需要在软件中补偿。
观测器与启动:对于无感FOC,开环启动到切换闭环的瞬间是最容易失败的。确保开环启动阶段,注入的电压矢量幅值足够克服静摩擦和负载,频率递增要平滑。切换到闭环的时机很重要,通常是在电机反电动势足够被观测器(如滑模观测器、扩展卡尔曼滤波器)可靠估算之后。扩展卡尔曼滤波(EKF)对电机参数和噪声统计特性比较敏感,调参需要耐心。
过流保护:一定要实现可靠的硬件过流保护,并在软件中做二次保护。STM32的FOC库中通常有
MC_FOC_CurrProcessing函数,里面会计算电流并检查是否过流。确保你的过流阈值设置合理,并且保护反应速度够快。
我个人的调试习惯是,先用一个非常低的电压(比如12V系统用5V)和空载来调试,这样即使出错也不容易烧器件。每一步都先用示波器看波形,确保和理论一致,再逐步增加电压和负载。SVPWM和FOC是一个系统性的工程,任何一个环节的微小错误都可能导致整个系统失效,耐心和细致的排查是关键。