ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TMS320F280049C CLA协处理器实战:初始化、任务触发与移植

TMS320F280049C CLA协处理器实战:初始化、任务触发与移植 这篇笔记是系列的第11篇上一期整理完ADC和PWM之后我顺手把手头的TMS320F280049C控制板翻到了CLA这一章。TMS320F280049C上的CLAControl Law Accelerator控制率加速器和普通外设完全是两回事它本质上是一个可以独立跑任务的32位浮点协处理器。对做电机控制、数字电源这类实时控制应用的人来说CLA是把中断里那堆高频控制算法挪出去的关键一步用得好CPU占用能降下来一大截。这篇就从官方CLA例程入手把这个核的使用逻辑完整拆开讲一遍。包括CLA和CPU怎么分工、例程里初始化链路每一句在干什么、任务触发和ADC数据读取的协同方式以及我实际改工程时踩过的一个典型坑——CLA直接读ADC结果寄存器时和ADCOFFTRIM偏移校正之间的冲突。另外还会把F280049C的例程往F28388D这种双CLA型号上移植时要注意的差异列一份清单。如果你正在啃这个模块希望这篇能帮你少走几段弯路。1. 为什么280049C上要专门学CLA从双核分工讲起很多人第一次看C2000的框图发现芯片里除了C28x CPU还画了一个CLA第一反应是“这难道不是多了一个核吗”理解上大方向是对的但CLA和CPU的“分工方式”跟你想的普通双核可能不太一样。1.1 控制环路里的Math加速需求做FOC电机驱动或者数字电源最典型的负载是电流环环路频率通常做到10kHz到20kHz甚至更高。每个中断周期里要做的事情串起来大概是读ADC结果、Clarke变换、Park变换、两个PI调节器、反Park变换、SVPWM占空比计算再叠加上各种保护和过流判断。F280049C主频是100MHz一个中断周期在20kHz下只有5000个时钟周期。CPU是带了FPU和TMU三角函数加速器没错但ISR里如果还要做通信解析、状态机切换、故障记录这些活留给控制率计算的时间余量就很紧张。CLA的作用就是把这些周期性的数学计算任务从CPU中断里拆出去交给一个专门干这活的协处理器。1.2 CLA和C28x CPU的分工边界CLA虽然叫“核”但它不是拿来做通用计算的。它有自己的指令集、寄存器组、浮点单元甚至有自己的程序内存和数据内存但它不能独立完成系统初始化也不能替CPU去配置外设寄存器。它的运行方式更接近一个“聪明点的DMA”挂在某个触发源上触发一次就跑到对应的任务入口去执行一段预先写好的算法代码执行完就停在那里等下一次触发。在控制应用里典型分工是这样的CPU负责启动初始化、通信、故障保护、系统状态管理这些逻辑复杂但频率低CLA负责高频控制率计算比如电流环的坐标变换和PI调节频率高但计算逻辑固定。两者之间通过共享RAM交换数据。CPU把给定值、使能标志写进共享RAMCLA在任务里读出来算算完把结果写回共享RAM。1.3 触达数据的方式共享RAM和消息RAMCLA读写数据的路径和CPU不完全一样。以F280049C为例内部有一批LSxRAM和GSxRAM这些RAM可以通过MEMCFG寄存器灵活划分归属是只能CPU访问还是CPUCLA共用甚至可以单独划给CLA做程序RAM或者数据RAM。数据交换最核心的点在于“谁写谁读怎么知道自己读到的是新数据”。社区里常见的写法是CPU写数据到共享RAM置一个软件标志位CLA任务被触发后读了数据再清掉标志位。如果要走得更严谨可以借用IPC机制或者消息RAM但大部分控制应用里一个volatile标志位配合触发源本身的事件顺序已经足够。2. 官方案例工程的初始化链路拆解C2000Ware里关于CLA的例程有好几个不同版本侧重点不一样有做数学库函数验证的有做多任务调度的。我手头用的这个例程主线是CPU初始化后把一段CLA任务代码加载到CLA程序RAM然后把Task1映射到ADC转换完成中断触发链路上CLA在Task1里读取ADC结果做一次处理结果写回共享RAM。下面把这套初始化链路拆开讲。2.1 工程里与CLA相关的文件先看工程结构。在C2000Ware自带的CLA例程里和CLA直接相关的文件大致有这么几类cla.c/cla_src.c存放CLA任务函数源码比如Cla1Task1、Cla1Task2这些中断函数实体。cla.h/cla_defs.hCLA结构体定义、寄存器位域定义以及库函数API声明。sysctrl.c/memcfg.c负责外设时钟使能和内存归属配置CLA要用到的共享RAM归属性就在这里定。.cmd链接文件需要显式定义CLA程序RAM的段分配包括加载地址通常放在Flash和运行地址CLA程序RAM中间要有一段拷贝代码在启动时把程序从Flash搬运到CLA的RAM里。这里有个新手容易忽略的点CLA任务函数虽然写在C文件里但编译后并不是自动就能在CLA上执行的。代码默认链接到CPU侧地址空间必须用#pragma或者段属性指定到特定的CLA程序段再由启动代码拷贝到CLA的本地RAM。不然你在CCS里看CLA程序内存完全找不到自己的函数任务一触发就跑飞。2.2 InitCLA函数逐行读以基于driverlib库的写法为例CLA初始化核心代码大概是这样// 1. 使能CLA时钟 SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_CLA0); // 2. 共享内存归属划分 MemCfg_setCLAMemType(MEMCFG_SECT_LS4, MEMCFG_CLA_MEM_CLA_PROG); MemCfg_setCLAMemType(MEMCFG_SECT_LS5, MEMCFG_CLA_MEM_CLA_DATA); // 3. 将CLA程序段从Flash拷贝到CLA程序RAM memcpy((void *)CLA_PROG_START, (uint32_t *)cla_prog_0_loadstart, (uint32_t)cla_prog_0_loadsize); // 4. 映射任务1入口地址 CLA_mapTaskVector(CLA0_BASE, CLA_TASK_1, (uint32_t)Cla1Task1); // 5. 使能CLA任务1触发和中断汇报 CLA_enableTaskInterrupt(CLA0_BASE, CLA_TASK_1);第一步没啥好说的任何一个外设先得开时钟。第二步非常关键如果不先把某个LSxRAM划分给CLA后面的程序拷贝和目标映射就是空谈。第三步里的cla_prog_0_loadstart这类符号是从cmd文件里导出的表示CLA程序段的加载地址和长度。调试时如果发现任务没跑起来先查这一步拷贝是否成功方法很简单在CCS Memory Browser里打开CLA程序RAM看看里面是不是真的有你的指令内容。第四步的“任务向量”值得多说一句。CLA一共有8个任务对应MVECT1到MVECT8这组寄存器。这些寄存器存的是任务函数的入口地址但注意是相对CLA程序空间起始地址的偏移不是CPU侧的绝对地址。所以官方封装CLA_mapTaskVector时会帮你做一次地址换算如果你自己写寄存器千万别直接拿函数指针赋值上去。2.3 任务注册和中断向量怎么挂初始化完成后触发链路还要和PIE中断挂上。CLA任务执行完毕后硬件可以上报一个中断给CPU这个中断在PIE里对应的是CLA1_INT。使用场景一般是CLA算完电流环主动通知CPU“新结果已经放到共享RAM里了你赶紧读”CPU再去处理速度环、通信这些低频任务。中断使能要开两层一层在CLA侧一层在PIE侧。代码通常是Interrupt_register(INT_CLA1, cla1ISR); Interrupt_enable(INT_CLA1);cla1ISR这个函数写在CPU侧里面做的事情越少越好。我见过有人把很多逻辑塞在这个ISR里等于最终还是让CPU背了重活CLA白引入了。建议这个ISR里只做一个动作置一个全局标志或者清一下中断标志主循环检测到之后再处理数据。3. 例程运行机制Task1如何被触发、何时读取ADC初始化只是把舞台搭好真正让CLA发挥价值的是任务触发机制。CLA和CPU的ISR不一样它不存在“当前正在执行哪个任务、来了更高优先级任务怎么办”这种复杂调度。每个任务由事先配置好的触发源触发同优先级按编号排队任务之间不互相抢占。3.1 例程中选择的触发源在官方例程中Task1最常见的触发方式是ADCINT1也就是ADC转换完成产生的内部中断信号直接触发CLA任务。选择这个触发源的原因很实在做实时控制算法必须在最新的ADC采样结果上执行。如果采用CPU软件写命令去触发CLA会引入额外延迟而且延迟不确定直接影响环路相位裕度。触发链路是这样的EPWM模块输出SOC信号让ADC启动采样ADC转换完成后产生ADCINT1事件这个事件不直接进CPU的PIE而是先触发CLA的Task1CLA拿到最新ADC结果执行控制率计算。这样一条链下来从采样到算法执行完全是硬件事件驱动CPU可以在旁边睡觉或者干别的事。如果是纯软件触发则可以用CLA_forceTask接口或者直接写MCTL寄存器里的软触发位。这个方式适合调试先在CCS里手动触发一次任务看共享RAM里的结果对不对确认算法逻辑没问题再切换到外设触发跑完整链路。3.2 task代码里做了什么一个最小控制率计算路径一个最小的CLA任务函数代码结构大致是这样// CLA侧任务函数Task1 __interrupt void Cla1Task1(void) { float adc_v 0.0f; float result 0.0f; // 从共享RAM读ADC转换结果 adc_v sharedAdcBuf[0]; // 做一次简单的比例计算或者PI调节 result adc_v * kGain - kOffset; // 结果写回共享RAM供CPU读取 sharedResultBuf[0] result; }这个函数和普通C函数有几处明显区别。首先是声明了__interrupt告诉编译器这是一段任务处理函数进入和退出要保存CLA任务现场。其次函数内不能随便调用标准库函数比如printf、malloc都不行因为CLA没有完整的运行库支持。官方提供了一个CLA数学库里面有sin、cos、sqrt这些控制算法常用的函数如果要调用数学函数尽量用数学库里的版本。任务执行完后函数返回硬件会把MCTL里的对应任务运行标志清掉然后根据MINT寄存器配置决定是否向CPU发中断。3.3 仿真验证时如何观察CLA状态调试CLA的时候有几个寄存器值得时刻盯着看MCTL主控制寄存器里面有当前任务参数、软触发位、任务优先级配置。MIRUN正在运行的任务标志位能看出当前是哪个任务在跑。MIFR任务触发标志位如果这里一直置1说明任务触发了但没执行完可能代码跑飞了。我调试时习惯在CCS里加一个小的Watch窗口把这三个寄存器拉出来看着。如果手动触发后MIRUN立刻清零、MIFR也清零说明任务正常执行完了如果MIFR一直为1那基本就是代码出问题优先查是不是指令访问了非法地址。另一个经验是CLA侧变量在CCS的Watch窗口里经常显示不出来或者显示的是旧值。因为Watch机制通常走的是CPU侧的调试访问而CLA的数据RAM归属可能已经划给了CLA总线。想看结果最简单的方法是加一个CPU侧的中断ISR在ISR里把共享RAM的数据拷到CPU侧的普通变量上再去看那个变量。4. 一个容易栽的坑CLA直读ADC结果与ADCOFFTRIM偏移校正这个坑是我在把例程改成自己工程的路上踩到的当时花了整整一个晚上才从数据手册的注意事项里找到线索。如果你在CLA任务里直接读ADC结果寄存器一定要认真看这一节。4.1 现象任务函数里读到的ADC结果不对劲我的现象是这样的CPU侧中断里读ADCRESULT寄存器值和示波器/万用表实测完全对得上但在CLA的Task1里用同样的方式直读同一个ADC结果寄存器读出来的值总是差那么几个LSB而且不是随机抖动是固定偏移。开始时我以为是共享RAM的同步问题后来发现根本不是。排查过程是逐步缩小范围的。先在CLA任务里把读到的原始值原封不动写到共享RAM然后停掉CLA让CPU也读同一通道做对比。结果发现差值恒定完全跟输入信号大小无关只和ADC的偏移校正配置有关。这让我怀疑到ADC模块的ADCOFFTRIM寄存器上去了。4.2 根因偏移校正与CLA读寄存器的时序限制先说明ADCOFFTRIM是什么。F280049C的ADC模块支持偏移校正也就是说转换完成后硬件会在原始结果上叠加一个修正量让零输入对应的转换结果更接近理想值。这个修正量就是写入ADCOFFTRIM寄存器的值。CPU侧读到的结果是已经做过偏移校正后的值。问题出在CLA的总线访问路径上。F28004x系列为了让CLA能直接访问外设寄存器在硬件上开了一条CLA侧的外设访问通道ADC结果寄存器就在可访问名单里。但芯片手册里有一条很隐蔽的说明当CLA通过这条通道读取ADC结果寄存器时在某些条件下读到的可能是尚未应用ADCOFFTRIM校正的原始转换值也就是说CLA读到的结果和CPU读到的结果之间存在一个固定偏差偏差大小正好等于偏移校正量。换句话说同一时刻同一通道CPU和CLA读同一个寄存器完全可能拿到两个不同的数。并不是寄存器里存了两个值而是访问路径不同硬件交付给你的数据经过了不同级别的处理。这个问题在28004x的Technical Reference Manual里是有明确的注释说明的我后来翻到那句时才明白踩的坑有多深。4.3 推荐的做法既然知道了根因解决办法就好定了。根据你的应用精度要求有三种做法可供选方案A不让CLA直接读ADC结果寄存器。改动最小、最稳妥。ADC转换完成后在CPU的ADC中断里把结果搬运到共享RAMCLA任务从共享RAM读。这个方案多了一次拷贝但数据一致性最好也是最常见的官方参考做法。方案B关闭硬件偏移校正把偏移校正放到CLA算法里自己算。适用于校正量固定、实际计算里反正要做标定的场景。缺点是需要自己维护一份校正参数。方案C如果必须让CLA直读外设寄存器那就得把校正逻辑包含在CLA任务里从结果里减去或加上ADCOFFTRIM对应的换算量。我个人在普通电机控制项目里倾向于方案A因为CPU的ADC中断本来就要做故障判断和状态记录顺路搬一次数据根本不增加多少开销。只有在极高频的环路应用里方案A多出来的那几百纳秒延迟不可接受时才会考虑方案B/C。5. 从280049C的例程移植到其他C2000型号时的差异清单学CLA例程的过程中我同步在排查一个F28388D的项目因为网上搜索引擎里“TMS320F28388D例程”这个词经常和280049C一起出现。两个型号在CLA使用思路上相似但落到代码和配置上差异点不少。这块单独列一节给准备跨型号移植的读者做参考。5.1 和F28388D双CLA配置的差异先看一张对比表把关键差异拿出来项目F280049CF28388DCLA数量1个CLA02个CLA1、CLA2C28x主频100MHz200MHzCLA时钟与CPU同频与CPU同频可访问外设范围部分模拟/数字外设范围更广包含更多共享外设内存归属复杂度较低主要是LSx/GSx复杂涉及CPU1/CPU2仲裁典型触发源ADCINT、EPWM、软件类似但可两个CLA分别配不同触发链从280049C迁移到F28388D最直接的差异就是“单CLA变双CLA”。在多环路应用里这是个很大的优势电流环放CLA1速度环放CLA2互不干扰。但代价是初始化代码里所有CLA_BASE都要改成对应的CLA1_BASE或CLA2_BASE内存划分也要分开指定。5.2 移植时容易漏掉的内存归属配置跨型号移植最容易在内存归属配置上翻车。F28388D因为多了一个ARM Cortex-M4核整个内存访问仲裁链路比280049C复杂了一个量级。在280049C上你可能只配置MemCfg_setCLAMemType就够了在F28388D上除了CLA本身的内存归属还要考虑CPU1和CPU2之间对GSxRAM的访问权限漏配任何一层表现都是CLA任务一跑就跑飞或者共享RAM里的数据总是旧值。我的建议是移植前先仔细核对目标型号的TRM内存映射图画一张自己的内存归属表把每一块RAM的访问者标注出来CPU1可读写、CPU2可读写、CLA可读写核对这些权限是否和你的数据流一致。别只改芯片型号就编译下载多半会卡在莫名其妙的地方。另一个容易漏的是中断向量。F28388D的PIE中断源于280049C不完全一样CLA1和CLA2对应的中断号不同移植时Interrupt_register里的中断编号要同步改。我在项目里就看到过一种错误代码里注册了INT_CLA1但实际项目用的是CLA2结果CLA2任务跑完了CPU这边根本收不到消息。5.3 例程扩展思路多任务与多触发源官方例程通常只演示了一个任务怎么跑但实际工程里CLA的价值恰恰在于多个任务协同。你可以把Task1配置给ADCINT1触发做电流环Task2配置给CPU定时器触发做速度环Task3留给软件触发做参数标定预计算。任务优先级通过MCTL寄存器的TASK1到TASK8优先级字段设置数字越小的任务优先级越高。需要注意CLA任务之间是不抢占的高优先级任务只能等当前任务跑完才能开始。所以不要把某个任务的执行时间设计得太长否则低优先级任务的实时性会受影响。我在实际项目里习惯给每个任务加一个执行时间统计任务开始读一个计数器结束再读一次记录下来评估CPU/CLA负载余量。扩展时要特别小心任务向量和共享RAM的分配。每个任务入口地址必须对应到唯一的MVECT寄存器数据RAM要按任务访问范围分区避免两个任务同时读写同一个地址造成数据串扰。最后说一段我自己的体会从第一次看CLA例程一脸懵到现在能熟练地把一段控制率算法从CPU中断里完整挪到CLA上最大的感受是CLA的学习曲线不在“写算法”而在“改思维方式”。CPU的任务是自己一脚踹出来的CLA的任务是硬件事件拉起来的整个代码结构、数据流设计、调试手段都因此不同。如果你刚开始接触建议先用软件触发跑通一个最简任务确认初始化链路没问题再逐渐切换到ADC触发最后再上多任务。每一步都验证通过再往下走比一次性写完然后面对一堆诡异现象要省时间得多。另外提一个我自己的调试习惯CLA代码里一定只做数学计算不要在任务函数里做任何耗时操作开关GPIO这类动作也只能用于调试且要极短。这样出了问题你才能把“算法算错”和“触发时序乱掉”这两类问题干净地区分开。
返回列表