让DSP从FLASH启动——step bystep
TI的资料上的说明如下:
位于CE1空间的ROM中的代码首先通过DMA/EDMA被搬入地址0处。加载过程在复位信号撤消之后开始,此时CPU内部保持复位状态,由DMA/EDMA执行1个单帧的数据块传输。传输完成后,CPU退出复位状态,开始执行地址0处的指令。
对于C6416,片上的Bootloader工具只能将1 KB的代码搬入内部RAM。通常情况下,用户应用程序的大小都会超过这个限制。所以,需要在外部Flash的前1
KB范围内预先存放一小段程序,待片上Bootloader工具把此段代码搬移入内部并开始执行后,由这段代码实现将Flash中剩余的用户应用程序搬移入内部RAM中。此段代码可以被称作一个简单的二级Bootloader。[1]
由于执行二级Bootloader时C的运行环境还未建立起来,所以必须用汇编语言编写。如果你不会写汇编程序,这段代码可以从网上下。TI发布的标准bootloader是对存储格式有要求的,即引导表格式。C64XX的引导表格式如下:
字
内容(大、小端根据体系结构而定,小端时先写LSB)
1
程序入口点entry_point
2
第一段的块大小(4的整数倍字节)
3
第一段在RAM
中的起始地址
4
第一段的内容
……
……(直到第一段结束)
第二段的块大小(4的整数倍字节)
第二段在RAM
中的起始地址
第二段的内容
……
……(直到第二段结束)
……
……
第n段的块大小(4的整数倍字节)
第n段在RAM
中的起始地址
第n段的内容
……
……(直到第n段结束)
引导表结束符(写4次0x00000000)
以下是对应的bootloader汇编代码,笔者在官方版本上稍作了修改,不妨使用。
;boot.asm
.title "Flash bootup utility for c6416"
.option D,T
.length 102
.width 140
COPY_TABLE .equ 0x64000400
EMIF_BASE .equ 0x01A80000
.sect ".boot_load"
.global _boot
_boot:
;************************************************************************
;* Debug Loop- Comment out B for Normal Operation
;************************************************************************
zero B1
_myloop: ; [!B1] B _myloop
nop 5
_myloopend:nop
;************************************************************************
;* ConfigureEMIF
;************************************************************************
;CONFIG THE EMIFB_GBLCTL
MVKL 0x01A80000, A4
||MVKL 0x000120BC, B4
MVKH 0x01A80000, A4
||MVKH 0x000120BC, B4
STW B4 ,*A4
;CONFIG THE EMIFB_CE1CTL
MVKL 0x01A80004, A4
||MVKL 0x1171C109, B4
MVKH 0x01A80004, A4
||MVKH 0x1171C109, B4
STW B4 ,*A4
nop 4
;****************************************************************************
;* Copy codesections
;****************************************************************************
mvkl COPY_TABLE, a3 ; load tablepointer
mvkh COPY_TABLE, a3
ldw *a3++, b1 ; Load entrypoint
copy_section_top:
ldw *a3++, b0 ; byte count
ldw *a3++, a4 ; ram startaddress
nop 3
[!b0] bcopy_done ; have we copied allsections?
nop 5
copy_loop:
ldb *a3++,b5
sub b0,1,b0 ; decrementcounter
[ b0] b copy_loop ; setup branch if not done
[!b0] b copy_section_top
zero a1
[!b0] and 3,a3,a1
stb b5,*a4++
[!b0] and -4,a3,a5 ; round address up to next multiple of4
[ a1] add 4,a5,a3 ; round address up to next multipleof 4
;****************************************************************************
;* Jump toentry point
;****************************************************************************
copy_done:
b .S2 b1
nop 5
再说说如何烧写flash。[2]
把代码等写入Flash的办法大体上可分为以下几种:
①使用通用烧写器写入。
②使用CCS中自带的FlashBurn工具。
③用户自己编写烧写Flash的程序,由DSP将内存映像写入Flash。
其中,使用通用烧写器烧写需要将内存映像转换为二进制或十六进制格式的文件,而且要求Flash器件是可插拔封装的。这将导致器件的体积较大,给用户的设计带来不便。
使用TI公司提供的FlashBurn工具的好处在于使用较为直观。FlashBurn工具提供的图形界面可以方便地对Flash执行擦除、编程和查看内容等操作。但这种力法需要FlashBurn工具运行时下载一个.out镜像(FBTC,FlashBurn
Target Component)到DSP系统中,然后由上位PC机通过仿真器发送消息(指令和数据)给下位DSP,具体对Flash的操作由FBTC执行。FlashBurn工具不能识别.out文件,只接受.
hex的十六进制文件,因此,需要将.out文件转换为.hex文件。这个转换的工具就是TI公司提供的Hex6x.exe工具。转换过程的同时,需要一个cmd文件指定作为输入的.out文件,输出的.hex文件的格式,板上Flash芯片的类型和大小,需要写入Flash中的COFF段名等。
使用用户自己编写的烧写Flash的程序较为灵活,避免了文件格式转换的繁琐。不过,此方法要求用户对自己使用的Flash芯片较为熟悉。在此仅对这种方法具体介绍。
先把用户应用程序(包含二级Bootloader)编译生成的.out文件装载到目标DSP系统的RAM中,再把烧写Flash的工程编译生成的.out文件装载到目标DSP系统RAM的另一地址范围,执行Flash烧写程序,完成对Flash的烧写。这个办法要注意避免两次装载可能产生的地址覆盖,防止第2次装载修改了应该写入Flash的第1次装载的内容。
具体方法是:如果你使用DSP/BIOS,可以修改烧写程序的.cdb文件中的system ->MEM->ISRAM,右键-〉属性,使base=0x00080000,
len=0x00020000 (6416的内部RAM是1M,要根据你的CPU内部RAM改写)。这样生成的.cmd文件就会把烧写程序放到0x00080000开始的内存中去。同样方法将应用程序限制在0x00000400~0x00080000的范围(前边1k留给bootloader)。不过这样做有一个缺点,就是后边0x00020000的内存被浪费了,所以你可以写成0x00000400
~0x00100000的范围,只要应用程序没用到后面的0x00020000,就不会产生抵制覆盖。如果你没用DSP/BIOS,那你一定自己写了.cmd文件,直接改.cmd文件的SECTIONS,将程序个段放入自己想放的位置,就不用我多说了吧。
.out文件遵守COFF(公共目标文件格式)格式。一个COFF段就是占据一段连续存储空间的程序或数据块。COFF段分为3种类型:代码段、初始化数据段和未初始化数据段。
对于EMIF加载方式,需要加载的镜像由代码段(如.vectors和.text等)和初始化数据段(如.cinit,.const,.switch,.data等)构成。所有未初始化的数据段(如.bss等)都不需要烧入到Flash中。各位可以参考边以后生成的.map文件(在Debug文件夹),通常看起来像这样:
>>Linked Mon Aug 10 14:45:30 2009
OUTPUT FILENAME: <./Debug/my_timer.out>
ENTRY POINTSYMBOL: "_c_int00" address:00001060
SECTIONALLOCATION MAP
output attributes/
section page origin length input sections
-------- ---- ---------- ---------- ----------------
frt 0 00000400 00000000 UNINITIALIZED
.prd 0 00000400 00000000 UNINITIALIZED
.sysregs 0 00000400 00000000 UNINITIALIZED
……
.cinit 0 00001120 00000b24
00001120 0000033c my_timercfg.obj (.cinit)
0000145c 00000004 --HOLE-- [fill = 0]
……
通常包含以下内容:程序入口点ENTRY POINT,就是程序开始的位置;段名称,对应的起始地址、长度、初始化状态。凡是后边是UNINITIALIZED的,都不用写入FLASH中,以免占地方,FLASH烧写很慢,少烧点是点。
如果想减少代码量以下有几点建议:
1、 尽量使用DSP
提供的API函数,而不用标准库函数。我曾在代码中用printf函数,结果代码量很大,删掉就少了很多。
2、 可以删除RTDX的监测和追踪,具体方法是:打开.cdb文件->system->Global
Setting->右键property->删掉Enable Real Time Analysis和Enable
All TRC Trace Event Classes。
3、 尽量不用DSP/BIOS,能省了很多废话。
如果不介意烧写时间长,可以用最简单粗暴的方法:将整个程序作为一个段,烧写进FLASH,存储器并不真正区分这些代码到底属于哪个段。少些代码可以如下:
#include
#include
#include
#include
#include
#include
#include
#include"flash.h"
#include"section.h"
#include"flashbootcfg.h"
unsignedchar* boot_addr = (unsigned char*)0x0; //0x64000000;
void main()
{
unsigned int i;
//NMI使能
IRQ_nmiEnable();
//开总中断
IRQ_globalEnable();
//整片擦除
// reset_fls();
printf( "Erasing flashchip...");
erase_chip();
printf( " OK
");
printf( "writingbootloader...");
//在地址0x64000000处写BOOT
for(i = 0; i < BOOT_SECTION_SIZE; i++)
{
flash_writes(boot_addr++,*(( char*)(BOOT_SECTION_ADDRESS+i)));
}
printf( " OK
");
printf( "writing entrypoint...");
//在地址0x64000400处写ENTRY_POINT
小端模式
boot_addr=(unsigned char *)0x400;
flash_writes(boot_addr++,(char)ENTRY_POINT);
flash_writes(boot_addr++,(char)(ENTRY_POINT>>8));
flash_writes(boot_addr++,(char)(ENTRY_POINT>>16));
flash_writes(boot_addr++,(char)(ENTRY_POINT>>24));
printf( " OK
");
printf( "writing programsection...");
//应用程序段长度
flash_writes(boot_addr++,(char)PROGRAM_SIZE);
flash_writes(boot_addr++,(char)(PROGRAM_SIZE>>8));
flash_writes(boot_addr++,(char)(PROGRAM_SIZE>>16));
flash_writes(boot_addr++,(char)(PROGRAM_SIZE>>24));
//应用程序段在RAM中的地址
flash_writes(boot_addr++,(char)PROGRAM_ADDRESS);
flash_writes(boot_addr++,(char)(PROGRAM_ADDRESS>>8));
flash_writes(boot_addr++,(char)(PROGRAM_ADDRESS>>16));
flash_writes(boot_addr++,(char)(PROGRAM_ADDRESS>>24));
//应用程序的数据
for(i = 0; i < PROGRAM_SIZE; i++)
{
flash_writes(boot_addr++,*(( char*)(PROGRAM_ADDRESS+i)));
}
printf( " OK
");
printf( "writing tableend...
");
//table end
for(i = 0; i < 3; i++)
{
flash_writes(boot_addr++,(unsignedchar)TABLE_END);
flash_writes(boot_addr++,(unsignedchar)(TABLE_END>>8));
flash_writes(boot_addr++,(unsignedchar)(TABLE_END>>16));
flash_writes(boot_addr++,(unsignedchar)(TABLE_END>>24));
}
printf( "flash program isover!
");
//boot();
return;
}
//flash.c
#include
#include
#include"flash.h"
structERASE_CHIP ec={
{0xaaa, 0x555, 0xaaa, 0xaaa, 0x555,0xaaa},
{0xaa, 0x55, 0x80, 0xaa, 0x55, 0x10}
};
structERASE_SECT es={
{0xaaa, 0x555, 0xaaa, 0xaaa, 0x555,0x80000},
{0xaa, 0x55, 0x80, 0xaa, 0x55, 0x30}
};
structWRITE_FLS wf={
{0xaaa, 0x555, 0xaaa, 0x80000},
{0xaa, 0x55, 0xa0, 0}
};
intout_range(unsigned char * addr)
{
if ((unsignedint)addrFLS_MAX)
{
return 1;
}
else
{
return 0;
}
}
voidreset_fls()
{
*(unsigned char *)FLS_BASE=0xf0;
}
charflash_reads(unsigned char * addr)
{
addr+=FLS_BASE;
if(out_range(addr))
{
return -1;
}
else
{
return *addr;
}
}
intflash_readm(unsigned char * from, unsigned char * to, const int size)
{
int i;
for(i=0; i
{
*(to+i)=flash_reads(from+i);
}
return i;
}
interase_chip()
{
int i;
// struct ERASE_CHIP ec;
for(i=0; i<6; i++)
{
*(volatile unsigned char*)(FLS_BASE+ec.addr[i]) =ec.data[i];
}
while((*(volatile unsigned char*)(FLS_BASE+0xaaa) & 0x80) != 0x80);
for(i=0;i
{
while(*(volatile unsigned char*)(FLS_BASE+i)!=0xff);
}
reset_fls();
return 0;
}
interase_sect(unsigned char * page)
{
int i;
page+=FLS_BASE;
es.addr[5]=(unsigned int)page;
if (out_range(page))
{
return -1;
}
for (i=0; i<6; i++)
{
*(char*)(FLS_BASE+es.addr[i])=es.data[i];
}
while((*(unsigned char *)page & 0x80)!= 0x80);
return 0;
}
intflash_writes(unsigned char * addr, const char what)
{
int i=0;
wf.addr[3]=(unsigned int)addr;
wf.data[3]=what;
for (i=0;i<4;i++)
{
*(char*)(wf.addr[i]+FLS_BASE)=wf.data[i];
}
while(*( char *)(FLS_BASE+addr) != what);
return 1;
}
intflash_writem(const unsigned char *from, unsigned char *to, const int size)
{
int i;
if(out_range(FLS_BASE+to) ||out_range(FLS_BASE+to+size))
{
return 0;
}
for ( i=0; i
{
flash_writes(to+i, *(from+i));
}
return i;
}
unsigned char* judge_sector(unsigned char * addr)
{
unsigned char * sector;
sector=addr-(unsigned int)addr%0x20000;
return sector;
}
再将前文给出的bootloader代码一并加入到烧写程序的工程中。
其中定义了一些符号,BOOT_SECTION_ADDRESS和BOOT_SECTION_SIZE表示.bootload段的起始地址和大小,在烧写程序的.map文件中查找;ENTRY_POINT,PROGRAM_SIZE,PROGRAM_ADDRESS在应用程序里查找。前文已述,PROGRAM_ADDRESS可以是一个初始化段的开始地址,PROGRAM_SIZE用最后一个初始化段结束地址减去一个初始化段的起始地址。这些定义看起来像这样:
//section.h
#define ENTRY_POINT 0x00001060
/* boot段的长度和RAM中地址设定 */
#defineBOOT_SECTION_SIZE 0x000000a0
#defineBOOT_SECTION_ADDRESS 0x000926e0
/* program段的长度和RAM中地址设定 */
#definePROGRAM_SIZE 0x0000750C
#definePROGRAM_ADDRESS 0x000008f4
//flash.h
#defineFLS_BASE 0x64000000 //BCE1:0x64000000~0x67ffffff
#defineFLS_MAX 0x640fffff //addr space 20bits
#defineFLS_MID 0x64080000
#defineCHIP_SIZE 0x80000
#defineSECT_SIZE 0x20000
structERASE_CHIP {
const unsigned int addr[6];
const char data[6];
};//
structERASE_SECT {
unsigned int addr[6];
const char data[6];
};//
//alias ERASE_SECT.addr[5]sect_addr;
structWRITE_FLS {
unsigned int addr[4];
char data[4];
};
intout_range(unsigned char * addr);
voidreset_fls();
charflash_reads(unsigned char * addr);
intflash_readm(unsigned char * from, unsigned char * to, const int size);
interase_chip();
interase_sect(unsigned char * page);
intflash_writes(unsigned char * addr, const char what);
intflash_writem(const unsigned char *from, unsigned char *to, const int size);
unsigned char* judge_sector(unsigned char * addr);
你也可以将boot.asm放在应用程序中,然后专门为这段程序指定一个段地址,原理是一样的,只不过我们试过,这里不推荐。
将应用程序和烧写程序下载到RAM后,运行烧写程序,这个过程很慢,耐心等。烧写完成后关掉DSP电源,再重新启动,加载速度也不快,行不行等5分钟就知道了。
参考文献:
[1] 本文参考过网上一些资料,但能保证我的方法是对的。
[2] TMS320C6000系列DSP的Flash启动设计;作者:赵凡 丑武胜等;来源:单片机及嵌入式系统应用