
前阵子调一块F28069的控制板客户坚持要把ADC采样、相位计算和ePWM相位更新全交给CLAC28x主核只负责通信和状态机。一开始我觉得多此一举等代码跑起来才发现这条路确实值。F28069是TI C2000家族里很常见的一颗DSP主核C28x带FPU旁边还挂着一个独立的浮点协处理器CLA它有自己的程序空间、数据空间和任务触发机制可以在不占用主核时间的情况下执行整条控制信号链。这篇文章就把我在板子上实现ADC采样与PWM相位实时联动控制的过程拆开讲一遍偏实用适合正在做数字电源、电机驱动、并网逆变器或者刚接触C2000 CLA想找落地参考的工程师。1. 为什么选CLAF28069协处理器到底解决了什么问题1.1 主核加协处理器一个线程干两种活F28069这类芯片很有意思它的内部相当于有两个独立内核在跑。C28x主核负责通用逻辑比如Modbus通信、按键扫描、状态机切换、故障上报偶尔也做点浮点运算CLA则是独立的浮点协处理器拥有自己的程序RAM和专用寄存器组可以执行8个任务任务之间由硬件调度不需要主核参与。我见过不少工程师一上来就把控制逻辑放在主核的中断里因为这样做最简单ADC转换完成中断一到主核立刻去读结果、算控制律、更新PWM。但当你把通信、显示、诊断这些代码越加越多之后主核的负担会迅速上升控制中断延迟开始不可控。把整条控制信号链搬到CLA之后C28x主核就从“实时控制”这个紧箍咒里解放出来了它可以慢慢悠着跑通信不用再担心偶尔一条慢指令把电流环拖垮。CLA的任务触发非常直接它可以通过PIE外设中断向量被ADC中断、ePWM事件或者软件指令触发。每个任务执行前由硬件自动保存上下文任务执行完再自动恢复这个开销比主核软件入栈出栈小得多。换句话说CLA就是为“每个载波周期必须算完一次控制律”这种场景设计的。1.2 中断延迟与确定性控制环最怕的“时间抖动”做控制的工程师都有这个经验从ADC采样到PWM更新中间隔的时间越短越好而且这个时间必须可控。如果它忽长忽短电流波形就会粗环路相位裕度也很难调。主核中断方案最大的问题就在这CPU响应中断要等当前指令结束遇到流水线比较深的长指令可能多等好几个周期进入中断后还要保护现场、保存寄存器出中断时又要恢复。这些开销加在一起每一次采样的响应延迟都可能不一样。CLA不一样。它的任务触发是硬件行为触发事件到达后只要当前更高优先级任务结束它就自动切入不需要主核干预。任务里的上下文管理由专用寄存器堆完成不需要软件压栈。所以CLA控制回路的时间确定性比主核中断好很多这对移相全桥、交错并联、PWM整流器这类对相位和时序非常敏感的系统尤其重要。我早期在纯中断方案里调试一组诊断代码PWM输出偶尔会出现相位抖动查了几天才发现是主核中断响应被某个长指令阻塞了。换成CLA之后这个问题再也没有出现过。1.3 这招适合哪些控制场景CLA不是万能药它适合的是那些“每个PWM周期都要算一遍、且计算模型相对固定”的控制场景。比如移相全桥的输出稳压需要根据输出电压误差实时调整两个桥臂之间的移相角PWM整流器需要让输入电流相位跟随电网电压有源滤波器需要动态补偿谐波电流电机驱动里的电流环、速度环也可以放CLA。判断依据很简单控制频率在10kHz到100kHz之间单次控制律计算需要几微秒主核同时还要跑通信和界面逻辑这时候CLA就是理想选择。反过来如果控制算法特别复杂需要大量条件分支和复杂数据结构CLA反而施展不开因为它更擅长直线型计算分支越多硬件加速的优势越不明显。还有一点如果整个项目只有一个控制环主核空转率很高那也没必要上CLA直接写进主核中断就行别为了用而用。2. ADC采样与PWM相位联动先把控制链捋清楚2.1 相位联动到底在联什么“相位联动”这个词听起来玄乎拆开看就是三件事ADC采样得到某个模拟量CLA根据这个量计算出目标相位值再把相位值更新到PWM模块。典型的例子是移相全桥变换器。原边有两个半桥两个半桥驱动信号的相位差决定了变压器原边电压的占空比从而决定输出能量。输出电压偏低就增大移相角输出电压偏高就减小移相角。这个移相角的调整如果每个开关周期都做一次就会发现动态响应非常快输出电压的波动能被迅速压下去。交错并联变换器也是类似思路。多路Buck并联时各路PWM之间错开一定相位可以大幅降低输入输出纹波。负载变化时CLA实时调整各路之间的相位差让电流纹波始终最小。做并网逆变器时PWM桥臂输出的电流相位必须时刻跟踪电网电压相位一旦出现相位偏差就要在下一拍修正。这些话概括起来就是标题里那四个字实时联动。2.2 载波周期、同步事件与ADC触发时机PWM相位不是一个独立存在的数字它必须建立在“时基同步”的基础上。C2000的ePWM模块内部有一个时基计数器TBCTR它从0数到TBPRD然后再跳回0或者反向数回来这个过程就构成了载波周期。多个ePWM模块之间通过SYNC信号统一对表SYNC事件到来时每个模块都会把TBPHS这个相位寄存器里的值装载到自己的TBCTR里。所以如果两个ePWM模块的TBPRD相同各自的TBPHS不同它们在同一个SYNC事件之后就有了固定的相位差。ADC采样要做到“想采哪个时刻就采哪个时刻”靠的是ePWM事件触发采样。比如在中心对齐模式下计数器到达0或者到达周期值时ePWM可以产生一个触发事件ADC收到这个事件后启动SOC转换。这样采样点就牢牢钉在载波周期的边界上不会出现采样时刻漂移。我习惯把采样时刻定在电流波形的波峰或波谷因为那里di/dt较小采样受到的开关噪声干扰也最小。时间点事件作用TBCTR0ePWM触发SOC启动ADC转换采样窗口打开ADC转换完成ADCINT置位触发CLA任务读取结果CLA任务执行计算相位修正量输出TBPHS更新值下一个SYNC事件TBPHS装载到TBCTR新的相位差生效2.3 更新时机的设计当拍计算下一拍生效调整PWM相位最忌讳的是“写到一半被打断”比如先改了占空比寄存器相位寄存器还没改完PWM输出已经按新旧混合的参数跑了一拍。C2000的ePWM模块在设计时考虑到了这个问题比较值CMPA/CMPB都有影子寄存器可以在计数器等于0或者等于周期值时统一装载。我建议把相位更新和占空比更新的装载时机都设在同一个同步点这样所有参数同时生效不会出现中间状态。CLA计算完成后写入TBPHSTBPHS虽然没有影子寄存器但它同样不会马上影响输出要等到下一个SYNC事件到来时才会被装载进TBCTR。这个特性就相当于天然的“当拍计算、下一拍生效”机制。如果你用软件强制同步信号也可以让新相位立刻生效但在移相控制里我一般不这么做因为周期中间强制同步会让波形出现相位跳变反而给环路引入扰动。还有一个容易忽略的细节死区时间。H桥和移相全桥都要加死区死区期间变压器不传输能量等效占空比会丢失。CLA计算相位时最好把死区丢失的时间折算进去否则输出稳压精度会差一点。简单处理方式是把死区时间换算成时基计数数量然后在相位修正量里加上这个偏置死区不一定正好补在相位上但先算清楚它对应多少个计数再决定怎么补偿排障时会省很多事。3. CLA实战从寄存器配置到代码落地的完整过程3.1 初始化ADC、ePWM、CLA三方配合先把三个外设的初始化顺序理一理。我习惯按照“PWM先配、ADC再配、CLA最后配”的顺序走因为ADC的触发源依赖PWM事件CLA的触发源又依赖ADC中断顺序反了容易出现时序漏洞。PWM部分重点是时基分频和同步关系。TBCTL里的CLKDIV和HSPCLKDIV决定了时基时钟TBCLK的频率如果系统时钟是90MHz经过分频后TBCLK可能是10MHz或者更低这直接决定了一个载波周期有多少个计数。移相精度就是用计数个数来表达的TBCLK越高能分辨的相位步进越小。比如一个50kHz的载波周期TBCLK是90MHz那一个周期有1800个计数每个计数对应约0.2度的相位步进对大多数电源控制来说已经够用了。ADC部分要配置SOC通道、采样窗口ACQPS和触发源。采样窗口的宽度要根据信号源内阻和外部滤波电容来定。如果ADC输入前端做了一阶RC滤波采样窗口至少要能完成对采样电容的充电否则会采到比实际值偏低的结果。经验值是从几十纳秒到一两百纳秒不等现场要根据波形微调。// ePWM1主时基配置中心对齐模式20kHz载波 EPwm1Regs.TBCTL.bit.CTRMODE TB_COUNT_UPDOWN; EPwm1Regs.TBCTL.bit.HSPCLKDIV TB_DIV1; EPwm1Regs.TBCTL.bit.CLKDIV TB_DIV1; EPwm1Regs.TBCTL.bit.PHSEN TB_DISABLE; EPwm1Regs.TBCTL.bit.SYNCOSEL TB_CTR_ZERO; EPwm1Regs.TBPRD 2249; // ePWM1在计数器为0时触发ADC采样 EPwm1Regs.ETSEL.bit.SOCAEN 1; EPwm1Regs.ETSEL.bit.SOCASEL ET_CTR_ZERO; EPwm1Regs.ETPS.bit.SOCAPRD ET_1ST; // ADC SOC0由ePWM1 SOCA触发采样通道1采样窗口14个ADCCLK AdcRegs.ADCSOC0CTL.bit.CHSEL 1; AdcRegs.ADCSOC0CTL.bit.TRIGSEL ADC_TRIG_EPWM1_SOCA; AdcRegs.ADCSOC0CTL.bit.ACQPS 14;3.2 CLA读ADC结果别绕开adcofftrim的坑F28069的ADC内部有偏置校准逻辑12位转换结果通常会自动应用偏置修正保证0V输入时结果接近0满量程输入时结果接近4095。但这个修正并不是对所有访问路径都无条件生效CLA在读取ADC结果寄存器时有可能读到的是尚未应用ADCOFFTRIM修正的原始值。这个问题在控制环路里特别隐蔽因为主核读出的结果是准的CLA读出的结果却带了一个固定偏置PI调节器会把偏置当成误差来处理最终导致输出静态偏差。我在现场遇到过类似情况输出电压稳态偏高了大概20mV怎么调PI参数都压不下去。后来把CLA读到的值和主核读到的值打出来对比才发现两边差了十几个LSB。定位到原因后处理方法是在主核初始化阶段把偏置校准值从ADCOFFTRIM读出来存到一块CLA和主核共享的RAM里CLA任务运行时从共享RAM取出这个值对ADC原始结果做一次补偿。这样虽然绕了一圈但每次采样都能拿到与主核一致的干净结果。// 主核初始化时把校准偏置写入共享RAM volatile int16 gAdcOffset; gAdcOffset (int16)AdcRegs.ADCOFFTRIM.bit.TRIM1; // CLA任务里补偿 __interrupt void Cla1Task1(void) { uint16 raw AdcResult.ADCRESULT0; int32 corrected (int32)raw - (int32)gAdcOffset; float32 voltage (float32)corrected * gVoltPerCount; // 后面用voltage做控制律计算 }3.3 相位实时更新TBPHS写入与总线仲裁CLA可以直接访问ePWM寄存器这是它实时性的关键。但要注意CLA和C28x主核都挂在同一个外设总线上如果两边同时访问同一个寄存器总线仲裁会插入等待周期严重时会拖慢CLA任务的执行时间。解决办法是尽量避免主核在控制回路运行期间去写CLA正在使用的PWM寄存器把控制参数的修改统一放到共享RAM里由CLA在任务开始时自行读取。TBPHS的更新要放在任务尾部尽量用“准备值”再写寄存器的方式。实际写法是先用局部变量算好targetPhase做一遍限幅和越界判断最后一条写进EPwm模块。不要把运算过程直接写在寄存器赋值里调试起来不直观。一旦写入TBPHS会等到下一个同步事件才被装载到TBCTR这符合前面说的“下一拍生效”原则。有一个很多人问的问题能不能在CLA里直接强制产生软件同步可以通过TBCTL.SWFSYNC就能触发。但我不建议在控制任务中频繁使用因为它会把所有PWM模块的计数器统一拉到一个相位点上如果多路PWM的相位关系正在动态调整强制同步会让输出瞬间错乱。正常运行时还是让硬件同步事件按载波周期自然到来CLA只负责提前把相位值准备好。3.4 一份可直接参考的代码骨架把上面的内容串起来一份最基本的代码骨架长这样。主核初始化外设CLA任务做完整控制链主循环只做参数交换和状态上报。// 主核初始化后启动控制 void main(void) { InitSysCtrl(); InitGpio(); InitPieCtrl(); InitPieVectTable(); InitEPwm(); InitADC(); InitCLA(); // 使能CLA任务对应的PIE中断 PieCtrlRegs.PIEIER11.bit.INTx1 1; IER | M_INT11; EINT; for(;;) { // 主核只处理通信、显示、参数下发 ProcessComm(); UpdateDiagnostics(); } } // CLA任务ADC采样结果 - 控制律 - 更新相位 __interrupt void Cla1Task1(void) { uint16 raw AdcResult.ADCRESULT0; int32 corrected (int32)raw - (int32)gAdcOffset; float32 volt (float32)corrected * gVoltPerCount; float32 phaseAdjust gKp * (gRefVolt - volt) gKi * gIntegral; gIntegral Saturate(gIntegral phaseAdjust, INTEG_LIMIT); float32 newPhase gCurrentPhase phaseAdjust; newPhase Saturate(newPhase, PHASE_MIN, PHASE_MAX); // 写从模块的相位寄存器同步事件后生效 EPwm2Regs.TBPHS.bit.TBPHS (uint16)newPhase; }这个骨架里使用了简单的饱和函数Saturate限制积分和相位输出范围。实际项目中还要加前馈、斜坡限制和各种保护但控制主路径就是这几行。4. 现场排障与避坑实录4.1 ADC采样值漂移和抖动怎么处理做ADC采样最常遇到的现象就是读数来回跳。如果跳动幅度只有几个LSB多半是电源噪声或者参考电压纹波引起的可以先加大采样窗口试试让采样电容充电更充分。如果还是不行就在CLA任务里做滑动平均或者过采样平均。12位ADC做16次累加后右移4位可以有效压低随机噪声但代价是采样周期变长控制带宽会降低取舍要看系统需求。PCB布局对ADC信噪比的影响也很明显。三个要点一是模拟电源和参考电源要做单独去耦ADC的参考电压不要直接从功率级的3.3V里取最好经过一个低噪声LDO或者基准源二是模拟地单点连接不要让采样回路的电流和功率级的di/dt电流走同一段地线三是ADC输入走线要远离PWM输出线和时钟线特别是不要把采样线跟高频PWM线平行走很长距离否则采样结果里会耦合进开关噪声。漂移和抖动是两个问题。抖动是随机的测多次平均值能压下去漂移是慢变的每次上电还不一样这类问题基本都和偏置校准有关回查一下CLA读取ADC结果时有没有做ADCOFFTRIM补偿十有八九能发现端倪。4.2 PWM相位跳变先查装载时机和限幅示波器上看到相位突然大跳或者占空比在一个周期内突变这是相位控制调试里最头疼的现象。排查路径我通常按下面这个顺序走先确认TBPHS写入时间点是否在同步事件之前。如果CLA任务写寄存器太晚赶不上下一个同步点那么新相位要再多等一个周期才生效表现出来就是控制延迟变大容易引发环路振荡。再确认比较值装载模式。CMPA如果被配置成立即装载CLA更新占空比时会立刻改变输出这在某些实验阶段可能不是想要的行为。中心对齐控制里建议把CMPA装载点设在计数器等于0或者等于周期值让输出变化集中在开关边界。最后检查限幅。CLA计算出的相位修正量如果没做限幅或者限幅上限设得过大调节初期PI输出一个很大的值相位一步跳几十个计数输出波形自然就是“跳变”而不是“平滑调整”。我习惯把单拍相位修正量限制在预设的步进上限以内宁可多算几个周期到目标也不要一步跨过去。现象可能原因排查方法相位随机跳变总线访问冲突或主核干扰检查主核是否频繁写同一PWM寄存器每周期都跳TBPHS写入时间点太晚拉长CLA任务执行时间余量上电首拍跳变初始相位未初始化主核启动时先写默认相位再使能PWM调节过程中过冲单拍相位限幅过宽缩小单拍步进上限4.3 CLA任务没进中断向量与存储映射排查CLA调试里最常见的问题是任务压根没执行。这时候别急着看控制律先确认三件事程序有没有正确放到CLA的程序RAM里PIE中断向量有没有和实际触发源对应上触发源有没有使能。F2806x的CLA任务1对应PIE中的INT11.1任务2对应INT12.1以此类推。如果ADC中断给了INT11.1但PIE使能写的是INT12.1任务永远跑不起来。调试手段上我习惯先在CLA任务首尾各写一个共享RAM变量主核不定时读取这两个变量来判断任务是否进出。确认任务能进之后再在CCS里给CLA代码打断点观察中间寄存器值。需要注意CLA的程序跑在独立RAM空间部分调试器对CLA代码的软件断点支持不好这时要用硬件断点或者干脆用共享RAM变量做“土法打点”。存储映射也是个容易踩坑的地方。CLA程序段默认是链接到某个由用户分配的段如果在CMD文件里没有把程序段分配到LS0-LS5中的某一块程序编译能过但运行时CLA取指令会跑到错误地址。建议在初始化时把CLA程序段和数据段都映射到明确的内存块并且用MemCfgRegs把对应LSx分配给CLA后再使能任务。4.4 异常保护把“飞车”扼杀在相位越界之前相位实时联动控制最怕的是CLA算出一个离谱的值直接写进PWM模块。移相角一旦越界轻则输出失控重则桥臂直通、电机飞车所以要设置多层保护。第一层是CLA内部的输出限幅这是软件保护。每个控制周期算出的新相位都要经过饱和函数单拍步进也要限幅从根源上防止数据异常传递到PWM输出。第二层是主核的越限检测主核周期性地从共享RAM读取CLA输出的相位值如果连续几个周期都超过安全阈值就认为控制回路异常直接封锁PWM输出。第三层是硬件保护C2000的TZ模块可以做到微秒级响应一旦外部故障或者比较器触发TZ事件ePWM输出引脚立即被强制成安全电平完全不受CLA和主核的控制。// TZ保护配置示例DCAEVT1触发时封锁PWM输出 EPwm1Regs.TZSEL.bit.DCAEVT1 1; EPwm1Regs.TZCTL.bit.TZA TZ_FORCE_LO; EPwm1Regs.TZCTL.bit.TZB TZ_FORCE_LO; EPwm1Regs.DCACTL.bit.EVT1SRCSEL DC_EVT1_SOC;这套三层方案下来即使CLA程序本身写崩了硬件保护也能兜底。实际项目中我还见过有人在CLA任务里加看门狗计数主核定期检查CLA是否还在正常“呼吸”一旦CLA任务超时未运行就按故障处理。这个思路也可以借鉴控制回路再快安全底线也要有人守住。我个人在CLA调试上最大的体会是先跑通最简任务再接真实控制律。每次新板子我都会先写一个什么都不干、只翻转全局标志的CLA任务确认触发源、存储映射和PIE中断全部连通了再把控制律填进去这样可以省掉大量排查时间。CLA代码本身也不适合写得太花哨分支越多、嵌套越深性能和可调试性都跟着下降。想让控制环跑得又稳又准就把CLA当一条干净利落的信号流水线采样、计算、更新每一步都清清楚楚。