理解CMD原理到掌握CMD编写 DSP(一)

一、CMD文件的起源 
在DSP系统中,存在大量的、各式各样的存储器,CMD文件所描述的,就是开发 工程师对物理存储器的管理、分配和使用情况。 
先复习一下存储器。目前的物理存储器,种类繁多,原理、功能、参 数、 速度各不相同, 有 PROM、 EPROM、 EEPROM、 FLASH、 NAND FLASH、 NOR FLASH 等(ROM 类) ,还有SRAM、DRAM、SDRAM、DDR、DDR2、FIFO 等(RAM 类) 。 从断电后保存数据的能力来看,存储器只有两类:断电后仍然能够保存数据的叫做非易失性存储器(non-volatile,本文称为ROM 类) ,数据丢失的叫做易失性存储器(本文
称为 RAM 类) ; ROM 类的芯片都是非易失性的, 而 RAM 类都是易失性的。 即使同为 ROM类或同为 RAM 类存储器,仍然存在速度、读写方法、功耗、成本等诸多方面的差别。比 如 SRAM 的读写速度,从过去的15ns、12ns,提高到现在的 8ns、10ns,FLASH的读取 速度从 120ns、75ns,到现在的 40ns、30ns。 
理想的存储器就应当无论掉电与否可永远保存数据(即非易失),而且希望读写速度为每秒无穷多字节,是 0ns(即速度快)。 
“非易失”和“速度”就是一对典型的矛盾。非易失的 ROM 类存储器,可以“永远”地 保存数据,但读写速度却很低,比如30ns;RAM 的速度(8ns)一般都比 ROM(30ns) 快得多,但却不能掉电保存。这是很无奈的现实。假如有那么一天,ROM 类的读写速度 和 RAM 一样快,或者RAM 也可以掉电保存数据,就不存在易失和非易失的区别了,那将 是革命性的进步。那时,智能芯片和智能系统的设计将会有很大的变化,编写 CMD 文件 就会很简单,甚至不需要了。 

在用到存储器时,需要考虑:数据保存和速度。一般情况下,程序代码一般都要存储在ROM 类存储器中,以保证断电后数据能保存。程序运行时,为提高速度,须在RAM 中运行。 ROM 和 RAM 需配合使用解决数据永久保存并程序快速运行的问题。另外,出于功能、参数、速度、读写方法、功耗、工艺、成本等考虑,往往要同时使用多种存储器。 

CMD的专业名称叫链接器配置文件,这两类存储器为主轴,用户通过编写 CMD 文件,来管理、分配系统中的所有物理存储器和地址空间,以解决存储器的“非易失”和“速度”矛盾 DSP芯片的片内存储器, 只要没有被TI占用, 用户都可以全权支配。用户编写完的程序经过开发环境(编译器)的编译 ,转换为芯片可以识别的机器码,最后下载到芯片中运行。CMD 文件就是在编译源程序、生成机器码的过程中,发挥作用的,它作为用户的命令或要求,交给编译器去执行:就这么分配物理存储器和地址空间。 

CMD文件包括两方面的内容: 

1、用户声明系统的存储器资源。包括 DSP 芯片自带、外扩 的存储器和空间,都要一一声明:有哪些存储器, 及位置和大小。
2、用户声明资源分配情况 这是编写 CMD文件的重点。 

二、编写CMD文件之——声明系统中可用的存储器资源。 
TI 规定,CMD文件的资源清单用关键字“MEMORY”作为标识,具体内容写在后 面的大括号 { } 里面。如下面的形式: 
MEMORY 

PAGE 0: 
 xxx  : org = 0x1234 ,  length = 0x5678  /*This is my house.*/ 
PAGE 1: 
 aaa  : org = 0x1357 ,  length = 0x2468  /*My home here.*/ 

其中,MEMORY,PAGE n,org,length,包括冒号、等于号、花括号,都是关键 字符,必不可少。 

PAGE n表示把可用的资源空间再划分成几个大块,最多允许分 256块,从PAGE 0到 PAGE 255。 

如果把 MEMORY比作图书馆, PAGE n就是其中的 “社科类” 、 “工程类” 、“外文类”等。大家都习惯于把PAGE 0作为程序空间,把 PAGE 1作为数据空间。凡智能芯片都离不开这两种“空间” ,冯·诺依曼结构和哈佛结构都是建立在程序空间和数据空间两种结构的基础上,我们面对的DSP也是如此。

CMD文件中还可以写上注释,用“/*”和“*/”包围起来,但不允许用“//” ,这一 点和 C语言不同。 
上面的例子,仅仅就是个“例子” ,不针对任何特定的芯片。带注释的语句有两行, 每一行都是一项声明,表示在程序空间或数据空间下,再细分更小的块,好比是“社科类” 又分了几个书架。比如 
xxx : org = 0x1234 , length = 0x5678 

表示在程序空间 PAGE 0里面,划分出一个命名为 xxx的小块空间,起始地址从存储单元0x1234 开始, 总长度为 0x5678 个存储单元, 地址和长度通常都以十六进制数表示。 所以,xxx空间的实际地址范围从 0x1234开始,到 0x1234 + 0x5678 – 1 = 0x68AB 结束(起始地址加长度再减一) ,这一段连续的存储区域,就属于xxx小块了。

上面的例子中,PAGE 0和 PGAE 1各包含了只有一个“小块” ,用户可以根据自己的情况,按照同样的格式任意增加。在支持多个CMD文件的开发环境里,某个或某几个 CMD文件中, “小块”的数量以为 0,也就是说,关键字 PAGE 0或 PAGE 1下面,可以是空白的。但不允许所有的CMD 文件的同一空间都是空白。另外,没有资料提到过“小块”数量上限的限制,需要去查阅文档或咨询TI 公司。 

很多关键字,还允许有别的写法,比如“org”可以写为“o” , “length”可以写为 “len” 。这些规定和其他细节,可以去查阅 TI 的 pdf 文档,一般叫做“xxxxx Assembly  Language Tools User's Guide.pdf” ,汇编语言工具指南,xxxxx是芯片的型号或系列。  
实践证明,至少对于 C2000 系列的 2407 和 2812 而言,存储单元的单位是“字 word” ,即 16bit。但 TI 的文档却说是“字节byte” ,应当是 TI 写错了。 
要特别注意以下几点: 
1、必须在DSP芯片的空间分配的架构体系以内,分配所有的存储器。这里举两个 例子: 
a、对于 2407,程序空间和数据空间都是从地址 0x0000 到 0xFFFF,最大数 值是四个 F,共 64K 字范围。所以,2407 的 CMD文件中不能出现五位数的地址,也不允 许任何一个小块空间的地址范围覆盖到 64K 以外的区域,因为2407根本就无法控制这些 区域,或者说不能访问、无法寻址。要注意,起始地址和长度不要算错了。2812也有同样 的问题。 
b、2407的数据空间里,0x0100~0x01FF 和其他几块区域,是 TI 声明的保留 空间(Reserved 或 illegal) ,也是芯片无法访问的,分配资源的时候不能涉及到这些区域。 同样地,2812的程序空间和数据空间,都有大片的保留区域,不能使用。 
2、每个小块的空间,必须是一片连续的区域。因为,编译器在使用这块区域的时 候,默认它是连续的,而且每个存储单元都是可用的。 
3、同一空间下面,任何两个小块之间,不能有任何的相互覆盖和重叠。  在外扩存储器时, 要保证片外的存储空间之间, 特别是片外与片内的存储空间之间, 不要发生冲突。有些空间,已经被 DSP 芯片的内部存储器占用了,用户是不可更改的, 或只能通过模式配置,在一定范围内改动,用户自行扩展存储器时,要避开这些地方。 
4、用户所声明的空间划分情况,必须与用户电路板的实际情况相符合! 
PS.对于自制电路板,这是很容易出错的地方,通常会出现两种错误: 
a、在设计硬件电路的时候,通常用 CPLD 作为片外存储器的选通信号,用verilog 或者 VHDL 进行编程;也有用 74 或 4000 系列芯片来搭建的,已经很少了。如果CPLD 逻辑出错,或者逻辑并没有真正写入 CPLD 芯片里面,即使 CMD 文件是正确的,即使编译已经通过,在仿真下载或者烧写的时候,PC机都会报错而无法继续操作。 
b、 电路板有虚焊的地方, 主要发生在DSP 芯片的管脚、 电平转换芯片的管脚,及片外存储器的管脚上。这种情况,效果等同于上面所说的CPLD 逻辑错误。更要命的是,补焊一次、两次甚至几次,虚焊仍然存在,这最容易把人搞糊涂了。
出现硬件错误时一定要保持清醒的头脑:先检查原理设计;再检查硬件电路板,保证逻辑正确,焊接可靠;最后再去检查 CMD文件。 
5、初学者会找一些现成的 CMD 文件来,大胆 改一改,试一试。DSP芯片 上的存储器,只要没有被TI 用作专门的用途,用户都可以全权支配。空间的划分,是由用 户决定的,可以根据需要和个人的喜好划分,名称也可以随意起,和C语言的变量 名一样。 

三、编写CMD文件之——声明资源分配情况 
要合理地分配存储器资源,首先要搞清一个问题:资源要分配给谁?有哪些东东需 要占用存储器? 
我们看下面这段不严格的C程序: 
main() 

unsigned  int  i; 
i ++; 
这“段”程序只是笔者建立的一个模型,用它来代表几乎所有的程序:哪怕变量(包 括数组)有一千个、一万个,都用一个“i”来代表;哪怕程序主体包含了各种搬移、运算、 逻辑等动作,哪怕有一万行那么长,都用一句“i++”来表示。 
站在 TI 公司和编译器的角度,来考虑:程序经过编译以后,会 产生哪些对存储器资源有要求的“状况”? 
至少要产生两种情况: 
1、指令码,即二进制形式的指令,需要占用芯片的“程序空间” 。这些数据,完全等价于或等同于用户编写的程序,只是转换成了另一种形式而已。这种“数据”有两个特 点:a、只要用户程序编写完成,这些“数据”就已经是可知的、可预期的,是由用户编 写的程序代码和编译器共同决定的。b、在系统运行过程中,这些数据的内容不会发生任 何变化,只会被读取,不会被修改。 

2、在运行过程中,动态变化的“量” ,需要占用“数据空间” 。上面例子程序中的变量 i,就属于这种情况。这些数据,在设计师编写程序的时候,有时会预先写入具体的数值,即初始化,有时甚至根本不需要进行初始化。在运行过程中,既要被读取,又会被改写,经常在变化。设计师自己也很难确切知道,在某一时刻,这些数据的具体的数值是什么,最多只知道它们的位数、最大和最小值的范围。 

那么,什么样的物理存储器适合于数据空间使用,什么样的存储器适合于程序空间使用呢? 

简单回答:RAM存储器适合于数据空间使用,ROM存储器适合于程序空间使用。

数据空间的最基本、最首要的要求是速度快,并不要求掉电保存数据的能力,应当由RAM 类存储器承担。但是,并非数据空间只能连接 RAM 芯片,只要能够接受比较慢的速度,并且安排好芯片的控制时序,完全可以在数据空间扩展 ROM 类存储器。 

程序空间代码数据要求掉电保存,只能由 ROM 承担 。针对ROM 的读取速度慢的问题,低速芯片和高速芯片解决方案不同:对于低速智能芯片,ROM 的速度慢一点可以接受,则直接从ROM 中读取代码指令,然后译码、执行( MCS51、PIC 系列单片机)  。另外有一些低端智 能芯片,生产商通过特殊的技术手段,在一定范围内等效地提高内部程序 ROM 的读取速 度,比如 NXP 公司的 ARM 芯片 LPC213x,虽然 ARM 内核的数据接口只有 32 位,但 LPC213x 的片内 FLASH程序存储器, 与内核之间的接口居然是 128位宽度, 通过所谓 “加 速器”相连接。对于高速智能芯片,从 ROM 直接读取代码并执行,已经不能满足速度 的要求了,通常的解决方法是,把程序代码储存在 ROM 中,在每次上电运行时,通过“引 导程序”把用户代码读出并保存在RAM 中,然后从 RAM 中运行,这样做既解决了ROM 速度慢的问题,又解决了 RAM 掉电丢失数据的问题。 (Bootloader 在后面的博文中介绍)
实际操作中,并不是只有指令码和变量 i 这么简单,除这两项以外,还会出现很多 小“状况” ;而且,当芯片型号不同,甚至用户源程序不同时,出现的细节也是变化的。恰 恰就是这些变化,导致CMD文件变得复杂。  但是,任何大“状况” 、小“状况” ,都归属于对程序空间和数据空间的操作,不存在第三种空间。 (有些 DSP 的所谓“IO 空间” ,实质上是数据空间的一个变种,但又脱离 了数据空间,不属于CMD文件考虑的范围。 ) 
编写CMD文件,就是要搞清楚以下情况,并对编译器做出声明: 
1、你的系统都有哪些存储器资源? 
2、哪些存储器安排在程序空间,哪些在数据空间? 
3、你的系统会产生哪些大“状况”和小“状况”? 
4、哪些状况属于程序空间,哪些属于数据空间? 
5、程序空间的“状况”如何安排在程序空间的资源里,数据空间的“状况”如何 安排在数据空间的资源里? 
笔者想从事情的起源入手,逐步引导初学者自己去发现“资源要分配给谁?有哪些东东需要占用存储器?”这个问题的答案,所以使用了一些不正规的术语,比如“状况”这个词。 
从一个实际使用过的 2407 芯片的 CMD文件来展开说明: 
/**********************************************************************************************/ 
-stack 200h                  /* #1  */ 
/**********************************************************************************************/ 
MEMORY                  /* #2  */ 

PAGE 0 : 
 VECS  : origin = 0000h , length = 0040h  /* 中断向量   */  /* #3  */ 
 PROG  : origin = 0100h , length = 7F00h  /* 片上 FLASH  */  /* #4  */ 
 
PAGE 1 : 
 B2   : origin = 0060h , length = 0020h  /* DARAM B2 块 */  /* #5  */ 
 B0B1  : origin = 0200h , length = 0200h  /* DARAM B0B1 块 */  /* #6  */ 
 SARAM : origin = 0800h , length = 0800h  /* SARAM 块  */  /* #7  */ 
 ExtSRAM : origin = 8000h , length = 8000h  /* 外部存储器  */  /* #8  */ 

/**********************************************************************************************/ 
SECTIONS                  /* #9  */ 

 .vectors :  > VECS  PAGE 0   /* 中断向量表  */  /* #10 */ 
 .text  :  > PROG  PAGE 0   /* 代码    */  /* #11 */ 
 .cinit  :  > PROG  PAGE 0          /* #12 */ 
 
 .bss  :  > SARAM PAGE 1          /* #13 */ 
 .stack  :  > B0B1  PAGE 1          /* #14 */ 
 
 .extdata :  > ExtSRAM PAGE 1          /* #15 */ } 
/**********************************************************************************************/ 
#2行至 #8行,MEMORY  {……} 部分,即系统可用 资源的声明,包括程序空间 PAGE 0和数据空间 PAGE 1 两部分。  程序空间PAGE 0,又分为 VECS区域和 PROG 区域。  #4行所声明的PROG区域,是为用户指令码分配的存储空间,这部分空间一般都很大(如 0x7E00h) 。  #3行声明的 VECS 区域是一个特殊的“小状况” , TI 在设计 2407 的硬件电路时,用这块区域来保存各种中断服务程序的入口地址,即中断 向量,与硬件电路挂钩,不能与一般的程序代码相混杂,所以要单独声明。

按照芯片手册的说法,0x0000 至 0x003F 共0x40 个存储器单元是中断向量,0x0040 至 0x0043 四个单元是保留位置。在上面的例子中,由于0x0040 ~ 0x0043四个单元暂时无用(reserved) ,所以,VECS区域只覆盖了 0x0000 ~ 0x003F;如果把 0x0040 ~ 0x0043 也覆盖进来,估计也没有问题,因为存放中断服务程序入口地址,是编译器根据用户的声明填充的,它会把有用的地址数据安排到对应的单元里,至于没用的空间,无论保存了什么样的地址,对于用户都无所谓。另外,按手册的说法,用户代码似乎应当从0x0044单元开始(User code begins at 0044h) ,实际上可以这么做,也可以不这么做,只要在芯片的程序空间里,与其他空间不发生冲突,从哪个单元开始都可以,编译器自然会安排,上面的例子就是从0x0100单元开始存储程序代码。长度也是用户确定的,不一定要象例子那样,在 0x7FFF 单元结束。 

此处斜体字部分略懂,标注

笔者自行扩展了一块 SRAM 存储芯片,型号为 IS61LV6416,是 ISSI 公司的产品,总容量 64K 字(word) ,通过 CPLD 逻辑电路,把一半的容量安排在程序空间的 0x0000至0x7FFF,覆盖了 PROG和VECS两块区域。所谓“安排” ,就是常说的“映射” 。仔细看一下 2407 的 Memory Map,程序空间从 0x0000 至 0x7FFF,已经全部被片内 FLASH存储器“占用” ,怎么能分配给其他芯片呢?再说,程序代码保存到 SRAM 里面,掉电岂不丢失?

TI 在设计 2407硬件电路的时候,给用户提供了一个 MP/MC管脚,该管脚接0电平时,程序空间通向外部存储器的接口(External Memory Interface)被切断,只对片内的 FLASH 存储器进行寻址,程序空间全部被 FLASH 占用;该管脚接 1 电平时,片内 FLASH 被隔离,只对外接的存储器进行访问。在开发阶段,程序代码写入 SRAM,断电当然就丢失了,但这只麻烦开发人员一个人,每次都要重新往SRAM 里写一遍,开发的时候,程序本来就在变,就必须重写;开发成功了,再写入 FLASH 里,交付用户。那么,TI 这么做,是否多此一举,直接在 FLASH 里开发,不行吗?笔者不好妄下结论,估计是出于以下考虑:a、烧写 FLASH,需要特殊的算法即时序,在仿真状态下进行烧写可能有困难,或存在其他问题;b、在FLASH 中运行程序时,难以同时进行仿真;c、FLASH存储器的烧写寿命有限。各位可以结合自己的经验,考虑一下这个问题。

总之,TI 设计了这种方式, 在仿真开发阶段, 使用外扩的SRAM存储器, 工程师把VECS数据和PROG数据,通过仿真器和CCS环境的“load program”指令,下载到 SRAM 芯片里运行;开发成功以后,再通过TI 提供的专用烧写插件,把代码烧到FLASH 存储器的对应空间里,交付用户使用。所以,开发成功以后,程序空间外扩的SRAM 芯片也就不需要了,完全可以删除,说不定还能节省一些产品成本呢。 

顺便说一下,对于 2407,无论是仿真开发还是脱离仿真,最好不要使用0x8000 ~ 0xFFFF 的高 32K 程序空间,原因有三:a、仿真阶段和脱离仿真器运行时,无法使用同一个 CMD 文件;b、会出现中断不正常的问题,在网上的论坛里,经常有人提问;c、最重要的原因,是笔者的经历,曾经搞一个项目,代码量超出了32K,需要在高32K空间扩展程序存储器,咨询 TI 公司后得知,必须由 TI 提供特殊的 CCS 文件,而且 TI 不能保证结果的正确性! 后来笔者只好缩减代码。在CMD文件中,有意把片内 FLASH的地址和片外的 SRAM 地址相重合,只需要用跳线改变 MP/MC 管脚的电平, 就能同时避开 a和b 两个问题,何乐而不为呢 ?!在仿真阶段和脱离仿真阶段,完全可以使用同一个 CMD文件。 
IS61LV6416 的另一半,安排在数据空间,下文会进一步说明。至于把IS61LV6416 的低 32K 安排在程序空间、高 32K 安排在数据空间,还是正好相反,都无所谓,也都是 可以实现的,仅仅CPLD的逻辑不同而已,很多人会在这里糊涂半天。 
PAGE 1是数据空间。#5、#6、#7三行所声明的 B2、B0B1、SARAM 三块存储区, 是 2407芯片内部集成的存储器,彼此的地址都不连续,所以要分别声明。B0B1块,是由 B0块和B1块合并组成, 但二者是有区别的: B1块地址始终固定在数据空间的 0x0300h ~  0x03FF区域,B0块在芯片复位后的默认地址是数据空间的0x0200h ~ 0x02FF区域,但 用户可以通过软件设置CNF位,把 B0块转移到程序空间里。如果用户需要这样的转移, 就不能把B0、B1 合并起来;如果用户不做这样的转移,就可以象这个例子一样,B0B1 合并起来整体使用,占用地址范围为0x0200h ~ 0x03FF。 
#8行所声明的区域,就是上文所说的 IS61LV6416 芯片的“另一半” 。之所以安排 在数据空间的0x8000 至 0xFFFF 区域,原因很简单,因为这里是TI 指定的外扩数据存储 器的位置,数据空间的其他位置,基本上都被片内集成的存储器所占用,或者被禁用。起 始位置和长度是由用户自己决定的,你可以把这片区域分成几个小块来使用,只要相互不 重叠,不超出 0x8000 至 0xFFFF 区域,并且修改和增加对应的声明,就可以。声明语句 的格式都是一样的。另一方面,如果片内的 2K 多的存储器已经够用,就不必外扩了,也 就不再需要#8这一行的声明。所以,如果只是用 2407 做个流水灯之类的小东东,就不需 要外扩任何存储器,片上的 ROM 和 RAM 资源,在仿真状态下已经足够用了,脱离仿真 器运行也足够。 #4~#8 五行所声明的空间,都可以进一步拆分,如我在前面所说的,是由用户决定 的,可以根据需要,甚至个人的喜好来划分。但#3 行的 VECS 区域,因为与硬件挂钩的 缘故,一般都不再细分。#3~#8 共六行资源声明里,VECS、PROG、B2、B0B1、SARAM、 ExtSRAM这些名称, 都允许用户自己来起名, 和C语言的变量名一样; 但在后面 #10 ~ #15 的几行里引用的时候,必须使用同样的名称。 
片上的存储器,B2、B0、B1三块是 DARAM,全称是 dual-access RAM,根据手 册的说明,它能够在同一个“循环”内(in the same cycle) ,同时完成读出和写入;另一 块是SARAM, 全称 single-access RAM,不要与“SRAM”相混淆。手册上和 TI 网站的 其他材料上, 没有进一步的介绍。 我们可以推断, DARAM 的速度要比 SARAM 快, SARAM 比 SRAM 快,但是电路结构的复杂性、实现的成本也与速度成正比关系,所以,2407 的 片内DARAM只有544个字,SARAM 却有2K字之大。好了,我们不必知道它们的细节, 总线接口、读取方式、写入方式、刷新方式、指标参数,这些是TI 更关心的事,我们只要 记住它们的特点:它们都是 RAM 类存储器,掉电要丢失数据的; DARAM 的读写速度最 快,SARAM 次之。我们分配资源的时候要考虑这些特点,量才适用。 
#9行至#15行,SECTIONS  {……} 部分,就是所谓资源的分配。 
首先,SECTIONS,PAGE,包括花括号、冒号,都是关键字符。注意:SECTIONS 字符是复数形式。在花括号内,每一行最左侧的“.vectors” 、 “.text” 、 “.cinit” 、 “.bss” 、 “.stack”这些名称,包括小数点,都是 TI 默认的关键字符,只有“.extdata”是用户自 己定义的名称。另外, “VECS”、 “PROG” 、 “SARAM” 、 “B0B1” 、 “ExtSRAM”必须是 在 MEMORY 里声明过的资源名称。除此以外,有些字符也允许有别的写法,参见 “Assembly Language Tools User's Guide.pdf” ,汇编语言工具指南。
初次接触这些名称,一定会一头雾水:这些都是什么东西?从哪里冒出来的? 

在 TI 的《汇编语言工具指南》里,这些名词统称为“directives” , “指令”的意思,实际上是针对编译器的“伪指令” ,在芯片的指令集里是找不到这些指令的,不要把二者相混淆。 “.vectors” 、 “.text” 、 “.cinit” 、 “.bss” 、 “.stack” ,这些包括小数点的单词,都是 TI规定的关键字(这些定义应当隐含在TI 提供的某个文件中,比如 .lib 库文件) ,在用户自己的源程序中,一般不能也不需要对这些关键字做定义或声明,只是去引用它们但“.extdata”与这些关键字不同,是由用户自己定义的。每条伪指令,要求编译器在程序空间或数据空间里,保留指定数量的存储单元,这些存储单元叫做“sections” ,一般翻译为“段” ,与笔者所说的“状况”相对应。大家在提到这些伪指令名词的时候,有时来代表这些指令,更多的时候是代表它们所对应的段。如果使用汇编语言开发 DSP,一定要用到这些伪指令,也会对它们有较深刻的理解。 段分为两类:已初始化段(Initialized Sections)和未初始化段(Uninitialized Sections) 。所谓“已初始化” ,具有两个特点:a、只要用户程序编写完成,这些“数据”就已经是可知的、可预期的,是由用户编写的程序代码和编译器共同决定的;b、在系统运行过程中,这些数据的内容不会发生任何变化,只会被读取,不会被修改,下次再通电,这些数据依然存在。显然,指令码就属于“已初始化的” ,但“已初始化”并非只包含指令码,指令码只是“段”的一种而已,一般还会有其他的“段” 。所谓“未初始化” ,就是上文所说的, “设计师自己也很难确切知道,在某一时刻,这些数据的具体的数值是什么”的意思,上面提到的变量  i 就属于“未初始化的” ,同样地, “未初始化”还包括其他段。这里的“初始化” ,是从编译器的角度来考虑的,是“可预知、可预期”的意思,并不是我们通常说的,给某个变量赋予初始值的那个“初始化” 。 

已初始化的段:.text  .cinit  .const  .econst  ..pinit  .switch .vectors

.const: 包含字符串常量和初始化的全局变量和静态变量(由const)的初始化和说明

.econst: 包含字符串常量和初始化的全局变量和静态变量(far const)的初始化和说明

.pinit:  全局构造器(C++)程序列表

.switch:  包含switch 声明的列表

未初始化的段:.bss  .ebss  .stack  .sysmem  .esysmen

.bss:  为全局变量和局部变量保留的空间,在程序上电时.cinit空间中的数据复制出来并存储到.bss空间中

.ebss:  为使用大寄存器模式时的全局变量和静态变量预留的空间,在程序上电时,cinit空间中的数据复制出来并存储在.ebss

.stack:  为系统堆栈保留的空间,主要用于和函数传递变量或为巨变变量分配空间

.sysmem:  为动态存储分配保留的空间,如果有宏函数,此空间被空函数占用,如果没有的话,此空间保留为0

.esysmen:  为动态存储分配保留的空间,如果有far函数,此空间被相应的占用,如果没有的话,此空间保留为0。


当芯片型号不同,甚至用户源程序不同时,编译产生的“段”也是不同的;反之, 产生哪些段,是由芯片型号和用户的源程序共同决定的。 

怎么知道工程项目会产生哪些“段”?工程项目在编译之后,会在项目文件夹内产生一个 .map 文件,用随便一个文本编辑器就可以打开,内容也很容易理解。初学者可以先找一个现成的 CMD 文件,稍作修改或者不修改,加入项目中进行编译,如果编译失败(failure或error) ,则根据提示进行修改,如果只是告警(warning)则不必理会。成功编译之后,查看 .map  文件中“output  section”那一列,那些长度(length)非 0 的段,就是你的项目真正会产生的段;那些长度为 0 的段,基本都可以从CMD 文件中删除。有时也存在这样的情况:某些长度为 0 的段,即使开发人员并没有在CMD文件中作出声明,仍然会在 .map 文件里出现,这对我们的开发并没有影响。 

通过前面的 CMD 例子,来看这些段都是什么意思

“.text” ,就是编译后生成的二进制指令代码段,即所有可执行的代码和常量。我们甚至可手工把 C 程序或汇编程序,翻译成二进制指令代码,所以,它显然属于“已初始化的”段。我们编写的main 主函数, “子”函数或子程序,中断服务函数或程序,它们都会产生指令代码,也都属于这个段。通过#11 行的声明 .text  :  > PROG     PAGE 0 编译器就知道设计师的意图了,是要把所有的二进制代码,按顺序串行地汇集起来,一起编入 PROG区域,即#4行已经声明的,程序空间 PAGE 0的 0x0100h ~ 0x7FFF地址范围内。每个函数的代码块的首地址,长度等信息,都记录在 .map 文件中。至于这些代码最终写入哪个物理存储器,是片内的FLASH,还是片外扩展的程序 SRAM,是由MP/MC管脚决定的(对于2407芯片) ,已经不是CMD文件的责任了。 

 “.vectors” ,表示“中断向量段”,也就是中断服务程序的入口地址段。很显然,这个段要求物理存储器必须能够掉电保存数据。我们在编写用户程序的时候,普通的函数完全按照标准C语言的语法,比如void  main(void)  { …… } ,但所有的硬件中断服务函数, 必须在前面加一个关键字 “interrupt” , 比如某个服务函数abc( )是这样写的: interrupt  void  abc( )  { …… } 。对于2407,这些还不够,源程序还必须包含一个 vectors.asm 文件,其中的一句声明,把中断服务函数  abc ( ) 与具体的硬件中断对应起来: 

int1:  b _abc 

有了这些声明,编译器就会把函数abc ( ) 的代码块的首地址,编排到与 int1中断对应的向量中,写入#3行定义的0x0000h ~ 0x0040处,中断向量的地址空间里。这个首地址,编译器当然是知道的,所以“.vectors”也属于“已初始化的”段。用户如果想知道中断服务程序的入口地址,只能去查看 .map 文件。对于 2812,情况基本相同,不同之处是用 C 语句代替  vectors.asm  文件:   PieVectTable.TINT0 = &abc;   或   者PieVectTable.XINT1 = &xint1_int;  之类。 

“.cinit” 段,定义比较模糊,有文章解释为“对全局变量和静态变量初始化的常数” 。按笔者理解,我们经常用到的数表,比如七段显示器的代码表、液晶的显示字符代码表、正弦数表等,都属于这个段。那么,还包括哪些内容呢?笔者也不是很确定。但有一点是肯定的:它属于“已初始化的”段,必须作为代码,存储在程序空间里,而且必须能够掉电保存。 

“.stack” ,就是我们常说的堆栈,我们根本不可能知道堆栈内部数据的变化情况,所以,它属于“未初始化的”段,定位在数据空间。在调用函数、保存现场时,一定要用到这个段,但笔者怀疑,还会有其他的用途,比如用堆栈来批量交换数据。显然,堆栈内部的数据,没有掉电保存的必要。在上面作为例子的 CMD文件中,#1行是用户对堆栈空间的大小所作的声明,是按照TI 公司规定的语法,200h表示512 个单元;如果用户没有作出#1行的声明,编译器将按照默认的数量来分配空间。一般来说,如果你无法确定程序运行究竟需要多大的堆栈,就尽量设置大一点。例子中把整个 B0B1 存储器块,都作为堆栈使用。

  “.bss” ,定义同样比较模糊,但很容易意会。有文章介绍为“保存全局变量和静态变量” 。很显然,它属于“未初始化的”段,要定位在数据空间。前面 C 程序例子中,变量 i 就定位在这里。这个段,同样不要求掉电保存数据。 

 “.extdata” , 是笔者自行定义的段, 属于 “未初始化的” , 专门用来保存一个 20000 个元素的数组  xyz[20000] ,数据定位在外扩的 SRAM 中,不需要掉电保存。这是 TI 为 用户设计的,把某些内容从 bss 段里分出来,进行特殊定位的方法。具体作法是: 
a、在CMD文件中作出 #15行的声明 
.extdata :  > ExtSRAM PAGE 1 
“extdata”是用户自行定义的名称。 
b、在源程序中包含如下的语句: 
unsigned  int   xyz[20000]; 
#pragma DATA_SECTION (xyz,".extdata") 
其中,#pragma、DATA_SECTION和小括号,都是规定的关键字符。 
这样,用户的声明就完整了,编译器将把数组 xyz[20000] 从 bss 段里分出来,单 独定位。所以,这个指令的优先级要高于bss。 
我们再来看几个未初始化段的物理定位: 
.bss  :  > SARAM PAGE 1    /* #13 */ 
.stack  :  > B0B1  PAGE 1    /* #14 */ 
.extdata :  > ExtSRAM PAGE 1    /* #15 */ 
这三个段的数据,都不需要掉电保存。例子中,三个段都定位在数据空间的 RAM 类存储 器中,但分别属于三个不同的物理存储器。实际上,你完全可以按照自己的意愿甚至个人 喜好,随意地定位,比如把任意一个段定位在任意一个物理存储器里,或者把某两个段定 位在同一个物理存储器里,或者三个段都挤在一个物理存储器里,都可以,实际运行起来 都没有任何问题!反正三个存储器都属于数据空间,反正谁都可以闲着谁都可以忙起来, 反正爱谁闲着就闲着爱谁忙就忙,不需要顾及它们的情绪。当然,前提是物理空间够用。 但是,想把 stack 分成几块,分别定位在几个存储器,可能是行不通的。bss 也无法分成 两块,因为无论你怎么分,剩下的还叫做 bss,除非你用前面介绍的方法,把所有的变量 都人工定位,然后是什么情况,笔者也不知道了。那么,分来配去,总要追求点什么意义 才好吧?!前面说过,这些存储器的区别主要是速度,能够最大程度提高系统运行速度, 才是应当追求的目标。具体该怎么分配,就靠读者自己去体味吧。笔者讲一个例子,供读 者参考:前面的数组 xyz[ 20000] ,如果没有对它单独定位,它就会被编译器并入bss 段, bss 段的长度将超过 20000了,由于片内存储器的容量都没有这么大,只能把bss 定位在外扩的 SRAM 中。 但是, 笔者又不愿意浪费片内的 SARAM, 它的访问速度毕竟要比 SRAM 要快一些,闲着实在可惜,所以才把数组从 bss 中分出来,以便给 SARAM 和 SRAM 分 别派上适当的用场。 

另外一个CMD例子:
DSP281x_Headers_nonBIOS.cmd
MEMORY
{
 PAGE 0:   
 PAGE 1:   
   DEV_EMU     : origin = 0x000880, length = 0x000180    
   ...
   SCIB        : origin = 0x007750, length = 0x000010    
   MCBSPA      : origin = 0x007800, length = 0x000040    
   CSM_PWL     : origin = 0x3F7FF8, length = 0x000008    
}
SECTIONS
{
   PieVectTableFile : > PIE_VECT,   PAGE = 1
   DevEmuRegsFile    : > DEV_EMU,     PAGE = 1
   FlashRegsFile     : > FLASH_REGS,  PAGE = 1
   CsmRegsFile       : > CSM,         PAGE = 1
   XintfRegsFile     : > XINTF,       PAGE = 1
   CpuTimer0RegsFile : > CPU_TIMER0,  PAGE = 1 
   PieCtrlRegsFile   : > PIE_CTRL,    PAGE = 1     
   SysCtrlRegsFile   : > SYSTEM,      PAGE = 1
   SpiaRegsFile      : > SPIA,        PAGE = 1
   SciaRegsFile      : > SCIA,        PAGE = 1
   XIntruptRegsFile  : > XINTRUPT,    PAGE = 1
   GpioMuxRegsFile   : > GPIOMUX,     PAGE = 1
   GpioDataRegsFile  : > GPIODAT      PAGE = 1
   AdcRegsFile       : > ADC,         PAGE = 1
   EvaRegsFile       : > EVA,         PAGE = 1
   EvbRegsFile       : > EVB,         PAGE = 1
   ScibRegsFile      : > SCIB,        PAGE = 1
   McbspaRegsFile    : > MCBSPA,      PAGE = 1
   ECanaRegsFile     : > ECANA,       PAGE = 1
   ECanaLAMRegsFile  : > ECANA_LAM    PAGE = 1  
   ECanaMboxesFile   : > ECANA_MBOX   PAGE = 1
   ECanaMOTSRegsFile : > ECANA_MOTS   PAGE = 1
   ECanaMOTORegsFile : > ECANA_MOTO   PAGE = 1
   CsmPwlFile        : > CSM_PWL,     PAGE = 1
}
解释:从page1可看出,从DEV_EMU到MCBSPA都位于外设帧0上(0x000800-ox000D00),而最后一个CSM_PWL显然要位于FLASH中的设置密码区域(0x3F7FF8-0X3F8000)。这样把数据可使用的空间定义出来了。
而在section中有这么一句 PieVectTableFile : > PIE_VECT,   PAGE = 1  说明PieVectTableFile段位于PIE_VECT,从字面上可以看出,是把pie向量表存入PIE_VECT这块区域中。而PieVectTableFile段已在 DSP281x_GlobalVariableDefs.c 这个文件中的下面语句定义了
#ifdef __cplusplus
#pragma DATA_SECTION("PieVectTableFile")
#else
#pragma DATA_SECTION(PieVectTable,"PieVectTableFile");
#endif
struct PIE_VECT_TABLE PieVectTable;
即通过pragma来对PieVectTable在PieVectTableFile中进行地址分配,而PieVectTable又是已定义过的结构体,其结构体标号为PIE_VECT_TABLE。这个结构体是在DSP281x_PieVect.h中定义。
DSP281x_PieVect.h这个文件通过PINT来定义一些中断函数,而这些中断函数中可以放置中断程序的入口地址。通过DSP281x_PieVect.c中的下面子程序进行初始化:
void InitPieVectTable(void)
{
       int16      i;
       Uint32 *Source = (void *) &PieVectTableInit;
       Uint32 *Dest = (void *) &PieVectTable;
       EALLOW;  
       for(i=0; i < 128; i++)
             *Dest++ = *Source++;    
       EDIS;
      // Enable the PIE Vector Table
    PieCtrlRegs.PIECRTL.bit.ENPIE = 1;
}

四、DSP系统硬件电路板的故障排查步骤和方法。 

第一步,一个 DSP电路板刚装配完,必须先排除电路板的硬故障,比如电流异常, 芯片发热,DSP始终处于复位状态,振荡器没有起振等。这些故障没有“放之四海而皆准” 的方法,每个人遇到的情况都会不一样。一般来说,把所有的引脚重新焊一遍,可以解决 大部分故障。 
第二步,看仿真器能否与目标板连接。把PC机与仿真器连接(要保证仿真器已经 正确驱动) ,仿真器与目标电路板正确连接,目标板通电。这些硬件操作完成后,再启动 CCS(要保证CCS已经按照目标板的芯片型号进行了设置) 。几秒种后,如果已经正确连 接,在 CCS界面的左下角会出现“目标板已经连接”的提示。当然还有其他的提示方式, 比如弹出汇编语言窗口等。如果仿真器无法成功与目标板连接,就说明目标板上有故障。
据笔者的经验,一般是 JTAG 接口的几条线上有短路或断路,数据线、地址线上有短路或 断路,READY信号错误。 

第三步,这时就可以调试我们的程序了。当我们把程序编译完成,通过 CCS 开发平台的“load  program”命令,把代码调入外扩 SRAM的时候,经常会遇到错误告警,说在某地址处出现“校验错误” 。这大多是因为地址总线、数据总线,或者控制线的虚焊、短路引起的,主要发生在 DSP 芯片的总线管脚、电平转换芯片的管脚,及片外存储器的管脚上;或者片选逻辑电路有错误。CCS 把代码写入 SRAM 之后,会逐个单元读出,与原始数据进行比较,如果二者不相同,就会提示这样的错误。 遇到这个问题怎么办呢?这里介绍一个用软件去查找硬件故障的方法,非常有效。前面说过,DSP的最小系统不需要外扩任何存储器,其自身的存储器足够运行在仿真器状态,或脱离仿真器的状态。我们可以利用这个特点,把外扩的SRAM或其他外设,都作为外设来操作,用软件生成一些有规律的波形,帮助我们定位故障的部位。具体操作如下: 

a、编写一段程序,向外扩 SRAM的存储单元里,按地址顺序,依次写入连续变化的数据,象下面这段程序: 

unsigned int  i,*R; 
for (  i = 0x8000;  i <= 0xFFFF;   i++  ) 
   { 
   R = ( unsigned int *) i; 
   *R = i; 
   } 
这里的0x8000 ~ 0xFFFF是外扩的数据存储器的地址。无条件地、反复地运行这段程序,就会在地址线、数据线上产生连续的方波,最低位的 A0 和 D0 的周期最短、频率最高,A1和D1的周期分别是 A0、D0 的两倍,A2、D2是 A1、D1的两倍,依次类推。  
b、在 CMD文件中,把这段程序定位在片内程序空间的 RAM 类存储器中,而不是外扩的存储器, 就是前面说的思路: 利用 DSP的最小系统, 把外扩 RAM 作为外设来操作。  
c、在仿真状态下,全速运行,用示波器检查所有数据总线、地址总线、控制总线和片选信号的波形,顺藤摸瓜,就能查到故障位置。 
  • 20
    点赞
  • 77
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值