DSP

把DSP TMS320F28XXX的程序段从flash复制到ram中运行

2019-07-13 11:01发布

翻译自TI应用手册SPRAAU8

摘要

这个应用报告和相关的代码提供了一种把编译后的程序段从TMS320F28xxx的flash复制到ram的功能,这样可以提高代码的运行速度。这个解决方案在直接启动之后,进入c_int00 ——C语言代码运行之前实现此功能。
本应用报告中所讨论的项目内容和源代码可以从以下网址下载:http://www-s.ti.com/sc/techlit/spraau8.zip
1.引言: 在许多应用中,代码的执行速度是至关重要的。例如在医疗,监控,电机控制等等一些对时间有严格要求的终端设备。许多应用使用TMS320F28xxx DSCs是因为它的内置flash储存器。内置flash是TMS320F28xxx的一个优势,因为它使得设计者不需要外接flash来储存代码。使用内部flash缺点是访问Flash需要等待状态,这使得程序的运行变慢。在大多数应用中,这不是一个问题。其他一些应用中可能会为了获得最高的运行速度要求无等待状态。内部RAM存储器具有零等待状态,它是易失性存储器。所以,引导的初始化代码段不可以存储在此存储器中。 现在提供的解决方案,使得设计者能够在运行时把被编译器初始化的代码段从flash复制到ram里,获得最大的运行速度。这使代码执行从多达15个等待状态的提升到0等待状态。另一种解决方案是只将某些函数从Flash复制到RAM。详见:《Running an Application from Internal Flash Memory on the TMS320F28xx DSP》
(SPRA958)。这种方法应该使用在大多数使用C2000™ DSC的应用上,其他要求严格的时序和连续的零等待状态的应用程序应采用这里提出的解决方案。
编写汇编程序来完成代码从Flash到RAM的复制该汇编代码在复位向量后调用c_int00之前执行。这保证了在c_int00调用mian()之前完成复制 有一些工程比较小,可以把所有初始化了的段都复制到ram。然而,其他一些工程的初始化了的段比所有的内部ram还要大。这些工程可能不可以把所有的初始化了的段都复制到ram,但是用这种方法复制其中一部分段。
2.编译的代码段: 编译器生成的包含代码和数据的多个部分,称为段。这下段被分为两个不同的组:初始化了的和没被初始化的,初始化的部分是由所有的代码,常量和初始化表组成的。下表列出了由编译器产生的初始化段
初始化段 段名 内容
限制 .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中,因为它们是在代码执行期间被初始化的。
关于更多编译段和连接的信息,请参见:《TMS320C28x Assembly Language
Tools User’s Guide 》(SPRU513) 和《 the TMS320C28x Optimizing C/C++ Compiler User’s Guide》(SPRU514)。
德州仪器(TI)提供了多个例子显示如何使用链接器命令文件分配编译段。其中一个就是《Running an Application from Internal Flash Memory on the TMS320F28xx DSP 》(SPRA958)。此应用文档提供的例子,演示了使用基于RAM和Flash的项目的链接器命令文件。
3.软件: 本应用文档相关的代码文件,包括修改后的版本的CodeStartBranch.asm文件和非DSP/BIOS™项目用的文件DSP28xxx_SectionCopy_nonBIOS.asm,由the C/C++ Header Files and Peripheral Examples提供。每个TMS320F28xxx处理器都提供了现成的连接器命令文件。提供的示例项目演示了如何使用这些文件。本应用文档以TMS320F2808为例。 该软件独立存放于F28xxx_Flash_to_Ram文件夹中。代码使用的来自the C/C++ Header Files and Peripheral Examples的几个文件,经过了Code Composer Studio™ 3.3和F28xxx代码生成工具5.0.0B3版本的测试。
3.描述: 一般的程序流程是这样子的:code_start->wd_disable->copy_sections->c_int00->mian() 。这个软件流程比标准的软件流程仅仅多了调用复制代码段函数。标准的软件流程:code_start->wd_disable->c_int00->mian()。
程序开始和关闭看门狗: code_start 和wd_disable 的运行代码由DSP28xxx_CodeStartBranch.asm文件提供。上电后,code_start正常执行,因为它被分配给Flash的引导地址的0x3F7FF6。详见:《Running an Application from Internal Flash Memory on the TMS320F28xx DSP
》(SPRA958)
  1. WD_DISABLE .set 1 ;set to 1 to disable WD, else set to 0
  2. .ref copy_sections
  3. .global code_start
  4. ***********************************************************************
  5. * Function: codestart section
  6. *
  7. * Description: Branch to code starting point
  8. ***********************************************************************
  9. .sect "codestart"
  10. code_start:
  11. .if WD_DISABLE == 1
  12. LB wd_disable ;Branch to watchdog disable code
  13. .else
  14. LB copy_sections ;Branch to copy_sections
  15. .endif
  1. WD_DISABLE .set 1 ;set to 1 to disable WD, else set to 0
  2. .ref copy_sections
  3. .global code_start
  4. ***********************************************************************
  5. * Function: codestart section
  6. *
  7. * Description: Branch to code starting point
  8. ***********************************************************************
  9. .sect "codestart"
  10. code_start:
  11. .if WD_DISABLE == 1
  12. LB wd_disable ;Branch to watchdog disable code
  13. .else
  14. LB copy_sections ;Branch to copy_sections
  15. .endif



这个函数从the C/C++ Header Files and Peripheral Examples提供的CodeStartBranch.asm文件修改而来,只是第二个调用用copy_sections代替了_c_int00。这个调用仅仅在WD_DISABLE为0时执行。 上面的代码,WD_DISABLE 被设置为1。这使得wd_disable运行。wd_disable的代码如下:
  1. ***********************************************************************
  2. * Function: wd_disable
  3. *
  4. * Description: Disables the watchdog timer
  5. ***********************************************************************
  6. .if WD_DISABLE == 1
  7. .sect "wddisable"
  8. wd_disable:
  9. SETC OBJMODE ;Set OBJMODE for 28x object code
  10. EALLOW ;Enable EALLOW protected register access
  11. MOVZ DP, #7029h>>6 ;Set data page for WDCR register
  12. MOV @7029h, #0068h ;Set WDDIS bit in WDCR to disable WD
  13. EDIS ;Disable EALLOW protected register access
  14. LB copy_sections ;Branch to copy_sections
  15. .endif
  1. ***********************************************************************
  2. * Function: wd_disable
  3. *
  4. * Description: Disables the watchdog timer
  5. ***********************************************************************
  6. .if WD_DISABLE == 1
  7. .sect "wddisable"
  8. wd_disable:
  9. SETC OBJMODE ;Set OBJMODE for 28x object code
  10. EALLOW ;Enable EALLOW protected register access
  11. MOVZ DP, #7029h>>6 ;Set data page for WDCR register
  12. MOV @7029h, #0068h ;Set WDDIS bit in WDCR to disable WD
  13. EDIS ;Disable EALLOW protected register access
  14. LB copy_sections ;Branch to copy_sections
  15. .endif
这要求看门狗在copy_sections和c_int00函数运行期间被除能,否则,看门狗可能会在进入main()之前超时。这个函数也是从the C/C++ Header Files and Peripheral Examples提供的CodeStartBranch.asm文件修改而来,只是用copy_sections代替了_c_int00。
Copy_sections: DSP28xxx_SectionCopy_nonBIOS.asm文件提供了copy_sections的代码,第一次运行到这里,看门狗是关闭的,段已经准备好被复制,段大小被存放在累加器,装载地址放在XAR6中,执行地址放在XAR7中,这个功能例子如下:
  1. MOVL XAR5,#_text_size ; Store Section Size in XAR5
  2. MOVL ACC,@XAR5 ; Move Section Size to ACC
  3. MOVL XAR6,#_text_loadstart ; Store Load Starting Address in XAR6
  4. MOVL XAR7,#_text_runstart ; Store Run Address in XAR7
  5. LCR copy ; Branch to Copy
  1. MOVL XAR5,#_text_size ; Store Section Size in XAR5
  2. MOVL ACC,@XAR5 ; Move Section Size to ACC
  3. MOVL XAR6,#_text_loadstart ; Store Load Starting Address in XAR6
  4. MOVL XAR7,#_text_runstart ; Store Run Address in XAR7
  5. LCR copy ; Branch to Copy

段的大小,装载开始标志,执行开始标志都由连接器产生,这是在内存分配 -链接器命令文件一节讨论。
在地址和段长度都被存放好之后,copy程序被调用来确定段是否被编译器产生,这由检测累加器是否为0来确定。
  1. copy:
  2. B return,EQ ; Return if ACC is Zero (No section to copy)
  3. RPT AL ; Copy Section From Load Address to
  4. || PWRITE *XAR7, *XAR6++ ; Run Address
  5. return:
  6. LRETR ; Return
  1. copy:
  2. B return,EQ ; Return if ACC is Zero (No section to copy)
  3. RPT AL ; Copy Section From Load Address to
  4. || PWRITE *XAR7, *XAR6++ ; Run Address
  5. return:
  6. LRETR ; Return

如果累加器为0,程序会返回到调用前的地址,如果累加器不为0,有段需要被复制。这用上面所示的PWRITE指令来实现,PWRITE复制XAR6指向的存储器的内容到XAR7指向的内容。在这里,就是复制装载代码的地址的内容到运行代码的地址。这样,一直到累加器为0,完成整个段的复制,当所有段都被复制完,程序就会跳到c_int00,如下:
  1. LB _c_int00 ; Branch to start of boot.asm in RTS library
  1. LB _c_int00 ; Branch to start of boot.asm in RTS library


到这里,C语言环境被建立,main()是可进去的。 完整的copy_sections程序请参见相关文件夹中的DSP28xxx_SectionCopy_nonBIOS.asm。

内存分配 - 连接命令文件(.cmd)
如第二节所述,连接命令文件(.cmd)是用来告诉连接器怎么分配编译器产生的段的。The C/C++ Header Files and Peripheral Examples提供了标准的连接命令文件(.cmd) 相关代码文件提供了三个链接器命令文件用于配置内存分配。
· F280xx_nonBIOS_flash.cmd
· F281x_nonBIOS_flash.cmd
· F2833x_nonBIOS_flash.cmd
每个文件一般都用相同的方法编写,只是在存储器方面有很小的一些差异(特殊设备)。连接命令文件(.cmd)的Memory部分是根据设备的内存空间来连接编译好的段的。详情参见具体控制器的数据手册。 下表展示TMS320F2808的存储器映射:

TMS320F28xxx系列控制器内置RAM,可以被分配为一个单独的段,或者更多的段,因为它是连续的存储器映射。如上图所示,F2808有映射到存储器空间的L0,L1和H0 SARAMs,允许生成一个大的内存块,这个块可以被CMD文件的MEMORY部分如下定义:
  1. RAM_H0L0L1 : origin = 0x008000, length = 0x004000 /* on-chip RAM */
  1. RAM_H0L0L1 : origin = 0x008000, length = 0x004000 /* on-chip RAM */

其余的也可以定义在MEMORY部分,完整的内存分配,请参见相关文件中的CMD文件。 链接器命令文件的第二部分是SECTIONS。这是实际编译器把段连接到的存储区。所有DSP28xxx_CodeStartBranch.asm 和 DSP28xxx_SectionCopy_nonBIOS.asm的段都被装载到flash中运行,这部分如下所示分配:
  1. codestart : > BEGIN_FLASH, PAGE = 0 /* Used by file CodeStartBranch.asm */
  2. wddisable : > FLASH_AB, PAGE = 0 /* Used by file CodeStartBranch.asm */
  3. copysections : > FLASH_AB, PAGE = 0 /* Used by file SectionCopy.asm */
  1. codestart : > BEGIN_FLASH, PAGE = 0 /* Used by file CodeStartBranch.asm */
  2. wddisable : > FLASH_AB, PAGE = 0 /* Used by file CodeStartBranch.asm */
  3. copysections : > FLASH_AB, PAGE = 0 /* Used by file SectionCopy.asm */



其他被初始化的段被下载到flash,但是在ram中运行。这是通过load和run指令来实现。下面展示一个例子:
  1. .text : LOAD = FLASH_AB, PAGE = 0 /* Load section to Flash */
  2. RUN = RAM_H0L0L1,PAGE = 0 /* Run section from RAM */
  3. LOAD_START(_text_loadstart),
  4. RUN_START(_text_runstart),
  5. SIZE(_text_size)
  1. .text : LOAD = FLASH_AB, PAGE = 0 /* Load section to Flash */
  2. RUN = RAM_H0L0L1,PAGE = 0 /* Run section from RAM */
  3. LOAD_START(_text_loadstart),
  4. RUN_START(_text_runstart),
  5. SIZE(_text_size)


为了获得与一个段相关联的特定地址,如上所示,使用了LOAD_START, RUN_START, 和SIZE指令。这些指令的地址和大小在DSP28xxx_SectionCopy_nonBIOS.asm文件使用到,用以在复制过程中指向正确的地址。DSP28xxx_SectionCopy_nonBIOS.asm把这些值创建为全局变量,如下图所示
  1. .global _cinit_loadstart, _cinit_runstart, _cinit_size
  2. .global _const_loadstart, _const_runstart, _const_size
  3. .global _econst_loadstart, _econst_runstart, _econst_size
  4. .global _pinit_loadstart, _pinit_runstart, _pinit_size
  5. .global _switch_loadstart, _switch_runstart, _switch_size
  6. .global _text_loadstart, _text_runstart, _text_size
  1. .global _cinit_loadstart, _cinit_runstart, _cinit_size
  2. .global _const_loadstart, _const_runstart, _const_size
  3. .global _econst_loadstart, _econst_runstart, _econst_size
  4. .global _pinit_loadstart, _pinit_runstart, _pinit_size
  5. .global _switch_loadstart, _switch_runstart, _switch_size
  6. .global _text_loadstart, _text_runstart, _text_size

测试例子: 提供的示例在TMS320F2812,TMS320F2808,TMS320F28335eZdsp开发上进行了测试。板子上LED的闪烁可以从视觉上证实程序是否正确运行。下面的程序是基于F2808eZdsp评估板设计和测试的。同样的,这种方法可以用于其他eZdsp开发板。

Code Composer Studio环境: 1.使用USB线连接F2808eZdsp开发板到PC,接上电源线给板子供电2.打开Code Composer Studio,设置F2808 eZdsp 仿真器。 3.打开和编译Example_280xx_Flash_to_RAM_nonBIOS.pjt。 4.下载.out文件到芯片的flash中。 5.调试程序(debug)。 6.运行程序(run)。
在eZdsp电路板上的LED应闪烁,表示程序正在运行。
应用: 现有的Flash应用程序可以很容易地通过移植相关代码文件来实现此功能。基本的移植步骤如下: 1.用DSP28xxx_CodeStartBranch.asm替换CodeStartBranch.asm。
2.在工程中添加DSP28xxx_SectionCopy_nonBIOS.asm文件。 3.用特殊生成的CMD文件代替现有的CMD文件。
这个基本步骤不适用于一些特殊情况,比如用户自己定义的段,等
应用例子: 为了演示的应用程序集成的过程,在C280x,C2801x
C / C ++头文件和外设示例的Example_2808_Flash.pjt中使用下列步骤移植。
1.下载安装C280x, C2801x C/C++ Header Files and Peripheral Examples。 2.如上所述连接板,打开项目文件。 3.删除项目中的DSP280x_CodeStartBranch.asm文件,在项目中添加DSP28xxx_CodeStartBranch.asm文件。 4.在项目中添加DSP28xxx_SectionCopy_nonBIOS.asm文件。 5.删除项目中的cmd文件,在项目中添加F280xx_nonBIOS_flash.cmd文件。 6.把DSP280x_usDelay.asm中的.sect “ramfuncs”改为.text,使DSP28x_usDelay在被分配在.test段中。 7.删除DSP280x_SysCtrl.c文件中的#pragma CODE_SECTION(InitFlash, “ramfuncs”);。使得InitFlash( )函数被分配到.test而不是ramfuncs。 8.删除Example_280xFlash.c文件中的#pragma CODE_SECTION(epwm1_timer_isr, “ramfuncs”);和#pragma CODE_SECTION(epwm2_timer_isr, “ramfuncs”);。使得中断服务函数被分配到.test而不是ramfuncs。 9.删除Example_280xFlash.c文件中的MemCopy(&RamfuncsLoadStart, &RamfuncsLoadEnd, &RamfuncsRunStart);和
InitFlash( );。由于代码已经被复制到RAM,这些是不需要的了。
10.如上所述,编译连接程序,把程序下到芯片里运行。
在eZdsp电路板上的LED应闪烁,表示程序正在运行。
存储空间占用: 因为仅仅在DSP28xxx_SectionCopy_nonBIOS.asm文件中增加了copy_sections的代码。增加的占用的片内flash为0x3C。code_start 和wd_disable函数没有增加额外的代码,他们本来就在C/C++ Header Files and Peripheral Examples的所用项目中被使用。


测试: 因为这个功能开机后直接实现,闪存等待状态,锁相环(PLL)都没有配置,因此,它们都运行在默认值Flash等待状态为15个周期,对于F280xx/F281x设备SYSCLKOUTOSCCLK/2,对于F2833x设备SYSCLKOUTOSCCLK/ 4。使用Code Composer Studio分析功能可以测量运行时间。下表给出了每个F28xxx控制器从启动到main()函数的第一个指令所用的时间,如下所示,由于个平台的代码长度和系统时钟不一样,他们的运行时间也不一样。


限制: 此实现的限制因素为使用的TMS320F28xxx控制器内部RAM的大小。这限制了那些工程可以使用这种方法,如果工程太大,以至于没法放进RAM里,这种方法是不能用的。
建议: 有一些项目需要这种功能,但不是所有被初始化段都要复制到RAM或者没有足够的RAM放下所有的段。仅仅需要复制应用代码本身。这种情况下,仅仅需要复制.text段到RAM。这样子,可以把DSP28xxx_SectionCopy_nonBIOS.asm文件和cmd文件中复制其他段的代码删掉,把其他段放在flash中运行。减少flash的占用空间和缩短了运行到main()的时间。 应该确定应用程序可以处理复制代码执行时间的一点滞后。如果应用程序不能处理这段时间,可以使用Running an Application from Internal Flash Memory on the TMS320F28xx DSP (SPRA958)中的方法复制一部分主要的代码到ram。 如果使用DSP的引导,建议使用Running an Application from Internal Flash Memory on the TMS320F28xx DSP (SPRA958)中的方法复制一部分主要的代码到ram。一个使用DSP / BIOS的项目,通常是一个较大的项目,不建议使用此方案。

结语: 这份应用文档展示,在建立C语言环境之前,通过把flash的代码复制到ram,可以使TMS320F28xxx的控制器实现零等待状态运行。这方案给出了代码和存储空间的限制,为设计者提供了实现了这种功能的相关文件。