—— 结合逆变电源RMS计算与PID控制例程深度解析
本指南基于实际工程代码(RMSCalculate.cla、cla_cpu01.c、cla_shared.h、adc_epwm_cpu02.c),详细拆解如何在 CPU2 上配置CLA(控制律加速器),以实现高实时性的ADC采样处理、RMS(有效值)计算及闭环PID调节。该例程充分利用了CLA与CPU的并行性,将繁重的平方累加和浮点运算卸载给CLA,CPU2则专注于流程控制、PID运算和IPC通信。
第一部分:项目整体架构与数据流概览
1.1 硬件资源分配
-
CLA1:隶属于CPU1子系统,但通过内存配置(
LSxMSEL)将LS0/LS1/LS4划归给CLA使用,CPU2可触发并控制CLA1。 -
CPU2:主控核心,运行主循环、状态机、PID算法,并通过IPC与CPU1交互。
-
ADC-A:16通道SOC,由EPWM1/EPWM2触发,采样初级电流(通道0~14)和次级电压(通道15)。
1.2 核心数据流(代码逻辑映射)
第二部分:内存配置与共享变量映射(关键步骤)
2.1 物理内存分配(adc_epwm_cpu02.c 与 cla_cpu01.c)
在F28377D中,LS0~LS5是CLA与CPU共享的本地RAM。您的代码通过 CLA_configClaMemory() 明确划分:
c
// 文件:cla_cpu01.c / adc_epwm_cpu02.c
void CLA_configClaMemory(void) {
EALLOW;
// 1. 将LS4配置为CLA程序空间(存放CLA任务机器码)
MemCfgRegs.LSxMSEL.bit.MSEL_LS4 = 1;
MemCfgRegs.LSxCLAPGM.bit.CLAPGM_LS4 = 1;
// 2. 将LS0/LS1配置为CLA数据空间(存放共享变量)
MemCfgRegs.LSxMSEL.bit.MSEL_LS0 = 1;
MemCfgRegs.LSxCLAPGM.bit.CLAPGM_LS0 = 0; // 0表示数据空间
MemCfgRegs.LSxMSEL.bit.MSEL_LS1 = 1;
MemCfgRegs.LSxCLAPGM.bit.CLAPGM_LS1 = 0;
// 3. 初始化CLA消息RAM(用于额外通信)
MemCfgRegs.MSGxINIT.bit.INIT_CLA1TOCPU = 1;
while(MemCfgRegs.MSGxINITDONE.bit.INITDONE_CLA1TOCPU != 1);
EDIS;
}
2.2 共享变量在数据空间的具体定位(adc_epwm_cpu02.c)
#pragma DATA_SECTION 是连接CPU和CLA的关键纽带。以下变量均被放置在 CLADataLS0(即物理LS0 RAM),使得CPU2和CLA均可直接寻址:
| 变量名 | 数据类型 | 功能描述 | 代码中的位置 |
|---|---|---|---|
ClaPCurrVal | volatile Uint32 | 初级电流前8通道的平方累加和 | adc_epwm_cpu02.c |
ClaPCurrVal1 | volatile Uint32 | 初级电流后8通道的平方累加和(拆分防竞争) | adc_epwm_cpu02.c |
ClaVoltVal | volatile Uint32 | 电压平方累加和 | adc_epwm_cpu02.c |
ClaTask1Result1 | volatile Uint32 | 合并后的总电流平方和(供CPU中断读取) | adc_epwm_cpu02.c |
ClaTask1Result2 | volatile Uint32 | 电压平方和(供CPU中断读取) | adc_epwm_cpu02.c |
WeldProcessVariable1 | volatile struct | 包含CLA Task2/3计算的RMS、峰值等焊接参数 | adc_epwm_cpu02.c |
tempAdc0/tempAdc1 | volatile uint16_t | 用于CLA汇编指令中暂存ADC原始值 | adc_epwm_cpu02.c |
sharedVars | volatile struct | 用于测量逆变频率等时间参数 | adc_epwm_cpu02.c |
关键设计思想:将
ClaPCurrVal拆分为Val和Val1是为了规避32位数据竞争。在CLA中,MADD32指令对32位内存的读-改-写操作不是原子的。若CPU在CLA写中间插入读取(尽管代码尽量避免,但中断延迟可能导致重叠),可能读脏数据。拆分后,CPU只读合并后的ClaTask1Result1,而合并操作在CLA内部完成,极大缩小了竞争窗口。
2.3 头文件声明(cla_shared.h)
此文件作为CPU和CLA编译时的“契约”,使用 extern 声明所有共享变量。注意 volatile 关键字必不可少,防止编译器优化掉对共享内存的重复访问。
c
// 文件:cla_shared.h extern volatile Uint32 ClaPCurrVal; extern volatile Uint32 ClaPCurrVal1; extern volatile Uint32 ClaTask1Result1; extern volatile Uint32 ClaTask1Result2; // ... 以及所有CLA任务和CPU ISR的原型
第三部分:CLA任务的具体实现深度解析(RMSCalculate.cla)
您的 .cla 文件包含了Task1~Task8的实现,其中Task1~Task3承担了核心的数值预处理工作。
3.1 Task1:高速ADC结果读取与平方累加(硬实时)
触发源:ADCINT2(ADC转换完成)。
执行频率:与PWM周期同步(4kHz或8kHz)。
代码剖析(以第一个通道为例):
c
// RMSCalculate.cla - Task1
__interrupt void Cla1Task1 ( void ) {
// 1. 清零累加寄存器(防止上次残留)
asm(" MMOVI32 MR0, #0"); // MR0用于暂存转换结果
asm(" MMOVI32 MR1, #0"); // MR1用于累加
// 2. 读取ADC结果寄存器(12位整型)
tempAdc0 = AdcaResultRegs.ADCRESULT0;
asm(" MNOP"); // 编译器屏障,确保读取完成
// 3. 整型转浮点、平方、再转回整型累加
asm(" MI16TOF32 MR1, @_tempAdc0"); // 12位整型 -> 浮点
asm(" MMPYF32 MR2, MR1, MR1"); // 浮点平方
asm(" MF32TOUI32 MR0, MR2"); // 浮点 -> 32位整型
asm(" MMOV32 MR1,@_ClaPCurrVal"); // 加载当前累加值
asm(" MADD32 MR2, MR1, MR0"); // 整型加法
asm(" MMOV32 @_ClaPCurrVal , MR2");// 存回
// ... 重复通道1~14,其中前8个累加到ClaPCurrVal,后8个累加到ClaPCurrVal1
// 通道15(电压)累加到ClaVoltVal
// 4. 合并结果,供CPU中断使用
ClaTask1Result1 = ClaPCurrVal + ClaPCurrVal1;
ClaTask1Result2 = ClaVoltVal;
}
为什么用汇编而不用C?
CLA的C编译器支持有限,使用内联汇编可以精准控制寄存器分配(MR0~MR3),并确保单周期完成关键运算,最大程度压缩任务执行时间(需小于125us的PWM周期)。
3.2 Task2与Task3:周期性统计与RMS计算(准实时)
Task2由CPU在特定时刻(如每100个中断)触发,或者通过软件强制触发。它负责持续累加瞬时值的平方(从Task1间接获取),并追踪峰值。
c
// RMSCalculate.cla - Task2
__interrupt void Cla1Task2 ( void ) {
// 累加平方和(用于后续RMS)
WeldProcessVariable1.ClaTask2CurrSum +=
WeldProcessVariable1.ClaTask2CurrCurrentVal *
WeldProcessVariable1.ClaTask2CurrCurrentVal;
// 追踪峰值
if(WeldProcessVariable1.ClaTask2CurrCurrentVal >
WeldProcessVariable1.ClaTask2CurrPeekPv) {
WeldProcessVariable1.ClaTask2CurrPeekPv =
WeldProcessVariable1.ClaTask2CurrCurrentVal;
}
// 计数
WeldProcessVariable1.PirCNT++;
}
Task3在采样点数达到预设值(WeldProcessVariable1.ClaTask2FlagSet)后由CPU强制触发(Cla1ForceTask3()),执行开方运算得到最终RMS。
重要警告:您的Task3中使用了
__sqrt(),这是C28x CPU的库函数,CLA无法直接调用!在CLA中必须使用硬件指令MEISQRTF32。正确写法应为:c
float temp = (float)sum / count; asm(" MEISQRTF32 MR0, MR1"); // MR1为输入,MR0输出 // 或使用CLAmath库的 __meisqrtf32()否则链接器会报错或生成软件模拟开方,极耗时间。
第四部分:CPU2初始化和中断服务程序(cla_cpu01.c & adc_epwm_cpu02.c)
4.1 CLA初始化流程(main函数中调用)
c
// adc_epwm_cpu02.c - main
void main(void) {
// 1. 基础系统时钟、GPIO、PIE初始化...
InitSysCtrl(); InitPieCtrl(); InitPieVectTable();
// 2. 【关键】配置CLA内存和任务向量
CLA_configClaMemory(); // 见第二部分
CLA_initCpu2Cla1(); // 设置MVECT1~8,配置触发源
// 3. 配置ADC(设置SOC0~15的触发源为EPWM1/2)
ConfigureADC();
CurrControlMode = SetupADCEpwm(4, 3, 5); // 初级电流通道4,电压通道3
// 4. 配置EPWM以产生SOC触发
InitEPwm1Example(); InitEPwm2Example();
// 5. 使能PIE中断(CLA1_1~8对应PIE组11)
PieCtrlRegs.PIEIER11.all = 0xFFFF;
IER |= M_INT11;
EINT; // 开启全局中断
}
4.2 触发源配置详解(CLA_initCpu2Cla1)
c
// cla_cpu01.c
void CLA_initCpu2Cla1(void) {
EALLOW;
// 1. 设置任务入口地址
Cla1Regs.MVECT1 = (uint16_t)(&Cla1Task1);
// ...
// 2. 使能任务及IACK指令
Cla1Regs.MCTL.bit.IACKE = 1;
Cla1Regs.MIER.all = 0x00FF; // 使能所有8个任务
// 3. 【核心】将ADCINT2映射为Task1的触发源
DmaClaSrcSelRegs.CLA1TASKSRCSEL1.bit.TASK1 = 2; // 2代表ADCINT2
// 4. 注册CPU端的任务结束中断ISR
PieVectTable.CLA1_1_INT = &cla1Isr1;
// ...
EDIS;
}
4.3 CPU中断回调函数(数据消费端)
cla1Isr1(对应Task1结束):
负责将CLA算出的12次采样平方和进行累加,并最终开方得到RMS(用于PID)。
c
// cla_cpu01.c
__interrupt void cla1Isr1 () {
// 跳过前11个采样点(去除暂态),第12个计算RMS
if(ClaTask1IntIndex > 0 && ClaTask1IntIndex < 12) {
ClaSum1 += ClaTask1Result1; // 电流平方累加
}
ClaSum2 += ClaTask1Result2; // 电压平方累加
if(++ClaTask1IntIndex == 12) {
// 计算真实RMS(注意180代表点数:12次中断 * 15个有效通道)
ClaRms1 = (Uint32)(__sqrt((float)ClaSum1 / 180));
ClaRms2 = (Uint32)(__sqrt((float)ClaSum2 / 12));
// 清零,准备下一轮
ClaSum1 = 0; ClaSum2 = 0;
ClaTask1IntIndex = 0;
ClaFinish = 1; // 通知主循环数据就绪
}
PieCtrlRegs.PIEACK.all = M_INT11;
}
cla1Isr3(对应Task3结束):
处理焊接质量的上下限判定,并最终将结果打包发送给CPU1。
第五部分:CPU主循环如何消费CLA数据
在 adc_epwm_cpu02.c 的 main() 主循环中,CPU不断检测 ClaFinish 标志,一旦置位便读取 ClaRms1 进行PID调节,并更新PWM比较值。
c
// adc_epwm_cpu02.c - main loop (示意)
do {
// 等待CLA Task1完成一轮数据采集
if(ClaFinish) {
ClaFinish = 0;
// 1. 读取最新RMS值
float feedback = (float)Pri_rms(); // 调用函数处理ClaRms1
// 2. PID计算
PID_Out = PID_Calculate(&myPID, feedback);
// 3. 更新PWM脉宽
CMP_Value = (Uint16)PID_Out;
// 4. 数据上传到CPU1(通过IPC)
if(条件满足) WRITEtoCPU1();
}
} while(1);
第六部分:关键实战经验总结(基于您的代码)
6.1 数据竞争解决范例
您将电流平方和拆为两个32位变量,并在CLA中合并后供CPU读取。这是一种“生产者-消费者”握手的有效变种,代价是额外消耗了一个内存单元。更标准的做法是使用 CLA1ToCPUMsgRAM 并配合标志位。
6.2 中断嵌套与优先级
在 adca1_isr(ADC中断)中,您动态切换了 EPWM1/EPWM2 的SOC触发源。需注意ADC中断优先级默认高于CLA任务结束中断(PIE组1 vs 组11),若需实时响应,可调整PIE优先级。
6.3 调试监测点
-
Cla1Regs.MIRUN:指示当前正在执行的CLA任务号,若Task1长时间占用(>125us),说明代码优化不足,需缩减汇编指令或降低采样点数。 -
ClaFinish与ClaFinish1:作为简单的信号量,确保CPU不在CLA写数据时读取。您已正确使用,但需注意volatile修饰。
6.4 移植注意事项
若将本工程移植到其他项目:
-
修改ADC通道:调整
SetupADCEpwm的channel参数。 -
调整采样点数:修改
ClaTask1IntIndex的判断阈值(当前为12),以及RMS计算的分母。 -
替换开方函数:若使用CLA Task3,务必替换
__sqrt为MEISQRTF32。
第七部分:完整代码调用关系索引表
| 功能模块 | 涉及文件 | 关键函数/变量 |
|---|---|---|
| CLA内存划分 | cla_cpu01.c | CLA_configClaMemory() |
| CLA任务向量设置 | cla_cpu01.c | CLA_initCpu2Cla1() |
| 共享变量定义 | adc_epwm_cpu02.c | #pragma DATA_SECTION(... "CLADataLS0") |
| CLA任务实体 | RMSCalculate.cla | Cla1Task1~8 |
| CPU端中断响应 | cla_cpu01.c | cla1Isr1~8 |
| ADC+PWM触发 | adc_epwm_cpu02.c | SetupADCEpwm(), InitEPwm1Example() |
| 主循环消费数据 | adc_epwm_cpu02.c | main() 中的 if(ClaFinish) 块 |
| 数据上传CPU1 | adc_epwm_cpu02.c | WRITEtoCPU1() |
通过以上配置,您的系统实现了 “ADC采样 → CLA预处理 → CPU中断响应 → PID调节 → PWM输出” 的全链路硬件加速。CLA的介入使得CPU2的负载大幅降低,能够从容处理焊接状态机、IPC通信和复杂的人机交互逻辑。如需进一步优化,可以将PID计算也下放到CLA Task2/3中,实现真正的控制环路全硬件化。
1828

被折叠的 条评论
为什么被折叠?



