4. 定时器与PWM生成:高级定时器TIMER0配置、互补PWM输出与死区插入、中心对齐模式与边沿对齐模式选择
各位做电机控制的朋友,咱们今天来聊聊GD32的定时器。说实话,FOC控制里最核心的外设就是高级定时器。没有它,你连最基本的PWM波都发不出来,更别提什么矢量控制了。
我个人习惯把TIMER0叫做“电机控制专用定时器”。为什么?因为它有6个独立的PWM通道,支持互补输出,还能插死区。这些功能,说白了就是为三相逆变器量身定做的。
4.1 TIMER0硬件架构概览
先看整体结构。TIMER0本质上是一个16位的自动重装载计数器。但它比普通定时器多了几个关键模块:
- 三个捕获/比较通道:每个通道对应一对互补PWM输出
- 死区发生器:在互补信号之间插入延迟
- 刹车功能:紧急情况下强制关闭输出
- 编码器接口:可以直接接霍尔或正交编码器
我在项目中遇到过一个问题:有人把TIMER0当普通定时器用,结果发现PWM输出死活不对。后来一查,原来是没配置成高级模式。嗯,这里要注意——TIMER0默认是基础定时器模式,你得手动把它切换到“中心对齐”或“边沿对齐”模式才能输出PWM。
核心要点:TIMER0的6个PWM输出通道(CH0~CH5)分别对应三相逆变器的上管和下管。CH0/CH1控制U相,CH2/CH3控制V相,CH4/CH5控制W相。这个映射关系是固定的,别搞混了。
4.2 互补PWM输出与死区插入
你想想看,三相逆变器最怕什么?上下管直通!一旦上管还没关断、下管就打开了,那电流直接短路,MOS管瞬间冒烟。我刚开始做电机驱动时,就因为这个烧过好几块板子。后来学乖了——必须加死区。
死区,说白了就是在上下管切换时插入一段“都关断”的时间。TIMER0的死区发生器可以精确控制这个时间,精度达到一个时钟周期。
4.2.1 死区配置方法
死区时间通过TIMER0的DTG寄存器设置。这个寄存器有8位,但配置方式有点绕:
| DTG[7:5] | 死区计算方式 | 示例(CK_INT=168MHz) |
|---|---|---|
| 000 | DT = DTG[7:0] × T_dts | 0x50 → 80 × 5.95ns = 476ns |
| 100 | DT = (64 + DTG[5:0]) × 2 × T_dts | 0x90 → (64+16) × 11.9ns = 952ns |
| 110 | DT = (32 + DTG[4:0]) × 8 × T_dts | 0xC8 → (32+8) × 47.6ns = 1.9μs |
| 111 | DT = (32 + DTG[4:0]) × 16 × T_dts | 0xE8 → (32+8) × 95.2ns = 3.8μs |
我的经验:对于常见的IRFS3006这类MOS管,死区设在500ns~1μs比较合适。太小了有直通风险,太大了会影响波形质量。我曾经试过3μs的死区,结果电机在低速时抖动得厉害——死区时间太长,电流波形畸变了。
4.2.2 互补输出配置代码
下面这段代码是我项目里实际用过的。配置TIMER0输出互补PWM,带1μs死区:
// 配置TIMER0通道0为互补输出
timer_oc_parameter_struct timer_ocinitpara;
timer_break_parameter_struct timer_breakpara;
// 使能TIMER0时钟
rcu_periph_clock_enable(RCU_TIMER0);
// 复位TIMER0
timer_deinit(TIMER0);
// 配置死区:1μs @ 168MHz
// DTG = 0xB0 → 模式2: (64+16) × 2 × 5.95ns ≈ 952ns
timer_breakpara.deadtime = 0xB0;
timer_breakpara.output_state = TIMER_CCX_ENABLE;
timer_breakpara.outputn_state = TIMER_CCXN_ENABLE;
timer_breakpara.break_state = TIMER_BREAK_DISABLE;
timer_breakpara.break_polarity = TIMER_BREAK_POLARITY_HIGH;
timer_breakpara.breakn_state = TIMER_BREAKN_DISABLE;
timer_breakpara.breakn_polarity = TIMER_BREAKN_POLARITY_HIGH;
timer_breakpara.lock_state = TIMER_LOCK_OFF;
timer_break_config(TIMER0, &timer_breakpara);
// 配置PWM模式1
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, 500);
timer_channel_output_mode_config(TIMER0, TIMER_CH_0, TIMER_OC_MODE_PWM1);
timer_channel_output_shadow_config(TIMER0, TIMER_CH_0, TIMER_OC_SHADOW_DISABLE);
注意:配置死区时,一定要先调用timer_break_config(),再配置通道输出。顺序反了的话,死区可能不生效。我踩过这个坑,查了两天才发现是配置顺序的问题。
4.3 中心对齐模式 vs 边沿对齐模式
这两种模式,说白了就是计数器计数的两种方式。但它们在FOC控制里的影响天差地别。
4.3.1 边沿对齐模式
计数器从0数到ARR,然后跳回0。PWM波形只有一边在变化。这种模式的好处是简单,PWM频率可以做得比较高。但缺点也很明显——电流采样时机不好把握。
你想想看,在边沿对齐模式下,所有通道的PWM都在同一个时刻翻转。这时候你去采样电流,正好赶上开关噪声最大的时候。我早期做FOC时用边沿对齐,电流波形上全是毛刺,滤波都滤不干净。
4.3.2 中心对齐模式
计数器从0数到ARR,再从ARR数回0。PWM波形两边都在变化。这种模式的好处是:
- 电流采样窗口大:在计数器达到ARR或0时,所有通道都处于稳定状态,这时候采样电流最干净
- 谐波小:中心对齐PWM的谐波分量比边沿对齐低,电机运行更平稳
- 死区影响对称:死区对波形的影响在正负半周是对称的,补偿起来更容易
但代价是:同样ARR值下,PWM频率只有边沿对齐的一半。不过对于FOC来说,10kHz~20kHz的PWM频率完全够用。
| 对比项 | 边沿对齐模式 | 中心对齐模式 |
|---|---|---|
| 计数方式 | 0 → ARR → 0 | 0 → ARR → 0 → ARR → 0 |
| PWM频率 | CK_CNT / (ARR+1) | CK_CNT / (2 × (ARR+1)) |
| 电流采样时机 | 在ARR或0时刻 | 在ARR或0时刻(更稳定) |
| 谐波特性 | 奇次谐波较大 | 谐波分布更均匀 |
| 适用场景 | 高频PWM、简单控制 | FOC矢量控制 |
我的建议:做FOC控制,无脑选中心对齐模式。虽然频率低一点,但电流采样质量好太多了。我所有量产项目都用中心对齐,从来没出过问题。
4.3.3 中心对齐模式配置
配置中心对齐模式其实就一行代码的事:
// 配置TIMER0为中心对齐模式1
// 模式1:计数器向上计数时CHx有效,向下计数时CHx无效
// 模式2:计数器向上计数时CHx无效,向下计数时CHx有效
// 模式3:计数器向上和向下计数时CHx都有效
timer_center_aligned_mode_config(TIMER0, TIMER_COUNTER_CENTER_ALIGNED_MODE_1);
// 设置ARR和PSC
timer_autoreload_value_config(TIMER0, 4199); // ARR = 4200-1
timer_prescaler_value_config(TIMER0, 1); // PSC = 2-1
// 使能TIMER0
timer_enable(TIMER0);
这里有个细节:中心对齐模式下,ARR的值决定了PWM频率。比如CK_CNT=84MHz,ARR=4199,那么PWM频率就是84MHz / (2 × 4200) = 10kHz。这个频率对FOC来说刚刚好。
小技巧:中心对齐模式下,建议把ADC触发配置在计数器溢出事件(UEV)上。这样每次计数器到0或ARR时,ADC自动采样,正好避开开关噪声。我在项目里就是这么干的,电流波形干净得像仿真出来的。
4.4 刹车功能与安全保护
最后聊一下刹车功能。TIMER0的刹车输入可以紧急关闭所有PWM输出。这个功能在电机控制里太重要了——万一出现短路、过流、堵转,你得能在微秒级把管子全关掉。
刹车源可以是外部引脚(BKIN),也可以是软件触发。配置方法:
// 配置刹车功能
timer_breakpara.break_state = TIMER_BREAK_ENABLE;
timer_breakpara.break_polarity = TIMER_BREAK_POLARITY_HIGH;
timer_breakpara.breakn_state = TIMER_BREAKN_DISABLE;
timer_break_config(TIMER0, &timer_breakpara);
// 软件触发刹车
timer_break_enable(TIMER0);
刹车触发后,所有PWM输出立即进入空闲状态(高阻或低电平,取决于配置)。要恢复输出,必须重新初始化通道。
安全提醒:刹车功能一定要接硬件保护信号,比如过流比较器的输出。别只依赖软件检测——万一程序跑飞了,硬件刹车是最后一道防线。我曾经见过一个项目,软件检测过流没问题,但主频跑飞后MOS管全烧了。加了硬件刹车后再也没出过事。