DSP28335的CMD文件中各个段的解释 1 为什么DSP里面有CMD文件,而普通的单片机却没有? 通常DSP内部集成了存储器或外部扩展存储器。存储器统一映射到程序空间或者数据空间。DSP中,存储器映射空间除SRAM和FLASH外还有保留空间(可用于扩展外部存储器与外设),比较杂乱。DSP的编译器自身不能定位执行代码位置,因此设计人员需要自己去定义代码存放和加载位置。单片机就比较简单了,编译器自身会定位代码地址,所以一般无需去定位地址。
2 什么是段信息 Ti的编译器生成的目标文件是一种模块化的ELF格式或COFF格式文件(这两种格式可选)。代码和数据在ELF文件中以段的形式组织。一个段在内存空间中会占连续一块code或data。
知识点: Ti的编译器生成的目标文件就是可烧录在DSP中的可执行文件,后续说的可执行文件、目标文件、目标码是同一文件,个人叫法不同,不必深究。
可执行文件内部:
头部信息:用以声明文件格式等信息。
段信息表:包含了段的完备信息,如段的绝对地址、名字、属性以及数据等。
段信息:代码和数据在文件中以段的形式保存。
知识点: 可以使用对应编译链工具来查看文件的段信息表用以分析段信息是否全面。
3 CMD由几部分组成,各有什么功能? CMD由输入输出定义(这部分可以通过css的“Build Option…”菜单设置,obj链接的目标文件,lib链接的库文件,map生成的交叉索引文件,out生成的可执行代码)、MEMORY和SECTIONS三部分组成。MEMORY用于定义每个存储器块的名字、起始地址和长度。SECTIONS主要用于描述哪个段映射到了哪段存储空间(包括所在段的页类型,其中PAGE0规定为程序存储区,PAGE1为数据存储区)。
4 CMD文件中有哪些段?各自作用? CMD文件里有两个基本的段:初始化段和非初始化段。初始化段包含代码和常数等必须在DSP上电之后有效的数。故初始化块必须保存在如片内FLASH等非易失性存储器中,非初始化段中含有在程序运行过程中才向变量内写数据进去,所以非初始化段必须链接到易失性存储器中,如RAM。
已初始化的段 :.text,.cinit,.const,.econst,.pinit和.switch..
.text:所有可以执行的代码和常量。
.cinit:全局变量和静态变量的C初始化记录,包含未用const声明的外部(extern)或静态(static)数据表。
.const:包含字符串常量和初始化的全局变量和静态变量(由const)的初始化和说明。
.econst:包含字符串常量和初始化的全局变量和静态变量(由far const)的初始化和说明,与.const不同的是,.const分配范围被限制在低64k 16位数据区,而.econst的分配范围是4M 22位数据区。
.pinit:全局构造器(C++)程序列表。
.switch:包含switch声明的列表。
非初始化的段 :.bss,.ebss,.stack,.sysmem和.esysmem(更好的理解就是,这些段就是存储空间而已)
.bss:为全局变量和局部变量保留的空间,在程序上电时.cinit空间中的数据复制出来并存储在.bss空间中。
.ebss:为使用大寄存器模式时的全局变量和静态变量预留的空间,在程序上电时,cinit空间中的数据复制出来并存储在.ebss中,与.bss不同的是.bss分配范围被限制在低64k 16位数据区,而.ebss的分配范围是4M 22位数据区。
.stack:为系统堆栈保留的空间,用于和函数传递变量或为局部变量分配空间。
.sysmem:为动态存储分配保留的空间。如果有malloc函数,此空间被宏函数占用,如果没有的话,此空间保留为0。(可以理解为堆空间)
.esysmem:为动态存储分配保留的空间。如果有far malloc函数,此空间被相应的占用,如果没有的化,此空间保留为0。(可以理解为堆空间)
编译器生成的包含代码和数据的多个部分,称为段。这下段被分为两个不同的组:初始化了的和没被初始化的,初始化的部分是由所有的代码,常量和初始化表组成的。下表列出了由编译器产生的初始化段。
初始化段
段名
内容
限制
.cinit
显式初始化的全局变量和静态变量表
代码
.const
显式初始化的全局和静态的const变量和字符串常量
不超过64K长度
.econst
长调用的常量
数据中的任何地方
.pinit
全局对象的构造函数表
代码
.switch
switch语句产生的表
代码或者数据
.text
可执行代码和常数
代码
没初始化的段是由未初始化的变量,堆栈和malloc产生的内存。下表列出了由编译器产生的没初始化段。
没初始化段
段名
内容
限制
.bss
全局和静态变量
不超过64K长度
.ebss
长调用的全局或静态变量
数据中的任何地方
.stack
堆栈空间
不超过64K长度
.sysmem
malloc函数产生的内存
不超过64K长度
.esysmem
far_malloc函数产生的内存
数据中的任何地方
一旦编译器生成的这些段,连接器会从各个源文件中取出这些段,并结合它们来创建一个输出文件。连接器命令文件(.cmd)就是用来告诉连接器去哪里找这些段的。初始化段必须分配到非易失性存储器,如flash/ ROM,当电源被撤除时,程序不会消失。未初始化的段可以被分配到RAM中,因为它们是在代码执行期间被初始化的。
当需要把程序从flash复制到RAM里时,各个段分配参考如下:
.cinit
Flash
.cio
RAM
.const
Flash
.econst
Flash
.pinit
Flash
.switch
Flash
.text
Flash
.bss
RAM
.ebss
RAM
.stack
Lower 64Kw RAM
.sysmem
RAM
.esysmem
RAM
.reset
RAM1
5 地址映射
上图是TI官方手册上F28335地址映射图。整张图分为左右两个部分:左侧部分是F28335的片内资源,右侧部分是F28335的预留拓展部分。
右边部分比较简单,大部分都是保留区域,实际中可以利用的只有三块区域:
Zone0:地址范围为0x004000 ~ 0x004FFF,大小为8K*16bit。
Zone6:地址范围为0x100000 ~ 0x1FFFFF,大小为1M*16bit。
Zone7:地址范围为0x200000 ~ 0x2FFFFF,大小为1M*16bit。
注意: F28335外引的地址线只有20根,XA[0-19],经计算能够操控的地址为0x000000 ~ 0x100000。Zone6和Zone7却是在这个地址之外。这是因为在此处,通过写入对应的地址空间,DSP会自动将片选信号(XZCS0/XZCS6/XZCS7)置为有效位。所以,高位的地址信号,就不必引出。
左边是DSP片内自身携带的资源。总有256K * 16的Flash,34K * 16的SRAM,8K * 16的BOOT ROM,2K * 16的OTP ROM,下面一一介绍。
SRAM,共计34K * 16:
M0:0x000000 - 0x0003FF。1K * 16,其中0x000000 - 0x000040常常用作特殊用途。
M1:0x000400 - 0x0007FF。1K * 16,这部分通常作调试程序的堆栈。
L0 ~ L7:0x008000 - 0x00FFFF。每一块为4K * 16,共计32K * 16。后面还有一块区域为L0 - L3(0x3F8000 - 0x3FBFFF),这是一个双映射空间,用来数据备份。两块区域中的L0 - L3部分受到CSM密码的保护。
ROM,共计266K:
Flash:0x300000 - 0x33FFFF。共256K * 16。其中0x33FFF8 - 0x33FFFF为128位CMS密码。若此部分设置数据,那么若想要读取密码保护区域,需要首先解锁密码。在调试的时候通常不用。但要注意的是,程序写入到Flash的时候,可能会因为程序数据过大,误写入此区域。
OTP ROM:0x380400 - 0x3807FF。共2K * 16。其中有1K * 16区域为 Ti OTP,为保留区域。此部分区域受到CSM密码保护。需要注意的是,这部分只能写一次,不能被再次擦除!
BOOT ROM:0x3FE000 - 0x3FFFFF。共8K * 16。其中最后一部分用于中断向量表。
其余部分:
PF0 - PF3:这个部分是用来存放外设寄存器的,这部分不能够随便设置。其中PF1 - PF3是受保护区域。受保护区域的寄存器不能够直接写入,需要EALLOW声明。声明方法见(F28335第二篇——系统控制初始化 )。
PIE中断向量表:存放PIE中断向量的地方,此处也不可以随便设置。
6 CMD文件 上面罗列了F28335的存储空间,开发工程师们还需要通过CMD文件对于DSP存储器的管理和分配。CMD主要实现两个功能:
声明整个系统的存储资源。——列出系统中的存储器,他们的地址与大小。
资源的分配。——把程序与数据合理地安排到存储器的区域。
在一个项目中,一般至少包含两个cmd文件:
28335_RAM_lnk.cmd
DSP2833x_Headers_nonBIOS.cmd
当然名称无所谓,也可以自定义自己的cmd。这里列出的名称是TI提供的。第一个cmd文件可以通过CCS建立新工程的时候自己生成,其中主要罗列DSP中SRAM和ROM资源及经典分配方法。第二个cmd文件在TI提供的技术支持中可以找到,其中主要罗列DSP的外设帧(Peripheral Frame)的映射位置,也就是DSP所有外设寄存器的地址及长度,另外还有就是中断向量表。
在cmd文件中,TI已经进行了详细的注释。我也添加了一部分中文注释。由于是程序用于在线调试,所以分配的大多数是SRAM空间。CMD文件整体分为两个部分:MEMORY和SECTIONS。其中MEMORY是用来罗列系统包含的存储空间。SECTIONS是用来规定具体的数据块应该放在系统MEMORY罗列清单的具体位置。在MEMORY中,可以通过PAGE方式将存储空间组织起来,但是习惯上,分成两个部分,PAGE 0表示程序空间,PAGE 1表示数据空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 MEMORY { PAGE 0 : BEGIN : origin = 0x000000 , length = 0x000002 BOOT_RSVD : origin = 0x000002 , length = 0x00004E RAMM0 : origin = 0x000050 , length = 0x0003B0 RAML0 : origin = 0x008000 , length = 0x001000 RAML1 : origin = 0x009000 , length = 0x001000 RAML2 : origin = 0x00A000 , length = 0x001000 RAML3 : origin = 0x00B000 , length = 0x001000 ZONE7A : origin = 0x100000 , length = 0x00FC00 CSM_RSVD : origin = 0x33FF80 , length = 0x000076 CSM_PWL : origin = 0x33FFF8 , length = 0x000008 ADC_CAL : origin = 0x380080 , length = 0x000009 RESET : origin = 0x3FFFC0 , length = 0x000002 IQTABLES : origin = 0x3FE000 , length = 0x000b50 IQTABLES2 : origin = 0x3FEB50 , length = 0x00008c FPUTABLES : origin = 0x3FEBDC , length = 0x0006A0 BOOTROM : origin = 0x3FF27C , length = 0x000D44 PAGE 1 : RAMM1 : origin = 0x000400 , length = 0x00400 RAML4 : origin = 0x00C000 , length = 0x001000 RAML5 : origin = 0x00D000 , length = 0x001000 RAML6 : origin = 0x00E000 , length = 0x001000 RAML7 : origin = 0x00F000 , length = 0x001000 ZONE7B : origin = 0x10FC00 , length = 0x000400 } SECTIONS { codestart : > BEGIN, PAGE = 0 ramfuncs : > RAML0, PAGE = 0 .text : > RAML1, PAGE = 0 .cinit : > RAML0, PAGE = 0 .pinit : > RAML0, PAGE = 0 .switch : > RAML0, PAGE = 0 .stack : > RAMM1, PAGE = 1 .ebss : > RAML4, PAGE = 1 .econst : > RAML5, PAGE = 1 .esysmem : > RAMM1, PAGE = 1 IQmath : > RAML1, PAGE = 0 IQmathTables : > IQTABLES, PAGE = 0 , TYPE = NOLOAD IQmathTables2 : > IQTABLES2, PAGE = 0 , TYPE = NOLOAD FPUmathTables : > FPUTABLES, PAGE = 0 , TYPE = NOLOAD DMARAML4 : > RAML4, PAGE = 1 DMARAML5 : > RAML5, PAGE = 1 DMARAML6 : > RAML6, PAGE = 1 DMARAML7 : > RAML7, PAGE = 1 ZONE7DATA : > ZONE7B, PAGE = 1 .reset : > RESET, PAGE = 0 , TYPE = DSECT csm_rsvd : > CSM_RSVD PAGE = 0 , TYPE = DSECT csmpasswds : > CSM_PWL PAGE = 0 , TYPE = DSECT .adc_cal : load = ADC_CAL, PAGE = 0 , TYPE = NOLOAD }
在实际应用中,CMD文件有两种基本数据块(section)。初始化块和未初始化块。注意,此处初始化和程序中的初始化含义并不相同。初始化含义是,数据不会发生变换的数据,例如程序,常数。而未初始化是会经常发生变换的数据,例如,变量,堆栈,动态申请的空间等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 MEMORY { PAGE 0 : PAGE 1 : DEV_EMU : origin = 0x000880 , length = 0x000180 FLASH_REGS : origin = 0x000A80 , length = 0x000060 CSM : origin = 0x000AE0 , length = 0x000010 ADC_MIRROR : origin = 0x000B00 , length = 0x000010 XINTF : origin = 0x000B20 , length = 0x000020 CPU_TIMER0 : origin = 0x000C00 , length = 0x000008 CPU_TIMER1 : origin = 0x000C08 , length = 0x000008 CPU_TIMER2 : origin = 0x000C10 , length = 0x000008 PIE_CTRL : origin = 0x000CE0 , length = 0x000020 PIE_VECT : origin = 0x000D00 , length = 0x000100 DMA : origin = 0x001000 , length = 0x000200 MCBSPA : origin = 0x005000 , length = 0x000040 MCBSPB : origin = 0x005040 , length = 0x000040 ECANA : origin = 0x006000 , length = 0x000040 ECANA_LAM : origin = 0x006040 , length = 0x000040 ECANA_MOTS : origin = 0x006080 , length = 0x000040 ECANA_MOTO : origin = 0x0060C0 , length = 0x000040 ECANA_MBOX : origin = 0x006100 , length = 0x000100 ECANB : origin = 0x006200 , length = 0x000040 ECANB_LAM : origin = 0x006240 , length = 0x000040 ECANB_MOTS : origin = 0x006280 , length = 0x000040 ECANB_MOTO : origin = 0x0062C0 , length = 0x000040 ECANB_MBOX : origin = 0x006300 , length = 0x000100 EPWM1 : origin = 0x006800 , length = 0x000022 EPWM2 : origin = 0x006840 , length = 0x000022 EPWM3 : origin = 0x006880 , length = 0x000022 EPWM4 : origin = 0x0068C0 , length = 0x000022 EPWM5 : origin = 0x006900 , length = 0x000022 EPWM6 : origin = 0x006940 , length = 0x000022 ECAP1 : origin = 0x006A00 , length = 0x000020 ECAP2 : origin = 0x006A20 , length = 0x000020 ECAP3 : origin = 0x006A40 , length = 0x000020 ECAP4 : origin = 0x006A60 , length = 0x000020 ECAP5 : origin = 0x006A80 , length = 0x000020 ECAP6 : origin = 0x006AA0 , length = 0x000020 EQEP1 : origin = 0x006B00 , length = 0x000040 EQEP2 : origin = 0x006B40 , length = 0x000040 GPIOCTRL : origin = 0x006F80 , length = 0x000040 GPIODAT : origin = 0x006FC0 , length = 0x000020 GPIOINT : origin = 0x006FE0 , length = 0x000020 SYSTEM : origin = 0x007010 , length = 0x000020 SPIA : origin = 0x007040 , length = 0x000010 SCIA : origin = 0x007050 , length = 0x000010 XINTRUPT : origin = 0x007070 , length = 0x000010 ADC : origin = 0x007100 , length = 0x000020 SCIB : origin = 0x007750 , length = 0x000010 SCIC : origin = 0x007770 , length = 0x000010 I2CA : origin = 0x007900 , length = 0x000040 CSM_PWL : origin = 0x33FFF8 , length = 0x000008 } SECTIONS { PieVectTableFile : > PIE_VECT, PAGE = 1 DevEmuRegsFile : > DEV_EMU, PAGE = 1 FlashRegsFile : > FLASH_REGS, PAGE = 1 CsmRegsFile : > CSM, PAGE = 1 AdcMirrorFile : > ADC_MIRROR, PAGE = 1 XintfRegsFile : > XINTF, PAGE = 1 CpuTimer0RegsFile : > CPU_TIMER0, PAGE = 1 CpuTimer1RegsFile : > CPU_TIMER1, PAGE = 1 CpuTimer2RegsFile : > CPU_TIMER2, PAGE = 1 PieCtrlRegsFile : > PIE_CTRL, PAGE = 1 DmaRegsFile : > DMA, PAGE = 1 McbspaRegsFile : > MCBSPA, PAGE = 1 McbspbRegsFile : > MCBSPB, 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 ECanbRegsFile : > ECANB, PAGE = 1 ECanbLAMRegsFile : > ECANB_LAM PAGE = 1 ECanbMboxesFile : > ECANB_MBOX PAGE = 1 ECanbMOTSRegsFile : > ECANB_MOTS PAGE = 1 ECanbMOTORegsFile : > ECANB_MOTO PAGE = 1 EPwm1RegsFile : > EPWM1 PAGE = 1 EPwm2RegsFile : > EPWM2 PAGE = 1 EPwm3RegsFile : > EPWM3 PAGE = 1 EPwm4RegsFile : > EPWM4 PAGE = 1 EPwm5RegsFile : > EPWM5 PAGE = 1 EPwm6RegsFile : > EPWM6 PAGE = 1 ECap1RegsFile : > ECAP1 PAGE = 1 ECap2RegsFile : > ECAP2 PAGE = 1 ECap3RegsFile : > ECAP3 PAGE = 1 ECap4RegsFile : > ECAP4 PAGE = 1 ECap5RegsFile : > ECAP5 PAGE = 1 ECap6RegsFile : > ECAP6 PAGE = 1 EQep1RegsFile : > EQEP1 PAGE = 1 EQep2RegsFile : > EQEP2 PAGE = 1 GpioCtrlRegsFile : > GPIOCTRL PAGE = 1 GpioDataRegsFile : > GPIODAT PAGE = 1 GpioIntRegsFile : > GPIOINT PAGE = 1 SysCtrlRegsFile : > SYSTEM, PAGE = 1 SpiaRegsFile : > SPIA, PAGE = 1 SciaRegsFile : > SCIA, PAGE = 1 XIntruptRegsFile : > XINTRUPT, PAGE = 1 AdcRegsFile : > ADC, PAGE = 1 ScibRegsFile : > SCIB, PAGE = 1 ScicRegsFile : > SCIC, PAGE = 1 I2caRegsFile : > I2CA, PAGE = 1 CsmPwlFile : > CSM_PWL, PAGE = 1 }
我们可以看到通过这个cmd,TI将F28335中所有的寄存器变量和自己所在地址连接了起来。关于具体的连接方式可以参考我的一篇博客(F28335第三篇——寄存器文件结构 )。
当然有了上面的案例,我们也可以创建自己的cmd。在Zone6区外扩了自己的存储芯片,所以,可以分配到自己的cmd文件中。
自己创建的CMD 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 MEMORY { PAGE 0 : PAGE 1 : ZONE6A : o = 0x100000 , l=0x080000 ZONE6B : o = 0x180000 , l=0x040000 ZONE6C : o = 0x1C0000 , l=0x040000 } SECTIONS { Zone6AFile :> ZONE6A, PAGE = 1 Zone6BFile :> ZONE6B, PAGE = 1 Zone6CFile :> ZONE6C, PAGE = 1 }
如上文,由于我将外扩的区域只用来存放数据,所以将所有存储空间放置在数据空间中(PAGE 1)。补充一句,origin可以写作o或者org。length可以写作len或者l。
7 修改CMD文件时应该注意什么? 不同DSP的存储空间映射地址差异很大。我们看一下2407、28035、2812的存储空间地址。
故修改CMD文件中MEMORY时注意,需比照相应DSP的存储器地址,来修改段。各个段的地址不能跨界;数据段空间不要映射到程序段地址。保留空间如用于外扩外设或存储器方可使用,如外部未使用保留空间,则不能使用保留空间 SECTION中,通常,.text、.cint、.switch等初始化段连接至ROM或RAM空间,且必须位于程序存储器空间(PAGE0)。.const块可以链接至ROM或RAM空间,且必须是数据存储器空间(PAGE1)。而.bass、.ebass、.sysmem、.eysmem等未初始化段必须连接至RAM空间,且必须位于数据存储器空间(PAGE1)。
7 注意事项 对于程序在FLASH中运行时,需要注意的是:DSP在150M时钟频率下,FLASH中只能提供大约120M的时钟频率,所以有时候我们希望在RAM中运行时间敏感或计算量很大的子程序(比如AD采样)。但是我们所有代码都放在FLASH中,这就必须在上电后将FLASH中的这段敏感程序复制到RAM中运行,加快速度。这时在 .CMD 文件就必须划分一段用来设置RAM的载入和运行地址,程序代码如下:
1 2 3 4 5 6 7 8 SECTIONS {……… ramfuncs : LOAD = FLASHD, RUN = RAML0, LOAD_START(_RamfuncsLoadStart), LOAD_END(_RamfuncsLoadEnd), RUN_START(_RamfuncsRunStart), PAGE = 0 }
CMD小技巧:
如果 .text 文件很大,将其放在一段放不下,需将其放到两个程序段中最长的一个length=0x002000,也放不下时,可以这样处理:
1 2 3 4 5 6 PAGE 0 : PRAMH0 : origin = 0x3F8002 , length = 0x0014FE L0RAM : origin = 0x008000 , length = 0x001000 SECTIONS .text:{*(.text)} >>PRAMH0|L0RAM
这样就可以将 .text 文件放在两个定义段中。