拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32F334 HRTIM 三路互补PWM配置:死区、占空比与故障保护详解

简介针对STM32F334系列单片机的HRTIM定时器应用示例包完整演示了如何配置高分辨率定时器输出三路互补PWM波形并支持在运行时动态调整占空比配合死区时间设置可有效避免上下桥臂直通适合电机驱动、数字电源等需要精确脉宽控制的场景。压缩包内共169个文件以C源文件、H头文件及Keil工程文件为核心并包含编译生成的hex/axf固件、IOC初始化配置及调试描述文件整体大小约6.28MB可按需查看源码或直接烧录固件运行。目前已有557人学习下载适合嵌入式开发者作为HRTIM入门与进阶的参考。资源提供了完整的HAL库底层驱动和可复用的工程模板关键代码涵盖定时器初始化、死区配置、输出映射和占空比更新等环节通过分析f334_one工程可清晰梳理互补PWM的完整配置流程并能快速移植到实际项目中。1. STM32F334 的 HRTIM 为什么值得为三路互补 PWM 单独开一篇做数字电源或三相逆变器第一道坎往往不是算法而是 PWM 载波本身稳不稳。用 STM32F334 的 HRTIM 定时器产生 3 路互补 PWM 波很多人第一反应是用通用定时器加 GPIO 翻转去凑但这样既吃 CPU抖动也压不下去。HRTIM 真正值钱的地方在于把互补输出、死区插入、故障保护全部做成了硬件行为三个定时器单元 TimerA/B/C 天然就是三对互补输出占空比可变只需要在周期中断里写三个比较寄存器。这篇文章只围绕一件事讲从初始化到在线改占空比再到验证和故障保护把这一整套 HRTIM 配置路径走通。适合正在从软件 PWM 切到专用外设的电机控制或数字电源开发者。2. 从时基到引脚HRTIM 三路互补输出的配置思路2.1 资源划分MST、TimerA/B/C 这 4 条时基各干什么HRTIM 是一个多时基外设常见理解是把它当成一个“定时器集群”。除了主定时器 MST还有五个子定时器单元 A、B、C、D、E。做三路互补 PWM 时我一般只用 MST 加 TimerA/B/CTimerD/E 留作它用或者干脆不使能。每个子定时器单元自带一对输出比如 TimerA 输出 TA1 和 TA2这一对输出天然互补而且是硬件生成的不是软件取反。这意味着死区插入、故障时成对关断都由 HRTIM 内部逻辑完成CPU 不参与。MST 在这里不直接输出波形主要承担两个任务给三个子定时器提供统一时基基准以及产生周期中断供我们在中断里同步更新三路占空比。单元输出引脚互补对典型用途MST无无周期同步、周期中断、多定时器联动TimerATA1 / TA2第 1 对互补第一组半桥上下管TimerBTB1 / TB2第 2 对互补第二组半桥上下管TimerCTC1 / TC2第 3 对互补第三组半桥上下管这个资源划分是固定的后续所有配置都围绕“三个子定时器周期一致、比较值独立、由 MST 统一节拍”来展开。2.2 PWM 频率与周期寄存器的换算关系HRTIM 的时钟来自芯片内部 PLL常见配置是把 HRTIM 时钟倍频到 144MHz。这里先确认一个基础公式fPWM fHRTIM / ((PER 1) * 2^Prescaler)Prescaler 为 0 时每个计数周期是 6.94ns对应分辨率约 6.94ns。周期寄存器 PER 决定载波频率比较寄存器决定占空比。比如要 20kHz 载波144MHz 除以 7200PER 就是 7199。目标载波频率预分频PER 值计数分辨率20 kHzDIV171996.94 ns50 kHzDIV128796.94 ns100 kHzDIV114396.94 ns这里有两个容易错的地方。第一PER 是实际周期减一写 7199 实际是 7200 个计数时钟周期。第二占空比计算公式是占空比 CMP1 / PER比如 PER 是 7199CMP1 写 3600占空比就是约 50%。如果和通用定时器一样把周期和比较值写反波形频率会翻好几倍排查起来很痛苦。2.3 CubeMX 里的最小设置顺序用 CubeMX 配 HRTIM 比直接啃寄存器快得多但要注意勾选顺序。项目里新建一个 F334 工程确认 HRTIM 时钟在 Clock Configuration 中已经倍频到 144MHz然后按下面顺序操作在 HRTIM1 外设配置页使能 HRTIM1。依次勾选 TimerA、TimerB、TimerC 的 PWM 输出对应 TA1/TA2、TB1/TB2、TC1/TC2。确认三个子定时器的时基参数一致Period 设置为 7199Compare1 设置一个初值比如 3600。在 GPIO 配置里确认 6 个输出引脚的模式为 Alternate Function复用功能由 CubeMX 自动分配。如果需要周期中断在 NVIC 设置中使能 HRTIM1 的 Master 定时器中断。CubeMX 生成代码后建议先看一遍MX_HRTIM1_Init确认生成的初始化顺序和时基参数再动手写业务逻辑。不要直接在 CubeMX 里把所有功能一次性勾完三路互补输出链路本身不长每加一个功能就编译烧录一次排查起来更省时间。3. 初始化三段式互补输出、死区和输出使能顺序3.1 用 HAL 分步配置 HRTIM 时基和比较值CubeMX 生成的代码把初始化封装成了一个函数但实际写项目时我习惯手动用 HAL 的独立配置函数再走一遍这样每个参数都看得见。核心是用HAL_HRTIM_TimeBaseConfig配置时基再用HAL_HRTIM_CompareConfig配置比较值。void MX_HRTIM1_Init(void) { HRTIM_TimeBaseCfgTypeDef timeBase {0}; HRTIM_CompareCfgTypeDef compare {0}; hhrtim.Instance HRTIM1; HAL_HRTIM_Init(hhrtim); /* TimerA 时基20kHz 载波 */ timeBase.Period 7199; timeBase.Repetition 0; HAL_HRTIM_TimeBaseConfig(hhrtim, HRTIM_TIMERINDEX_TIMER_A, timeBase); /* TimerA 比较值初始占空比 50% */ compare.CompareValue 3600; HAL_HRTIM_CompareConfig(hhrtim, HRTIM_TIMERINDEX_TIMER_A, HRTIM_COMPAREUNIT_1, compare); /* TimerB、TimerC 重复同样配置 */ HAL_HRTIM_TimeBaseConfig(hhrtim, HRTIM_TIMERINDEX_TIMER_B, timeBase); HAL_HRTIM_CompareConfig(hhrtim, HRTIM_TIMERINDEX_TIMER_B, HRTIM_COMPAREUNIT_1, compare); HAL_HRTIM_TimeBaseConfig(hhrtim, HRTIM_TIMERINDEX_TIMER_C, timeBase); HAL_HRTIM_CompareConfig(hhrtim, HRTIM_TIMERINDEX_TIMER_C, HRTIM_COMPAREUNIT_1, compare); }这里的关键参数是HRTIM_TIMERINDEX_TIMER_A和HRTIM_COMPAREUNIT_1。前者指定操作哪个定时器单元后者指定比较单元 1也就是和周期寄存器配合产生 PWM 的那个比较值。不要只配置一个子定时器三路互补输出需要三个子定时器都有完整的时基和比较值否则对应的那一路会一直保持无效电平。3.2 死区时间计算500ns 死区应该填什么值互补 PWM 之所以需要死区是因为一对半桥上下管不能同时导通。上管关断到下管导通之间必须有延迟这个延迟就是死区时间。HRTIM 的子定时器内部有独立的死区生成器上升沿和下降沿可以分别配置延迟值适合处理非对称死区需求。死区设置的 HAL 接口是HAL_HRTIM_DeadTimeConfig配置结构体的核心字段是Prescaler、RisingValue和FallingValue。HRTIM_DeadTimeConfigTypeDef deadTime {0}; deadTime.Prescaler HRTIM_TIMDEADTIME_PRESCALERR_DIV1; deadTime.RisingValue 72; /* 上升沿延迟 */ deadTime.FallingValue 72; /* 下降沿延迟 */ HAL_HRTIM_DeadTimeConfig(hhrtim, HRTIM_TIMERINDEX_TIMER_A, deadTime); HAL_HRTIM_DeadTimeConfig(hhrtim, HRTIM_TIMERINDEX_TIMER_B, deadTime); HAL_HRTIM_DeadTimeConfig(hhrtim, HRTIM_TIMERINDEX_TIMER_C, deadTime);在 HRTIM 时钟 144MHz、预分频 DIV1 的情况下死区延迟近似等于计数值除以 144MHz。填 72对应约 500ns。Prescaler可以进一步放大步进当死区时间需求超过当前精度范围时优先加大预分频而不是直接调大RisingValue这样可以保留更细的调节粒度。要注意死区并不是越大越好。死区时间过长会压缩有效输出占空比尤其在载波频率较高时死区占整个周期的比例变大输出的最大有效电压会明显下降。后面我会具体算这个关系。3.3 输出使能顺序先波形输出再检查主输出状态初始化配置完成后还需要显式启动波形输出。HRTIM 在这点上和普通定时器不太一样单纯使能定时器计数并不会让引脚立刻输出 PWM必须调用输出启动函数。HAL_HRTIM_WaveformOutputStart(hhrtim, HRTIM_OUTPUT_TA1 | HRTIM_OUTPUT_TA2); HAL_HRTIM_WaveformOutputStart(hhrtim, HRTIM_OUTPUT_TB1 | HRTIM_OUTPUT_TB2); HAL_HRTIM_WaveformOutputStart(hhrtim, HRTIM_OUTPUT_TC1 | HRTIM_OUTPUT_TC2);HAL_HRTIM_WaveformOutputStart会把对应子定时器的波形输出通道和 HRTIM 主输出使能一起打开。每次调用传参只面向一个子定时器单元但参数里可以同时传两个输出通道比如HRTIM_OUTPUT_TA1 | HRTIM_OUTPUT_TA2一次启动一对互补输出。如果发现引脚一直是低电平先查这个启动函数有没有被调用再查主输出使能状态。很多人把比较值、死区都配好了唯独漏了这一步波形死活不出来。另一个排查点是有没有在启动前把 GPIO 复用成 HRTIM 功能有些库版本只改外设时钟不改 GPIO也会导致引脚无输出。4. 在线改占空比CMP 更新时机、周期中断与渐变4.1 直接改比较寄存器CMP1R 是最短路径占空比可变的关键在于运行中更新比较值。HRTIM 的每个子定时器都有独立的比较寄存器 CMP1R这个寄存器对应的比较单元 1 就是参与 PWM 生成的比较值。在运行中直接写入新值下一个周期就会按新占空比输出。void update_duty(uint16_t duty_a, uint16_t duty_b, uint16_t duty_c) { /* 直接写比较寄存器三个子定时器各写各的 */ HRTIM1-sTimA.CMP1R duty_a; HRTIM1-sTimB.CMP1R duty_b; HRTIM1-sTimC.CMP1R duty_c; }duty_a、duty_b、duty_c是直接对应三个子定时器的比较值不是百分比。如果业务层用百分比表示占空比需要先换算CMP (uint16_t)((uint32_t)PER * duty_percent / 100)PER 是 7199 时50% 占空比对应 3599这个值和 3600 差 1因为整数除法有截断。实际项目中尽量把换算放在中断外做中断里只做寄存器赋值否则除法会占用大量中断时间。4.2 在周期中断里更新三路避免三相之间差一拍直接调用update_duty看起来很简单但在三相场景里有一个隐患三次寄存器写操作不是原子的如果在某个瞬间执行到一半时 PWM 周期翻转这一路已经用新值、另一路还用旧值会多出一个周期的占空比偏差。所以我会把三路更新统一放到 HRTIM 的周期中断回调里。volatile uint16_t duty_target_a; volatile uint16_t duty_target_b; volatile uint16_t duty_target_c; void HAL_HRTIM_TIM_PeriodElapsedCallback(HRTIM_HandleTypeDef *hhrtim) { if (hhrtim-Instance HRTIM1) { /* 周期中断里顺手更新三路比较值保证同一时刻生效 */ HRTIM1-sTimA.CMP1R duty_target_a; HRTIM1-sTimB.CMP1R duty_target_b; HRTIM1-sTimC.CMP1R duty_target_c; } }业务代码里只需要修改duty_target_a、duty_target_b、duty_target_c这三个变量实际写入发生在下一次周期中断天然同步。这个回调在 HRTIM 的 period 事件触发时被调用前提是在 CubeMX 或初始化代码里使能了 MST 的周期中断并且把HAL_HRTIM_TIM_PeriodElapsedCallback这个弱回调重写掉。三个目标变量必须声明为volatile否则编译器优化后可能只把变量缓存在寄存器里主循环里改了值中断里读到的还是旧值。这是嵌入式里老生常谈的坑但在 HRTIM 这种中断频率较高的场景里表现会非常明显占空比“有时能变、有时不能变”。4.3 软启动渐变中断里按步长逼近目标占空比很多电源应用上电时不能直接拉满占空比需要软启动。常见做法是每次周期中断里让当前占空比朝目标值走一小步因为周期固定每一步的时间就是固定的总启动时间可以精确算出来。void soft_start_step(void) { if (duty_target_a 3600) { duty_target_a 20; } }以 20kHz 载波为例每个周期耗时 50us每步加 20从 0 走到 3600 需要 180 步总时间约 9ms。步长 20 对应占空比约 0.28%这个粒度对大多数逆变器应用是够的。如果需要更平滑的启动曲线可以把步长换成查表或者根据当前占空比动态调整步长。这里要特别注意软启动的步进逻辑不要写在中断回调里同时又写在主循环里两处都改会有竞争。最稳妥的做法是中断里只消费目标值主循环里更新目标值两者方向一致时逻辑不会乱。5. 故障保护与波形验证让三路互补 PWM 既出得来也收得住5.1 硬件故障输入让 PWM 在异常时比中断更快关断HRTIM 的故障保护是硬件链路不是靠中断里软件判断。F334 的 HRTIM 提供了多个故障输入源可以配置成低电平有效或高电平有效故障触发后对应输出立刻进入预设无效状态不需要等待 CPU 响应中断。这在过流、过压保护场景里比软件关断可靠得多。故障输入配置可以直接用 HAL 的HAL_HRTIM_FaultConfig配置结构体主要选择故障源、输入极性和恢复模式。锁存模式下故障触发后即使输入信号恢复输出也保持关闭需要软件确认后清除自动恢复模式下输入恢复后输出自动回到正常状态。电源类应用我通常选锁存模式。发生过流之后只靠信号恢复并不能证明故障已经排除锁存住让上位机或保护逻辑来决定是否重新启动比自动恢复安全。三路输出在故障状态下会同时关断互补对的两个输出都为无效电平不会出现上下管同时导通。5.2 没有示波器也能验证用定时器输入捕获量占空比验证 HRTIM 输出最直接的工具是示波器看频率、看占空比、看死区宽度。如果手边只有逻辑分析仪或者干脆没有测量设备可以用 STM32 自己的定时器输入捕获来交叉验证。把 HRTIM 的一路输出短接到另一个通用定时器的捕获输入引脚配置上升沿和下降沿捕获测出高电平时间和周期。捕获逻辑可以放到HAL_TIM_IC_CaptureCallback里做。上一周期捕获值和当前捕获值做差得到周期高电平时长和周期的比值就是占空比。这个方法还能顺带验证 HRTIM 输出频率是否和配置一致。只要另外那个定时器用的是同一个系统时钟源测量误差在百分之零点几以内足够判断占空比更新逻辑有没有跑通。5.3 几个常见的坑死区吞占空比、GPIO 复用冲突、比较值越界死区会压缩最大有效占空比。死区时间 500ns、PWM 周期 50us 时每半周期有 500ns 的高阻或无效区间占空比上限不是 100%而是约 99%。如果死区设置得很大比如 5us 配 20kHz 载波占空比上限会掉到 90% 以下这一点在满占空比输出的电源启动场景里很容易踩中。GPIO 复用冲突是另一个高频问题。HRTIM 的多个输出通道可能复用同一组引脚三路互补输出加故障输入全开时需要逐个确认 6 个 PWM 输出和一个故障输入引脚之间没有冲突。CubeMX 在引脚冲突时会直接报错但如果是手工修改工程就很容易忽略。比较值越界也会让输出行为“看起来很怪”。CMP1R 写了比 PER 还大的值HRTIM 行为取决于具体寄存器取值可能输出一直为高也可能在边界处计算顺序翻转。代码里最好在更新占空比前做个范围约束比如限制在 0 到PER - 死区对应计数值之间。这样既避开了越界也保住了死区时间不被占空比蚕食。if (duty_value 7199 - 72) { duty_value 7199 - 72; }先把死区用上限钳位再让目标值往里走实际输出最安全。先用输入捕获或者示波器确认死区存在再验证占空比是否能随目标值变化最后拉一次故障输入看输出是否快速关断这套冒烟流程走完HRTIM 的 3 路互补 PWM 链路基本就是健康的。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门