在DSP系统中通常贴片式FLASH存储器保存程序,并且在上电或复位时再将存储在FLASH中的程序搬移到DSP片内或者片外的RAM中全速运行。这个“程序搬移”的过程叫做自举加载。
本文以TMS320C5416 DSP对MBM29LV400BC存储器的操作为例,详细阐述了在线烧写FLASH并实现自举加载的方法。该方法适合于大多数C54x系列DSP对符合JEDEC标准的FLASH的操作。为便于读者使用,本文的程序全部采用C语言编写。
1 TMS320C5416与MBM29LV400BC的硬件接口
MBM29LV400BC与TMS320C5416的接口很方便,前者只需作为后者的外部数据存储器与其进行连接,而中间的逻辑电路采用CPLD实现即可。这里使用16位数据宽度,所以BYTE引脚通过一个上拉电阻接到3.3V电源。相应的VHDL语言程序为:
FLASH_CE<=DSP_DS;
FLASH_OE<=(NOT DSP_R_W)OR DSP_MSTRB;
FLASH_WE<=DSP_R_W OR DSP_MSTRB;
2 TMS320C5416自举引导过程
当MP/MC=0时,TMS320C5416被置于微计算机模式。上电或复位时,程序指针指向片内ROM区的FF80H单元,该单元放置了一条跳转指令,使程序跳转到F800H单元。而F800H就是自举加载器(Bootloader)引导程序的起始单元。
Bootloader的任务就是将存放在外部FLASH中的程序“搬运”到DSP内部或外部的RAM区,“搬运”完后跳转到程序入口处执行。存放在外部FLASH中的用户程序与一些必要的引导信息组合在一起,称为Boot表示(自举表)。16位模式下通用的Boot表结构如表1所示。
TMS320C5416提供了多种自举加载的方法。在此使用并行加载模式,因此令INT2=1和INT3=1。在并行模式下,自举表放在外部数据存储器的32K高端地址区间:8000H~0FFFH。自举表首地址放在数据空间的0FFFFH单元。加载时,Bootloader读取数据空间的0FFFFH单元中的内容,将其作为首地址,从该地址开始复制数据到内部的程序空间。复制完毕后,Bootloader便跳转到指定的程序入口地址,开始执行用户程序。
3 MBM29LV400BC的操作命令字及其C语言程序
MBM29LV400BC是FUJITSU公司的FLASH产品,容量为4M,其外部引脚和控制命令字都符合JEDEC标准,内部存储区分成11扇区。
对FLASH的读取可以直接进行。但对FLASH的写入和擦除等操作却是通过命令字进行的。考虑到FLASH的起始单元是8000H,加上命令字所提供的偏移地址,可以得到如下几常用的操作命令字:
读/复位命令:往FLASH任意一个单元写入数据0F0H,都可导致FLASH复位,从而使其处于“读”模式。
编程命令:需要四个总线周期。在字模式(16位数据宽度)下,如表2所示。
在向FLASH写入上述命令的时候,当最后一个总线周期完成时,FLASH便会启动内部算法,实现自动擦除、编程等内部操作。
对FLASH的正确操作顺序是:先复位,再擦除(片擦除或者扇区擦除),最后编程。
FLASH进行内部操作需要一定的时间,在这个过程中,FLASH会提供一些标志信号,通知用户内部操作过程是否已经结束。为简单起见,在对FLASH操作时不查询任何标志,而是采取延时的方法,等待FLASH内部操作结束,再进行下一步。延时的时间应该足够长,以保证FLASH擦除或编程成功。具体的延时时间应根据不同的系统确定。
根据上述FLASH的操作原理,编写了如下几个主机的C语言操作子程序:
typedef unsigned char BYTE;
void ResetFlash() //FLASH复位子程序
{
BYTE *pa;
pa=(BYTE *)0x8000;
*pa=0x0F0;
}
void EraseFlash() //FLASH片擦除子程序
{
BYTE *pa;
pa=(BYTE *)0x8555;
*pa=0x0AA;
pa=(BYTE *)0x82AA;
*pa=0x055;
pa=(BYTE *)0x8555;
*pa=0x080;
pa=(BYTE *)0x8555;
*pa=0x0AA;
pa=(BYTE *)0x82AA;
*pa=0x055;
pa=(BYTE *)0x8555;
*pa=0x010;
delay_10s(); //延时10s
}
void WriteFlash(BYTE *pa,int pd)//写FLASH某个单元的子程序
{
BYTE *tmp;
tmp=(BYTE *) 0x8555;
*tmp=0x0AA;
tmp=(BYTE *)0x82AA;
*tmp=0x055;
tmp=(BYTE *)0x8555;
*tmp=0x0A0;
*pa=pd;
delay_200ms(); //延时200ms
}
延时子程序可用简单的加法计数实现,例如延时10s的子程序示例如下:
delay_10s()
{
int i;
int j;
for(i=0;i<0x100;i++)
{
for(j=0;j<0x01000;j++);
}
}
具体的延时时间需要根据系统的时钟设置,应重新调整延时子程序中的i和j的值来确定,直到能够成功操作FLASH为止(可以通过CCS集成开发环境的“View→Memory...”菜单命令来查看被操作的FLASH单元是否成功擦除或写入)。
4 TMS320C5416在线烧写MBM29LV400BC的C语言程序
利用CCS编译并链接得到的目标文件(*.out文件)是二进制的COFF格式文件,需要利用Hex转换工具将其转换为大多数编程器能够接收的格式之一(如ASCII码十六进制格式、Intel格式等),然后利用专门的烧写工具烧写FLASH,这个方法在一般的用户系统上不便于实现。但是根据前面所述DSP的Boot原理,则可以写一段很简单的烧写程序,按照Boot表格式,将目标代码在线烧写进FLASH。
现在用一个实际的例子来说明在线烧写的过程。具体过程如下:
建立两个独立的工程文件:MyProject.pjt和FlashBurn.pjt。前者生成的目标文件就是要烧入到FLASH中的用户程序,后者则用来实现烧入过程。MyProject.pjt有两个程序段:第一段是“.text”段,位于0x1000开始的单元,长度为0x2e。它用中断的方法实现LED灯的闪烁;第二段为“.VECTORS”段,位于0x0080开始的单元,长度为0x0078。它实现中断向量表的重新映射。程序执行入口地址也在0x1000单位。烧写的只需烧写用户程序的已初始化段(代码或数据表)。用户程序各段的起始单元及其长度可以参考该工程编译链接后的性成的.map文件(如本例的MyProject.map),这是编写烧写程序的依据。
烧写工程文件MyProject.pjt只有一个程序段,定位在0x7000开始的存储区(注意不要与MyProject.pjt所占的程序空间有重叠),并将其_c_int00直接定位到该区域。
用户程序拟烧写到外部FLASH的0x8000开始的存储区。
两个工作建立并且编译完毕后,在CCS中先打开MyProject.pjt工程文件,用“File→Load Program...”菜单命令下载用户程序目标代码MyProject.out;再打开MyProject.pjt工程文件,下载FlashBurn.out,运行FlashBurn.out,即可将FlashBurn.out代码及其Boot引导信息写入到FLASH中。
脱离仿真器,令MP/MC=0,上电复位,即可实现自举加载并自动运行。
本例的烧写工程文件MyProject.pjt的C语言主程序如下:
typedef unsigned char BYTE;
void main()
{
BYTE *FlashPtr; //指向FLASH的指针
Int FlashData; //写往FLASH的数据
Int i;
BYTE *OriMem; //代码在片内RAM的源地址
Int iDatalen; //代码段长度
ResetFlash(); //复位FLASH
EesetFlash(); //整片擦除FLASH
//开始烧写FLASH,下面为Boot表引导信息
FlashPtr=(BYTE *)0x8000;
FlashData=0x10AA; //置16位存储器格式
WriteFlash(FlashPtr++,FlashData);
FlashData=0x7FFF; //置SWWSR初始化值
WriteFlash(FlashPtr++,FlashData);
FlashData=0x0F800; //置BSCR初始化值
WriteFlash(flashPtr++,FlashData);
FlashData=0x0000; //程序执行入口偏移地址XPC
WriteFlash(FlashPtr++,FlashData);
FlashData=0x1000; //程序执行入口地址PC
WriteFlash(FlahsPtr++,FlashData);
FlashData=0x002e; //第一个程序段的长度
WriteFlash(FlashPtr++,FlashData);
FlashData=0x0000; //第一个程序要装入的内部RAM区偏移地址
WriteFlash(FlashPtr++,FlashData);
FlashData=0x1000; //第一个程序段要装入的内部RAM区地址
WriteFlash(FlashPtr++,FlashData); //开始烧写第一段程序
OriMem=(BYTE *)0x1000; //第一段程序首地址
iDatalen=0x002e; //第一段程序长度
for(i=0;i<iDatalen;i++)
{
FlahsData=*OriMem++;
WriteFlash(FlashPtr++,FlashData);
}
//第二段程序(中断向量表)Boot引导信息
FlashData=0x0078; //第二个程序段的长度
WriteFlash(FlashPtr++,FlashData);
FlashData=0x0000; //第二个程序段要装入的内部RAM区偏移地址
WtireFlash(FlashPtr++,FlashData);
FlashData=0x0080; //第二个程序段要装入的内部RAM区地址
WriteFlash(FlashPtr++,FlashData); //开始烧写第二段程序(中断向量表)
OriMem=(BYTE *)0x080; //第二段程序首地址
iDatalen=0x0078; //第二段程序长度
for(i=0;i<iDatalen;i++)
{FlashData=*OriMem++;
WriteFlash(FlashPtr++,FlashData);
}
//程序烧写结束,写入Boot表结束标志
FlashData=0x0000; //引导表结束标志
WriteFlash(FlashPtr,FlashData); //在数据空间0xFFFF写入引导表起始地址
FlashPtr=(BYTE *)0x0FFFF;
FlashData=0x8000;
WriteFlash(FlashPtr,FlashData);
for(;;); //FLASH烧写完毕
}
本文所叙述的方法简单方便,无需考虑FLASH的共储格式。如果要烧写其它用户程序,只需修改FlashBurn.pjt中有关用户程序的起始地址、代码段长度等参数即可,因此通用性较好。文中所提供的程序已全部经过调试,能够成功运行。