
最近一直在折腾F28069这颗C2000芯片上的CLA协处理器起因是一个移相全桥电源项目功率级要求ADC在每个开关周期采集一次电流然后实时调整两路PWM之间的相位差动态响应要压到微秒级。如果全部依赖C28x主核的中断光现场保护、压栈、进中断、读寄存器、算PI、更新比较寄存器这一套下来即使主频跑到90MHz也会留下不小的相位滞后而且每个项目周期越接近极限抖动就越明显。后来把控制环整体切到CLAControl Law Accelerator上整个回路的延迟从几微秒降到了几百纳秒主核只负责通信、监控和参数下发系统清爽很多。这篇博文就是这次实战的完整记录F28069上如何用CLA实现ADC采样与PWM相位的实时联动包括原理、寄存器配置、代码结构、链接文件处理以及调试时踩过的坑。如果你的产品也在用C2000做数字电源、电机控制或者正被“CLA核怎么用”折磨这篇文章应该能帮上忙。1. 项目全景为什么把控制环搬进CLA1.1 CPU中断方案的两个痛点先说结论CPU中断不是不能用而是很难做到“确定性的逐周期控制”。在F28069上C28x主核的中断响应需要经过PIE、保存上下文、跳转ISR这一路的延迟虽然不长但它是抖动的中段优先级不同、主循环里是否有关中断操作、Flash等待周期都会让延迟在几十个周期里漂移。对于100kHz的PWM来说10个C28x周期大概就是100多ns几十个周期漂移就会让相位误差跑到几度以上这在移相全桥和LLC里是能直接体现在输出纹波上的。另一个痛点是CPU和PWM之间的“相位关系”难保证。理论上在ADC中断里读结果、更新比较寄存器下个周期就能生效可实际工程里往往还要处理其他外设中断、通信服务、慢速环路一旦中断嵌套或主循环里有Flash操作ADC结果到PWM边沿之间的时间就不固定了。电源环路最怕的不是慢而是时间不确定。1.2 F28069的CLA到底是什么CLA全称Control Law Accelerator是TI在C2000上放的一个独立运算协处理器。它有自己的指令集、寄存器组和流水线可以独立于C28x主核运行。关键点在于CLA能直接访问ADC结果寄存器、ePWM比较寄存器、相位寄存器等外设也能访问和内CPU共享的RAM区域。也就是说它完全可以扮演“控制环专用小核”ADC转换完成事件触发CLA任务CLA读取结果、执行算法、更新PWM寄存器整套动作不需要唤醒主核。F28069虽然比不上后来F2837x、F2838x上的双核CLA那么豪华但这颗芯片上也是真心实意放了一个完整的CLA1支持最多8个任务每个任务由不同的硬件事件源触发。对于单路移相控制或单电机内环一个CLA任务甚至都用不满资源绰绰有余。1.3 相位联动控制的核心指标以移相全桥为例ePWM1驱动原边第一组桥臂ePWM2驱动另一组桥臂两组桥臂之间有一个可控相位差通过改变这个相位差来调节输出功率。传统模拟方案用UC3855这类PWM控制芯片配合模拟补偿网络现在换成数字控制目的就是希望每个开关周期都能根据当前电流值重新计算相位从而更快地响应输入电压和负载的变化。实现“实时联动”的硬指标有两个延迟足够小。从ADC采样点对应的真实物理时刻到PWM相位寄存器被新值改写总延迟控制在几个微秒以内。更新频率足够高。每个开关周期至少刷新一次相位也就是100kHz系统里10us内要完成一次采样、计算和更新。CPU完成这些操作也能跑但会占掉很多主循环时间用CLA之后主核就彻底解放了。我在实测中拿GPIO翻转量过CLA任务从ADC结果寄存器开始读到写完TBPHS寄存器大约280ns90MHz主频下这个速度是CPU中断路径没法稳定做到的。2. 硬件与开发环境准备2.1 硬件平台和必备设备本次用的是TI的F28069 LaunchPad也就是常见的那块LAUNCHXL-F28069M板子。板上板载仿真器和若干LED、按键做小功率移相测试足够了。如果是自研板只要芯片是F28069或F2806x系列外设模块基本一致代码可以平移。除了主控板之外建议准备一个能输出的直流电源给控制板和功率级分别供电一台双通道以上示波器至少100MHz带宽用来观察ePWM1A和ePWM2A的波形数字示波器更佳一个简单RC负载或电子负载方便做动态阶跃测试逻辑分析仪不是必须的但如果你要抓CLA任务触发时序、对比中断路径和CLA路径的延迟有逻辑分析仪会省事很多。2.2 开发工具与工程模板选择软件还是用CCS版本不要太老CCS 8.0以上都行。需要先安装C2000Ware或者老的controlSUITE里面有F2806x的器件支持、外设头文件、Flash烧写插件和一整套例程。建议直接找一个“带CLA ADC ePWM”的官方例程作为起点不要从空工程开始搭。C2000Ware里F2806x的示例代码有不少是以电机控制和数字电源为背景的哪怕你用不上里面的算法至少也能白拿一份正确的CMD链接文件、CLA段配置和启动代码。空工程最麻烦的地方在于链接文件中CLA的RAM段、数据段、程序段都要自己指定少写一个段任务要么跑飞要么编译链接时就直接报错。如果一定要自己建工程下面这几步逃不掉新建CCS工程目标芯片选TMS320F28069包含F2806x头文件和库目录在工程属性里把编译器的“CLA Support”设置为cla1否则CLA相关的关键字和指令无法识别加入TI提供的2806x_CLA.cmd链接命令文件这个文件里已经放好了CLA程序段和CLA数据段的地址分配全局定义CPU1老例程里常用和F2806x宏。2.3 最小工程结构我的工程目录大致是这样F28069_CLA_PhaseShift/ ├── 2806x_headers/include/ ├── 2806x_common/source/ ├── cla_task.c // CLA任务源码 ├── main.c // CPU初始化与后台 ├── system_init.c // 时钟/PIE/外设初始化 └── F28069_CLA.cmd // 链接文件cla_task.c里的函数会放在单独的段里编译后由启动代码拷到CLA运行RAM中。这个拷贝动作由MemCpy完成如果你忘了调用CLA任务完全不会跑而且不会报错这是新手最容易踩的坑之一。3. CLA与ADC/PWM的联动设计3.1 让ADC中断直接触发CLA任务F28069的CLA任务支持多种触发源包括软件触发、ePWM事件、ADC中断等。这里要让ADC转换完成事件直接触发CLA配置时主要有两步在ADC模块里使能ADCINT1并将它映射到CLA1任务1作为触发源在CLA1的寄存器里选择TASK1的SRCSEL为ADCINT1。代码样例如下// 配置ADCINT1触发CLA任务1 Cla1TaskSrcSelRegs.CLA1TASKSRCSEL1.bit.TASK1 0x0; // 0x0表示ADCINT1? 查阅对应位域映射 // 确保ADC中断使能 AdcRegs.ADCINT1.bit.ADCINT1E 1;这里需要说明不同型号的C2000CLA1TASKSRCSELx位域映射不完全一样不要在F28069上盲目照搬F2837x的宏定义最好打开头文件确认一下。实际配置时我一般直接看寄存器手册里的CLA1TASKSRCSEL字段表对照着填值避免宏定义踩坑。配置完成之后一旦ADC转换序列结束硬件就会自动向CLA发出触发脉冲。CLA接到脉冲后如果之前空闲就会立即开始执行TASK1的入口函数整个过程不需要C28x主核参与。3.2 让ADC采样点与PWM相位严格对齐ADC采样的触发时机决定了CLC拿到的数据代表哪一个开关状态。如果采样点乱跳后面再快的CLA也只是在“算错的数据上加速”。通常我使用ePWM1作为主PWM并让它在计数周期内产生ADCSOC触发信号。例如配置为在PWM计数器等于CMPB时触发ADC采样这样每个开关周期采样一次采样点正好落在PWM周期内一个固定的时间点上与相位基准严格对齐。// 配置ePWM1的ADCSOC触发计数到CMPB时触发 EPwm1Regs.ETSEL.bit.SOCAEN 1; EPwm1Regs.ETSEL.bit.SOCASEL 4; // TBCNT CMPB EPwm1Regs.ETPS.bit.SOCAPRD 1; // 每个周期触发一次 EPwm1Regs.CMPB EPwm1Regs.TBPRD / 2; // 中点触发为什么选CMPB而不选周期溢出或零事件因为周期溢出和零事件通常离PWM开关动作太近采样时容易抓到功率管开关造成的噪声尖峰。中点触发时上下管都处于较稳定的状态采样值更干净。如果要采电流峰值可以把CMPB设到离边沿一段距离的位置避开死区和振铃。3.3 相位更新通路TBPHS寄存器优先实现PWM移相有两种主流方式改比较值、改相位寄存器。我做移相全桥时更优先用TBPHS。思路是这样的ePWM1产生基准载波同时它的SYNCO信号输出作为ePWM2的SYNCIN输入。ePWM2配置为允许同步载波装载PHSEN1这样每当ePWM1的同步信号到来ePWM2的计数器就从TBPHS寄存器里的值重新开始计数。TBPHS就是“ePWM2落后于ePWM1的相位”。因此CLA只需要在每个周期算出新的相移量写进EPwm2Regs.TBPHS.all下一个同步信号到来时就会自动加载完成相位更新。// ePWM2初始化外部同步装载TBPHS EPwm2Regs.TBCTL.bit.PHSEN 1; // 允许相位装载 EPwm2Regs.TBCTL.bit.SYNCSEL 0; // 选择ePWM1SYNCO作为同步输入 EPwm2Regs.TBCTL.bit.PRDLD 0; // 立即装载周期值可选改TBPHS的好处是相位变化是连续的不会像改CMPA那样需要重新计算一组边沿关系也不容易造成占空比跳变。缺点是它只能在一个完整周期边沿生效而不是立刻生效。但在逐周期控制中每个周期更新一次恰恰够用。3.4 CLA任务里的算法骨架CLA任务是一个中断类型的函数写法如下__interrupt void Cla1Task1_ISR(void) { // 读取ADC结果寄存器注意用右移对齐根据实际分辨率换算 float iSample (float)(AdcResult.ADCRESULT0) * kAdcScale; // 简单的比例调节根据电流误差计算相位 float phaseCmd Kp * (iRef - iSample); // 限幅 if (phaseCmd kPhaseMax) phaseCmd kPhaseMax; if (phaseCmd kPhaseMin) phaseCmd kPhaseMin; // 写入ePWM2的相位寄存器 EPwm2Regs.TBPHS.all (Uint16)phaseCmd; }这里有几个细节值得注意不要在CLA任务里做复杂的浮点库调用。CLA虽然是浮点协处理器能跑sin/cos但库函数调用会增加代码体积和延迟。做移相控制时简单的乘加和限幅就够了。全局变量需要明确放置到CLA数据RAM段并且CPU和CLA共享使用时最好加临界保护或仲裁逻辑否则可能出现主核写一半、CLA读一半的脏数据。CLA任务里不能随便使用DELAY这类函数因为任务执行期间会阻塞下一次触发。如果必须等待尽量用中断触发代替延时轮询。4. 实操实现从初始化到任务跑起来4.1 系统初始化与时钟配置初始化流程顺序很重要我一般是先关看门狗、初始化系统时钟、关全局中断再初始化PIE然后依次配置GPIO、ADC、ePWM、CLA和触发源。void main(void) { // 1. 关狗、初始化时钟和外设时钟 DisableDog(); InitSysCtrl(); // 2. 清中断初始化PIE DINT; InitPieCtrl(); IER 0x0000; IFR 0x0000; InitPieVectTable(); // 3. 初始化CLA任务地址映射、RAM拷贝 InitCLA(); // 4. 初始化ADC和ePWM InitAdc(); InitEPwmModules(); // 5. 选择CLA任务触发源 InitClaTriggers(); // 6. 使能中断开PWM同步 EINT; ... }在这套工程里InitCLA()承担的任务比较关键把Cla1Task1_ISR函数入口地址写到CLA的目标地址寄存器里同时把CLA任务代码从Flash或初始化存储区拷贝到CLA RAM。很多例程用memcpy完成这个动作不要漏。4.2 CLA任务代码段的CMD分配链接文件对CLA来说就是命脉。我用的CMD文件里大致有这样几个段SECTIONS { .Cla1Prog : LOAD FLASH_RAM, RUN CLA_RAM, LOAD_START(_Cla1ProgLoadStart), RUN_START(_Cla1ProgRunStart), SIZE(_Cla1ProgSize) .Cla1Data : CLA_DATA_RAM }编译时.Cla1Prog会放在Flash里但运行时需要拷到CLA的RAM区因为CLA不从Flash执行。LOAD_START等符号就是给memcpy用的。很多第一次用CLA的朋友遇到“任务不执行”查来查去最后发现就是这个拷贝没做。数据段方面CPU和CLA共享的控制参数比如iRef、Kp这些变量要放进CLA数据RAM通常用#pragma DATA_SECTION指定#pragma DATA_SECTION(iRef, Cla1Data); #pragma DATA_SECTION(Kp, Cla1Data); float iRef; float Kp;这样CPU写入这些全局变量时CLA任务是能直接看到的不需要通过中断邮箱转发。4.3 ADC和ePWM的初始化细节ADC初始化时除了设置采样窗口、分频、通道选择还要特别注意上电校准。F2806x的ADC在Boot ROM里有一段校准代码上电后会自动把修正值写入ADCOFFTRIM寄存器。如果使用的是早期的库函数或自己写的初始化有可能跳过这一步导致ADC读数带固定偏置。ADC采样窗口我设置为7个周期因为采样信号源阻抗不高窗口太短容易采到不稳定的值。对于更复杂的电路可以把采样窗口调到14个周期但ADC采样率会下降逐周期控制时要算好时间预算。PWM初始化时除了前面说的同步链还要配置好计数模式、周期、死区。计数模式建议用递增/递减up-down模式这样中点采样触发的时刻正好对应载波峰值比较适合做中心对齐的移相PWM。4.4 主核后台与CLA数据交换主核不是完全闲着。它负责在系统启动后把iRef从0慢慢加到目标值避免直接大给定导致过流它还周期性地把CLA计算出来的phaseCmd读出来用于上传监控或本地显示。后台主循环里加一个标志位以确认CLA是否真的在跑extern volatile long ClaTaskTick; __interrupt void Cla1Task1_ISR(void) { ClaTaskTick; // 每次任务执行加1 ... }调试时直接在CCS的Expressions窗口看ClaTaskTick如果它一直增长说明CLA任务已经触发成功。这个数字比任何寄存器都能说明问题。4.5 实测波形与响应时间我用示波器同时抓ePWM1A和ePWM2A再做一次负载阶跃把负载电流从1A跳到2A观察两路PWM之间的相位差变化。在CLA模式下相位差在下一个开关周期就明显改变了从示波器上数格子响应时间约20us以内两个周期内完全稳定。同时我也用GPIO翻转的方式量过CLA任务本身的执行时间一个简单的比例算法加上一次TBPHS写入GPIO高电平约280ns换算成时钟周期大概25个周期。这个速度比传统的CPU中断路径快了接近一个数量级。5. 常见问题与排错指南5.1 CLA任务完全没有执行排查顺序很重要我一般按下面几步走确认ClaTaskTick有没有变化。如果没变先检查触发源配置是否正确ADCINT1是否真的产生了中断事件。确认CLA任务的入口地址有没有被正确设置。在调试器里看CLA寄存器区的任务入口地址是否与Cla1Task1_ISR的地址一致。确认memcpy是否执行。把M0/M1或指定RAM里的机器码和Flash里的原始代码对比一下能看出有没有拷对。经常有朋友在初始化里把InitPieCtrl()放在CLA配置之后导致PIE的寄存器状态把ADC中断屏蔽了CLA自然收不到触发。调试时先保证ADC中断触发源能通过PIE再谈CLA。5.2 CLA读到的ADC结果像蒙了一层偏移这个问题一定要单独讲。F2806x的ADC在转换完成后结果寄存器里的值默认是经过片内偏移校准的但前提是ADCOFFTRIM已经由Boot ROM的校准函数写入。如果你的初始化流程是直接从Flash启动了应用而应用代码把Boot ROM的校准给绕过了或者CLA任务启动时间早于校准完成时间那么CLA读到的ADCRESULT0很可能就是还没应用ADCOFFTRIM的原始值直流偏置可能达到几十个LSB折算到电压就是好几mV甚至几十mV。这个偏置在电源控制环路里不会被立刻发现但会导致稳态时相位差出现固定的偏角。解决办法是在初始化开始阶段显式调用ADC校准或者参考TI的ADC_cal()实现把校准值写入ADCOFFTRIM同时确保CLA任务在ADC校准完成后才使能触发。如果板上环境比较差还要在CLA算法里增加一个软件零点漂移补偿项。5.3 CPU和CLA同时访问RAM导致等待CLA和CPU之间要交换数据自然会共享一片RAM。当两者同时访问同一个物理RAM块时总线仲裁会令一方插入等待周期。这对于CPU来说不太致命但如果恰好在CLA任务执行期间访问共享变量可能会让CLA的关键循环多出十几个周期的延迟。缓解办法是把“CPU写、CLA读”的参数放在一块RAM把“CLA写、CPU读”的状态放在另一块RAM避免同一块RAM被双方同时高频访问。这个优化在F28069上效果明显。5.4 PWM相位抖动、波形不稳如果是相位跳变而不是缓慢调节先怀疑ADC采样点是否落在了噪声窗口中。我之前把触发点设在PWM边沿后200ns处结果每次开关瞬间的振铃都被采进结果里算出来的相位指令自然剧烈跳动。后来把CMPB改成载波中点相位就稳了。PCB布局也是关键。ADC采样通道要远离PWM驱动走线尤其是在自研板子上模拟电源和数字电源要做好滤波。给ADC的基准和供电引脚加0.1uF和1uF的退耦电容采样电阻尽量靠近芯片的模拟输入引脚。这些都做到位ADC结果的信噪比才有保障。5.5 死区对相位的影响移相全桥和半桥都离不开死区。死区的时间虽然只有几百ns但在高频下对应的相位角不小。如果CLA把TBPHS更新得很准可实际输出的波形相位却差了几个角度很可能就是死区偏移在捣乱。解决办法是先测量实际波形确定死区造成的固定相位偏移然后在CLA算法里加修正量或者把死区置为固定值避免死区寄存器被意外修改。6. 一些调完才明白的经验6.1 先用软件触发验证算法再切硬件触发我这次实际踩坑后强烈建议CLA任务代码写完后先配置成软件触发模式在主循环里手动触发一次用调试器观察计算结果。如果算法有问题软件触发模式下定位速度快得多。等确认计算正确后再改成ADC中断硬件触发这样可以把“算法问题”和“触发问题”彻底分开。6.2 CLA任务函数不要写得“太高级”CLA支持C语言但它毕竟是个协处理器编译器对某些高级特性支持不够成熟。特别是涉及浮点库、递归调用、复杂结构体时编译可能很慢或生成低效代码。我自己的经验是CLA任务里只放纯粹的数学计算和寄存器读写其它所有逻辑放CPU侧。这样CLA任务体积小、执行时间确定、触发周期稳定。6.3 善用GPIO翻转测量任务负载在CLA任务开头和结尾各翻转一次GPIO用示波器抓高电平宽度就能知道任务本身占了多少时间。带着这个数据作进一步优化比瞎猜靠谱得多。F28069主频90MHz一个GPIO翻转操作约十几个周期测量误差不大。实测中我的CLA任务占用约280ns在10us的开关周期里只占2.8%完全不影响下一个周期任务触发。6.4 从F28069扩展到更高端型号这次用的方案在F28069上跑通以后可以直接平移到F2837x、F2838x这类带多个CLA和更强PWM的芯片上。F2837x有CLA1和CLA2可以同时跑电流环和速度环高分辨率PWM还能提供更细的相位步进配合CLA可以做出比传统模拟方案更优秀的移相控制。底层的思路是完全一样的让CLA成为独立的控制环执行者让CPU专注全局调度。如果以后要做多电平变换器或同步整流的精细控制这套“ADC触发CLA、CLA直接写TBPHS”的做法也值得继续沿用。硬件在快速迭代但CLA这种“外设直接触发协处理器、绕过主CPU”的控制路径在很长一段时间里都是C2000做实时控制最合理的答案。