ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32G474 HRTIM高分辨率PWM配置详解与DLL校准实战避坑指南

STM32G474 HRTIM高分辨率PWM配置详解与DLL校准实战避坑指南 前阵子在一个数字电源项目里调STM32G474的HRTIM被宣传里的“5.44GHz超高精度”唬了一下以为这定时器真能跑到5.44GHz。实际看完参考手册和寄存器之后才明白这个数字指的是HRTIM在开启高分辨率模式后PWM边沿能实现约184ps级别的步进精度等效成一个5.44GHz的时基在跑而芯片真正的计数时钟依然是170MHz。把这件事弄透之后时钟配置和DLL校准就成了整个项目的第一个关键卡点也踩了不少坑。这篇把HRTIM时钟配置从原理到实操完整梳理一遍重点是我自己调试时遇到过的问题希望能让你少走点弯路。1. 别被5.44GHz吓到先搞清楚HRTIM的分辨率机制1.1 170MHz计数器和184ps步进是怎么回事STM32G474的HRTIM本身是一个16位计数器工作在170MHz系统时钟下。如果不做任何额外处理它每个计数周期就是1/170MHz约5.88ns。这意味着PWM的边沿调整粒度最细也就5.88ns在很多场景下够用但数字电源、LLC、移相全桥这类对死区精度和占空比细腻度要求比较高的应用里5.88ns的步进会让输出电压纹波变大甚至引起环路抖动。HRTIM真正厉害的地方在于内部集成了一条DLL也就是延迟锁定环。这个DLL会把170MHz的时钟周期切分成更细的延迟阶梯校准之后每级大约184ps。于是计数器虽然还是按170MHz跑但PWM信号边沿可以在一个计数周期内按184ps的粒度微调。拿1秒除以184ps约等于5.44GHz这就是那个宣传数字的由来。需要说清楚的是HRTIM永远不会“跑”在5.44GHz这是一个等效时基精度的概念别被数字吓到。这个机制对工程实际的影响非常大。比如载波频率200kHz时PWM周期是5微秒普通模式下占空比只能按5.88ns去切整个周期的步进数量只有大约850级折算下来不到10位分辨率。开启高分辨率模式后同样的5微秒周期里可以实现的占空比调整步数飙升到两万多级等效分辨率接近14.7位。在数字电源里这直接决定了环路调节的细腻程度。1.2 谁用得上这玩意儿HRTIM高分辨率模式不是给所有项目准备的如果你的应用只是LED调光、电机起停、普通Buck变换那用普通TIM定时器就够了HRTIM反而复杂。真正需要它的场景我列几个出来你感受一下数字电源、LLC谐振变换器、移相全桥死区时间精度直接影响软开关效果高分辨率模式下死区可以按184ps步进微调这是普通定时器做不到的。高精度电机控制特别是需要精确控制换相时刻和PWM最小脉宽的场合HRTIM的高分辨率输出能减少转矩脉动。激光雷达、超声驱动、测试测量信号源需要精确的脉冲宽度、上升沿位置或特定调制格式。当然用了HRTIM不等于自动获得高精度很多细节没配好输出抖动反而比普通定时器更难看。时钟配置是第一道门槛也是最容易翻车的地方。2. HRTIM时钟配置的三个基础点2.1 时钟树HRTIM的kernel clock不是随便选的HRTIM虽然是165MHz主频的“高级货”但它的时钟输入不是默认就跟系统时钟绑死的。在STM32G4系列里HRTIM1的kernel clock可以通过RCC单独选择常见做法是从PLL1的Q路输出取时钟。我手头的项目HSE外部晶振是24MHz目标是把SYSCLK配到170MHz同时让PLL1Q也输出170MHz给HRTIM。实际配置顺序是这样的先配置PLL1作为系统时钟源把P、Q、R三路分频都设置好然后单独调用RCCEx的接口配置HRTIM1的时钟选择最后再使能HRTIM1外设时钟。很多人在CubeMX里只勾了系统时钟忘了在Clock Configuration里指定HRTIM1的时钟源结果生成的代码里没有RCC_PERIPHCLK_HRTIM1相关的配置HRTIM直接不工作或者完全按错误时序跑。这里有一个我特别想提醒的坑不要让HRTIM用内部HSI或者CSI这类RC振荡器做参考。RC振荡器的精度和温漂都比较差DLL校准后虽然能锁定但环境温度一变化输出的PWM边沿抖动会明显增加。我测试过同一套代码HSE正常工作时输出抖动大概在几百皮秒级别换成HSI之后抖动差了近一个数量级。所以稳定性敏感的场景老老实实用无源晶振或者有源晶振作为源头。2.2 HRTIM时基单元主定时器和子定时器的关系HRTIM内部不只有一个定时器而是一个定时器组。它有一个主定时器Master以及Timer A到Timer F共6个子定时器每个子定时器可以独立产生自己的PWM输出对。我第一次看这个结构也有点懵后来把它理解成一个“主编”和六个“专栏作者”主编负责产生基准周期和同步事件专栏作者各自按主编的节拍写自己的输出内容。多路PWM需要严格同频同相时通常把主定时器配置成连续计数模式让它产生周期更新事件去触发各个子定时器。子定时器可以配置成“从模式”收到主定时器事件后启动或者复位计数。这样无论六路PWM的频率和占空比怎么调它们的起点始终是对齐的相位关系不会漂。时钟配置的坑就在这里主定时器和子定时器各自有独立的预分频和周期值很多人在CubeMX图形界面里只配置了想要的PWM频率但忘了检查预分频和计数周期是否合理。比如想输出200kHz计数器时钟是170MHz周期值应该是170000000/200000-1849。如果预分频设成了1/2甚至1/4周期值过大或者过小输出频率就完全不对了。而且HRTIM每个子定时器的预分频是独立设置的改一个不代表其他的都跟着改。2.3 DLL延迟锁定环是精度的灵魂DLL是整个高分辨率模式的核心。简单说它像一把高倍放大镜把170MHz的一个时钟周期均匀切成很多份供PWM边沿使用。这个“均匀”不是靠晶振保证的而是DLL内部一个反馈环路持续校准的结果。所以DLL不仅是启动时校准一次就完事它在运行过程中还要跟踪温度和电压变化持续微调内部延迟阶梯。正式使用DLL之前必须先做一次校准。校准的目的是让DLL内部延迟阶梯的总长度刚好等于一个170MHz时钟周期。校准需要的时间不长但是必须在HRTIM开始输出PWM之前完成否则边沿定位是乱的。更严格地说在DLL锁定且校准完成之前你最好不要写任何依赖高分辨率精度的比较值因为此时写进去的微调量完全没有意义。HAL库里对应的操作是启动DLL校准然后等待校准完成我的代码里通常会这样写HAL_HRTIM_DLLCalibrationStart(hrtim1, HRTIM_CALIBRATIONRATE_1); HAL_HRTIM_DLLCalibrationWait(hrtim1, HAL_MAX_DELAY);上面两行只做了一半工作接下来还有一个很容易漏掉的步骤必须在HRTIM初始化完成、时钟稳定之后才启动校准同时校准期间不要去动HRTIM的时基和输出配置。我在调试过程中有一次是在配置PWM输出之前就启动了DLL校准结果DLL锁定后我再写入比较值输出前几个周期明显有个别脉宽异常的毛刺逻辑分析仪抓了好久才定位到是校准时机的问题。3. 实操初始化HRTIM输出200kHz高精度互补PWM3.1 时钟初始化代码先上时钟部分的初始化。这里以一个外部24MHz晶振为例目标是系统时钟170MHzPLL1Q输出170MHz给HRTIMFlash等待周期按170MHz对应的设置来配void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct {0}; RCC_PeriphCLKInitTypeDef PeriphClkInit {0}; RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLM RCC_PLLM_DIV3; // 24MHz/38MHz RCC_OscInitStruct.PLL.PLLN 85; // VCO8MHz*85680MHz RCC_OscInitStruct.PLL.PLLP RCC_PLLP_DIV4; // 680/4170MHz SYSCLK RCC_OscInitStruct.PLL.PLLQ RCC_PLLQ_DIV4; // 680/4170MHz HRTIM RCC_OscInitStruct.PLL.PLLR RCC_PLLR_DIV4; // 680/4170MHz 备用外设 HAL_RCC_OscConfig(RCC_OscInitStruct); RCC_ClkInitStruct.ClockType RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider RCC_HCLK_DIV1; HAL_RCC_ClockConfig(RCC_ClkInitStruct, FLASH_LATENCY_2); PeriphClkInit.PeriphClockSelection RCC_PERIPHCLK_HRTIM1; PeriphClkInit.HRTIM1ClockSelection RCC_HRTIM1CLKSOURCE_PLL1Q; HAL_RCCEx_PeriphCLKConfig(PeriphClkInit); __HAL_RCC_HRTIM1_CLK_ENABLE(); }这里有几个细节值得说明。PLLM取3把24MHz外部时钟降到8MHz是因为PLL输入频率有范围限制不是随便分频。PLLN取85算出VCO频率680MHz再分别除以4得到170MHz。之所以让Q路也输出170MHz是为了让HRTIM时钟和系统时钟同频这样跨时钟域交互最简单。如果你项目的HRTIM时钟和系统时钟不同频那DMA请求、中断同步、比较值更新这些环节都要格外小心我在后面会展开讲。3.2 HRTIM初始化时基、比较、输出、死区时钟配好之后HRTIM本身的初始化就是重头戏了。第一步是初始化HRTIM句柄然后配置主定时器的时基。以下是配置主定时器和Timer A的核心代码载波频率设为200kHzHRTIM_HandleTypeDef hrtim1; HAL_HRTIM_TimeBaseConfigTypeDef sMasterTimeBase {0}; HAL_HRTIM_TimeBaseConfigTypeDef sTimerATimeBase {0}; HAL_HRTIM_CompareConfigTypeDef sCompareConfig {0}; HAL_HRTIM_WaveformOutputConfigTypeDef sOutputConfig {0}; hrtim1.Instance HRTIM1; hrtim1.Init.ClockSource HRTIM_CLOCKSOURCE_INTERNAL; hrtim1.Init.ClockDivider HRTIM_CLOCKDIVIDER_DIV1; hrtim1.Init.SynchronizationSource HRTIM_SYNCSOURCE_NONE; hrtim1.Init.SynchronizationMode HRTIM_SYNCMODE_NONE; hrtim1.Init.SynchronizationTrigger HRTIM_SYNCTRIGGER_NONE; HAL_HRTIM_Init(hrtim1); sMasterTimeBase.Mode HRTIM_MODE_CONTINUOUS; sMasterTimeBase.Period 849; // 170MHz/200kHz-1 sMasterTimeBase.PrescalerRatio HRTIM_PRESCALERRATIO_DIV1; sMasterTimeBase.RepetitionCounter 0; HAL_HRTIM_TimeBaseConfig(hrtim1, HRTIM_TIMERINDEX_MASTER, sMasterTimeBase); sTimerATimeBase.Mode HRTIM_MODE_CONTINUOUS; sTimerATimeBase.Period 849; sTimerATimeBase.PrescalerRatio HRTIM_PRESCALERRATIO_DIV1; sTimerATimeBase.RepetitionCounter 0; HAL_HRTIM_TimeBaseConfig(hrtim1, HRTIM_TIMERINDEX_TIMER_A, sTimerATimeBase);时基配置好后要设置比较单元。HRTIM每个子定时器有好几个比较单元PWM的上升沿和下降沿可以分别由不同比较单元触发。以Timer A为例我想要输出两路互补的PWM占空比50%可以把比较值放在CMP1另一个补比较放在CMP2。这样当计数器达到CMP1时输出翻高达到CMP2时输出翻低CMP1和CMP2的差值就是脉宽sCompareConfig.CompareValue 425; // 170MHz下约2.5us对应50%占空比 HAL_HRTIM_CompareConfig(hrtim1, HRTIM_TIMERINDEX_TIMER_A, HRTIM_COMPAREUNIT_1, sCompareConfig); sCompareConfig.CompareValue 425; HAL_HRTIM_CompareConfig(hrtim1, HRTIM_TIMERINDEX_TIMER_A, HRTIM_COMPAREUNIT_2, sCompareConfig);接下来是输出波形配置。HRTIM的输出不是简单把比较器结果直接引到引脚它有一个完整的输出整形链路包括输出极性、置位源、复位源、空闲电平、死区逻辑等。我的配置是把TA1置位源设为CMP1复位源设为CMP2TA2是TA1的反相所以它的置位源用CMP2复位源用CMP1sOutputConfig.Polarity HRTIM_OUTPUTPOLARITY_HIGH; sOutputConfig.SetSource HRTIM_OUTPUTSET_TIMCMP1; sOutputConfig.ResetSource HRTIM_OUTPUTRESET_TIMCMP2; sOutputConfig.IdleLevel HRTIM_OUTPUTIDLELEVEL_INACTIVE; sOutputConfig.IdleMode HRTIM_OUTPUTIDLEMODE_NONE; HAL_HRTIM_WaveformOutputConfig(hrtim1, HRTIM_TIMERINDEX_TIMER_A, HRTIM_OUTPUT_TA1, sOutputConfig); sOutputConfig.SetSource HRTIM_OUTPUTSET_TIMCMP2; sOutputConfig.ResetSource HRTIM_OUTPUTRESET_TIMCMP1; HAL_HRTIM_WaveformOutputConfig(hrtim1, HRTIM_TIMERINDEX_TIMER_A, HRTIM_OUTPUT_TA2, sOutputConfig);看到这里你可能会问互补的TA2输出已经在逻辑上反相了为什么还要配置SetSource和ResetSource因为HRTIM的互补输出不是简单一个反相器它要保证两个输出在切换瞬间不产生毛刺尤其是加入死区之后置位和复位源必须明确指定否则死区插入逻辑不知道从哪里对齐。这也是很多人配置互补PWM时踩坑最多的地方。启动PWM输出前还需要处理DLL和高分辨率模式。我的习惯是在所有配置写完、但还没启动输出时先做一次DLL校准并且等待校准完成。启动顺序我这里也给你一个参考HAL_HRTIM_DLLCalibrationStart(hrtim1, HRTIM_CALIBRATIONRATE_1); HAL_HRTIM_DLLCalibrationWait(hrtim1, HAL_MAX_DELAY); HAL_HRTIM_WaveformOutputStart(hrtim1, HRTIM_OUTPUT_TA1 | HRTIM_OUTPUT_TA2); HAL_HRTIM_TimeBaseStart(hrtim1, HRTIM_TIMERINDEX_MASTER); HAL_HRTIM_TimeBaseStart(hrtim1, HRTIM_TIMERINDEX_TIMER_A);注意启动顺序必须是DLL校准先完成再启动输出和时基。时序上如果反过来比如先启动主定时器再校DLL那么DLL校准窗口内计数器已经开始跑PWM实际输出的头几个周期会比设定值偏宽或偏窄属于典型的启动毛刺。3.3 死区时间的设置与精度验证在数字电源里死区时间的设置直接关系到开关管能不能实现软开关。HRTIM支持每路输出单独设置死区时间而且在高分辨率模式下死区步进同样是184ps级别。死区寄存器的值不是直接用纳秒而是要用期望的死区时间除以DLL分辨率来换算。比如要200ns死区200ns除以184ps大约是1087个tick。如果死区寄存器只支持10位那这个值超出上限了就需要对死区时钟做预分频。预分频打开后死区步进会变成原来的2倍、4倍甚至更大所以不要盲目为了大死区去调预分频要结合自己需要的死区精度来选择。这里我的建议是先计算实际tick数确认是否超限再决定要不要用预分频不要一上来就默认DIV1。验证高分辨率是否真的生效最直接的办法是拿示波器看PWM波形的边沿位置变化。我常用一个简单方法让占空比从50%开始每次改变很小时比如每次增加5个DLL tick然后观察脉宽的变化是否均匀。在普通模式下你改很小的比较值可能根本看不到变化因为边沿按5.88ns跳动。如果HR模式生效每次增加5个tick对应约0.92ns的变化好的示波器已经能看到明显的边沿移动。抖动评估就看cycle-to-cycle jitter我实测在HSE稳压电源良好去耦的情况下输出抖动可以稳定在500ps以内这在200kHz的PWM里表现已经相当不错。4. 常见问题与排查技巧实录4.1 启动毛刺前几个周期脉宽不对这个现象我在前面提到过启动PWM后前几个周期的脉宽明显比设定值窄或者宽后面才恢复正常。最开始我以为是电源瞬间拉低导致的后来用示波器单次触发发现毛刺只出现在DLL校准未完成的窗口里。排查思路是先看启动序列。DLL校准完成之前不要启动主定时器和输出。HAL的DLL校准等待函数要放在TimeBaseStart和WaveformOutputStart之前。如果已经这么做了还有毛刺那就要检查HRTIM的kernel clock在启动瞬间是否稳定。PLL刚锁定时有一个稳定过程如果HRTIM时钟直接来自PLL1Q而你在PLL锁定后立即初始化HRTIM极端情况下PLL还没完全稳定DLL校准结果就会有偏差。解决方法是等系统时钟稳定标志再往下走或者简单粗暴地在PLL配置后加一个小延时。4.2 输出频率整体偏差但不抖动如果你的PWM频率和理论计算的频率差了一个固定比例比如本应200kHz实际成了100kHz那大概率是预分频配置错了或者主定时器和子定时器的周期值不匹配。HRTIM的时钟路径上有多级分频包括HRTIM本身的ClockDivider、各定时器的PrescalerRatio再加上周期寄存器。每一级都看一遍尤其是CubeMX生成代码和手写代码混用的时候很容易漏掉某一级的默认值。还有一种情况是HRTIM时钟源选错了。比如你以为HRTIM用的是PLL1Q170MHz实际上PeriphClkInit里没有配置HRTIM默认用了不太确定的时钟路径频率自然就不对。这种问题靠看代码不容易发现最好直接在调试器里读RCC相关的时钟选择寄存器确认HRTIM时钟源和PLL1Q的配置值。4.3 DLL校准失败或一直锁定不了DLL校准失败是比较让人头疼的问题。原因往往不在HRTIM本身而在它的供电和参考时钟。如果VDD噪声太大或者HRTIM专用供电引脚上的去耦电容不足DLL反馈环路就会不稳定表现为校准无法完成或者校准完成后很快失去锁定。再有一个容易被忽视的点校准速率。HAL库的DLL校准接口传入一个校准速率参数有些资料上写成CalibrationRate这个参数会影响校准持续时间和精度。如果你把校准速率调到最快虽然时间短了但校准精度会下降极端情况下锁定误差会直接反映到PWM边沿精度上。对精密应用我用HRTIM_CALIBRATIONRATE_1也就是最慢但最准的那一档除非系统对启动时间有极苛刻要求否则不建议为了省那几百微秒牺牲精度。4.4 更新比较值时输出突然出现毛刺这是个典型的影子寄存器使用问题。HRTIM的比较值寄存器有影子机制你写入的值不会立刻生效而是在特定更新事件时才加载到实际比较器。如果你在计数器运行期间反复写比较值又没搞清楚更新时机就可能出现边沿定位混乱甚至输出瞬间的窄脉冲。我的经验是频率固定、只调占空比的场合比较值更新一定要等到计数器周期更新事件发生。HAL库里通常会在发生更新事件时产生中断或置标志位你在中断里改比较值才不会撕裂。如果占空比需要连续调整就要考虑DMA burst传输或者双缓冲机制。总之不要在计数器正走到一半时直接写比较寄存器这是我做HRTIM调试以来最深刻的几个教训之一。4.5 避坑速查表为了方便你以后排查我把这些问题的现象、原因和检查点汇总成一张表。现象常见原因检查点输出频率整体不对预分频配置错、HRTIM时钟源选错读RCC时钟选择寄存器逐级检查分频启动前几个周期毛刺DLL校准未完成就启动输出确保DLLCalibrationWait在TimeBaseStart之前输出抖动偏大HSI/CSI做时钟源、电源去耦差改HSE检查HRTIM供电去耦电容DLL校准无法完成电压不稳、校准速率过快检查供电使用较慢校准速率修改比较值后毛刺影子寄存器更新时机不对在周期更新事件中修改或用DMA两路输出相位不对主从定时器同步配置漏了检查主定时器复位/触发子定时器配置这些坑基本覆盖了HRTIM时钟配置的绝大部分常见场景真遇到了对照表格能省不少排查时间。5. 一些调试心得最后分享一个我自己的小习惯。每次在HRTIM上做新项目我都会先用普通模式把整个链路调通确认时基、比较、输出、死区都正常了再开启DLL校准和高分辨率模式。这样可以有效隔离问题普通模式下不正常先查基础时钟和寄存器配置普通模式正常但高分辨率模式异常再集中精力排查DLL或高分辨率相关配置。还有一点HRTIM的调试信息单纯靠示波器看很难把所有细节都看清楚。我建议在代码里把关键状态位比如DLL锁定标志、校准完成标志、时基更新标志映射到空闲的GPIO上用逻辑分析仪抓这些GPIO的时序关系配合PWM输出来判断启动顺序是否正确。这个办法帮我定位过好几次看起来像是芯片BUG、实际上是时序顺序错误的问题。HRTIM是个功能很强的外设但它的复杂度也远高于普通定时器。只要你理解5.44GHz这个数字的实质把时钟树和DLL校准这两个基础打牢后面再复杂的高精度PWM应用也不会太难。
返回列表