第二讲 高精度 PWM 发波
引言
上一讲我们完成了单相逆变器的硬件原理分析与参数设计。从本讲开始,我们正式进入软件控制部分。
逆变器要输出正弦波,核心动作是:用单片机产生高频 PWM,驱动全桥开关管按照 SPWM 调制通断。
本讲围绕 STM32G474RBTx 主控芯片,带你完成以下内容:
- 理解高分辨率定时器(HRTIM)的架构与工作原理
- 利用 HRTIM 产生两组带死区的互补 PWM,分别驱动全桥两个桥臂
- 掌握 PWM 频率计算与死区配置方法
前置知识:了解 C 语言基础、STM32 开发环境(Keil / CubeMX),以及第一讲中全桥拓扑与 SPWM 的基本概念。
一、为什么选择高分辨率定时器(HRTIM)?
在第一讲中我们知道,全桥逆变器的同一桥臂有上管和下管,它们必须互补导通 ,且切换时必须插入死区时间防止直通短路。STM32 的高级定时器(TIM1 等)虽然也支持互补输出和硬件死区,但分辨率受限于 170 MHz 时钟(约 5.9 ns)。
STM32G474 提供了一个更强大的外设------HRTIM(高分辨率定时器) 。它通过内部 DLL(延迟锁定环)倍频技术,将时间分辨率提升至约 184 ps ,相当于等效时钟频率 5.44 GHz。这意味着:
- 占空比调节更精细:50 kHz 下可实现超过 10 万级细分,输出波形质量更高
- 死区控制更精确:纳秒级精度,避免过大死区引起的波形畸变
- 多路独立控制:6 个从定时器可独立配置,天然适合全桥拓扑
因此,本课程选用 HRTIM 作为 PWM 发波的核心外设,这也是开关电源工程实践中的主流方案。
二、HRTIM 整体架构
STM32G474 的 HRTIM1 由 1 个主定时器(Master Timer) 和 6 个从定时器(Timer A ~ Timer F) 组成,总共可输出 12 路高分辨率 PWM。

对于单相全桥逆变器,我们只需要 Timer A 和 Timer B,分别控制两个桥臂:
| HRTIM 定时器 | 输出通道 | GPIO 引脚 | 驱动对象 |
|---|---|---|---|
| Timer A | TA1(主)/ TA2(互补) | PA8 / PA9 | 桥臂 A 上管 / 下管 |
| Timer B | TB1(主)/ TB2(互补) | PA10 / PA11 | 桥臂 B 上管 / 下管 |
每个从定时器包含以下核心模块:
① 时钟源与 DLL 校准
HRTIM 要实现高分辨率,必须由 PLL 的高频输出 直接供给时钟。具体来说,初始化时需要两步:一是在 RCC_CFGR3 中为 HRTIM 选择 PLL 输出作为时钟源,二是使能 APB2 总线上的 HRTIM 寄存器时钟。也就是说,APB2 只是 HRTIM 寄存器的访问通路,真正驱动高分辨率计数的是 PLL 输出(170 MHz)。
上电后还必须进行 DLL 校准,DLL 在 PLL 时钟基础上进行延迟细分,校准完成后才能启用高分辨率倍频功能。
DLL 校准是 HRTIM 正常工作的前提,跳过则高分辨率功能不可用。DLL 仅在 100~170 MHz 范围内工作,分辨率随输入频率从 312 ps 到 184 ps 缩放。
② 时基单元
每个从定时器有独立的时基,核心寄存器包括:
- 周期寄存器(PERxR):决定计数器溢出值,即 PWM 频率
- 计数器(CNTxR):按设定模式计数,溢出后归零
- 比较寄存器(CMP1xR ~ CMP4xR):产生 PWM 边沿、触发 ADC 等事件
STM32G474 搭载的是 HRTIMv2 ,从定时器支持递增计数 和递增-递减计数(Up-Down Mode)两种模式。递增-递减模式通过 HRTIM_TIMxCR2 寄存器的 UDM 位使能,可以产生中央对齐的对称 PWM 波形。本课程的配置中使用递增计数模式 (UpDownMode = HRTIM_TIMERUPDOWNMODE_UP)。
③ 互补输出与死区插入
每个从定时器有两路输出。开启死区插入(Dead-Time Insertion) 后,第二路(如 TA2)自动成为第一路(TA1)的互补输出,HRTIM 硬件自动生成互补波形并插入死区,无需额外配置。
三、PWM 产生原理
3.1 基本原理
PWM 的核心思想:在计数器频率固定的前提下,频率 由周期寄存器决定,占空比由比较寄存器决定。
以 HRTIM 递增计数模式为例(如下图所示):

- 计数器从 0 开始递增
- 到达周期值(PERxR)时,输出置位(拉高),计数器归零
- 到达比较值(CMP1xR)时,输出复位(拉低)
改变 CMP1xR 的值,就能实时调节占空比------这正是 SPWM 的实现基础。
3.2 PWM 频率计算
HRTIM 的 PWM 频率公式:
fPWM=fHRTIMPERxRf_{PWM} = \frac{f_{HRTIM}}{PERxR}fPWM=PERxRfHRTIM
在我们的配置中,选用 HRTIM_PRESCALERRATIO_MUL16,等效计数频率为 170 MHz × 16 = 2.72 GHz。
实例计算:开关频率 50 kHz:
PERxR=2.72 GHz50 kHz=54400PERxR = \frac{2.72\text{ GHz}}{50\text{ kHz}} = 54400PERxR=50 kHz2.72 GHz=54400
这就是代码中 HRTIM_PWM_PERIOD_TICKS = 54400 的由来。
3.3 占空比计算
在我们的配置中(PER 事件置位、CMP1 事件复位):
D=CMP1xRPERxRD = \frac{CMP1xR}{PERxR}D=PERxRCMP1xR
CMP1xR 越大,高电平持续越长,占空比越大。运行时,控制算法每个开关周期动态更新这个值。
四、互补输出与死区控制
4.1 什么是互补输出?
互补输出是指主通道(见黄色波形)和互补通道(见绿色波形)输出逻辑相反的 PWM 信号------一个为高时另一个为低。这正是同一桥臂上下管所需的驱动信号形式。

4.2 为什么需要死区时间?
现实中 MOSFET 存在开关延迟 ------关断需要时间。如果下管在上管还没完全关断时就开通,两管短暂同时导通,形成直通短路,瞬间过流可能烧毁器件。
死区时间就是在上管关断到下管开通之间强制插入的空白延迟,确保一管彻底截止后另一管才开通。

从波形图可以看到,主通道和互补通道切换时,中间有一小段两者都为低电平的区间------这就是死区时间。
4.3 死区时间计算
HRTIM 的死区发生器支持上升沿和下降沿分别配置,分辨率同样为亚纳秒级。
tDT=DeadTimeValuefDTt_{DT} = \frac{DeadTimeValue}{f_{DT}}tDT=fDTDeadTimeValue
在我们的配置中,死区预分频为 MUL8(死区时钟 = 170 MHz × 8 = 1.36 GHz,步进约 0.735 ns),死区值设为 200:
tDT=200×0.735 ns≈147 nst_{DT} = 200 \times 0.735\text{ ns} \approx 147\text{ ns}tDT=200×0.735 ns≈147 ns
50 kHz 开关频率对应 20 μs 周期,百纳秒级死区既能保证安全切换,又不会引入明显的波形畸变。
五、代码实战:HRTIM PWM 配置
5.1 关键宏定义
c
#define HRTIM_PWM_PERIOD_TICKS (54400U) /* PWM 周期 → 50kHz */
#define HRTIM_CMP1_INIT_TICKS (0U) /* CMP1 初值 = 0(上电不输出有效 PWM) */
#define HRTIM_DEADTIME_TICKS (200U) /* 死区计数值 → 约 147ns */
5.2 初始化代码解读
整个 MX_HRTIM1_Init() 函数的流程如下:
① DLL 校准(必须最先完成)
c
hhrtim1.Instance = HRTIM1;
hhrtim1.Init.HRTIMInterruptResquests = HRTIM_IT_NONE;
hhrtim1.Init.SyncOptions = HRTIM_SYNCOPTION_NONE;
HAL_HRTIM_Init(&hhrtim1);
/* DLL 校准------高分辨率功能的前提 */
HAL_HRTIM_DLLCalibrationStart(&hhrtim1, HRTIM_CALIBRATIONRATE_3);
HAL_HRTIM_PollForDLLCalibration(&hhrtim1, 10);
DLL 校准完成后会周期性自动重校准,补偿温度漂移。
② 时基配置(Timer A / Timer B 共用)
c
pTimeBaseCfg.Period = HRTIM_PWM_PERIOD_TICKS; /* 54400 → 50kHz */
pTimeBaseCfg.RepetitionCounter = 0x00; /* 每周期产生更新事件 */
pTimeBaseCfg.PrescalerRatio = HRTIM_PRESCALERRATIO_MUL16; /* ×16 倍频 */
pTimeBaseCfg.Mode = HRTIM_MODE_CONTINUOUS; /* 连续计数 */
HAL_HRTIM_TimeBaseConfig(&hhrtim1, HRTIM_TIMERINDEX_TIMER_A, &pTimeBaseCfg);
HAL_HRTIM_TimeBaseConfig(&hhrtim1, HRTIM_TIMERINDEX_TIMER_B, &pTimeBaseCfg);
③ 比较值配置
c
/* Timer A:CMP1 控制占空比(上电为 0) */
pCompareCfg.CompareValue = HRTIM_CMP1_INIT_TICKS;
HAL_HRTIM_WaveformCompareConfig(&hhrtim1,
HRTIM_TIMERINDEX_TIMER_A, HRTIM_COMPAREUNIT_1, &pCompareCfg);
/* Timer B:CMP1 控制占空比(上电为 0) */
HAL_HRTIM_WaveformCompareConfig(&hhrtim1,
HRTIM_TIMERINDEX_TIMER_B, HRTIM_COMPAREUNIT_1, &pCompareCfg);
上电时 CMP1 = 0,确保控制算法启动前不会输出有效 PWM,避免误触发功率管。
④ 死区配置
c
pDeadTimeCfg.Prescaler = HRTIM_TIMDEADTIME_PRESCALERRATIO_MUL8;
pDeadTimeCfg.RisingValue = HRTIM_DEADTIME_TICKS; /* 上升沿死区 = 200 */
pDeadTimeCfg.RisingSign = HRTIM_TIMDEADTIME_RISINGSIGN_POSITIVE;
pDeadTimeCfg.FallingValue = HRTIM_DEADTIME_TICKS; /* 下降沿死区 = 200 */
pDeadTimeCfg.FallingSign = HRTIM_TIMDEADTIME_FALLINGSIGN_POSITIVE;
HAL_HRTIM_DeadTimeConfig(&hhrtim1, HRTIM_TIMERINDEX_TIMER_A, &pDeadTimeCfg);
HAL_HRTIM_DeadTimeConfig(&hhrtim1, HRTIM_TIMERINDEX_TIMER_B, &pDeadTimeCfg);
上升沿和下降沿死区设为相同值(对称死区),也可根据实际 MOSFET 的开通/关断速度差异分别调整。
⑤ 输出通道配置
c
/* 主输出 TA1 / TB1:PER 置位,CMP1 复位 */
pOutputCfg.SetSource = HRTIM_OUTPUTSET_TIMPER;
pOutputCfg.ResetSource = HRTIM_OUTPUTRESET_TIMCMP1;
HAL_HRTIM_WaveformOutputConfig(&hhrtim1,
HRTIM_TIMERINDEX_TIMER_A, HRTIM_OUTPUT_TA1, &pOutputCfg);
HAL_HRTIM_WaveformOutputConfig(&hhrtim1,
HRTIM_TIMERINDEX_TIMER_B, HRTIM_OUTPUT_TB1, &pOutputCfg);
/* 互补输出 TA2 / TB2:无需配置事件,死区插入自动生成 */
pOutputCfg.SetSource = HRTIM_OUTPUTSET_NONE;
pOutputCfg.ResetSource = HRTIM_OUTPUTRESET_NONE;
HAL_HRTIM_WaveformOutputConfig(&hhrtim1,
HRTIM_TIMERINDEX_TIMER_A, HRTIM_OUTPUT_TA2, &pOutputCfg);
HAL_HRTIM_WaveformOutputConfig(&hhrtim1,
HRTIM_TIMERINDEX_TIMER_B, HRTIM_OUTPUT_TB2, &pOutputCfg);
这是整个配置中最关键的部分。TA1/TB1 的 PWM 逻辑是:计数器溢出时拉高、到达 CMP1 时拉低。TA2/TB2 则完全交给硬件------因为定时器配置中已开启 DeadTimeInsertion = ENABLED,HRTIM 会自动生成带死区的互补波形。
⑥ GPIO 引脚配置
c
/* PA8 → TA1, PA9 → TA2, PA10 → TB1, PA11 → TB2 */
GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF13_HRTIM1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
5.3 启动与运行时更新
初始化完成后,还需要显式启动计数器和输出:
c
/* 启动计数器 */
HAL_HRTIM_WaveformCounterStart(&hhrtim1,
HRTIM_TIMERID_TIMER_A | HRTIM_TIMERID_TIMER_B);
/* 启动 PWM 输出 */
HAL_HRTIM_WaveformOutputStart(&hhrtim1,
HRTIM_OUTPUT_TA1 | HRTIM_OUTPUT_TA2 |
HRTIM_OUTPUT_TB1 | HRTIM_OUTPUT_TB2);
运行过程中,控制算法在每个开关周期计算出两个桥臂的占空比值 Da 和 Db,直接写入比较寄存器:
c
/* 动态更新占空比(Da、Db 范围 0 ~ 54400) */
hhrtim1.Instance->sTimerxRegs[HRTIM_TIMERINDEX_TIMER_A].CMP1xR = Da;
hhrtim1.Instance->sTimerxRegs[HRTIM_TIMERINDEX_TIMER_B].CMP1xR = Db;
对于不同的调制策略,Da 和 Db 的赋值方式不同:
- 双极性调制 :两个桥臂的对角开关管交替导通,桥臂输出在 +Udc+U_{dc}+Udc 和 −Udc-U_{dc}−Udc 之间切换,
Da和Db取相同的调制量 - 单极性调制 :
Da和Db取相反的调制量(vd和-vd),正半周桥臂输出在 +Udc+U_{dc}+Udc 和 000 之间切换,负半周在 000 和 −Udc-U_{dc}−Udc 之间切换
调制策略的原理与波形对比将在后续课程详细展开。
5.4 实际运行效果
下面是配置完成后,用示波器观测 PA8(TA1)和 PA9(TA2)的实测波形:


- TA1 和 TA2 的互补 PWM 波形(50kHz,占空比约 50%)
- 放大后可见死区区间(两通道同时为低的间隔约 147ns)
从波形中可以验证:
- PWM 频率:周期约 20 μs,对应 50 kHz,与计算一致
- 互补关系:TA1 为高时 TA2 为低,反之亦然
- 死区时间:切换瞬间两通道同时为低,持续约 147 ns,与配置吻合
小结
本讲以 HRTIM 高分辨率定时器为核心,完成了逆变器 PWM 发波的配置:
-
HRTIM 架构:Timer A 和 Timer B 分别驱动全桥两个桥臂,各输出一组互补 PWM(PA8/PA9、PA10/PA11)
-
频率与占空比:PERxR = 54400 对应 50 kHz,运行时动态写 CMP1xR 更新占空比
-
死区保护:开启 DeadTimeInsertion,硬件自动生成约 147 ns 的互补死区,防止桥臂直通
-
代码流程:DLL 校准 → 时基配置 → 比较值初始化 → 死区配置 → 输出通道配置 → 启动发波
下一讲预告: 聚焦调制方式,对比双极性与单极性 SPWM 的原理与波形差异,敬请期待!