ARTICLE DETAIL

资讯详情

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

STM32G474 HRTIM触发ADC实现PWM中间时刻采样,消除开关噪声

STM32G474 HRTIM触发ADC实现PWM中间时刻采样,消除开关噪声 做数字电源和电机控制的朋友应该都经历过这种事PWM驱动桥臂ADC老老实实放在定时器中断里触发结果采样回来的电压电流波形边缘全是毛刺开关管动作瞬间的高频噪声全部灌进了采样结果。我以前在F103上调这个用的是“触发后延时再开ADC”的土办法延时长短全凭手感换个负载波形就歪。后来换了STM32G474才真正把这个痛点解决掉。STM32G474这颗MCU真正值钱的地方不只是170MHz主频也不只是5路独立ADC而是它那颗可以做高分辨率输出的HRTIM。把HRTIM的触发事件和ADC采样时刻对齐就能实现真正意义上的PWM中间时刻采样——也就是让ADC在每个PWM开关周期内挑最干净的时间点去转换而不是被动地在开关沿附近硬扛噪声。这篇文章我会把整套方案的原理、CubeMX配置、代码实现和调试经验完整讲一遍适合正在被开关噪声困扰、或者刚上手G4系列做电源/电机控制的开发者参考。1. 方案拆解为什么非要用HRTIM触发ADC1.1 它到底解决了什么痛点很多人在做DC-DC电源、电机FOC或者逆变器时都希望采样输出电流和母线电压。PWM信号驱动半桥开关管开通关断的瞬间电流变化率di/dt和电压变化率dv/dt都非常大这些突变电流流过PCB走线寄生电感、MOSFET寄生电容时会产生一串高频振荡。这个振荡会通过多种方式耦合到ADC采样电路里比如地弹噪声、参考电压抖动、采样保持电容上的电荷耦合。只要ADC的采样保持窗口落在开关沿附近采到的值就不是“当前时刻的真实物理量”而是叠加了振铃噪声的信号。特别是电流采样如果用的是采样电阻或电流互感器开关沿后几百纳秒内信号都在震荡这个区间你去触发ADC每个开关周期采到的噪声相位还不一样结果就是波形毛刺大、电流环数据跳动。所谓的PWM中间时刻采样思路很朴素把ADC触发点放在PWM周期里距离两个开关沿都比较远的“中间安全区”让采样保持电容在信号已经稳定的区间捕获电压。这个思路实现的前提是触发时序必须和PWM边沿严格对齐。1.2 为什么不推荐只用TIM1/TIM8可能有人会说STM32F4的TIM1/TIM8也能用更新事件或比较事件触发ADC用起来也一样吧。确实可以但有几个现实问题。第一高级定时器的更新事件触发ADC触发点通常对应PWM周期起点或者终点这个位置恰恰是开关动作位置触发间隔和开关沿太近噪声最脏。除非你用另一个通道的比较事件做触发但高级定时器的比较事件就是产生PWM的边缘触发点等于占空比切换点本身想额外找一个和PWM比较值无关的触发点资源上比较紧张。第二高级定时器PWM本身输出的死区精度、频率精度没问题但它的比较通道是0~65535的16位计数器在170MHz主频下要输出几十kHz的PWM时计数器周期值会分到一个相对较小的值想同时兼顾“高PWM频率”和“精确触发点”就受限制。HRTIM不一样。它本质上是一个带有多个高分辨率单元的复杂定时器每个子定时器都有一组独立比较寄存器可以同时做占空比控制事件和ADC触发事件两者互不干扰。它还能以极小的步进调整事件边沿的相位对高频电源应用来说这个能力是决定性的。1.3 G474的ADC和HRTIM触发链路是怎么接的G474的ADC是完全独立于定时器的外设但它有一个关键的属性规则组可以配置为外部触发启动。触发源里有一类就是HRTIM产生的ADC触发信号命名一般是HRTIM_ADCTRG1~HRTIM_ADCTRG10。触发链路是这样的HRTIM内部的子定时器计数器在运行过程中每当达到特定的比较值或周期值产生一个匹配事件这个事件被HRTIM的输出触发器打包成ADC触发脉冲送给对应的ADC外设ADC收到脉冲后启动规则组的扫描转换转换完一个序列后输出EOC信号DMA再把数据搬走。整条链路全部由硬件完成CPU不用管时序也是固定的不会因为中断响应延迟导致采样时刻抖动。这正是电源控制里非常在意的点ADC触发是异步于软件执行流时刻由硬件保证。2. 中间时刻采样背后的关键原理2.1 开关噪声是怎么混进ADC结果的我简单画过一个典型半桥电路用示波器抓过开关节点电压开通瞬间在几百ns内会出现一个高频振铃幅值可能叠加一两伏频率在几十MHz到上百MHz。这种噪声要进入ADC结果路径很多。一是地弹。功率回路的大电流从电源地回流到驱动地PCB上的地平面电位瞬时波动ADC的参考电压是相对地的地一抖转换结果就跟着抖。二是电磁耦合。开关节点和采样线如果平行走线高频振铃会通过寄生电容耦合到采样保持电容上。三是参考源抖动。如果MCU的VDDA和功率地共用地线开关噪声会直接污染模拟电源。所以单纯依赖MCU端软件滤波是治标不治本的通过调整采样时刻主动避开噪声频段最活跃的时间窗口比在软件里加再多滤波都有效。2.2 中心对齐模式下“中点”到底在哪里先明确一个概念。PWM有两种常见的计数模式边沿对齐和中心对齐。边沿对齐的计数器从0数到PERIOD后立刻归零PWM周期内高电平宽度由比较值决定中间点就是PERIOD/2附近。这种模式下要用CMP2额外产生一个“周期中点”触发事件比较值设为PERIOD/2这个值不参与PWM占空比控制。中心对齐则是对称的三角形计数计数器从0升到PERIOD再降到0通常一个完整的PWM周期包含一段上升和一段下降。这种模式下有一个非常巧妙的特点PWM输出高电平段的中心点正好对应计数器到达PERIOD的时刻也就是Period事件低电平段的中心点对应计数器降回0的时刻也就是Reset/Zero事件。我实际做20kHz电机电流采样时就喜欢把HRTIM子定时器配置为中心对齐模式。假设HRTIM时钟170MHz期望PWM频率20kHz那么计数周期就是170MHz/20kHz8500个时钟周期中心对齐模式下PERIOD设为4250计数器从0到4250再到0正好一个完整PWM周期20kHz。占空比由CMP1控制而ADC触发用Period事件或者Reset事件这两个事件和CMP1完全独立。2.3 占空比对安全窗口的影响和触发点选择这里有一个值得注意的细节高电平中点和低电平中点不一定都安全要看占空比。占空比接近100%时高电平时间很长Period事件两侧离开关沿很远采样非常安全但低电平时间很短Reset事件就在两个开关沿之间几乎没有安全窗口。反之占空比接近0%时Reset事件更安全Period事件不安全。所以实际项目中触发点应该根据占空比范围动态选择。如果PWM占空比经常在50%上下Period和Reset都行如果长期高占空比用Period事件采样高电平中段如果长期低占空比用Reset事件采样低电平中段。要想获得最大的抗噪窗口也可以配合比较事件自己计算。比如在边沿对齐模式下一个PWM周期内如果把ADC触发点设置在高电平开始后延迟一段时间的位置可以避开刚开通的振铃具体延迟多少可以用示波器观察开关节点振铃时间算出安全窗口后换算成HRTIM的计数值。2.4 HRTIM的触发事件如何精确定位HRTIM每个子定时器都有多个比较寄存器CMP1~CMP4等还有周期事件、复位事件。配置为ADC触发时可以在HRTIM的ADC Trigger控制器里指定这些事件源输出给ADC外部触发。HRTIM的高分辨率特性在这个场景有实际意义。它的比较事件可以专门用于触发ADC不受PWM输出比较值的约束你可以在一个PWM周期内放置多个不同的触发点比如高电平中点触发一次、低电平中点触发一次甚至放在死区结束之后某个精确时刻。这种灵活度传统高级定时器很难做到。HRTIM内部还有高分辨率延时单元默认情况下HRTIM时钟170MHz比较事件的分辨率是一个时钟周期约5.88ns。提一下这个精度已经足够绝大多数应用真正要ns级精度的场景可以启用HRTIM DLL校准的高分辨率模式事件可以再细分成更小的步进不过配置会复杂一些常规工程用默认精度就够了。3. 实操CubeMX一步步配置3.1 时钟树的基础配置新建G474工程时先把时钟树配好。HRTIM需要比较高的时钟频率一般直接用系统时钟170MHz作为HRTIM内核时钟。ADC的时钟建议单独分频G4系列ADC外设本身性能很强但ADCCLK不能太高通常我会把ADCCLK配在40MHz左右这样12位分辨率下单个通道转换时间大约在几百ns级别。具体在CubeMX的Clock Configuration页面里把PLL配置为170MHz然后设置ADCCLK分频让ADCCLK在40MHz上下具体值以CubeMX提示合法范围为准。注意ADC参考电压如果内部不好用外接基准源到VREF采样精度会好不少。3.2 HRTIM的PWM和ADC触发配置打开HRTIM外设选择TimerA作为PWM输出定时器。TimerA配置为中心对齐模式PWM时钟分频可保持默认。周期值设成按上文计算的4250Period寄存器就是周期计数目标。CMP1寄存器设置占空比比较值。PWM输出通道选TA1输出极性可以根据驱动电路决定通常先用高电平有效。死区根据你的驱动芯片参数来我习惯先设一个参考值后续再调整。关键的ADC触发配置在HRTIM的界面里有ADC Triggers选项。选择一个触发通道比如ADCTrigger1使能后Event Source选择TimerA的Period事件或者根据占空比情况选择TimerA Reset事件。这样HRTIM会在每个PWM周期产生一个与PWM中点对齐的ADC触发脉冲。如果占空比范围需要自适应你也可以在同一个HRTIM里配置两组ADC触发器一组用Period事件一组用Reset事件运行中通过寄存器切换触发源或者把两个信号分别送给ADC1和ADC2这在一些高端双采样控制里常用。3.3 ADC扫描模式和DMA配置ADC1配置为规则组扫描模式启用外部触发触发源选择HRTIM的ADC触发信号。扫描序列里可以加多个通道比如我加了3个采样通道序列长度设为3通道顺序按需求排列。打开ADC中断里的DMA请求并开启DMA的循环模式。这里有一个很关键的选项叫Continuous Requests Mode翻译过来是“连续请求模式”这个选项要特别留意必须打开。否则DMA在搬完一轮数据后就会停下来下一次HRTIM触发不会再产生DMA请求现象就是只采到一批数据后面数据不更新。DMA配置时外设地址选ADC1的数据寄存器内存地址指向一个uint16_t数组传输方向外设到内存数据宽度半字内存地址递增。传输运行模式设为循环模式。有些CubeMX版本里DMA事务长度填ADC通道数量比如3个通道就填3这样每完成一轮扫描DMA会产生传输完成中断。3.4 串口打印配置串口UART配置为115200、8N1即可。打印有两种方式阻塞发送和DMA发送。阻塞发送简单HAL_UART_Transmit一把梭但是会占用CPU等待如果打印数据量大会影响主循环。DMA发送需要自己管理缓冲区发送完成前不能再次往同一个缓冲区里写新数据否则数据会错乱。我实际项目中打印频率不会太高通常每个采样周期都更新数据但只有积累一定采样次数后才打印一次比如每200次采样打印一次也就是20kHz PWM下每秒打印100帧左右这个频率不影响实时控制。串口发送可以用DMA也可以直接阻塞发送因为间隔足够宽。4. 代码实现与关键点解析4.1 初始化顺序和完整框架初始化顺序上优先初始化DMA、ADC、UART、HRTIM这些外设然后先启动ADC的DMA模式再启动HRTIM定时器最后启动PWM输出。先后顺序要注意先启动ADC再启动HRTIM可以防止HRTIM启动后第一个触发脉冲来临而ADC还没准备好转换的情况。核心代码框架大概是这样的#define ADC_CH_NUM 3 uint16_t adc_raw[ADC_CH_NUM]; volatile uint8_t dma_flag 0; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_USART1_UART_Init(); MX_ADC1_Init(); MX_HRTIM_Init(); // 先启动ADC的DMA采集 HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_raw, ADC_CH_NUM); // 再启动HRTIM开始产生PWM和触发信号 HAL_HRTIM_SimpleBaseStart(hhrtim, HRTIM_TIMERINDEX_MASTER); HAL_HRTIM_SimpleBaseStart(hhrtim, HRTIM_TIMERINDEX_TIMER_A); HAL_HRTIM_WaveformCounterStart(hhrtim, HRTIM_TIMERINDEX_TIMER_A); HAL_HRTIM_WaveformOutputStart(hhrtim, HRTIM_TIMERINDEX_TIMER_A); while (1) { if (dma_flag) { dma_flag 0; // 打印逻辑也可以放这里 } } }4.2 ADC转换完成回调每个PWM周期HRTIM触发ADCADC扫描完3个通道后DMA搬运完成产生传输完成中断HAL库会调用HAL_ADC_ConvCpltCallback。在这个回调里你可以设置打印标志或者直接记录采样次数。void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { // 表示最新一轮采样数据已经在adc_raw数组中 dma_flag 1; } }4.3 打印显示处理打印时把uint16_t原始码值换算为实际电压。如果ADC参考电压是3.3V分辨率12位公式为电压adc_raw*3.3/4095。串口打印可以用printf重定向也可以自己拼字符串。我在g474工程里常用重定向到串口然后char buf[128]; snprintf(buf, sizeof(buf), CH0:%d(%d.%03dV) CH1:%d(%d.%03dV) CH2:%d(%d.%03dV)\r\n, adc_raw[0], adc_raw[0]*3300/4095/1000, adc_raw[0]*3300/4095%1000, adc_raw[1], adc_raw[1]*3300/4095/1000, adc_raw[1]*3300/4095%1000, adc_raw[2], adc_raw[2]*3300/4095/1000, adc_raw[2]*3300/4095%1000); HAL_UART_Transmit_DMA(huart1, (uint8_t *)buf, strlen(buf));打印频率由主循环里的计数来控制不要每周期都打印否则DMA发送可能来不及。4.4 关于DMA循环模式和“只能采一次”的现象我在实际调试中遇到过好几次“ADC只出第一批数据就停了”的情况基本原因都是ADC的Continuous Requests Mode没有打开。这个模式的含义是DMA搬运完一次后是否立刻重新准备接收下一轮转换数据。如果不开DMA在完成一次传输后就处于空闲状态即使ADC又完成新的转换数据也不会被搬运到内存里。打开这项后DMA循环搬运adc_raw数组里的数据每隔一个PWM周期就会刷新一轮。这才是“持续采样”的正确姿势。5. 常见问题与排查技巧5.1 数据整体错位怎么办如果打印出来的各通道数据和实际通道顺序对不上比如通道1的数据出现在通道0的位置多半是扫描序列顺序和DMA传输顺序不一致。G4的规则组扫描是固定顺序的从Rank1开始依次转换Rank2、Rank3……转换完一个通道DMA搬运一次数据到内存所以adc_raw[0]对应的是Rank1adc_raw[1]对应Rank2。如果CubeMX里配置Rank顺序和你想采样顺序相反输出自然反了。检查时直接改CubeMX里的Rank顺序就行比在代码里手动交换数组更直观。5.2 HRTIM不触发ADC数据永远不更新这个问题也比较常见。先确认HRTIM的计数器有没有跑起来用调试器看HRTIM寄存器TimerA的计数器值是否在变化。如果静止不动检查HRTIM WaveformCounterStart有没有调用。再检查ADC的外部触发源是否选对了。G4的ADC外部触发列表里HRTIM相关的触发源有很多个比如HRTIM_ADCTRG1、HRTIM_ADCTRG2等而CubeMX里HRTIM的ADCTrigger设置和你给ADC选择的触发源必须对应。比如HRTIM里配置的ADCTrigger1那ADC1外部触发源要选HRTIM_ADCTRG1选成2、3都不会触发。另外一定要检查HRTIM的时间基有没有使能有些模式下HRTIM子定时器默认没有运行时钟需要在HRTIM配置里把Timer A的时钟设置为内部时钟170MHz否则计数器永远不跑。5.3 采样到的数据噪声还是大怎么判断是采样点问题如果你把采样点设置在中间区域数据依然有毛刺就要分两步排查。第一步观察开关节点波形确定振铃持续时间。如果振铃能持续数微秒但PWM周期本身只有50us中间区域还是有足够安全时间如果振铃持续超过一半的周期那采样点自然没办法完全避开只能从硬件上削减振铃比如增加栅极电阻、优化PCB布局、在采样输入端加RC滤波。第二步利用HRTIM的灵活性动态改变触发点位置观察ADC数据的标准差变化。你可以同时配置两个ADC一个用Period事件触发一个用Reset事件触发对比不同时刻采样数据的波动情况。哪个数据波动小说明哪个位置更“干净”。这个实验用代码就能做非常方便。5.4 串口DMA发送第二次就卡死串口用DMA发送时有一个高频坑上一次发送还没有完成你又调用了HAL_UART_Transmit_DMA就会返回HAL_BUSY后续打印全部失效。严格来说这不影响采样但会让人误以为整个工程挂了。解决方法是维护一个发送完成标志。在HAL_UART_TxCpltCallback中置位标志只有上一次发送完成后才允许发送下一帧。volatile uint8_t tx_done 1; void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { tx_done 1; } }打印前检查tx_done如果为1则开始发送并清零标志否则跳过本次打印。虽然会丢几帧但保证了系统不死。5.5 硬件侧最容易忽略的两件事软件方案再好硬件底板太差也会白搭。采样电路在PCB布局上至少要注意两点一是功率地、模拟地单点连接ADC采样回路尽量短采样电阻和MCU的VSSA不要隔太远二是ADC输入引脚上保留RC滤波一般100Ω电阻加1nF电容组合既能滤高频又不会明显影响信号带宽。RC的截止频率按你实际被采信号的最高频率来算比如电流环带宽10kHz以内RC截止频率放到几百kHz影响很小但振铃衰减明显。还有个容易被忽略的细节是ADC参考电压引脚附近一定要放去耦电容至少一个1uF和一个100nF紧贴引脚。参考电压不稳中间时刻采样做得再好转换结果的信噪比也会受限。最后说一个我自己的习惯。所有G4的电源类项目我都不会在主循环里做任何改变采样时序的操作。HRTIM触发ADC、DMA搬数据、串口打印三个环节相互独立HRTIM负责时序DMA负责搬运主循环只做显示和逻辑这样逻辑怎么写都不会破坏采样时刻。这个习惯帮我避免了很多怪问题建议你也可以这么试。
返回列表