☰
基于F28069 CLA的ADC采样与PWM相位实时联动控制
2026/10/5 7:01:33 网站建设 项目流程

前阵子调一块F28069的控制板,客户坚持要把ADC采样、相位计算和ePWM相位更新全交给CLA,C28x主核只负责通信和状态机。一开始我觉得多此一举,等代码跑起来才发现,这条路确实值。F28069是TI C2000家族里很常见的一颗DSP,主核C28x带FPU,旁边还挂着一个独立的浮点协处理器CLA,它有自己的程序空间、数据空间和任务触发机制,可以在不占用主核时间的情况下执行整条控制信号链。这篇文章就把我在板子上实现ADC采样与PWM相位实时联动控制的过程拆开讲一遍,偏实用,适合正在做数字电源、电机驱动、并网逆变器,或者刚接触C2000 CLA想找落地参考的工程师。

1. 为什么选CLA:F28069协处理器到底解决了什么问题

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较小,采样受到的开关噪声干扰也最小。

时间点事件作用
TBCTR=0ePWM触发SOC启动ADC转换,采样窗口打开
ADC转换完成ADCINT置位触发CLA任务,读取结果
CLA任务执行计算相位修正量输出TBPHS更新值
下一个SYNC事件TBPHS装载到TBCTR新的相位差生效

2.3 更新时机的设计:当拍计算,下一拍生效

调整PWM相位最忌讳的是“写到一半被打断”,比如先改了占空比寄存器,相位寄存器还没改完,PWM输出已经按新旧混合的参数跑了一拍。C2000的ePWM模块在设计时考虑到了这个问题,比较值CMPA/CMPB都有影子寄存器,可以在计数器等于0或者等于周期值时统一装载。我建议把相位更新和占空比更新的装载时机都设在同一个同步点,这样所有参数同时生效,不会出现中间状态。

CLA计算完成后写入TBPHS,TBPHS虽然没有影子寄存器,但它同样不会马上影响输出,要等到下一个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当一条干净利落的信号流水线,采样、计算、更新,每一步都清清楚楚。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询