2. DSP基础回顾
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 是长度。我个人习惯把每个区域都加上注释,方便后期维护。你想想看,三个月后你自己回来看这段代码,没有注释真的会抓狂。
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 是栈。每个段都指定了存放区域。
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 代码段与数据段分配策略
段分配不是随便写的。我总结了一套经验法则,分享给大家:
- 时间关键代码 → L2SRAM(中断服务函数、核心算法)
- 频繁访问的数据 → L2SRAM(全局变量、栈)
- 大块数据 → DDR(图像帧、音频缓冲、查表数据)
- 只读常量 → DDR或Flash(系数表、字符串)
- 初始化数据 → 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 */
}
嗯,这里要注意一个细节:.text:isr 和 .text:fast 这种写法是GCC风格的段命名,TI的编译器也支持。你可以把不同优先级的代码分到不同子段,然后分别指定存放位置。
2.4 知识体系总览
下面这张图把CMD文件的核心逻辑串起来了。我建议你保存下来,写CMD文件时对照着看:
2.5 常见问题与避坑指南
最后,我把自己这些年踩过的坑整理一下,希望能帮大家少走弯路:
- 地址重叠:两个MEMORY区域地址范围重叠了,链接器不会报错,但运行时会互相覆盖。我曾经在调试一个音频算法时,发现数据莫名其妙被改写,查了三天才发现是L2SRAM和某个外设映射地址重叠了。
- 段未对齐:某些段要求对齐到特定边界(比如256字节)。如果不对齐,程序可能无法正常加载。我建议在SECTIONS里显式指定对齐:
.text > L2SRAM align = 256 - 栈溢出:.stack段分配太小,递归调用或局部变量过多时栈会溢出。我的经验是:先给个保守值(比如4KB),运行后通过查看栈指针最低位置来调整。
- DDR初始化时序:如果代码段放在DDR里,但DDR控制器还没初始化完成,程序就会死掉。解决办法是把启动代码放在L2SRAM里,等DDR初始化完成后再跳转到DDR里的主程序。