2. CMD文件与存储器映射:从入门到实战

CMD文件,说白了就是DSP的“地址分配地图”。很多刚入行的朋友觉得它就是个配置文件,随便抄一个就能用。我刚开始做DSP开发时也这么想,结果有一次程序跑飞了,查了整整两天才发现是CMD文件里一个段地址写错了。从那以后,我再也不敢小看这个文件了。

今天我们就来彻底搞懂CMD文件。我会从语法讲起,再到L2SRAM和DDR的地址配置,最后聊聊代码段和数据段怎么分配才合理。

2.1 CMD文件语法解析

CMD文件其实就两大块:MEMORY 和 SECTIONS。前者定义硬件上有哪些存储区域,后者告诉链接器把代码和数据放到哪里去。

2.1.1 MEMORY指令

先看一个典型的MEMORY定义:

MEMORY
{
    VECS:      o = 0x00800000  l = 0x00000200   /* 中断向量表 */
    L2SRAM:    o = 0x00800200  l = 0x0003FE00   /* 片内L2 SRAM */
    DDR2:      o = 0x80000000  l = 0x10000000   /* 外部DDR2 */
}

这里 o 是起始地址,l 是长度。我个人习惯把每个区域都加上注释,方便后期维护。你想想看,三个月后你自己回来看这段代码,没有注释真的会抓狂。

小技巧: 命名规则要统一。我一般用大写加下划线,比如 L2_SRAM、DDR_MEM。这样在SECTIONS里引用时一目了然。

2.1.2 SECTIONS指令

SECTIONS才是CMD文件的核心。它决定了你的代码、数据、堆栈到底放在哪个物理地址上。

SECTIONS
{
    .vectors  > VECS
    .text     > L2SRAM
    .bss      > L2SRAM
    .stack    > L2SRAM
    .far      > DDR2
    .switch   > L2SRAM
    .cinit    > L2SRAM
    .const    > L2SRAM
}

这里 .text 是代码段,.bss 是全局变量段,.stack 是栈。每个段都指定了存放区域。

注意: 中断向量表 .vectors 必须放在地址0处,或者芯片指定的向量表基地址。我曾经见过有人把 .vectors 放到DDR里,结果中断一触发就死机。这个坑我替你们踩过了。

2.2 片内L2SRAM与DDR地址映射配置

不同DSP芯片的地址映射不一样。以C6678为例,它的L2SRAM地址范围是 0x00800000 - 0x0083FFFF,总共256KB。DDR3的地址范围是 0x80000000 - 0x8FFFFFFF,大小取决于板子上焊了多少内存。

存储区域 起始地址 结束地址 大小 访问速度
L1P (程序缓存) 0x00E00000 0x00E07FFF 32KB 最快
L1D (数据缓存) 0x00F00000 0x00F07FFF 32KB 最快
L2 SRAM 0x00800000 0x0083FFFF 256KB 快
DDR3 0x80000000 0x8FFFFFFF 256MB-2GB 较慢

为什么要把代码放到L2SRAM而不是DDR?说白了就是速度。L2SRAM的访问延迟只有几个时钟周期,而DDR要几十甚至上百个周期。但L2SRAM只有256KB,放不下太大的程序。

我在项目中遇到过这样的情况:一个音频算法,核心处理函数放在L2SRAM里跑,初始化代码和查表数据放在DDR里。这样既保证了实时性,又解决了空间不足的问题。

2.3 代码段与数据段分配策略

段分配不是随便写的。我总结了一套经验法则,分享给大家:

  1. 时间关键代码 → L2SRAM(中断服务函数、核心算法)
  2. 频繁访问的数据 → L2SRAM(全局变量、栈)
  3. 大块数据 → DDR(图像帧、音频缓冲、查表数据)
  4. 只读常量 → DDR或Flash(系数表、字符串)
  5. 初始化数据 → DDR(启动时加载,运行时不改)

来看一个实际项目的CMD配置:

MEMORY
{
    VECS:      o = 0x00800000  l = 0x00000200
    L2SRAM:    o = 0x00800200  l = 0x0001FE00   /* 只留128KB给L2 */
    DDR_CODE:  o = 0x80000000  l = 0x00800000   /* 8MB给代码 */
    DDR_DATA:  o = 0x80800000  l = 0x0F800000   /* 剩余给数据 */
}

SECTIONS
{
    .vectors    > VECS
    .text:isr   > L2SRAM       /* 中断服务函数放L2 */
    .text:fast  > L2SRAM       /* 快速算法放L2 */
    .text       > DDR_CODE     /* 其他代码放DDR */
    .bss        > L2SRAM       /* 全局变量放L2 */
    .stack      > L2SRAM       /* 栈放L2 */
    .far        > DDR_DATA     /* 大数组放DDR */
    .const      > DDR_DATA     /* 常量放DDR */
    .cinit      > DDR_CODE     /* 初始化表放DDR */
    .switch     > L2SRAM       /* 跳转表放L2 */
}
核心原则: 把最频繁访问的代码和数据塞进L2SRAM,剩下的扔到DDR。如果L2SRAM不够用,优先保证中断和实时任务的驻留。

嗯,这里要注意一个细节:.text:isr 和 .text:fast 这种写法是GCC风格的段命名,TI的编译器也支持。你可以把不同优先级的代码分到不同子段,然后分别指定存放位置。

2.4 知识体系总览

下面这张图把CMD文件的核心逻辑串起来了。我建议你保存下来,写CMD文件时对照着看:

CMD文件与存储器映射知识体系 CMD文件 MEMORY 定义 SECTIONS 分配 L2SRAM (256KB) DDR (256MB+) L1P/L1D (缓存) .text (代码段) .bss / .stack (数据段) .const / .far (常量/大数组) 核心策略:时间关键代码 + 频繁访问数据 → L2SRAM 大块数据 + 只读常量 + 非实时代码 → DDR

2.5 常见问题与避坑指南

最后,我把自己这些年踩过的坑整理一下,希望能帮大家少走弯路:

  • 地址重叠:两个MEMORY区域地址范围重叠了,链接器不会报错,但运行时会互相覆盖。我曾经在调试一个音频算法时,发现数据莫名其妙被改写,查了三天才发现是L2SRAM和某个外设映射地址重叠了。
  • 段未对齐:某些段要求对齐到特定边界(比如256字节)。如果不对齐,程序可能无法正常加载。我建议在SECTIONS里显式指定对齐:.text > L2SRAM align = 256
  • 栈溢出:.stack段分配太小,递归调用或局部变量过多时栈会溢出。我的经验是:先给个保守值(比如4KB),运行后通过查看栈指针最低位置来调整。
  • DDR初始化时序:如果代码段放在DDR里,但DDR控制器还没初始化完成,程序就会死掉。解决办法是把启动代码放在L2SRAM里,等DDR初始化完成后再跳转到DDR里的主程序。
重要提醒: 每次修改CMD文件后,一定要做三件事:1) 重新编译整个工程;2) 查看.map文件确认段地址是否正确;3) 在调试器里检查内存布局。这三步缺一不可。