4. SVPWM的软件实现:定时器配置、死区补偿、SVPWM的C代码实现与波形验证

好,咱们进入实战环节。前面讲了SVPWM的原理和扇区判断,那些都是理论基础。但电机转不转,最终要看代码怎么写进MCU里。这一章,我带你手写SVPWM的C代码,搞定定时器配置和死区补偿,最后用示波器看波形——这一步走通了,你的电机才算真正“活”了。

4.1 定时器配置:生成中心对齐PWM

SVPWM的核心是输出三对互补的PWM波,而且必须是中心对齐模式。为什么?因为中心对齐模式下,PWM的占空比更新点正好在三角波的顶点和底点,这样能最大程度减少谐波,电流波形更平滑。

我个人习惯用STM32的高级定时器TIM1或TIM8来做。配置要点如下:

  • 计数模式:设置为中心对齐模式1(Center-aligned mode 1)。计数器先向上计数到ARR,再向下计数到0。
  • 自动重装载值(ARR):决定了PWM的周期。比如PWM频率20kHz,ARR = 系统时钟 / (2 × 20kHz) - 1。
  • 互补输出:使能CHx和CHxN,分别控制上下桥臂。
  • 死区插入:在刹车和死区寄存器(BDTR)中设置死区时间。

关键点:SVPWM的占空比更新必须在定时器更新中断或DMA中完成。我建议用DMA,因为中断太频繁(20kHz × 3相 = 60kHz中断),CPU扛不住。

下面是一个典型的定时器初始化代码片段:

// TIM1 中心对齐PWM配置(简化版)
void TIM1_SVPWM_Init(uint16_t arr, uint16_t deadtime) {
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_BDTRInitTypeDef TIM_BDTRInitStructure;

    // 时基配置
    TIM_TimeBaseStructure.TIM_Prescaler = 0;  // 不分频
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1;
    TIM_TimeBaseStructure.TIM_Period = arr;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

    // PWM1模式配置(CH1, CH2, CH3)
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;  // 互补输出
    TIM_OCInitStructure.TIM_Pulse = 0;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
    TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
    TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
    TIM_OC1Init(TIM1, &TIM_OCInitStructure);
    TIM_OC2Init(TIM1, &TIM_OCInitStructure);
    TIM_OC3Init(TIM1, &TIM_OCInitStructure);

    // 死区配置
    TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
    TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
    TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;
    TIM_BDTRInitStructure.TIM_DeadTime = deadtime;  // 死区时间
    TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable;
    TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
    TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
    TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);

    TIM_Cmd(TIM1, ENABLE);
    TIM_CtrlPWMOutputs(TIM1, ENABLE);  // 主输出使能
}

避坑指南:死区时间不是越大越好。我曾经在一个项目中把死区设成5μs,结果电机低速运行时电流波形毛刺特别多。后来发现死区太大导致电流续流不充分。一般建议死区时间设为PWM周期的1%~2%,比如20kHz周期50μs,死区设0.5μs~1μs就够。

4.2 死区补偿:别让波形“缺胳膊少腿”

死区是必须的,防止上下桥臂直通。但死区也会带来副作用——输出电压波形会失真,尤其在低速轻载时,电流过零点附近会出现“死区效应”,导致电流波形畸变,甚至引起转矩脉动。

说白了,死区让实际输出电压比期望值少了那么一点点。补偿的思路就是:根据电流方向,把丢失的电压补回去。

死区补偿的C代码实现逻辑:

  1. 采样三相电流:判断每相电流的极性(正/负)。
  2. 计算补偿量:补偿电压 = 死区时间 × 母线电压 / PWM周期。
  3. 调整占空比:电流为正时,该相占空比增加补偿量;电流为负时,占空比减少补偿量。
// 死区补偿函数
void DeadTime_Compensate(float *duty_u, float *duty_v, float *duty_w, 
                         float i_u, float i_v, float i_w, float vbus, uint16_t period) {
    float comp_voltage = (float)DEADTIME * vbus / (float)period;  // 补偿电压值
    float comp_duty = comp_voltage / vbus;  // 转换为占空比增量

    // 根据电流方向补偿
    if (i_u > 0) *duty_u += comp_duty;
    else *duty_u -= comp_duty;

    if (i_v > 0) *duty_v += comp_duty;
    else *duty_v -= comp_duty;

    if (i_w > 0) *duty_w += comp_duty;
    else *duty_w -= comp_duty;

    // 限幅处理
    *duty_u = CLAMP(*duty_u, 0.0f, 1.0f);
    *duty_v = CLAMP(*duty_v, 0.0f, 1.0f);
    *duty_w = CLAMP(*duty_w, 0.0f, 1.0f);
}

注意:死区补偿依赖电流采样精度。如果电流采样有偏置或噪声,补偿反而会引入更大的谐波。我建议在电流采样稳定后再使能补偿功能,或者加一个低通滤波。

4.3 SVPWM的C代码实现:从扇区到占空比

终于到核心了。SVPWM的C代码实现,说白了就是三步:扇区判断 → 计算作用时间 → 映射到占空比。前面我们已经推导了扇区判断的公式,这里直接上代码。

先看扇区判断的实现:

// 扇区判断函数
uint8_t SVPWM_Sector_Calc(float v_alpha, float v_beta) {
    uint8_t sector = 0;
    float v_ref1 = v_beta;
    float v_ref2 = 0.866025f * v_alpha - 0.5f * v_beta;   // sqrt(3)/2 = 0.866025
    float v_ref3 = -0.866025f * v_alpha - 0.5f * v_beta;

    if (v_ref1 > 0) sector |= 0x01;
    if (v_ref2 > 0) sector |= 0x02;
    if (v_ref3 > 0) sector |= 0x04;

    // 查表映射到实际扇区(1~6)
    const uint8_t sector_map[8] = {0, 1, 5, 0, 3, 2, 4, 0};
    return sector_map[sector];
}

然后计算两个相邻矢量的作用时间T1和T2:

// 计算T1和T2(输入为标幺化后的v_alpha, v_beta)
void SVPWM_Time_Calc(float v_alpha, float v_beta, uint8_t sector, 
                     float *t1, float *t2, float ts) {
    float t1_val, t2_val;

    switch (sector) {
        case 1:  // 扇区1:U0(100) 和 U60(110)
            t1_val = 0.866025f * v_alpha - 0.5f * v_beta;  // 对应T4
            t2_val = v_beta;                                 // 对应T6
            break;
        case 2:  // 扇区2:U60(110) 和 U120(010)
            t1_val = 0.866025f * v_alpha + 0.5f * v_beta;   // 对应T6
            t2_val = -0.866025f * v_alpha + 0.5f * v_beta;  // 对应T2
            break;
        // ... 其他扇区类似
        default:
            t1_val = 0; t2_val = 0;
            break;
    }

    // 标幺化到PWM周期
    *t1 = t1_val * ts;
    *t2 = t2_val * ts;

    // 过调制处理
    float t_sum = *t1 + *t2;
    if (t_sum > ts) {
        *t1 = *t1 * ts / t_sum;
        *t2 = *t2 * ts / t_sum;
    }
}

最后,把T1和T2映射到三相占空比:

// 占空比计算(七段式SVPWM)
void SVPWM_Duty_Calc(uint8_t sector, float t1, float t2, float ts,
                     float *duty_u, float *duty_v, float *duty_w) {
    float t0 = ts - t1 - t2;  // 零矢量作用时间
    float t_half = t0 * 0.5f; // 零矢量分到两边

    float ta, tb, tc;
    switch (sector) {
        case 1:
            ta = t_half;
            tb = ta + t1;
            tc = tb + t2;
            break;
        case 2:
            ta = t_half + t1;
            tb = t_half;
            tc = tb + t2;
            break;
        // ... 其他扇区
        default:
            ta = tb = tc = 0;
            break;
    }

    // 转换为占空比(0~1)
    *duty_u = ta / ts;
    *duty_v = tb / ts;
    *duty_w = tc / ts;
}

经验之谈:七段式SVPWM比五段式谐波更小,但开关损耗稍高。如果你的应用对效率要求极高(比如电池供电),可以考虑五段式。我一般先用七段式调通,再根据温升情况决定是否切换。

4.4 波形验证:用示波器“看”SVPWM

代码写完了,怎么知道对不对?上示波器。我个人习惯先看单相PWM波形,再看相电压波形,最后看线电压波形和电流波形。

验证步骤:

  1. 单相PWM:用示波器探头夹在某一相上管栅极(或输出端),看PWM频率、占空比是否随给定电压变化。中心对齐的PWM应该是等腰梯形波。
  2. 相电压(对地):测量U相和GND之间的电压。正常SVPWM输出应该是马鞍波,不是正弦波!马鞍波中间凸起,两端下凹,这是SVPWM的特征。
  3. 线电压(相间):测量U-V之间的电压。线电压应该是正弦波,频率和给定频率一致。
  4. 相电流:用电流探头测一相电流。正弦度越好,说明SVPWM实现得越完美。

避坑指南:我曾经在调试时发现线电压波形有“台阶”,查了半天发现是死区补偿没使能。还有一次电流波形有低频振荡,结果是扇区判断的边界条件写错了。所以波形验证这一步千万别省,它能帮你快速定位问题。

4.5 本章知识体系总览

下面这张图总结了SVPWM软件实现的完整流程,从定时器配置到波形验证,每一步都环环相扣:

SVPWM软件实现流程图 SVPWM软件实现流程 定时器配置 死区补偿 扇区判断 T1/T2计算 占空比映射 波形验证 说明: 1. 定时器配置是基础,中心对齐模式是SVPWM的前提。 2. 死区补偿在低速时效果明显,高速时可酌情关闭。 3. 波形验证是检验代码正确性的唯一标准,不要跳过。

嗯,到这里SVPWM的软件实现就讲完了。定时器配置、死区补偿、C代码实现、波形验证,这四个环节缺一不可。你可以在开发板上先跑一下代码,用示波器看看马鞍波和正弦波——看到波形的那一刻,你会觉得前面的努力都值了。