2、GD32定时器与PWM生成:高级定时器配置,互补PWM与死区插入
做FOC控制,说白了就是跟三个东西打交道:电流采样、PWM生成、还有算法运算。而PWM生成这块,是连接你代码和物理世界的桥梁。今天我们就来聊聊GD32的高级定时器,看看怎么用它生成FOC需要的互补PWM,以及那个让人又爱又恨的死区时间。
2.1 为什么FOC需要高级定时器?
普通的定时器,比如GD32的通用定时器(TIMER2、TIMER3这些),也能输出PWM。但FOC控制要求三相桥臂的上下管不能同时导通,否则就是短路。这就需要一对互补的PWM信号,而且上下沿之间要插入一段“死区时间”。
高级定时器(TIMER0、TIMER7)内部集成了硬件死区插入逻辑。你只需要配置好参数,硬件自动帮你处理。我刚开始做FOC时,曾经试图用软件延时来模拟死区,结果电机一启动就炸管。嗯,从那以后我再也不敢用软件做死区了。
2.2 GD32高级定时器结构概览
GD32的高级定时器,本质上是一个16位的自动重装载计数器。但它比通用定时器多了几个关键模块:
- 刹车功能(Break Function):紧急情况下强制关闭所有PWM输出
- 互补输出(Complementary Outputs):自动生成反向信号
- 死区发生器(Dead-Time Generator):可编程的死区插入
- 多通道同步:三相互补PWM必须严格同步
下面这张图是我自己整理的GD32高级定时器PWM生成路径,你看一眼就明白了:
2.3 互补PWM与死区插入配置
配置高级定时器输出互补PWM,其实就三步。我习惯按这个顺序来,不容易漏掉关键寄存器。
2.3.1 第一步:配置时基单元
时基单元决定了PWM的频率。FOC控制通常用10kHz到20kHz的PWM频率。我一般用16kHz,因为人耳对这个频率不太敏感,电机噪音小一些。
/* 配置TIMER0时基 */
timer_parameter_struct timer_init_struct;
timer_struct_para_init(&timer_init_struct);
timer_init_struct.prescaler = 107; // 108MHz / (107+1) = 1MHz
timer_init_struct.period = 62; // 1MHz / (62+1) = 16kHz
timer_init_struct.clockdivision = TIMER_CKDIV_DIV1;
timer_init_struct.repetitioncounter = 0; // 不重复计数
timer_init(TIMER0, &timer_init_struct);
2.3.2 第二步:配置互补输出通道
这一步是关键。你需要指定哪个通道输出上管,哪个输出下管。GD32的高级定时器,CHx是上管,CHxN是下管。
/* 配置通道0为PWM模式1 */
timer_oc_parameter_struct oc_init_struct;
timer_oc_struct_para_init(&oc_init_struct);
oc_init_struct.outputstate = TIMER_CCX_ENABLE; // 使能CH0输出
oc_init_struct.outputnstate = TIMER_CCXN_ENABLE; // 使能CH0N输出
oc_init_struct.ocpolarity = TIMER_OC_POLARITY_HIGH; // CH0高电平有效
oc_init_struct.ocnpolarity = TIMER_OCN_POLARITY_HIGH; // CH0N高电平有效
oc_init_struct.ocidlestate = TIMER_OC_IDLE_STATE_LOW; // 空闲时CH0低电平
oc_init_struct.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW; // 空闲时CH0N低电平
timer_channel_output_config(TIMER0, TIMER_CH_0, &oc_init_struct);
/* 配置PWM模式1:递增计数时,CNT < CCR时输出有效电平 */
timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_0, 31); // 50%占空比
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);
2.3.3 第三步:配置死区时间
死区时间取决于你用的功率管。IGBT一般需要2-3微秒,MOSFET可以短一些,0.5-1微秒就够了。我做过一个项目,客户用的国产MOSFET,死区设了1.2微秒,结果管子还是发热。后来换成1.5微秒才稳定。
/* 配置死区时间 */
/* DTG[7:0] = 死区时间值 */
/* 死区时间 = DTG * Tck_timer */
/* Tck_timer = 1 / 1MHz = 1us */
/* 设死区1.5us,DTG = 1.5 / 1 = 1.5,取整为2 */
timer_break_parameter_struct break_init_struct;
timer_break_struct_para_init(&break_init_struct);
break_init_struct.deadtime = 2; // 死区时间 = 2 * 1us = 2us
break_init_struct.breakstate = TIMER_BREAK_ENABLE; // 使能刹车功能
break_init_struct.breakpolarity = TIMER_BREAK_POLARITY_LOW;
break_init_struct.outputautomatic = TIMER_OUTAUTO_ENABLE; // 自动恢复输出
timer_break_config(TIMER0, &break_init_struct);
2.4 三相互补PWM的同步启动
FOC需要三相PWM严格同步。GD32的高级定时器支持多通道同时更新。你只需要把所有通道的CCR值准备好,然后通过一个软件事件同时加载。
/* 三相PWM同步更新 */
/* 先更新所有通道的占空比 */
timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_0, u_duty);
timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_1, v_duty);
timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_2, w_duty);
/* 产生更新事件,所有通道同时生效 */
timer_event_software_generate(TIMER0, TIMER_EVENT_SRC_UPG);
为什么要同步?你想想看,如果三相PWM不是同时更新的,那么在一个PWM周期内,三相的占空比可能来自不同的控制周期。这会导致电流波动,严重时电机抖动。我调试时遇到过这个问题,用示波器一看,三相PWM的上升沿差了几个微秒。加上同步更新后,问题就解决了。
2.5 刹车功能与故障保护
FOC系统必须考虑故障情况。比如过流、过压、电机堵转。GD32高级定时器的刹车功能,可以在硬件层面瞬间关闭所有PWM输出。
| 刹车源 | 触发条件 | 推荐配置 |
|---|---|---|
| 刹车输入引脚 | 外部故障信号(如过流比较器输出) | 低电平触发,使能自动恢复 |
| 软件刹车 | MCU检测到异常(如ADC过流) | 调用 timer_break_enable() |
| CPU崩溃刹车 | 看门狗复位或硬件错误 | 刹车引脚接上拉电阻,默认关闭 |
/* 使能刹车功能 */
timer_break_enable(TIMER0);
/* 当刹车触发时,所有PWM输出进入空闲状态 */
/* 空闲状态由 ocidlestate 和 ocnidlestate 决定 */
2.6 常见问题与避坑指南
做PWM配置时,有几个坑我踩过,分享给你:
- 极性搞反:CHx和CHxN的极性要一致。如果上管高有效,下管低有效,那么死区时间内上下管都是关断的。但如果极性设反了,死区时间内上下管可能同时导通。
- 空闲状态配置:刹车触发后,PWM进入空闲状态。如果空闲状态电平设错了,电机可能会反转或者抖动。我一般把空闲状态设成低电平,这样MOSFET是关断的。
- 重复计数器:GD32的高级定时器有重复计数器,用于产生更精确的PWM周期中断。FOC中一般不用,设成0就行。如果设成非零值,PWM更新频率会变慢。
- 影子寄存器:CCR和ARR都有影子寄存器。默认是使能的,也就是说你写入的值会在下一个更新事件才生效。如果你需要立即生效,可以关闭影子寄存器,但要注意可能产生不完整的PWM脉冲。