F28P55x CLA 配置踩坑:LS8 做程序 RAM 为什么变量全是 0

将 ADC 采样逻辑从 CPU 中断迁移到 CLA Task1 时,遇到一个隐蔽的问题:无论怎么触发,共享变量始终是 0,CLA 没有运行。本文记录完整的排查过程和两种可行方案。


结论

LS0~LS7 中任意一块做 CLA program memory(如 LS2),另一块做 CLA 数据区(如 LS7)。初始化顺序和 memcpy 写法都是标准的,无需特殊处理。这是推荐方案。

LS8/LS9 做 CLA program memory 同样可行,但需同时处理两个问题:memcpy 目标地址要加 0x10000U 偏移,且 memcpy 必须在 MEMCFG_init() 之前执行。

核心区别:

  • LS0~LS7 做 CLA program:普通 memcpy,SysConfig 自动生成代码可直接使用。
  • LS8/LS9 做 CLA program:memcpy 地址加 0x10000U,且必须在 MEMCFG 配置 LS8 之前执行。

方案一:LS2 + LS7(推荐)

SysConfig 配置

<!-- 图1:SysConfig MEMCFG 配置截图,LS2=CLA program memory,LS7=CPU/CLA shared data memory -->

复制代码
LS2 RAM → CLA program memory
LS7 RAM → CPU/CLA shared data memory

linker cmd

LS0~LS7 的 CPU 地址和 CLA 地址相同,不存在双映射问题。以 LS2 为例:

复制代码
RAMLS2  : origin = 0x009000, length = 0x000800
RAMLS7  : origin = 0x00B800, length = 0x000800

Cla1Prog :
{
    *(Cla1Prog)
} LOAD = FLASH_BANK1,
  RUN  = RAMLS2,
  LOAD_START(Cla1ProgLoadStart),
  LOAD_SIZE(Cla1ProgLoadSize),
  RUN_START(Cla1ProgRunStart),
  ALIGN(8)

.const_cla : LOAD = FLASH_BANK1,
             RUN  = RAMLS7,
             LOAD_START(Cla1ConstLoadStart),
             LOAD_SIZE(Cla1ConstLoadSize),
             RUN_START(Cla1ConstRunStart),
             ALIGN(8)

ClaSharedRam : > RAMLS7, ALIGN(4)
.scratchpad  : > RAMLS7, ALIGN(4)
.bss_cla     : > RAMLS7, ALIGN(4)

Flash 工程的 memcpy

LS2 方案不需要地址偏移:

复制代码
memcpy((uint32_t *)&Cla1ProgRunStart,
       (uint32_t *)&Cla1ProgLoadStart,
       (uint32_t)&Cla1ProgLoadSize);

初始化顺序

LS2 不存在地址双映射问题,SysConfig 自动生成的 Board_init() 顺序(先 CLA_init()MEMCFG_init())对该方案适用,无需调整。


方案二:LS8 + LS7(进阶)

LS8 与 LS2 的区别

F28P55x 的 LS8/LS9 在物理上位于高地址,超出 CLA 地址总线的 16-bit 范围。TI 通过硬件重映射,使 CLA 能够通过低地址访问:

<!-- 图2:LS8 地址双映射示意图,左边 CPU 视角 0x14000,右边 CLA 视角 0x4000,中间标注偏移 0x10000 -->

复制代码
CPU 视角:LS8 = 0x014000
CLA 视角:LS8 = 0x004000(硬件重映射)
偏移量  :0x010000

LS0~LS7 没有这个问题,CPU 和 CLA 看到同一个地址。

SysConfig 配置

<!-- 图3:SysConfig MEMCFG 配置截图,LS7=CPU/CLA shared data memory,LS8=CLA program memory -->

复制代码
LS7 RAM → CPU/CLA shared data memory
LS8 RAM → CLA program memory

自动生成代码在 LS8 场景下的两个问题

SysConfig 生成的 Board_init() 处理 Flash 工程时,先执行 CLA_init()(内含 memcpy),再执行 MEMCFG_init()。该顺序对 LS2 合理,但用于 LS8 时存在两个必须同时解决的问题:

问题一,地址偏移缺失。自动生成的 memcpy 直接以 &Cla1ProgRunStart 为目标地址,而 linker 中 RAMLS8_CLA origin = 0x004000,因此 Cla1ProgRunStart 的值是 CLA 视角的 0x4000,并非 CPU 能写入的 0x14000。缺少 0x10000 偏移,程序被拷贝到错误地址,CLA 取指时得到空内容。

问题二,配置顺序。LS8 一旦被 MEMCFG_init() 配置为 CLA program memory,CPU 便无法再写入。若按自动生成的顺序先配置后拷贝,memcpy 会静默失败,结果与问题一相同。

两个问题缺一不可。正确顺序为:先 memcpy(含偏移),再 MEMCFG 配置 LS8,最后初始化 CLA Task。

<!-- 图4:初始化顺序对比,左侧错误:MEMCFG→memcpy→CLA Task init,右侧正确:memcpy→MEMCFG→CLA Task init -->

不能修改 SysConfig 生成的文件

CLA_init()MEMCFG_init() 都位于 SysConfig 生成的 board.c 中,该文件在每次编译时被重新生成并覆盖,因此不能直接修改 board.c,任何改动都会在下次编译时丢失。

正确做法是在 main.c 中重写 CLA 的初始化逻辑:自己实现拷贝函数(带 0x10000U 偏移),不调用 Board_init(),改为手动按正确顺序调用各初始化函数。MEMCFG_init()myCLA0_init() 可继续复用 SysConfig 生成的版本,关键在于调用顺序由 main.c 控制。

linker cmd

复制代码
RAMLS7     : origin = 0x00B800, length = 0x000800
RAMLS8_CLA : origin = 0x004000, length = 0x002000  /* CLA 视角地址 */

Cla1Prog :
{
    *(Cla1Prog)
} LOAD = FLASH_BANK1,
  RUN  = RAMLS8_CLA,
  LOAD_START(Cla1ProgLoadStart),
  LOAD_SIZE(Cla1ProgLoadSize),
  RUN_START(Cla1ProgRunStart),
  ALIGN(8)

.const_cla : LOAD = FLASH_BANK1,
             RUN  = RAMLS7,
             LOAD_START(Cla1ConstLoadStart),
             LOAD_SIZE(Cla1ConstLoadSize),
             RUN_START(Cla1ConstRunStart),
             ALIGN(8)

ClaSharedRam : > RAMLS7, ALIGN(4)
.scratchpad  : > RAMLS7, ALIGN(4)
.bss_cla     : > RAMLS7, ALIGN(4)

在 main.c 中重写拷贝函数

main.c 中定义一个仅负责拷贝的函数,目标地址加 0x10000U 偏移。注意此函数只做拷贝,不包含 myCLA0_init(),以便与内存配置、Task 初始化在 main() 中按正确顺序分开调用:

复制代码
static void CLA_copyProgram(void)
{
    extern uint32_t Cla1ProgRunStart;
    extern uint32_t Cla1ProgLoadStart;
    extern uint32_t Cla1ProgLoadSize;
    extern uint32_t Cla1ConstRunStart;
    extern uint32_t Cla1ConstLoadStart;
    extern uint32_t Cla1ConstLoadSize;

    /* Cla1ProgRunStart = 0x4000(CLA视角),CPU 写 LS8 需要 0x14000 */
    memcpy((uint32_t *)((uint32_t)&Cla1ProgRunStart + 0x10000U),
           (uint32_t *)&Cla1ProgLoadStart,
           (uint32_t)&Cla1ProgLoadSize);

    /* .const_cla 放在 LS7,CPU/CLA 地址相同,不需要偏移 */
    memcpy((uint32_t *)&Cla1ConstRunStart,
           (uint32_t *)&Cla1ConstLoadStart,
           (uint32_t)&Cla1ConstLoadSize);
}

main() 中的初始化顺序

不调用 Board_init(),改为手动按顺序调用,将拷贝置于 MEMCFG_init() 之前:

复制代码
EALLOW;
PinMux_init();
SYNC_init();
ASYSCTL_init();

CLA_copyProgram();   /* 步骤一:拷贝 CLA 程序到 LS8(含偏移),此时 LS8 仍为普通 RAM,CPU 可写 */
MEMCFG_init();       /* 步骤二:将 LS8 配置为 CLA program memory */
myCLA0_init();       /* 步骤三:映射 Task 向量、设置触发源、使能 Task1 */

ADC_init();
CPUTIMER_init();
EPWM_init();
GPIO_init();
INTERRUPT_init();
EDIS;

CLA 相关三个文件

cla_shared.h / cla_shared.c

共享变量必须定义在 CPU 侧的 .c 文件中,通过 DATA_SECTION 放入共享 RAM,不能定义在 .cla 文件里,两侧数据页寻址方式不同:

复制代码
/* cla_shared.c */
#pragma DATA_SECTION(g_adcA0Raw, "ClaSharedRam");
volatile uint16_t g_adcA0Raw = 0U;

#pragma DATA_SECTION(g_adcB0Raw, "ClaSharedRam");
volatile uint16_t g_adcB0Raw = 0U;

/* cla_shared.h */
extern volatile uint16_t g_adcA0Raw;
extern volatile uint16_t g_adcB0Raw;

类型使用 uint16_t / int16_t 等固定位宽类型,避免使用 int / unsigned int。C28x 上 int 为 16-bit,跨核传递数据时容易出现隐式截断。

cla_tasks.cla

调试阶段先写固定值,验证 CLA 能运行、共享内存可写通:

复制代码
#pragma CODE_SECTION(Cla1Task1, "Cla1Prog");
__interrupt void Cla1Task1(void)
{
    g_adcA0Raw = 111;
    g_adcB0Raw = 222;
    g_adcC0Raw = 333;
    g_adcD0Raw = 444;
    HWREGH(ADCA_BASE + ADC_O_INTFLGCLR) = 0x0001U;
}

Watch 窗口确认变量为 111/222/333/444 后,替换为真实的 ADC 结果读取:

复制代码
g_adcA0Raw = HWREGH(ADCA_BASE + ADC_O_RESULT0);

踩坑过程记录

坑一:LS2 配成 data 而非 program

SysConfig 中 LS2 的默认选项是 CPU/CLA shared data memory,并非 CLA program memory。data memory 不能被 CLA 取指,Task1 不会执行。

修复:SysConfig → MEMCFG → LS2 → 改为 CLA program memory

坑二:CLA_init 在 MEMCFG_init 之前(LS2 场景)

SysConfig 自动生成的 Board_init() 顺序为先 CLA_init()MEMCFG_init()。对 LS2 而言此顺序适用,当时误判为问题来源,走了弯路。

坑三:改用 LS8 后 memcpy 仍写不进去

换到 LS8 后,在 CCS 中单步执行完 memcpy,立即查看 Memory Browser 的 0x014000,内容仍为全 0。

根本原因是前述两个问题同时存在:地址未加偏移,且此时 MEMCFG 已将 LS8 配为 CLA program memory,CPU 写不进去。在 TI E2E 上确认 TI 官方 cla_asin_ls8_9 示例的流程是先 memcpy 再配置 LS8,偏移同样不可省略。按此方案修改后,0x014000 出现指令数据,CLA_forceTasks 触发后变量正确变为 111/222/333/444。

坑四:触发源配置

SysConfig 中 CLA Task1 的 Trigger Source 选 ADCA1 时,需要 ADCA 的 ADCINT1 使能且 EPWM SOC 正常工作。调试阶段建议先改为 Software,用 CLA_forceTasks 手动触发,排除触发链路的干扰。


CLA 在 CCS 中的调试

CLA 是独立处理器核,需在 Debug 视图中单独 Connect:

  1. 暂停程序,在 Debug 视图找到 CLA1 核,右键 Connect Target
  2. .cla 源文件中正常打断点,CLA1 核会在断点处暂停
  3. Watch 窗口可直接查看共享变量,CPU 和 CLA 两侧都能读到当前值

CPU 核与 CLA 核的断点相互独立,互不影响。


参考