4. PWM生成与死区控制:高级定时器配置、互补PWM输出、死区时间计算、PWM更新策略

说到FOC控制,PWM生成绝对是绕不开的核心环节。你想想看,咱们的SVPWM算法算出来的占空比,最终都得靠定时器变成实实在在的开关信号,送到MOSFET栅极上去。GD32的高级定时器在这方面确实挺能打的,尤其是它的互补输出和死区控制,专门为电机驱动而生。

我个人习惯把这一章分成四个部分来讲:先搞定定时器的基本配置,再搞互补PWM输出,然后重点说说死区时间怎么算,最后聊聊PWM更新策略——这部分直接影响FOC环路的响应速度。

4.1 高级定时器配置

GD32的高级定时器(比如TIMER0、TIMER7)跟普通定时器最大的区别,就是多了互补输出、死区插入、刹车输入这些功能。说白了,它就是为桥式电路量身定做的。

先看一个最基本的配置流程:

// 定时器基本配置
timer_parameter_struct timer_initpara;

rcu_periph_clock_enable(RCU_TIMER0);           // 使能时钟
timer_deinit(TIMER0);

// 配置时基
timer_struct_para_init(&timer_initpara);
timer_initpara.prescaler = 1;                   // 预分频,假设主频200MHz
timer_initpara.period = 1999;                   // 自动重装值,决定PWM频率
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;           // 重复计数器,用于产生更新事件
timer_init(TIMER0, &timer_initpara);

这里有个关键点:period值决定了PWM的载波频率。比如主频200MHz,预分频设为1(实际分频2),period设为1999,那么PWM频率就是200MHz / (2 * 2000) = 50kHz。我在项目中一般用16kHz到20kHz,既能避开人耳可听范围,又能保证电流环的更新速率。

我的习惯:预分频尽量设小一点,让定时器计数精度更高。这样SVPWM算出来的占空比能更精细地映射到比较值上,电流纹波会小一些。

4.2 互补PWM输出

互补PWM,就是同一对桥臂的上下管,一个导通时另一个必须关断。GD32的高级定时器支持通道0~2的互补输出,每个通道对应一对PWM引脚。

配置互补输出需要做三件事:

  1. 配置输出模式——设为PWM模式1或2
  2. 使能互补输出——通过TIMER_CTLR2寄存器的CCxE和CCxNE位
  3. 设置极性——决定高电平有效还是低电平有效
// 配置通道0互补输出
timer_oc_parameter_struct timer_ocinitpara;

timer_channel_output_struct_para_init(&timer_ocinitpara);
timer_ocinitpara.outputstate = TIMER_CCX_ENABLE;      // 使能主输出
timer_ocinitpara.outputnstate = TIMER_CCXN_ENABLE;    // 使能互补输出
timer_ocinitpara.ocpolarity = TIMER_OC_POLARITY_HIGH; // 主输出高有效
timer_ocinitpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH; // 互补输出高有效
timer_ocinitpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
timer_ocinitpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
timer_channel_output_config(TIMER0, TIMER_CH_0, &timer_ocinitpara);

// 设置比较值
timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_0, 1000);

嗯,这里要注意极性配置。我见过不少新手在这里栽跟头——如果上下管都是高有效,那死区时间内两个管子都关断,没问题。但如果驱动芯片是低有效,极性就得反过来配。

我曾经踩过的坑:有一次我把互补输出的极性配反了,结果上下管同时导通,MOSFET瞬间冒烟。后来我养成了一个习惯——每次上电前先用示波器看波形,确认死区正常再加载大电流。

4.3 死区时间计算

死区时间,就是上下管切换时,特意插入的一段「同时关断」的时间。为什么要加死区?因为MOSFET的关断速度比开通速度慢,如果不加死区,上管还没完全关断,下管就开了,这就是传说中的「直通短路」。

GD32的死区时间由TIMERx_CAR寄存器的DTCFG位控制。这个寄存器分成两段:

位段 功能 说明
DTCFG[7:0] 死区时间配置 具体数值由DTG[7:0]决定
DTG[7] 死区时间倍率 0:乘以1;1:乘以2或乘以16

死区时间的计算公式分三种情况:

  • DTG[7] = 0:死区时间 = DTG[6:0] × Tck
  • DTG[7] = 1, DTG[6:5] = 00:死区时间 = (64 + DTG[4:0]) × 2 × Tck
  • DTG[7] = 1, DTG[6:5] = 01:死区时间 = (32 + DTG[4:0]) × 8 × Tck
  • DTG[7] = 1, DTG[6:5] = 10或11:死区时间 = (32 + DTG[4:0]) × 16 × Tck

其中Tck是定时器的时钟周期。假设定时器时钟200MHz,Tck = 5ns。

举个例子:我想配500ns的死区时间,DTG[7] = 0,那么DTG[6:0] = 500ns / 5ns = 100。但DTG[6:0]最大只有127,所以100是合法的。配置代码:

// 配置死区时间500ns (T_ck = 5ns)
timer_channel_dead_time_config(TIMER0, TIMER_CH_0, 100);

如果死区时间需要更大,比如2μs,那就得用第二种模式:DTG[7] = 1, DTG[6:5] = 00,死区时间 = (64 + DTG[4:0]) × 2 × 5ns。算一下:(64 + 0) × 10ns = 640ns,不够。设DTG[4:0] = 31,则(64 + 31) × 10ns = 950ns。还是不够?那就换第三种模式,乘8倍。

避坑指南:死区时间不是越大越好。死区太大,电流波形会失真,谐波增加,电机噪音变大。我一般根据MOSFET的datasheet来算:关断延迟时间 + 下降时间 + 一点余量。比如IRFS3004,关断延迟约50ns,下降时间约30ns,我取150ns死区,再加50ns余量,总共200ns。

4.4 PWM更新策略

FOC控制的核心是电流环,电流环的更新频率通常和PWM载波频率一致。每次PWM周期结束时,我们都要更新比较值,让下一个周期的占空比发生变化。

GD32支持两种更新方式:

  • 立即更新:写入比较寄存器后,下一个计数周期立即生效
  • 影子寄存器更新:写入比较寄存器后,等到更新事件(UEV)发生时再加载

在FOC应用中,我们通常使用影子寄存器模式,配合更新中断来实现同步更新。流程是这样的:

  1. 定时器产生更新中断(上溢或下溢)
  2. 在中断服务函数中读取电流采样值
  3. 执行电流环PI运算
  4. 计算新的占空比,写入比较寄存器
  5. 下一个周期开始时,新占空比自动生效
// 使能更新中断
timer_interrupt_enable(TIMER0, TIMER_INT_UP);

// 中断服务函数
void TIMER0_UP_IRQHandler(void) {
    if(timer_interrupt_flag_get(TIMER0, TIMER_INT_UP) != RESET) {
        // 1. 读取电流采样值(ADC已经转换完成)
        float ia = get_current_a();
        float ib = get_current_b();
        
        // 2. 执行Clark/Park变换
        // 3. 执行PI调节
        // 4. 执行SVPWM,计算占空比
        
        // 5. 更新比较值
        timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_0, duty_a);
        timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_1, duty_b);
        timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_2, duty_c);
        
        timer_interrupt_flag_clear(TIMER0, TIMER_INT_UP);
    }
}

这里有个性能问题:中断服务函数里做的事情太多,会影响最大PWM频率。我实测过,在200MHz主频下,如果PWM频率超过50kHz,中断里的运算时间就快赶上PWM周期了。这时候可以考虑用DMA来搬运比较值,或者把部分运算放到主循环里做。

我的优化经验:如果PWM频率在20kHz以下,直接在中断里做完整FOC运算没问题。如果频率更高,建议把电流环拆成两部分——在中断里只做采样和PI,SVPWM计算放到主循环。或者用GD32的DMA + 乒乓缓冲,让硬件自动加载比较值。

最后,我把这一章的核心逻辑画成了一张流程图,方便你理解整个PWM生成与更新的链路:

PWM生成与死区控制核心流程 1. 高级定时器时基配置 2. 互补PWM输出配置 3. 死区时间计算与配置 4. 使能更新中断 (UEV) 5. 中断中更新比较值 6. 互补PWM波形输出 关键参数 PWM频率:16~20kHz 死区时间:150~500ns 更新方式:影子寄存器 避坑提醒 极性配反→直通短路 死区过大→波形失真

这张图把整个链路串起来了。从定时器配置开始,到互补输出、死区插入、更新中断,最后输出波形。每一步都有对应的寄存器配置和注意事项。