
简介面向STM32F334单片机开发者与电机控制工程师这份资源包聚焦于利用内置高分辨率定时器HRTIM产生三路互补PWM波并支持占空比在线调节适用于电机驱动、开关电源等需要精密调速与死区保护的场景。压缩包共169个文件约6.28MB包含HAL库源码.h/.c、编译中间文件.o/.crf/.axf/.hex、IAR/Keil工程配置.uvprojx/.uvoptx、STM32CubeMX初始化文件.ioc/.mxproject及说明文档工程结构完整可直接打开编译或对照学习。资源已有557人学习热度不错。文件中包含HRTIM初始化、死区时间配置、比较寄存器动态修改等关键代码结合stm32f3xx_hal_hrtim.c等源文件可快速理解3路互补PWM的实现流程ioc文件便于在STM32CubeMX中重建引脚映射hex文件可用于直接烧录验证。适合中高级嵌入式开发者上手实践也能为相关课程设计与项目开发提供直接参考。1. 三路互补PWM为什么偏偏选STM32F334的HRTIM做电机控制或者数字电源的朋友应该都有体会普通定时器输出互补PWM不难难的是在频率、死区、占空比分辨率三者同时达标时定时器资源还够用。STM32F334这颗芯片在市面上被定位成“数字电源专用 MCU”核心卖点就是那颗主频 4.6GHz 等效的 HRTIM。它的时基计数器时钟来自 PLL 倍频后的 144MHz 系统时钟再经过内部延迟单元细分单通道 PWM 分辨率能做到 217ps 级别这意味着 100kHz 的 PWM 频率下占空比调节步进依然非常细腻普通 16 位定时器在同样频率下根本做不到这个精度。而三路互补输出对应的是三相全桥逆变、BLDC 方波或正弦波驱动这类场景。三个定时器实例各自输出一对互补信号同时通过交叉开关还能处理通道间的相位关系。资源包里给出的stm32f3xx_hal_hrtim.c和stm32f3xx_hal_tim.c是官方 HAL 库源码f334_one.axf是编译好的可执行文件配合f334_one_sct.Bak的链接脚本基本能还原出一个完整工程。这篇文章我会直接沿着“HRTIM 结构 → 初始化配置 → 互补输出 死区 → 动态调占空比 → 故障保护”这条线展开中间会穿插可直接抄的 HAL 代码和寄存器级说明方便你移植到自己的板子上。2. HRTIM 的定时器拓扑与互补 PWM 的硬件基础2.1 HRTIM 的五个定时器实例与主定时器HRTIM 其实是一个定时器组一个主定时器Master Timer加五个独立的从定时器实例 TIMER A 到 TIMER E。每个从定时器都有自己的时基、比较寄存器、死区发生器和输出引脚。主定时器不直接产生输出它主要用来做同步、触发 ADC 采样、产生周期中断还可以把事件广播给从定时器。这个结构和 STM32 传统的高级定时器 TIM1/TIM8 有本质区别TIM1 的互补输出只有三组但三组共享一个时基HRTIM 的每个定时器实例时基彼此独立你可以让 A 跑 100kHz、B 跑 200kHz 而互不干扰这在多路独立电源设计里非常实用。三路互补 PWM 在 HRTIM 上通常映射为 Timer A、Timer B、Timer C 三组输出。每组各占两个引脚比如 TA1/TA2 一组TB1/TB2 一组TC1/TC2 一组。注意 HRTIM 的引脚不是任意映射的它是通过 AFIO 固定在特定 GPIO 端口上F334 的 HRTIM 输出一般在 PA8-PA11、PB10-PB13 这些位置具体要看数据手册里的 AF 映射表。ST 的 CubeMX 里图形化配置一眼就能看清楚但如果你跟我一样习惯手写寄存器留意HRTIM_CR2和HRTIM_OUT寄存器中的输出使能位和极性位。2.2 互补输出与死区时间的关系互补 PWM 的关键在于同一桥臂的上下管不能同时导通否则就是直通短路。死区时间就是上下管切换时那段“两个管子都关断”的间隔。HRTIM 的每个定时器实例都内置了独立的死区发生器可以分别配置上升沿延迟和下降沿延迟单位是 HRTIM 的时钟 tick。HRTIM 的时钟一般取系统时钟 144MHz如果你开启了内部的延迟单元Delay Unit每 tick 可以短到 217ps。不过实际项目中死区时间通常在 100ns 到几微秒之间按 144MHz 算一个 tick 大约是 6.94ns设置死区寄存器值为 15 就得到约 104ns 的死区。软件上要区分两个概念PWM 信号的频率和死区时间是两个独立的参数。频率由周期寄存器PER决定死区由DTR决定两者互不影响。但在配置时顺序有讲究——必须先设置周期和比较值再设置死区时间因为死区发生器在输出端对原始 PWM 信号做延迟处理它修改的是信号的边沿位置不是周期本身。用寄存器的说法就是HRTIM_TIMxR里的DTR字段高八位是下降沿延迟DTFLK低八位是上升沿延迟DTRLK。2.3 三个定时器实例的同步启动三路互补 PWM 如果只是各自跑各自的相位关系没法保证。HRTIM 的解决方案是主定时器广播同步事件。把所有从定时器配置为接收来自主定时器的复位信号主定时器一旦溢出所有从定时器的计数器同时归零这样三路 PWM 的周期起点就对齐了。启动顺序上先启动主定时器再启动从定时器如果反了从定时器可能还没收到同步事件就已经在自由跑波形相位就是乱的。/* 主定时器周期设为 1440即 144MHz / 1440 100kHz */ HRTIM1-MASTER.R 1440 - 1; /* 使能主定时器周期中断同时作为同步事件源 */ HRTIM1-MASTER.IER | HRTIM_MASTER_IER_MREPU; /* 从定时器 A/B/C 都接收主定时器事件 */ HRTIM1-COMMON.CR1 | HRTIM_CR1_SYNC_IN_1;这段代码做了三件事配置主定时器周期、开启同步事件、把同步输入源选为主定时器。注意HRTIM_CR1_SYNC_IN_1是选择外部同步输入还是内部主定时器事件具体位段要看 f334 的参考手册第 25 章不同型号的位定义略有差异。从定时器那边还需要在各自的CR1里设置SYNCSTRTM和SYNCSTR表示收到同步信号后复位并重新开始计数。3. 基于 HAL 库的 HRTIM 初始化与三路互补输出配置3.1 时钟树与 HRTIM 时钟源选择HRTIM 挂在 APB2 总线上但它的内核时钟不是简单地等于 APB2 时钟。默认情况下 HRTIM 时钟等于系统时钟 SYSCLK也就是 144MHz。如果外部晶振是 8MHzPLL 倍频到 72MHz那么 HRTIM 也只有 72MHz 可用这时一个 tick 是 13.9ns死区最小步进变大精度会打折扣。所以想要发挥 HRTIM 的高分辨率特性第一步就是把系统时钟跑到最高的 144MHz。RCC_OscInitTypeDef osc {0}; RCC_ClkInitTypeDef clk {0}; osc.OscillatorType RCC_OSCILLATORTYPE_HSE; osc.HSEState RCC_HSE_ON; osc.PLL.PLLState RCC_PLL_ON; osc.PLL.PLLSource RCC_PLLSOURCE_HSE; osc.PLL.PLLM 1; osc.PLL.PLLN 18; osc.PLL.PLLP 0; /* PLLP0 对应 2 分频 */ osc.PLL.PLLQ 0; /* PLLQ 未使用 */ osc.PLL.PLLR 0; /* PLLR 未使用 */ HAL_RCC_OscConfig(osc); clk.ClockType RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; clk.SYSCLKSource RCC_SYSCLKSOURCE_PLLCLK; clk.AHBCLKDivider RCC_SYSCLK_DIV1; clk.APB1CLKDivider RCC_HCLK_DIV1; clk.APB2CLKDivider RCC_HCLK_DIV1; HAL_RCC_ClockConfig(clk, FLASH_LATENCY_1);这里 8MHz HSE 经 PLL 倍频到 144MHzPLLN18 表示 8MHz × 18 / 2 72MHz不对重算一下——F334 的 PLL 结构里 PLLM 做输入分频PLLN 做倍频PLLP 做输出分频。8MHz 晶振配 PLLM1、PLLN18、PLLP2 得到 72MHz这不够。要跑 144MHz标准配置是 PLLM1、PLLN18、PLLP1也就是 8 × 18 / 1 144MHz。代码里把 PLLP 置为 0对应分频系数是 1这样才是完整的 144MHz。如果你用的是 16MHz 晶振则 PLLM2、PLLN18、PLLP1 同样能得到 144MHz。时钟树配错最常见的症状是 PWM 频率偏了一半排查时先确认HAL_RCC_GetHCLKFreq()的返回值。3.2 HAL 库句柄结构体的填充HAL 库把 HRTIM 的配置抽象成了HRTIM_TimeBaseCfgTypeDef、HRTIM_CompareCfgTypeDef、HRTIM_DeadTimeCfgTypeDef这几个结构体。相比直接操作寄存器HAL 的封装确实繁琐但好处是代码可读性好而且 CubeMX 生成的工程直接可以用。下面给出一份可用的初始化代码对应 Timer A 的时基配置HRTIM_TimeBaseCfgTypeDef timeBase {0}; HRTIM_CompareCfgTypeDef compare {0}; HRTIM_DeadTimeCfgTypeDef deadTime {0}; /* 时基配置 */ timeBase.Period 1440 - 1; /* 100kHz 144MHz */ timeBase.RepetitionCounter 0; timeBase.PrescalerRatio HRTIM_PRESCALERRATIO_DIV1; timeBase.Mode HRTIM_MODE_CONTINUOUS; timeBase.StartSync HRTIM_SYNCSTART_DISABLED; timeBase.UpdateGated HRTIM_UPDATEGATE_INDEPENDENT; timeBase.ResetUpdate HRTIM_UPDATEONLY_PREEMPTIVE; HAL_HRTIM_TimeBaseConfig(hrtim1, HRTIM_TIMERINDEX_A, timeBase); /* 比较值配置初始占空比 50%即比较值为周期一半 */ compare.CompareValue 720; HAL_HRTIM_CompareConfig(hrtim1, HRTIM_TIMERINDEX_A, compare); /* 死区配置上升沿 20 个 tick下降沿 20 个 tick */ deadTime.DeadTime1 20; deadTime.DeadTime2 20; deadTime.DeadTime1Sign HRTIM_DEADTIME_SIGN_POSITIVE; deadTime.DeadTime2Sign HRTIM_DEADTIME_SIGN_POSITIVE; deadTime.DeadTime1Prescaler HRTIM_DEADTIME_PRESCALERRATIO_DIV1; deadTime.DeadTime2Prescaler HRTIM_DEADTIME_PRESCALERRATIO_DIV1; HAL_HRTIM_DeadTimeConfig(hrtim1, HRTIM_TIMERINDEX_A, deadTime);Timer B 和 Timer C 的配置与 Timer A 完全一样只是把HRTIM_TIMERINDEX_A换成HRTIM_TIMERINDEX_B和HRTIM_TIMERINDEX_C。把上面这段复制三份就可以生成三路频率一致、相位对齐的互补 PWM。Period 1440 - 1的原因是 HRTIM 的计数器从 0 计到 Period 值后溢出实际周期是 1440 个时钟 tick对应频率 144MHz / 1440 100kHz。比较值 720 正好是一半占空比 50%。3.3 输出通道映射与 GPIO 复用配置HRTIM 的输出引脚使能在HRTIM_OutputCfgTypeDef结构体里配置需要设置输出极性、空闲状态和通道使能。F334 上 Timer A 的输出通道是 TA1 和 TA2Timer B 是 TB1 和 TB2Timer C 是 TC1 和 TC2。它们对应的 GPIO 引脚分别是 PA8/PA9、PA10/PA11、PB12/PB13复用功能号是 AF1具体以参考手册的 AF 映射表为准。GPIO_InitTypeDef gpio {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /* TA1 - PA8, TA2 - PA9 */ gpio.Pin GPIO_PIN_8 | GPIO_PIN_9; gpio.Mode GPIO_MODE_AF_PP; gpio.Pull GPIO_NOPULL; gpio.Speed GPIO_SPEED_FREQ_VERY_HIGH; gpio.Alternate GPIO_AF1_HRTIM1; HAL_GPIO_Init(GPIOA, gpio); /* TB1 - PA10, TB2 - PA11 */ gpio.Pin GPIO_PIN_10 | GPIO_PIN_11; HAL_GPIO_Init(GPIOA, gpio); /* TC1 - PB12, TC2 - PB13 */ gpio.Pin GPIO_PIN_12 | GPIO_PIN_13; HAL_GPIO_Init(GPIOB, gpio);GPIO 的复用功能号GPIO_AF1_HRTIM1在 HAL 头文件里定义如果某个引脚复用号不对输出就是低电平或者无输出。调试时可以用逻辑分析仪直接量引脚也可以先用 CubeMX 生成工程对照 GPIO 初始化代码大多数引脚映射问题在这一步就能暴露。另外F334 的 HRTIM 输出默认是推挽模式带不动重负载板级电路上通常要加栅极驱动芯片如 IR2104 或 UCC27714MCU 引脚只输出 3.3V 逻辑电平。3.4 启动 HRTIM 定时器所有配置完成后启动顺序有讲究。先启动三个从定时器等它们进入等待同步状态最后启动主定时器主定时器一启动就立刻产生同步事件三路从定时器同时开始计数。如果先启动主定时器从定时器还没准备好接收事件就丢了第一个同步信号。HAL_HRTIM_WaveformCounterStart(hrtim1, HRTIM_TIMERINDEX_A); HAL_HRTIM_WaveformCounterStart(hrtim1, HRTIM_TIMERINDEX_B); HAL_HRTIM_WaveformCounterStart(hrtim1, HRTIM_TIMERINDEX_C); /* 最后启动主定时器产生同步事件 */ HAL_HRTIM_MasterCounterStart(hrtim1);启动之后如果发现输出波形频率正确但相位不对多半是同步源配置失败此时把三个定时器的StartSync字段从HRTIM_SYNCSTART_DISABLED改为HRTIM_SYNCSTART_ENABLED再试一次。StartSync表示收到同步事件后重新从零开始计数而ResetUpdate控制的是比较寄存器更新事件的发生方式这两个概念容易混淆对比一下前者管计数器的“归零”后者管比较寄存器值的“装载时刻”。4. 占空比在线调节与死区参数实测验证4.1 通过比较寄存器动态改变占空比占空比调整本质上就是修改比较寄存器CMP1的值。HRTIM 的比较寄存器都有预装载功能写入的新值不会立刻生效而是在下一个周期更新事件到来时统一装载。这个机制避免了在 PWM 周期中间改变比较值导致的输出毛刺。HAL 库中直接提供更新比较值的函数/* 动态调整占空比duty_cycle 范围 0.0 ~ 1.0 */ void set_duty_cycle(float duty_cycle) { uint32_t compare_value; if (duty_cycle 0.0f) duty_cycle 0.0f; if (duty_cycle 1.0f) duty_cycle 1.0f; /* 1440 是周期值这里乘系数得到比较值 */ compare_value (uint32_t)(1440.0f * duty_cycle); if (compare_value 1440) compare_value 1439; __HAL_HRTIM_SETCOMPARE(hrtim1, HRTIM_TIMERINDEX_A, compare_value); __HAL_HRTIM_SETCOMPARE(hrtim1, HRTIM_TIMERINDEX_B, compare_value); __HAL_HRTIM_SETCOMPARE(hrtim1, HRTIM_TIMERINDEX_C, compare_value); }__HAL_HRTIM_SETCOMPARE是一个宏底层实现是写HRTIM1-sTimRegs[channel].CMP1R寄存器。注意比较值必须小于周期值如果占空比设为 100%比较值等于周期值输出会恒为高电平但 HRTIM 的边沿模式在某些配置下会行为异常所以代码里限制了最大值 1439。占空比设为 0% 同理比较值为 0 时输出恒为低。实际电机调速时占空比不能一步从 10% 跳到 90%否则电流冲击很大。常规做法是软件斜坡每次只有比较值增加或减少一小步比如每个 PWM 周期变化 1 个 tick100kHz 频率下 1000 tick 的斜坡耗时 10ms这个速度对大多数电机来说足够平缓。4.2 死区时间的换算公式与实际验证死区时间计算不复杂但经常有人搞混寄存器的单位。HRTIM 的死区寄存器HRTIM_TIMxR中DTFLK和DTRLK各占一个字节最小递增单位是一个 HRTIM 时钟 tick。144MHz 时钟下死区时间 死区寄存器值 / 144MHz20 个 tick 就是约 139ns。注意这是理论值实际输出波形还要受 GPIO 翻转速率、负载电容、栅极驱动芯片传播延迟的影响。用示波器测到的死区时间应该等于理论值加上驱动器上升沿和下降沿的传输延迟差。如果实测发现死区比预期小很多甚至出现负死区那就不是寄存器的问题而是驱动芯片的传播延迟不对称需要增大寄存器的值。HRTIM_DeadTimeCfgTypeDef deadTime {0}; for (uint8_t i 0; i 3; i) { deadTime.DeadTime1 (i 1) * 10; deadTime.DeadTime2 (i 1) * 10; deadTime.DeadTime1Sign HRTIM_DEADTIME_SIGN_POSITIVE; deadTime.DeadTime2Sign HRTIM_DEADTIME_SIGN_POSITIVE; deadTime.DeadTime1Prescaler HRTIM_DEADTIME_PRESCALERRATIO_DIV1; deadTime.DeadTime2Prescaler HRTIM_DEADTIME_PRESCALERRATIO_DIV1; HAL_HRTIM_DeadTimeConfig(hrtim1, HRTIM_TIMERINDEX_A i, deadTime); }这里HRTIM_TIMERINDEX_A i利用了 HAL 枚举值连续递增的特性A0、B1、C2可以直接这样遍历。如果你在写正式工程代码不建议依赖枚举值连续性而是显式写三个case或三个独立的配置块后续维护的人不用去查头文件里枚举的定义。4.3 用示波器验证互补输出与死区验证环节我一般分三步走。第一步示波器两个通道分别接 TA1 和 TA2触发方式设为上升沿观察互补关系是否正确——上下管的波形应该相位相反中间有明显的低电平死区。第二步把示波器时间轴放大到每个 tick 级别确认死区宽度比如 139ns 的配置示波器上应该在 130~150ns 之间。第三步才是接功率电路先不带电机只带一个纯阻性负载确认电流波形没有直通尖峰。死区太短的现象是上下管波形几乎重叠中间只有极短的关断时间。死区太长的现象是波形中间有一段明显的空档虽然安全但会导致输出电压谐波增大、效率下降。F334 的 HRTIM 还有死区补偿功能——DeadTimeInsertion可以在 PWM 输出上自动加入额外的延迟使得最终死区时间与预期一致即使驱动芯片或功率管有不对称延迟。这个功能本质是先把原信号延迟一段固定时间再插入死区最终输出信号的边沿位置整体往后移。如果你有强烈的死区精度要求可以打开这个功能否则默认关闭就行。5. 故障保护机制与 PWM 刹车功能实战5.1 HRTIM 的故障输入通道电机控制器里过流保护必须硬件实现不能等软件轮询到异常再关 PWM——那已经晚了。HRTIM 提供故障输入引脚每个故障输入可以映射到任意一个或全部定时器输出上。故障触发后输出引脚立即进入预设的安全状态高阻、高电平或低电平整个过程由硬件完成不需要 CPU 参与。F334 上有多个故障输入引脚比如 FLT1、FLT2、FLT3它们可以来自比较器输出、外部引脚或者内部触发事件。在电机驱动板子上最典型的接法是把电流采样比较器的输出直接接到 HRTIM 的故障输入引脚。比如用片内比较器 COMP1 配合 DAC 做阈值比较当过流时比较器输出翻转触发 HRTIM 刹车。HRTIM_FaultCfgTypeDef fault {0}; fault.Source HRTIM_FAULTSOURCE_COMP1; fault.Level HRTIM_FAULTLEVEL_ACTIVE_HIGH; fault.Lock HRTIM_FAULT_LOCK_DISABLED; fault.Blanking HRTIM_FAULTBLANKING_DISABLED; fault.BlankingValue 0; fault.Retriggering HRTIM_FAULTRETRIGGERING_DISABLED; fault.InterruptEnable HRTIM_FAULT_INTERRUPT_ENABLED; HAL_HRTIM_FaultConfig(hrtim1, HRTIM_FAULT_1, fault);Source选择 COMP1 作为故障源Level是高电平有效还是低电平有效Blanking是故障消隐窗口用于忽略开关瞬间的电流毛刺Retriggering决定故障清除后是否需要重新触发。对于过流保护一般不建议开 Blanking因为开关节点上确实存在很高的 dV/dt 噪声但这个噪声窗口很短如果消隐时间设置不当真实过流信号可能被一起屏蔽掉。5.2 故障后的恢复策略故障触发后输出被封锁但 HRTIM 的计数器还在跑。此时如果要重新启动 PWM有两种做法。一是软件清除故障标志后将输出重新使能二是配置为自动重启模式。工业设备里我更倾向于软件清除——人工排查过流原因后再按下复位按钮重新使能输出避免故障反复时 PWM 反复通断烧管子。/* 清除故障标志 */ __HAL_HRTIM_CLEAR_FLAG(hrtim1, HRTIM_FLAG_FAULT1); /* 重新使能波形输出 */ HAL_HRTIM_WaveformOutputStart(hrtim1, HRTIM_TIMERINDEX_A | HRTIM_TIMERINDEX_B | HRTIM_TIMERINDEX_C);这里先清标志再使能输出顺序不能颠倒。如果先使能输出故障标志还没清硬件会立刻再次封锁输出。另外要注意HAL_HRTIM_WaveformOutputStart和HAL_HRTIM_WaveformCounterStart的区别CounterStart 是启动计数器开始计数OutputStart 是让内部生成的 PWM 信号出现在引脚上。调试时我习惯先启动计数器、不启动输出用示波器看不到波形再确认配置无误后启动输出。5.3 利用 PWM 故障保护实现软启动故障保护另一个实用场景是配合软启动电路。功率电路上电瞬间母线电容充电电流很大如果此时 PWM 已经以最大占空比运行冲击电流可能损坏整流桥。常规做法是 MCU 复位后默认把所有故障输入置为有效状态即封锁 PWM 输出等母线电压稳定后再软件解除故障状态。/* 上电后立即封锁所有输出 */ HAL_HRTIM_FaultLock(hrtim1, HRTIM_FAULT_LOCK_ENABLED); /* 延时等待母线电容充电完成 */ HAL_Delay(200); /* 解除封锁正常输出 */ HAL_HRTIM_FaultLock(hrtim1, HRTIM_FAULT_LOCK_DISABLED);HAL_HRTIM_FaultLock这个函数名起的容易误解它不是“锁定故障状态”而是锁定故障输出状态。锁定时故障触发后输出保持封锁状态即使故障源消失也不会自动恢复必须软件解锁。这样就能实现上电瞬间安全封锁、延时后自动恢复的效果。这是数字电源里非常标准的软启动手段比单纯靠软件延时控制 PWM 使能可靠得多。6. 三路互补 PWM 的相位偏移与中心对齐模式扩展6.1 中心对齐计数模式下的互补输出前面讲的都是边沿对齐模式——计数器从 0 递增到 Period然后跳回 0。这种模式的 PWM 波形左右不对称有效占空比中间存在偏移在低占空比时电流纹波比较大。中心对齐模式下计数器先递增到 Period再递减回 0比较值匹配时翻转输出。这样 PWM 脉冲位于周期正中央是电机驱动里更推荐的方式。HRTIM 切换到中心对齐模式只需要修改时基配置的Mode字段timeBase.Mode HRTIM_MODE_CONTINUOUS; timeBase.CountingMode HRTIM_COUNTINGMODE_UP_DOWN; HAL_HRTIM_TimeBaseConfig(hrtim1, HRTIM_TIMERINDEX_A, timeBase);注意HRTIM_COUNTINGMODE_UP_DOWN这个枚举是中心对齐模式向上计数和向下计数各占半个周期。此时实际 PWM 频率是计数器溢出频率的一半因为一个完整的 PWM 周期包括了“增计数 减计数”两段。要得到 100kHz 的 PWM 输出周期寄存器要设为 2880144MHz / 100kHz / 2。中心对齐模式的死区配置和边沿模式完全一样但有一点很关键增计数和减计数阶段的比较事件会各触发一次输出翻转死区发生器需要正确处理两个边沿。如果你在中心对齐模式下发现互补输出波形异常比如死区只出现在一个边沿检查一下HRTIM_TIMxCR的PSH位——它控制 PWM 输出在计数的哪个阶段置位。6.2 通道间相位差与移相 PWM三路互补 PWM 还有一个高级玩法是移相三路输出的频率相同但相位依次错开 120°。这在三相整流器Vienna 拓扑和 LLC 变换器里很常见。HRTIM 实现移相不需要额外同步逻辑只需要在每个定时器实例的计数器初值上做文章。/* Timer A 从 0 开始 */ HAL_HRTIM_WaveformCounterStart(hrtim1, HRTIM_TIMERINDEX_A); /* Timer B 滞后 120°即滞后 480 个 tick1440 / 3 */ HRTIM1-sTimRegs[HRTIM_TIMERINDEX_B].CNTR 480; HAL_HRTIM_WaveformCounterStart(hrtim1, HRTIM_TIMERINDEX_B); /* Timer C 滞后 240° */ HRTIM1-sTimRegs[HRTIM_TIMERINDEX_C].CNTR 960; HAL_HRTIM_WaveformCounterStart(hrtim1, HRTIM_TIMERINDEX_C);为什么可以这样写因为三个定时器共享主定时器的同步事件同步到来时各自计数器归零。在启动瞬间手动给 B 和 C 的计数器预置不同的值就相当于让它们在三相周期的不同点开始计 数。同步事件后每到周期终点都会重新对齐所以相位差能一直保持。这个技巧在文档里没有直接说明但从计数器行为上可以推出来也算一个比较实用的经验。需要注意的是启动顺序必须一致先配置计数器初值再启动从定时器最后启动主定时器产生同步。如果先启动主定时器同步事件已经发出B 和 C 的计数器初值设置就不再生效。另外占空比变化时三个通道的比较值要同时更新否则三相输出幅值会短暂不平衡用__HAL_HRTIM_SETCOMPARE连续写三个寄存器中间不加延时基本上能保证它们在同一个更新事件中生效。6.3 通过 DMA 实现占空比波形调制如果要在三路互补 PWM 上输出正弦波调制信号比如 SPWM 驱动三相异步电机那就不能手动一点点改比较值了。常规做法是用 DMA 把正弦查找表的数据搬进比较寄存器每个 PWM 周期搬一次频率由定时器更新事件触发。HRTIM 的 DMA 请求映射在HRTIM_DMAMASTERCR寄存器里配置。/* 配置 DMA 从内存传输 128 个半字到 Timer A 的比较寄存器 */ hdma_dac.Init.Direction DMA_MEMORY_TO_PERIPH; hdma_dac.Init.PeriphInc DMA_PINC_DISABLE; hdma_dac.Init.MemInc DMA_MINC_ENABLE; hdma_dac.Init.PeriphDataAlignment DMA_PDATAALIGN_HALFWORD; hdma_dac.Init.MemDataAlignment DMA_MDATAALIGN_HALFWORD; hdma_dac.Init.Mode DMA_CIRCULAR; HAL_DMA_Init(hdma_dac); __HAL_LINKDMA(hrtim1.DmaHandle, hrtim_dma, hdma_dac); HAL_HRTIM_DMAConfig(hrtim1, HRTIM_TIMERINDEX_A, HRTIM_DMA_MASTER, HRTIM_DMACONTROL_CMP1, DMA_REQUEST_HRTIM_A);这里HRTIM_DMACONTROL_CMP1指定每次 DMA 请求搬运数据的目标寄存器是 CMP1即比较寄存器。DMA 循环模式下128 个点的正弦表会不停循环写入占空比跟随正弦波变化。三路互补 PWM 各配一个 DMA 通道就能实现真正的三相 SPWM。这种做法的好处是 CPU 完全解放只要在初始化时配置好 DMA运行中不需要任何中断干预。调试 DMA 模式时有个典型坑HRTIM 的 DMA 请求频率很高100kHz 的 PWM 对应每秒 10 万次 DMA 请求如果 DMA 配置为正常模式而不是循环模式搬完一次数据后 DMA 通道就停了此时 PWM 占空比固定在最后一个值波形看起来像死了。用示波器观察的话输出占空比不再变化第一时间查 DMA 的NDTR寄存器是否归零就能快速定位问题。本文还有配套的精品资源点击获取