ARTICLE DETAIL

资讯详情

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

STM32G474 HRTIM触发ADC同步采样:攻克开关电源采样噪声

STM32G474 HRTIM触发ADC同步采样:攻克开关电源采样噪声 做双向DC-DC或者BLDC电机控制的时候我估计不少人都遇到过这个现象电流环明明算得好好的示波器抓ADC输出却总在开关边沿附近跳出一堆毛刺数值忽高忽低看着像随机噪声其实是采样点刚好砸在了开关管导通/关断的振铃窗口上。我这次用STM32G474把HRTIM当作触发源带动三路ADC同步采样再用DMA把结果搬到内存、串口打印出来算是把这个问题彻底捋顺了整个过程值得整理成一篇笔记。先说结论G474的HRTIM加ADC触发链路配合精心设计的中间时刻采样点能把开关噪声的影响压到很小同时采到的电感电流还在纹波中点附近对于峰值电流控制和平均电流控制都有参考意义。这篇文章不打算讲虚的直接从为什么HRTIM干这个活最合适、中间时刻怎么算、CubeMX怎么配、代码怎么写、实测遇到什么坑这五条线展开想把这一套链路复现出来的朋友照着往下走就行。1. 为什么非要用HRTIM普通定时器和它差距在哪1.1 高分辨率时钟背后的采样精度意义STM32G474的HRTIM高分辨率定时器最直观的优势是时钟分辨率。系统主频跑到170MHz时HRTIM的计数时钟可以倍频到680MHz主频的4倍对应的分辨率大约1.47ns。这是个什么概念我在早期方案里试过用普通高级定时器TIM1的中心对齐模式触发ADC它挂在170MHz的APB总线上计数周期就是5.88ns。表面看5.88ns和1.47ns好像都够快但在高频PWM场景下这个差距会被放大。比如开关频率500kHz周期2us普通定时器量化误差5.88ns相位误差约0.3%看起来也不大可一旦涉及多路ADC的触发延时一致性每路转换启动时刻可能会有几十纳秒的偏差这对于想要稳定采样电流纹波的场景来说已经足够让数据散开。HRTIM的1.47ns分辨率带来的不仅是边沿精度更重要的是事件发生器的灵活度。你可以在一个PWM周期内产生多个ADC触发事件每个事件对应不同的比较寄存器触发点任意平移同时不影响PWM本身的占空比输出。这点普通定时器很难做到——要么占用比较通道影响PWM输出要么触发通道数量不够限制很大。1.2 事件发生器让ADC在精准时刻“自动开工”HRTIM内部有五个独立的定时器单元A/B/C/D/E每个单元拥有自己的周期寄存器和四个比较寄存器CMP1到CMP4。比较匹配之后可以产生内部事件这些事件经过一个可编程的事件发生器Event Generator最终输出到ADC的触发输入。G474有两路ADC分别是ADC1和ADC2每路ADC可以通过外部触发源开启转换。HRTIM可以同时给两路ADC提供触发信号配合ADC的扫描模式一次触发就能把分配给这路ADC的多个通道按顺序转换完。我这次是三个采样点后面会细说是哪三路把其中两路挂到ADC1的扫描通道里一路挂到ADC2两个ADC并行工作DMA分别搬运结果效率和时序都能兼顾。这里有个很多人容易忽略的点HRTIM的事件发生器里每个ADC触发源都要单独使能。你在HRTIM定时器单元里设置了CMP匹配产生事件不代表ADC那边就会自动收到触发信号必须通过HRTIM的EECR寄存器把对应的事件映射到ADC1触发或ADC2触发线上同时在ADC的CR2寄存器里配置好EXTSEL选择对应的HRTIM事件源。这一套链路打通了ADC才会在指定的比较时刻准时启动。1.3 普通定时器方案的实际局限如果手头只有TIM1这样的高级定时器硬要做到同样的功能也不是不行但有几个绕不开的问题。高级定时器的比较通道通常直接用于产生PWM输出想在同一定时器里找一个不干扰输出的比较事件来触发ADC通道数很紧张。触发ADC的时刻一般锁定在周期上溢/下溢这些固定节点无法做到“脉宽范围内任意时刻触发”。想要开通过程中点采样就得用另外的定时器配合时序同步又变复杂。多路ADC触发的一致性、时延补偿也没有HRTIM做得干净实测下来数据抖动会明显一些。所以结论比较明确G474这颗芯片既然内置了HRTIM做高频电源控制就不该绕路。它是为这类场景专门设计的用对了地方能省掉很多外部逻辑和调试时间。2. 中间时刻采样从原理到采样点计算2.1 开关噪声为什么集中在那几个微妙的时间点先看一个基本事实MOSFET开通和关断不是瞬间完成的。栅极驱动电流给米勒电容充放电需要时间这个过程中漏源电压和电流交叠会产生开关损耗和振荡。再加上PCB寄生电感、续流二极管反向恢复开关边沿之后通常会有一段高频振铃幅度可能达到几十毫伏甚至几百毫伏。这段振铃通过接地回路、电磁耦合进入ADC输入引脚如果采样时刻正好落在振铃窗口内转换结果就会叠加一个不确定的噪声分量。这里可能有人问ADC输入前端不是有RC滤波吗不能滤掉吗可以滤掉一部分但RC滤波器会引入延时对电流环的相位裕度有影响。想要高带宽的电流环路RC截止频率又不能压太低。所以从源头避开噪声窗口比单纯靠滤波更可取。中间时刻采样就是这个思路——把采样点放在开关动作已经结束、信号恢复平稳的时间段里。2.2 边沿对齐模式和中心对齐模式下“中间”的含义PWM有两大类边沿对齐递增计数和中心对齐增减计数。采样点的几何位置取决于这两个模式。边沿对齐模式周期从0递增到周期值比如PERIOD然后立刻归零。开关管在计数器为0时导通到比较值CMP时关断。这个模式下的“中间时刻”是指导通脉宽的中点。比如占空比50%脉宽中点就在PERIOD/2的位置这时候开关管已经稳定导通了一段时间电流纹波经过上升段后正好处于平均值附近。中心对齐模式计数器从0递增到PERIOD再递减回0。在递增段的CMP处产生一次比较匹配递减段的另一个CMP匹配产生第二次事件。这个模式下PWM的“中间”天然就是计数器的中点PERIOD/2此时不管占空比是多少开关管都稳定处于某个确定状态。我在这个项目里用的是中心对齐模式。原因是中心对齐PWM在周期中点时电流纹波正好处于波峰或波谷的对称位置平均电流值就在这个中点附近采样点作为“准平均电流”的代表误差很小。而边沿对齐模式下中间时刻只有在占空比恰好50%时才和纹波中点重合占空比偏移时采样得到的电流会更接近峰值或谷值这取决于采样点放在脉宽中点的位置。2.3 比较事件的计算公式与触发点设置假设中心对齐模式周期寄存器PERIOD决定PWM频率频率 HRTIM时钟频率 / (2 × PERIOD)。HRTIM时钟是680MHz想跑500kHz的话PERIOD 680MHz / (2 × 500kHz) 680。中间时刻的计数器值就是PERIOD/2 340。我们把其中一个比较寄存器比如CMP2设为340配置它在计数递增阶段匹配时产生ADC触发事件这样每次PWM周期走到中点ADC就会自动开始转换。这个比较点不参与PWM输出占空比的生成只负责触发采样互不干扰。公式化的设置可以这样写设PWM周期计数范围是[0, PERIOD]中心对齐模式。中间采样点计数器值CMP_ADC PERIOD / 2。如果想要微调采样点避开残余噪声可以在PERIOD/2附近增减几十个计数对应的时间偏移大约是增减值 × 1.47ns。实测中我把触发点放在PERIOD/2往后偏移约25个计数约37ns能进一步避开开关边沿的残余振铃尾巴。这个偏移量不是固定的和PCB布局、驱动电阻、MOSFET型号都有关系现场调试时可以通过串口打印的数据波动幅度来反推最优位置。2.4 采样保持时间与ADC转换时钟的配合光有好的触发点还不够ADC本身转换需要时间。G474的ADC是逐次逼近型工作在最高170MHz时钟时采样时间可以配置为2.5到640.5个ADC时钟周期转换时间通常是固定的12.5个周期12位分辨率。设ADC时钟为170MHz采样时间配置为最大640.5周期那么一次完整转换大约是653个时钟周期换算下来约3.84us。中心对齐PWM周期为2us500kHz这个时间显然不够在一次周期内完成全部三路通道的转换。所以要么降低ADC时钟到合理范围要么把采样时间缩短要么把多路通道转换分配到多个PWM周期内完成。我实测后采用的方案ADC时钟配置为展频后约85MHzAPB2分频得到采样时间设为8.5个周期转换时间12.5个周期单通道一次转换总耗时约21个ADC时钟周期换算成约247ns三通道扫描一次大约740ns远小于2us的PWM周期所以即使扫描多路也能保证在中间时刻这一个“窗口”内完成全部转换不会拖到下一个开关周期。这里有个隐藏约束中间时刻窗口的“宽度”是有限的它等于从触发点开始到下一次开关动作之间的时间。占空比接近极端值时这个窗口会被压缩需要实时调整采样点或者放宽ADC采样时间。3. 三路ADC采样链路的硬件规划与CubeMX配置3.1 引脚分配与触发源的匹配我的电路板需要采样三路模拟量一路是电感电流通过采样电阻放大后的电压一路是输入母线电压电阻分压一路是输出电容电压电阻分压。三路信号都经过RC低通滤波后送入MCU的ADC引脚。触发源规划ADC1挂载电感电流和输入电压两个通道由HRTIM的事件发生器ATimer A产生触发。ADC2挂载输出电压一个通道由HRTIM的事件发生器CTimer C产生触发。这样两个ADC由同一个时间基准控制同时启动转换能保证三路数据的相位一致性。虽然三路信号的性质不同电流变化快电压变化慢但统一采样时刻点对系统辨识和控制算法有好处。引脚上的注意事项模拟输入引脚远离PWM输出引脚避免数字开关噪声耦合到模拟通道。如果板子空间紧凑至少保证输入信号的参考地是干净的模拟地不能和功率地直接混用一个过孔。3.2 CubeMX里HRTIM的配置要点在STM32CubeMX中启用HRTIM后我做了这些关键配置HRTIM时钟源选择内部时钟并确认HRTIM内核时钟为680MHz。配置Timer A为中心对齐模式周期设为680产生500kHz PWM用于一路功率开关的控制信号。将Timer A的CMP2设为340作为ADC触发点。在“Event Generator”标签页里把Timer A的CMP2事件使能映射到ADC1触发器具体来说是HRTIM_ADC1_TRG同时映射到ADC2触发器HRTIM_ADC2_TRG。很多人会在这一步漏掉“映射到ADC2”的选项导致ADC2始终无法被HRTIM触发。CubeMX里实际上是支持一个事件同时触发两个ADC的勾选对应选项就行。另外HRTIM的比较事件CMP2默认是PWM模式输出的一部分如果不需要CMP2引脚输出PWM波形可以在输出比较设置里把CMP2对应的输出通道禁止只保留事件功能。这样就不会因为CMP2的比较匹配而意外改变PWM引脚状态。3.3 ADCDMA的配置细节ADC1和ADC2都配置为分辨率12位扫描模式启用连续转换禁用用外部触发单次扫描防止ADC自己连续跑把DMA缓冲区刷爆外部触发源选择HRTIM事件输出具体的EXTSEL值根据CubeMX下拉框选择对应HRTIM事件采样时间按之前计算的8.5个周期设置DMA配置ADC1使用DMA1 Channel 1数据方向从外设到内存数据宽度半字16位循环模式开启。ADC2使用DMA1 Channel 2同样的配置。内存缓冲区定义为一个全局数组大小等于ADC1扫描通道数加ADC2扫描通道数。这里有一个容易踩的坑DMA的“循环模式”开启后DMA传输完一轮会重新从内存起始地址继续覆盖写入。如果配合CMSIS-DSP库做滤波算法缓冲区里的数据是不断被覆盖的必须配合DMA半传输中断或传输完成中断来做数据处理不能直接在主循环里轮询数组——因为你读到一半时DMA可能已经写入了新数据造成数据撕裂。我这次的数据量不大三路用了一个简单的办法DMA循环写满4组数据后由定时器中断触发一次软件拷贝把当前快照复制到另一块缓冲区供串口使用。4. 代码实现初始化顺序与数据链路4.1 HRTIM启动和ADC触发的先后顺序代码初始化的顺序有一点讲究。如果先启动HRTIM再启动ADC可能丢失第一个触发事件。反过来先启动ADCHRTIM还没输出PWM又会产生一段无触发状态。比较稳妥的顺序是先配置并启动DMA和ADC让ADC处于“待触发”状态。启动HRTIM让它产生PWM和ADC触发事件。通过一个全局标志位确认至少收到一次DMA传输完成中断后再使能串口打印。核心配置代码简写如下// ADC1 DMA初始化 __HAL_LINKDMA(hadc1, DMA_Handle, hdma_adc1); HAL_DMA_Init(hdma_adc1); HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc1_buf, ADC1_CHANNEL_COUNT); // ADC2 DMA初始化 __HAL_LINKDMA(hadc2, DMA_Handle, hdma_adc2); HAL_DMA_Init(hdma_adc2); HAL_ADC_Start_DMA(hadc2, (uint32_t *)adc2_buf, ADC2_CHANNEL_COUNT); // HRTIM启动(Master Timer必须先于Timer A启动) HAL_HRTIM_MasterStart(hhrtim, HRTIM_MASTER_TIMEREN_MASK); HAL_HRTIM_WaveformCounterStart(hhrtim, HRTIM_TIMERID_MASTER | HRTIM_TIMERID_TIMER_A); HAL_HRTIM_WaveformOutputStart(hhrtim, HRTIM_TIMERID_TIMER_A);注意HRTIM的启动有两个层面计数器启动和波形输出启动。计数器不转比较事件不会产生波形输出不使能PWM引脚不会有信号但ADC触发事件其实是由计数器比较产生的所以即便PWM输出引脚没使能只要计数器在跑触发事件照样能发。我一开始没想通这点把波形输出关了却发现ADC照样触发一度以为是配置错误。4.2 DMA数据的解析与校验DMA把ADC结果寄存器里的16位数据搬进数组后需要做几步处理确认数据右对齐12位分辨率下数据范围是0到4095直接除以4095乘以参考电压得到电压值。通道顺序匹配扫描模式下DMA缓冲区第一个元素对应CubeMX里配置的第一个通道第二个元素对应第二个通道以此类推。如果CubeMX里调整过通道顺序数组下标顺序会跟着变不能写死。我把adc1_buf设计成长度为8的数组两个通道、四轮扫描读取时用DMA半传输中断和全传输中断分别处理前4个和后4个元素。每轮转换完成后计算平均值作为当前采样值这样可以进一步滤掉单次转换的随机噪声。void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { snapshot_idx; for (int i 0; i ADC1_CHANNEL_COUNT; i) { adc1_snapshot[snapshot_idx][i] adc1_buf[i]; } } }4.3 串口打印的时序配合串口打印用DMA发送避免阻塞CPU。但有个细节如果直接在回调函数里调用HAL_UART_Transmit_DMA前一次发送还没结束就启动下一次会返回HAL_BUSY数据丢帧。我用的方案是“定时打印”用另一个定时器产生100ms中断在中断里拷贝当前快照到打印缓冲区然后通过DMA发送一次。100ms内三次采样快照都完成了所以打印频率远低于采样频率总线不会冲突。打印格式我选了最直观的CSV格式方便直接导入Excel或者MATLAB里看波形voltage_in,current_load,voltage_out 12.012,1.234,5.001 12.013,1.235,5.002用官方串口助手或者Python的pyserial读数都很好解析。实测波特率460800115200也行但高频调试时460800能少一点数据延时。5. 实测中的坑与调试手段5.1 现象第一帧DMA数据错位差了一个通道我最初把ADC1配置成“注入通道”加“规则通道”混合模式想着能更灵活结果DMA搬运顺序完全不是我预期的。现象是电流数据出现在数组下标1电压数据出现在下标0逻辑上乱了。排查过程把DMA缓冲区打印出来发现相邻两帧数据对调确定是扫描顺序问题。打开CubeMX的“ADC regular sequence”视图发现通道顺序是按我添加通道的先后排列的而不是按通道号排序。修改CubeMX里的扫描顺序或者代码里按实际顺序解析数组二选一。我选择了修改代码解析顺序因为CubeMX里的顺序调整有时会影响DMA请求的触发位置。另外一个小坑G474 ADC的DMA数据寄存器读取如果用字节8位宽度会因为地址对齐问题读到高低字节错乱的数据。必须把DMA数据宽度设为半字。5.2 采样值在占空比变化时跳水母线电压通道的数据在输入电压稳定时很平稳但电流通道偶尔会跳变。我怀疑是采样点窗口太窄占空比跑偏后采样点落在了开关边沿附近。通过串口观察出现跳变的时间点和PWM占空比超过70%之后高度重合——因为占空比越大开通过程中点越靠近周期起点而我的固定采样点在PERIOD/2两者在时间上的距离被压缩采样点逐渐靠近关断边沿的振铃区。解决方法是把采样点改成动态跟随占空比。好在HRTIM的CMP寄存器支持在运行时修改我写了一个简单的PID调节器计算占空比同时把“中间时刻”采样点同步更新为目标值的一半也就是把CMP2设置为PERIOD × 占空比 / 2这样采样点始终跟随脉宽的中点。实际效果对比状态采样点位置数据波动范围固定中点采样占空比50%±5 LSB固定中点采样占空比80%±28 LSB动态跟随采样占空比80%±8 LSB动态跟随采样占空比15%±6 LSB这个对比非常直观占空比偏离50%越多固定中点采样的劣势越明显。如果你的PWM占空比经常在较大范围内变化建议不要用固定中点动态跟随虽然代码多一点但效果差得很清楚。5.3 PCB布局上的三个要点除了MCU配置PCB布局对ADC采样质量的影响也很大。这方面我吃过亏总结几个实用要点ADC前端RC滤波的电容地必须单独走线回到MCU的模拟地引脚不能和功率MOSFET的源极地层共用否则开关电流会在公共阻抗上产生压降直接叠加进采样值。ADC输入走线尽量短并且远离HRTIM输出的PWM驱动信号线。如果交叉不可避免垂直交叉优于平行走线。在ADC引脚旁边放一个0.1uF的MLCC到模拟地能提供高频电流的退耦路径对抑制共模噪声有明显帮助。另外如果采样的是高频开关电源里的电流信号建议在运放输出和ADC引脚之间加一个二阶低通滤波截止频率设在开关频率的1/10到1/5。RC取值时要注意运放输出阻抗和ADC输入阻抗对分压精度的影响必要时加一级电压跟随器。5.4 调试时一个很实用的小工具函数调试这个项目时我写了一个简单的上位机绘图辅助脚本用Python的pyserial读取串口CSV数据实时画三个通道的波形图。配合串口打印能做到“数据链路全打通”的快速视觉确认。打印频率100ms波特率460800CPU占用很低。import serial import matplotlib.pyplot as plt import numpy as np ser serial.Serial(COM5, 460800, timeout0.5) fig, ax plt.subplots(3, 1, sharexTrue) data {k: [] for k in [vin, iload, vout]} plt.ion() while True: line ser.readline().decode().strip() parts line.split(,) if len(parts) 3: vin, iload, vout map(float, parts) data[vin].append(vin) data[iload].append(iload) data[vout].append(vout) for ax_i, key in zip(ax, data.keys()): ax_i.plot(data[key], labelkey) ax_i.legend() plt.draw() plt.pause(0.01)串口示波器虽然不是实时性最强的工具但在初期验证DMA数据是否正确、采样点是否合理这两个问题上足够用了。等逻辑理顺再上真正的示波器抓波形也不迟。6. 从这套链路延伸出去还能怎么扩展做完这套HRTIMADCDMA的链路后我发现它其实是个通用的“采样骨架”可以套到不少场景里多路并联DC-DC的均流控制每路输出电流一路ADCHRTIM的不同定时器单元分别触发各路采样保持相位一致。PFC电路中的母线电压和输入电流同步采样不需要额外加采样保持芯片靠HRTIM的精密触发就能在电网周期内精确锁定采样点。电机控制的相电流重构利用PWM中间时刻的窗口特性在有效矢量时间段内采样两相电流重构三相电流。动态调整CMP2跟随占空比的做法也可以推广成一种通用的“采样点自适应算法”只需要在占空比更新时同步更新ADC触发比较值代码逻辑并不复杂。最后一个小经验调试这套东西时串口打印的内容尽量精简。我最初把DMA状态、HRTIM计数器值、每个通道的原始ADC值全部打出来数据量太大反而掩盖了真正的问题。后来只在异常分支打印状态正常运行时只输出三路转换后的物理量问题定位速度明显变快。希望这套链路和排查思路对正在做类似项目的朋友有帮助。
返回列表