去年调一块F28379D的永磁同步电机驱动板,电流环写好了,PWM也出来了,结果一上电电流波形跟个锯齿似的,怎么补偿都压不住。查了一整天,最后发现根子不在PI参数,而是SDFM的滤波器输出节拍和ePWM载波完全没对齐——采样点总是落在IGBT开关边沿上,一采一个毛刺。今天就把这套ePWM与SDFM同步配置从寄存器到Driverlib完整梳理一遍。
这篇文章不是给你搬TI例程,而是把底层逻辑讲透:为什么SDFM必须跟着ePWM走、同步信号在芯片内部到底是怎么连的、寄存器的每个位段在干什么、用Driverlib写出来又是什么样。适合正在用F2837x/F28004x做电机控制或数字电源的开发者,尤其是想把电流环带宽往上推、或者自己画板子改采样方案的人。看完你至少能少走我当初两天的弯路。
1. 采样对齐的必要性:为什么SDFM必须跟着ePWM走
很多刚接触C2000的人会把SDFM当成一个"外挂ADC"来看,觉得只要把调制器位流接进去,读出来的数字就是电流值。这个理解不算错,但会漏掉最关键的一层:SDFM不是按你的意愿随时采样的,它的输出节拍由内部SINC滤波器决定,而滤波器什么时候"吐"一个数据,取决于你给它的同步信号。
1.1 Σ-Δ调制位流是如何变成数字电流的
先把SDFM的角色说清楚。外部Σ-Δ调制器(比如TI的AMC1306、AMC1200这类隔离式调制器)把模拟信号转成一位位流,输出的是连续0/1序列。这个位流里每一位单独看没有任何意义,必须通过数字滤波器抽取、累加,才能还原出模拟量对应的数字值。
C2000的SDFM模块内置的就是这种抽取滤波器,常见的是SINC1、SINC2、SINC3三阶可选。SINC3的阻带衰减最好,但延迟也最大;SINC1延迟最小,但对带外噪声的抑制最差。滤波器的核心参数是OSR(过采样率),比如OSR=32,意味着每32个位流时钟周期,滤波器输出一个数据。
这段关系是后面所有计算的基础,先记着:**滤波器每次输出一个数据,需要消耗OSR个位流时钟周期。**如果你对OSR和SINC阶数没概念,就把它想象成一个移动平均值窗口,窗口越大,数值越平滑,但反应越迟钝。
1.2 PWM开关噪声与"采样窗口"的冲突
电机控制里最麻烦的不是白噪声,而是开关噪声。IGBT或MOSFET在开关瞬间,母线电压会在极短时间内发生大摆率变化,通过寄生电容耦合到采样回路,产生幅度很大、持续几百纳秒到几微秒的尖峰。
如果用普通的逐次逼近型ADC,你还可以通过"等毛刺过去了再启动转换"来规避。但SDFM不同:SINC滤波器是个连续累加过程,如果开关噪声发生在滤波器抽取窗口内,这个噪声会被"积分"进最终输出值里。更糟的是,如果采样时刻恰好落在PWM边沿上,那每次采到的噪声相位不一样,输出就会出现周期性波动。
所以说白了,采样点必须避开开关边沿,也不要在死区时间内采样。避开之后选哪儿?通常选在PWM周期内电流已经稳定的区域,比如中心对齐PWM的载波顶点或者载波零点附近,具体位置由DATAPOS这些参数来微调。
1.3 多桥臂一致性对同步的需求
还有一个容易忽略的点:三相电机驱动需要同时采样至少两相电流(第三相可以算出来),如果三相电流的采样时刻互相错开,算出来的电流矢量就有相位误差,直接导致坐标变换后的d/q轴电流抖动。
所以理想情况是:所有SDFM滤波器的输出节拍,必须对齐到同一个时间基准上。这个时间基准最自然的选择就是PWM时基——毕竟PWM载波周期就是电流采样周期,而整个系统的控制定时都由它派生。这就是为什么我们不是简单地把SDFM置为"自由运行",而是要让它和ePWM"绑"在一起。
2. 同步的底层逻辑:从ePWM比较事件到SDFM输出节拍
知道了"为什么要同步",接下来看"怎么同步"。这部分的重点是理解芯片内部的一条信号链,顺着这条链走,后面配置寄存器就不会觉得是在碰运气。
2.1 一条完整的同步链路
在F28379D这类芯片上,链路是这样的:
- ePWM时基计数器TBCTR在UP-DOWN模式下循环计数。
- 当TBCTR等于比较寄存器CMPC时,事件触发子系统的SOCB信号被拉高。
- SOCB信号一方面可以触发ADC转换,另一方面在芯片内部也被引到了SDFM模块。
- SDFM收到这个同步脉冲后,SINC滤波器完成当前抽取周期,并在对应时刻更新输出数据寄存器。
也就是说,ePWM的CMPC比较事件,决定了SDFM滤波器什么时候输出数据。这其实就是标题里"同步"的核心含义:不是简单的电平同步,而是让SDFM的抽取周期和PWM载波周期建立起固定的相位关系。
还有一个外部路径:SDFM的DR(Data Ready)引脚可以接受外部输入同步信号。如果调制器有自己的独立时钟,或者你需要跨板同步,可以用这条路径。但板内做电机控制时,我建议直接用内部SOCB,理由后面会说。
2.2 DATAPOS:微调采样点在PWM周期内的位置
直接让SDFM在CMPC事件发生时输出数据,通常不够。因为CMPC对应的时刻还是有可能离开关边沿太近,或者离死区时间太近。为此,SDFM里有一个DATAPOS参数,允许你把滤波器输出时刻往后推若干个模块时钟周期。
打个比方:CMPC给出了一个"基准零点半",DATAPOS就是在它后面加一段偏移,把最终采样点挪到安全位置。这个偏移的单位是SDFM的模块时钟周期,不是PWM时基周期,配置的时候要看清楚。
实际工程里,我的做法是先估算死区时间和尖峰持续时间,比如死区2us、毛刺1us,那把DATAPOS设成能覆盖3~5us的数值,再实测波形微调。如果PWM周期100us,你完全有富裕空间把采样点放在周期中段。
2.3 同步源的选择:哪种方案更稳妥
内部SOCB同步和外部DR引脚同步,各有各的适用场景,我根据自己的项目经验对比如下:
| 同步方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 内部ePWM SOCB | 无需额外连线,相位关系天然对齐,延迟小,抗干扰好 | 只能和本芯片的PWM同步,跨板同步受限 | 单板电机驱动、数字电源,绝大多数情况 |
| 外部DR引脚 | 可由外部调制器或另一块板子提供同步,灵活 | 需要保证外部信号时序,连线多,容易受干扰 | 多板级联、大功率变频器、特殊同步需求 |
我在实际项目中99%用内部SOCB,只有一次做多逆变器并联时,为了对齐两台控制器的载波和采样节拍,才专门研究了外部同步方案。对大多数开发者来说,内部SOCB就是最省心、最可靠的选择。
3. 寄存器视角:关键位段逐项拆解配置过程
现在进入寄存器的层面。这个部分不是让你背寄存器,而是告诉你哪些位段和同步直接相关、它们之间是怎么配合的。我会以F2837x系列为例,F28004x等后续型号的寄存器名可能略有差异,但逻辑一致。
3.1 ePWM侧的关键寄存器:TBCTL、CMPC、ETSEL
ePWM侧要解决两件事:一是把时基配成你想要的工作模式,二是在指定计数点触发SOCB。
先看TBCTL。这里最重要的是时基时钟分频和计数模式。UP-DOWN模式下,PWM频率等于TBCLK除以(2×TBPRD),这个系数很容易被忽略。比如TBCLK=100MHz,要10kHz PWM,TBPRD应该是4999而不是9999,否则出来的频率只有5kHz。
再看CMPC。CMPA/CMPB通常用来控制占空比,CMPC/CMPD则经常用来干"事件触发"这类副业。把CMPC设成一个固定值,当TBCTR计数到这个值时,事件触发模块就能产生SOCB。我惯用的做法是把CMPC设在载波周期的25%~50%范围内,具体值配合DATAPOS的偏移一起计算,确保最终采样点落在安全区间。
ETSEL寄存器负责选择触发源。要把SOCB的触发源设为"计数器等于CMPC",并且设置触发事件分频ETPS——一般设成1,也就是每个PWM周期都触发一次。如果设成2或更大,那就变成每两个或更多PWM周期才同步一次,采样率会下降。
3.2 SDFM侧的关键寄存器:SDFMCTL、SDFMMCTL、SDCTL、SDDATPARM
SDFM侧寄存器看着多,其实和同步强相关的就那几个。
SDFMCTL是滤波器使能控制,每个滤波器需要单独使能。这一步漏了,后面配置全白做,还没中断。
SDFMMCTL是模块级控制寄存器,这里的SSF位段专门管同步源选择。以F2837x为例,你可以把每个滤波器的同步源配成ePWM1、ePWM2或ePWM3的SOCB,也可以配成外部DR引脚。具体编码查对应型号的TRM,但概念上就是给每个滤波器选一个同步源。
SDCTL寄存器里和同步关系最大的是数据模式和使能控制位。你需要在SDCTL里打开高速滤波器使能,并确认数据输出模式是PWM同步模式。所谓PWM同步模式,就是滤波器输出节拍跟着同步源走,而不是自由运行。如果忘了配这步,SDFM虽然也在出数据,但和PWM没有固定相位关系,等于没同步。
SDDATPARM寄存器就是前面说的DATAPOS参数所在的位置。它决定了从同步脉冲到滤波器输出数据之间隔多少模块时钟周期。这个值直接影响采样点在PWM周期里的实际位置,是整条配置链里最需要在示波器上反复验证的参数。
3.3 时钟与相位参数的计算方法
这里给出一个可复用的计算思路,我实际就是这么算的。
先确定系统时钟,假设EPWMCLK=100MHz、SDFM的模块时钟也是100MHz。目标PWM频率10kHz,UP-DOWN模式,则TBPRD=100MHz/(2×10kHz)-1=4999。
再确定SINC滤波器参数。假设选用SINC3、OSR=32。SINC3的群延迟经验值约1.5×OSR×位流时钟周期。如果位流时钟也是100MHz量级,那就是约1.5×32/100MHz=480ns。这个延迟虽然不大,但如果你把电流环带宽推到2kHz以上,就需要在控制环里做补偿,或者在配置采样点时留出余量。
DATAPOS的实际取值,需要把死区时间和开关尖峰时间考虑进去。假设死区2us、尖峰1.5us,那你希望采样点至少在PWM边沿后3.5us以上。DATAPOS的单位如果是一个100MHz时钟周期(10ns),那么从同步基准点往后推350个时钟周期左右比较安全。不过实际测试时我发现,取值还要考虑PCB布局带来的额外噪声,所以先按计算值设,再上板实测微调。
下面给一个我在项目里做参数估算时常用的表:
| PWM频率 | TBPRD(UP-DOWN) | OSR | SINC3群延迟 | 建议DATAPOS经验起点 |
|---|---|---|---|---|
| 5kHz | 9999 | 64 | 约1.92us | 400~600 |
| 10kHz | 4999 | 32 | 约0.48us | 300~500 |
| 16kHz | 3124 | 24 | 约0.36us | 200~400 |
| 20kHz | 2499 | 16 | 约0.24us | 150~300 |
注意DATAPOS经验起点只是参考,具体值一定要结合死区和尖峰实测调整。
4. Driverlib实战:F28379D上的可复用初始化代码
寄存器讲完,现在用Driverlib把这套配置写出来。我以TMS320F28379D + C2000Ware 3.x为例,外部用一个AMC1306隔离式Σ-Δ调制器做相电流采样。代码可以直接抄到工程里改参数用。
4.1 环境准备与引脚分配
我的板子配置如下,你可以参照修改:
- PWM输出:ePWM1A/1B,用于控制一个半桥,输出频率10kHz,中心对齐模式。
- 调制器位流输入:SDFM1的SD1_A引脚,接收AMC1306的位流输出。
- 调制器时钟:由ePWM2的PWM输出提供,20MHz方波,接到AMC1306的CLKIN。
- 同步信号:ePWM1的SOCB内部连接到SDFM1的滤波器1同步源。
先把引脚复用配好,这是很多人省略的一步。SDFM的SD引脚、PWM引脚、以及调制器时钟引脚都必须使能对应的GPIO复用功能,否则后面怎么配SDFM都不会有数据。
4.2 ePWM初始化代码
初始化ePWM1时,我习惯先配时基,再配比较,最后配触发。关键代码如下:
#include "driverlib.h" #include "device.h" #define PWM_FREQ_HZ 10000u #define TBCLK_HZ 100000000u // UP-DOWN模式:频率 = TBCLK / (2 * TBPRD) #define PWM_TBPRD ((TBCLK_HZ / (2u * PWM_FREQ_HZ)) - 1u) // =4999 #define PWM_CMPC_VALUE 1250u void initEPWM1_ForSDFMSync(void) { // 1. 时基配置 EPWM_setClockPrescaler(EPWM1_BASE, EPWM_CLOCK_DIVIDER_1, EPWM_HSCLOCK_DIVIDER_1); EPWM_setTimeBasePeriod(EPWM1_BASE, PWM_TBPRD); EPWM_setTimeBaseCounter(EPWM1_BASE, 0); EPWM_setTimeBaseCounterMode(EPWM1_BASE, EPWM_COUNTER_MODE_UP_DOWN); // 2. 占空比比较值,这里给50%,实际按控制需求修改 EPWM_setCounterCompareValue(EPWM1_BASE, EPWM_COUNTER_COMPARE_A, 2500); // 3. 事件触发:TBCTR == CMPC 时产生SOCB EPWM_setCounterCompareValue(EPWM1_BASE, EPWM_COUNTER_COMPARE_C, PWM_CMPC_VALUE); EPWM_setADCTriggerSource(EPWM1_BASE, EPWM_SOC_B, EPWM_SOC_TRIGGER_SOURCE_COUNTER_COMPARE_C); EPWM_setADCTriggerEventPrescale(EPWM1_BASE, EPWM_SOC_B, EPWM_SOC_TRIGGER_EVENT_PRESCALE_1); EPWM_enableADCTrigger(EPWM1_BASE, EPWM_SOC_B); }这段代码的核心是第三步:CMPC=1250,意味着在UP-DOWN计数的上升段,当计数器到达1250时产生SOCB。这个脉冲就是SDFM的同步基准。选择1250而不是5000,是为了给后面DATAPOS留出调整空间,方便把采样点放在周期中段。
如果你不输出PWM波,只是想生成同步信号,可以不用配action qualifier,但建议保留时基和SOCB配置。我调试初期就是这么干的:先把同步链路跑通,再接功率级。
4.3 SDFM初始化代码
SDFM的初始化分为四步:模块级配置、滤波器参数配置、同步源选择、使能。下面是完整代码:
void initSDFM1_ForCurrentSense(void) { // 1. 模块级初始化 SDFM_initModule(SDFM1_BASE); // 2. 滤波器1:SINC3 + OSR=32 SDFM_setFilterMode(SDFM1_BASE, SDFM_FILTER_1, SDFM_FILTER_MODE_SINC3); SDFM_setFilterOversampling(SDFM1_BASE, SDFM_FILTER_1, SDFM_FILTER_OSR_32); // 3. 数据位置:在同步基准后增加偏移,避开死区和尖峰 SDFM_setFilterDataPosition(SDFM1_BASE, SDFM_FILTER_1, 350); // 4. 同步源选择:ePWM1的SOCB // 注意:不同C2000Ware版本的函数名有差异,以头文件为准 SDFM_setSyncSource(SDFM1_BASE, SDFM_FILTER_1, SDFM_SYNC_SOURCE_EPWM1_SOCB); // 5. 使能滤波器1 SDFM_enableFilter(SDFM1_BASE, SDFM_FILTER_1); }这里我特别强调一下第3步的350这个值是怎么来的。10kHz PWM周期100us,CMPC=1250对应的时刻是载波从0往上走的第12.5us处。DATAPOS=350,如果模块时钟10ns一位,就是再往后3.5us。这样做是为了确保采样点落在CMPC之后的3.5us处,而这个位置离最近的开关动作边沿有足够距离。
不同SDK版本里,SDFM_setSyncSource这个函数名可能不太一样。我自己换过C2000Ware版本,遇到过函数被重命名或者参数结构变化的情况。遇到编译报错,最直接的办法就是打开C2000Ware安装目录下的sdfm.h头文件,查一下当前版本里同步源配置的API长什么样。别硬背函数名,这属于正常迭代。
4.4 中断与数据读取方式
SDFM的数据寄存器有两种读取路径:高速滤波器和低速滤波器。电流环要的是每个PWM周期更新一次的瞬时值,所以用高速滤波器输出SDDATAHP1。
数据就绪的判断可以用中断,也可以用轮询。中断适合控制周期规律性强的情况,轮询则更简单直接。我调试阶段建议先用轮询,减少中断优先级带来的干扰因素:
// 在控制任务里调用:读取最新的SDFM高速滤波值 uint16_t readSDFM1Filter1Raw(void) { // 等待数据就绪标志,SDDATAHP寄存器读取后会自动清除标志 while((HWREG(SDFM1_BASE + SDFM_O_SDFMIFLG) & SDFM_SDFMIFLG_DDATAIF1) == 0) { } return (uint16_t)HWREG(SDFM1_BASE + SDFM_O_SDDATAHP1); }读取出来的16位原始值,需要换算成实际电流。以AMC1306为例,它输出的是±250mV对应满量程的位流。换算公式大致是:
I = (int16_t)rawValue / 32767.0 * I_measure_range
其中I_measure_range由采样电阻阻值和调制器输入范围共同决定。比如你用0.5mΩ采样电阻流过50A满量程,产生25mV压降,那么I_measure_range就是按这个关系算出来的实际电流满量程。
5. 实测中的验证与踩坑记录
配置写完不算完,上板实测才是检验同步配置是否正确的唯一标准。这一节我把自己踩过的坑和验证方法整理出来,这些经验在TI的官方例程里基本找不到。
5.1 用"电流波形抽丝"判断同步是否成功
一个非常有效的调试方法:固定PWM占空比,让电机不转或堵转,然后连续读取SDFM的输出值,通过CCS的Graph工具把数据数组显示出来。
如果同步配置正确,采到的电流值应该是一条平滑的直线,或者只有非常小的随机抖动。如果采样点落在了开关边沿上,你会看到波形上有规律性的尖刺,而且尖刺频率和PWM频率一致——这就是我说的"锯齿形电流"的来源。
有一次我为了验证同步点是不是真的避开了噪声,故意把DATAPOS设成0,结果波形立刻出现大量尖刺,然后把DATAPOS调到经验值,尖刺几乎消失。这个对比实验非常直观,也适合用来教团队里的新人理解DATAPOS的作用。
5.2 OSR太大导致响应迟钝、太小导致纹波大
有个项目为了追求分辨率,把OSR设成了128,结果电流环带宽怎么提都提不上去,电机高速运行时机身发烫。后来算了一下,OSR=128配合SINC3,群延迟已经到好几微秒,对电流环来说相当于在反馈通道里加了一个大滞后,相位裕度直接被吃掉。
反过来,另一个项目把OSR压到8,纹波又大得无法接受。这两个项目最终都落在OSR=24~32这个区间。我的习惯是:先按OSR=32起步,如果纹波能接受,就尽量不往下调;如果带宽不够,再试着降到24,同时用更高阶的SINC3来弥补纹波性能。
OSR的调整不是孤立的,它会直接影响群延迟、输出数据和噪声性能,改完之后必须重新检查采样点位置和电流环参数,不能只改一个数就完事。
5.3 DATAPOS与死区时间的"躲猫猫"
这可能是最容易被忽略的坑。很多人把死区时间当作"PWM输出延迟",觉得反正就2us,影响不大。但在SDFM采样里,死区时间内电流并不流过采样电阻或分流器,而是通过续流二极管走。这个时候采到的"相电流"根本就不是真实的相电流,而是某种寄生路径上的未知电流。
换句话说,采样点不仅要在开关边沿之后,还必须在死区时间结束之后。否则你采到的值既不准,又没有重复性,电流环稍微跑起来就会抖动。
我在一块板子上就踩过这个坑:DATAPOS设好后,静态波形很漂亮,但负载一上去电流却有明显波动。最后用示波器对比了PWM驱动波形和SDFM的DR引脚输出,发现采样点刚好落在死区时间结束前一点点。把DATAPOS往后推了1us,问题立刻消失。
5.4 不同芯片系列寄存器名与Driverlib API的差异
F2837xD和F28004x都是C2000家族,但SDFM的寄存器布局和Driverlib API并不是完全统一的。比如F28004x的SDFM在同步源配置上,可选信号源列表就和F2837x不完全一样;某些寄存器位段的名字也变了。
我的建议是:拿到一颗新芯片,先不要急着复制之前的代码。打开对应型号的TRM,找到SDFM章节,重点看三个地方:SDFMMCTL的SSF位段、SDCTL的PWM同步模式位、SDDATPARM的DATAPOS位宽。这三个地方是同步配置最容易出差异的地方。
另外,C2000Ware版本升级经常会导致Driverlib函数名或者结构体定义变化,这种时候不要硬扛,直接查当前版本的头文件和迁移指南。我见过太多人因为API变了就卡在那里,实际上把函数签名校对一遍五分钟就解决了。
6. 写在最后的两个小建议
如果你刚开始做这件事,我建议先用最简单的配置跑通:内部SOCB同步源、SINC3、OSR=32、DATAPOS先给一个保守值,比如PWM周期的20%处。跑通之后,再用示波器、Graph工具去验证采样点位置,一步步优化。不要一开始就上极端参数,否则出了问题很难定位是同步的问题还是硬件的问题。
另外,调试同步问题时,优先确认ePWM的CMPC和SDFM的数据就绪标志是否和PWM周期严格对齐。如果SDFM的数据就绪标志出现周期性漂移或者间歇性缺失,多半是同步源没选对,或者滤波器配置处于非同步模式。先把这条链路彻底整明白了,后续的电流环、速度环才谈得上稳定。