2、PWM与ADC触发机制:PWM定时器如何触发ADC,实现电流采样同步
做电机控制的朋友都知道,电流采样是闭环控制的眼睛。但很多人一开始都会遇到一个问题:明明ADC采到了电流值,算出来的力矩却忽大忽小,电机跑起来一顿一顿的。
问题出在哪?大概率是采样时机没选对。
这一章我们就来聊聊,怎么用PWM定时器精准触发ADC,让电流采样和PWM开关动作保持同步。说白了,就是让ADC在“对的时间”干活。
2.1 为什么需要同步采样?
先看一个实际场景。三相逆变器驱动电机时,PWM在不停地开关。每次开关都会在母线上产生电流纹波和尖峰。如果你让ADC随便找个时间点采样,很可能采到的是纹波的波峰或波谷,而不是真实的平均电流。
我在项目里遇到过这种情况:用同一个电机跑同样的负载,不同批次的板子测出来的电流值能差20%。查了半天,发现是采样触发源配置不一样,导致采样点落在了不同的纹波位置上。
所以,同步采样的核心目标就两个:
- 避开开关噪声——不在开关动作瞬间采样
- 采到相电流中心值——在PWM载波波峰或波谷处采样,此时电流纹波最小,最接近真实值
核心原则:ADC采样时刻必须与PWM载波周期保持固定的相位关系。这个关系一旦确定,就不能随意改变。
2.2 PWM定时器的内部结构
要理解触发机制,得先看看PWM定时器长什么样。以STM32的通用定时器为例,它的核心部件包括:
| 部件 | 作用 | 与ADC触发的关系 |
|---|---|---|
| 计数器 (CNT) | 从0向上计数到ARR,然后归零 | 计数过程就是载波周期 |
| 自动重装载寄存器 (ARR) | 决定PWM周期 | ARR值决定ADC触发频率 |
| 比较寄存器 (CCRx) | 与CNT比较,产生PWM边沿 | 可配置为ADC触发源 |
| 触发输出 (TRGO) | 定时器事件输出信号 | 直接连接到ADC的触发输入 |
你想想看,计数器从0走到ARR,再跳回0,这个过程就是一次完整的PWM载波周期。在这个周期里,我们可以选择任意一个时间点,让定时器发出一个触发信号给ADC。
2.3 三种常见的触发模式
根据项目经验,我总结出三种最常用的触发方式。每种都有它的适用场景。
2.3.1 更新事件触发 (UP事件)
计数器溢出归零时产生更新事件。这个事件通过TRGO输出,直接触发ADC。
- 触发时机:载波波谷(计数器=0)
- 优点:配置最简单,只需设置定时器的更新事件输出
- 缺点:采样点固定,无法灵活调整
- 适用场景:对采样时序要求不高的低速电机
// STM32 HAL库配置示例:更新事件触发ADC
htim1.Instance = TIM1;
htim1.Init.Period = 1000 - 1; // PWM周期
// 使能更新事件TRGO输出
HAL_TIMEx_MasterConfigSynchronization(&htim1,
TIM_TRGO_UPDATE); // 更新事件作为触发输出
2.3.2 比较事件触发 (OCxREF)
当计数器值与某个CCR寄存器相等时,产生比较事件。这个事件也可以作为TRGO输出。
- 触发时机:载波周期内的任意位置(由CCR值决定)
- 优点:采样点可编程,灵活度高
- 缺点:需要额外占用一个比较通道
- 适用场景:需要避开特定噪声窗口的高性能控制
我的习惯:做FOC控制时,我通常把采样点设在PWM周期的正中间(即CCR = ARR/2的位置)。这样既能避开开关边沿的噪声,又能采到相电流的平均值。
2.3.3 双触发模式 (更新+比较)
有些高端定时器支持在一个周期内产生两次触发:一次在波谷(更新事件),一次在波峰(比较事件)。
- 触发时机:载波波谷和波峰各一次
- 优点:采样率翻倍,适合高速电机
- 缺点:ADC转换速度要够快,否则会丢数据
- 适用场景:需要双采样进行纹波重构的场合
2.4 触发信号的硬件路径
光配置好定时器还不够,你得确保触发信号能正确到达ADC。不同芯片的路径不太一样,但大体结构是类似的。
下面这张图展示了典型的信号流向:
2.5 配置实战:以STM32G4为例
纸上谈兵没意思,我们直接上手配置。以STM32G4系列为例,它内部有专门的HRTIM(高分辨率定时器),触发ADC的方式更灵活。但基本原理和普通定时器是一样的。
下面是一个完整的配置流程:
- 配置PWM定时器——设置周期、死区、互补输出
- 选择触发源——决定用更新事件还是比较事件
- 配置ADC触发输入——选择对应的定时器TRGO作为触发源
- 设置采样时间——确保ADC能在触发后完成转换
- 启动同步——先启动定时器,再使能ADC
// 完整配置示例:比较事件触发ADC,采样点设在PWM中间
void PWM_ADC_Sync_Init(void)
{
// 1. 配置TIM1,PWM频率20kHz,中心对齐模式
TIM1->ARR = 1000 - 1; // 20kHz @ 20MHz时钟
TIM1->CCR1 = 500; // 50%占空比
TIM1->CCMR1 = TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; // PWM模式1
TIM1->CR1 = TIM_CR1_CMS_1; // 中心对齐模式
// 2. 配置比较事件作为TRGO
// 使用CCR2作为触发比较值,设为ARR/2 = 500
TIM1->CCR2 = 500;
TIM1->CR2 = (5 << 4); // TRGO选择:OC2REF
// 3. 配置ADC1,由TIM1 TRGO触发
ADC1->CFGR2 = 1 << 4; // 触发源选择:TIM1_TRGO
ADC1->CFGR1 = ADC_CFGR1_EXTEN_0; // 上升沿触发
// 4. 设置采样时间,确保在PWM周期内完成
ADC1->SMPR = 7; // 采样周期:640.5个ADC时钟
// 5. 启动
TIM1->CR1 |= TIM_CR1_CEN; // 启动定时器
ADC1->CR |= ADC_CR_ADEN; // 使能ADC
}
曾经踩过的坑:有一次我配置完触发后,ADC死活不启动转换。查了半天发现是ADC的触发极性设反了。TIM1的TRGO默认是高电平有效,但ADC的触发输入配置成了下降沿触发。信号对不上,自然采不到数据。所以配置完一定要用示波器抓一下TRGO引脚的波形,确认极性对不对。
2.6 采样时序的精确计算
配置写好了,但你真的确定采样点落在正确的位置吗?我建议你算一下时序。
假设PWM频率20kHz,中心对齐模式,ADC时钟40MHz:
| 参数 | 值 | 说明 |
|---|---|---|
| PWM周期 | 50μs | 1/20kHz |
| 载波半周期 | 25μs | 从0到ARR的时间 |
| 触发点设置 | CCR2 = 500 | 对应半周期中点 |
| ADC采样时间 | 16μs | 640.5个ADC时钟 @ 40MHz |
| 采样窗口余量 | 9μs | 25μs - 16μs,足够避开开关边沿 |
从表格可以看出,采样点设在半周期中点,采样窗口有9μs的余量,完全避开了开关动作产生的噪声区间。这个时序是安全的。
2.7 多通道采样的同步问题
做FOC控制时,通常需要同时采集两相电流(比如U相和V相)。如果ADC只有一个转换器,就得用注入组或规则组轮流转换。
这里有个细节:注入组可以打断规则组的转换。如果你用规则组做常规采样,用注入组做同步电流采样,那么注入组的优先级更高,能保证在触发信号到来时立即响应。
我个人习惯的做法是:
- 规则组:采集母线电压、温度等慢速信号
- 注入组:采集两相电流,由PWM定时器触发
这样既保证了电流采样的实时性,又不耽误其他信号的采集。
2.8 常见问题与排查方法
最后,分享几个我在项目中遇到的实际问题,以及排查思路。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| ADC不启动转换 | 触发极性配置错误 | 用示波器抓TRGO引脚,检查电平变化 |
| 电流值跳动大 | 采样点落在开关噪声区 | 调整触发点位置,避开开关边沿 |
| 两相电流不同步 | 注入组通道配置顺序错误 | 检查注入组序列寄存器,确保两相连续转换 |
| 采样值偏小 | 采样时间不足,电容未充满 | 增大ADC采样周期,或降低ADC时钟 |
嗯,这一章的内容就到这里。PWM触发ADC这件事,说复杂也复杂,说简单也简单。核心就是记住一句话:让ADC在PWM周期里最安静的时刻干活。只要抓住这个原则,配置起来就不会跑偏。