如何使用Jlink烧录BIOS到GEC2440
1、 背景:
粤嵌教育嵌入式培训班ARM部分第一节课内容是带学生玩板子,烧BIOS(即bootloader)到flash,然后使用BIOS程序烧录linux内核镜像、根文件系统、WinCE系统NK等。拿到开发板光盘一看,烧录工具竟然是老掉牙的SJF2440···我的笔记本没有并口,也懒得去研究这个已经淘汰过时的烧录工具了,于是乎花了点时间研究了下使用Jlink烧录2440 nandflash、norflash的方法。
2、 实验环境:
2.1、Jlink:taobao买的山寨Jlink,固件版本V8,驱动版本为V4.08I。附图2.1
2.2、开发板:GEC2440_V1.1
2.3、LCD:群创7寸(480 * 272)
2.4、串口:使用PL2303 USB转串口线(我的板子DB9接头和电脑串口连接没反应···,也懒得详查是板子硬件问题还是串口线问题了)
2.5、MDK3.50:这个不用说了,用来调程序的。
http://www.rosoo.net/a/201006/9696.html。这篇资料在网上有N多版本,我也不知道哪个是原创了,总之感谢作者!
附图2.1, Jlink commander连接上后截图
不管是老式的并口Jtag工具,还是现在很流行的Jlink仿真器,总之都是提供一个从PC出来的Jtag接口跟目标Jtag设备连接。这里的目标Jtag设备指的就是CPU(譬如GEC2440开发板上的主芯片S3C2440)
不管是nandflash还是norflash,本身都是和CPU相连的,烧写他们只能通过CPU。即在CPU中运行flash擦除代码、flash编程代码,即可实现对flash的烧写。Bootloader的主要功能其实就是提供这些功能代码,所以一旦板子上已经烧录有bootloader的情况下,我们不必使用仿真器,只需要通过PC终端(譬如最常用的dnw)便可以烧写自己的代码bin文件到板子SDRAM运行,或者烧录他们到nor/nand flash。
问题出来了,板子上最初没有bootloader的时候怎么办呢?这就是本文主要描述的烧写bootloader的场景了。这时候外部的Jtag工具就成为必须了,不管是并口Jtag的软件模拟方式还是Jlink,都是以一个外部Jtag接口的角色,将最初的bin文件从PC机加载到板子运行,最终达到目标的。下面详细介绍Jlink工具实现烧录的细节。
其实真正的flash烧录并不是Jlink完成的,这个是大家必须首先明白的。你看开发板原理图上Jtag引脚和flash引脚有相连吗?没有。所以flash代码还是CPU运行烧录代码烧进去的。Jlink的作用仅仅是提供给CPU可以运行的代码,并且设置CPU使它从正确的地方开始运行。仅此而已。
话分两头,先说后半部分,Jlink弄给CPU用来烧录BIOS到flash的是什么代码呢?答案很有趣,就是BIOS代码本身。请大家看GEC2440的BIOS(这个BIOS在开发板光盘的目录为:开发板光盘\目标代码\目标代码\GEC2440_BIOS_V10.bin)运行的主菜单内容。其中第2功能项就是用来烧录代码到nandflash的。(之所以没有烧录到norflash的项目,是因为GEC2440开发板本身不支持norflash的启动,所以BIOS内也未实现该功能)这个第2功能选项可以将用户通过USB Port或者Serial Port事先download到SDRAM内的bin文件(这个bin文件可以是linux的image,rootfs,也可以是wince的NK.nb0,也可以是用户自己写的代码mycode.bin,当然也可以是我们的BIOS GEC2440_BIOS_V10.bin了)烧录到nandflash的指定分区内。
所以说,BIOS本身为我们烧录BIOS提供了两个有用功能:一是从PC下载GEC2440_BIOS_V10.bin到开发板的SDRAM,二是将下载的bin烧录到nandflash。所以我们唯一需要考虑的就是怎么样让这个BIOS在没有烧录前就在开发板上跑起来。这个就是重点中的关键点,也是Jlink大展神威的地方了。
附图4.1 GEC2440的BIOS运行时,dnw界面显示BIOS主菜单
接着说前半部分,就是怎么样让BIOS在没有烧录的情况下在开发板上跑起来的问题了。也就是说怎么样从PC下载BIOS的bin文件到开发板的SDRAM,并且做足够的设置,让开发板能够正确的从SDRAM内的BIOS开始运行。
这里主要依靠Jlink Commander这个工具。该工具是Jlink自带工具之一,安装Jlink驱动软件之后,在安装目录SEGGER下,J-Link ARM V4.08I目录下,即可找到JlinkCommander.exe。连接好Jlink和开发板,并且给开发板上电后,打开Jlink Commander,如果硬件无误就可以看到如图2.1所示的信息。可以看到,Jlink已经检测到目标CPU了。这时只要使用Jlink提供的命令loadbin,setpc,g,h,r等即可实现加载bin文件到开发板运行的目的。
附图4.2 Jlink Commander
上面两部分已经解释了所有的过程,但是还有一个问题。2440内部有两处可以执行代码的区域,一个是内部sram(即steppingstone),另一个是外部SDRAM。我们的BIOS有40KB左右,当然不可能加载到sram内运行了,只能加载到SDRAM运行。但是SDRAM运行代码前必须先初始化,这就要求我们在使用Jlink加载BIOS到SDRAM前,必须先使用Jlink加载一段初始化代码(我的示例工程中叫armplat.bin)到sram运行(sram不用初始化,可以直接加载bin运行,但是代码量必须限制在4KB之内,因为sram一共只有4KB大),这段初始化代码运行时会初始化SDRAM(后面大家会看到,其实还做了其他一些事情,譬如关看门狗和设置时钟)。然后等这段代码运行后,我们再加载BIOS到SDRAM运行,BIOS运行后再烧录BIOS到nandflash,于是乎一切都顺理成章了。
以下是初始化代码armplat.bin的主要代码部分。
IMPORTxmain
AREA RESET, CODE, READONLY
ENTRY
B Handler
Handler
ldr sp, =1024*4
B xmain
loop
B loop
END
最最精简的启动代码,reset后直接跳转到main函数(注意这里是xmain。至于为什么是xmain而不是main,那又是另一个故事了,此处且按住不表。)
#define rGPBCON (*(volatile unsigned int*)0x56000010)
#define rGPBDAT (*(volatile unsigned int *)0x56000014)
#define rGPBUP (*(volatile unsigned int *)0x56000018)
// Memory control
#define rBWSCON (*(volatile unsigned *)0x48000000) //Bus width & wait status
#define rBANKCON0 (*(volatile unsigned *)0x48000004) //Boot ROM control
#define rBANKCON1 (*(volatile unsigned *)0x48000008) //BANK1 control
#define rBANKCON2 (*(volatile unsigned *)0x4800000c) //BANK2 cControl
#define rBANKCON3 (*(volatile unsigned *)0x48000010) //BANK3 control
#define rBANKCON4 (*(volatile unsigned *)0x48000014) //BANK4 control
#define rBANKCON5 (*(volatile unsigned *)0x48000018) //BANK5 control
#define rBANKCON6 (*(volatile unsigned *)0x4800001c) //BANK6 control
#define rBANKCON7 (*(volatile unsigned *)0x48000020) //BANK7 control
#define rREFRESH (*(volatile unsigned *)0x48000024) //DRAM/SDRAM refresh
#define rBANKSIZE (*(volatile unsigned *)0x48000028) //Flexible Bank Size
#define rMRSRB6 (*(volatile unsigned *)0x4800002c) //Mode register set for SDRAM
#define rMRSRB7 (*(volatile unsigned *)0x48000030) //Mode register set for SDRAM
// CLOCK & POWER MANAGEMENT
#define rLOCKTIME (*(volatile unsigned *)0x4c000000) //PLL lock time counter
#define rMPLLCON (*(volatile unsigned *)0x4c000004) //MPLL Control
#define rUPLLCON (*(volatile unsigned *)0x4c000008) //UPLL Control
#define rCLKCON (*(volatile unsigned *)0x4c00000c) //Clock generator control
#define rCLKSLOW (*(volatile unsigned *)0x4c000010) //Slow clock control
#define rCLKDIVN (*(volatile unsigned *)0x4c000014) //Clock divider control
#define rCAMDIVN (*(volatile unsigned *)0x4c000018) //USB, CAM Clock divider control
// WATCH DOG TIMER
#define rWTCON (*(volatile unsigned *)0x53000000) //Watch-dog timer mode
#define rWTDAT (*(volatile unsigned *)0x53000004) //Watch-dog timer data
#define rWTCNT (*(volatile unsigned *)0x53000008) //Eatch-dog timer count
#define rINTMSK (*(volatile unsigned *)0x4a000008) //Interrupt mask control
// 延迟函数,用来实现LED闪烁
void delay(void)
{
inti;
for(i=0;i<500000;i++);
}
// 关所有中断
void disable_interrupt(void)
{
rINTMSK= 0xffffffff;
}
// 关看门狗
void wdt_disable(void)
{
rWTCON= 0x00;
}
// MPLL初始化,将主时钟设置为405MHz
void mpll_init(void)
{
rMPLLCON = (127<<12) | (2<<4) | (1<<0);
rCLKDIVN = (0x3<<1)|(0x1<<0);
}
// SDRAM初始化函数,这里的初始化参数来源于EXT_RAM.ini
void sdram_init(void)
{
rBWSCON= 0x22000000;
rBANKCON6= 0x00018005;
rBANKCON7= 0x00018005;
rREFRESH= 0x008404F3;
rBANKSIZE= 0x00000032;
rMRSRB6= 0x00000020;
rMRSRB7= 0x00000020;
}
// 主函数,依次调用上面各个功能函数,并最终以GPB5接得LED1闪烁作为成功执行的标// 志。
void xmain(void)
{
disable_interrupt();
wdt_disable();
mpll_init();
sdram_init();
//GPBCON[11:10]=01
rGPBCON&= ~(0x3<<10);
rGPBCON|=(0x1<<10);
rGPBUP|=(0x1<<5);
while(1)
{
//GPBDAT[5]=1
rGPBDAT|=(0x1<<5);
//DELAY
delay();
//GPBDAT[5]=0
rGPBDAT&=~(0x1<<5);
//DELAY
delay();
}
}
6.1、在MDK3.5中建立工程,编译以上代码。注意在选项->Target->ReadonlyMemory设置中,ROM1的start一定要设置为0x00000000,size设置为0x1000。因为我们的代码将来是要加载到内部sram中运行的,而且我们是选择从nandflash启动,nand启动时sram就是被映射到0x0地址的。选项栏中其他选项这里就不详细说了,详见我另一篇教程《Jlink+SDRAM裸奔2440教程》。
6.2、准备好刚才编译好得到的初始化代码armplat.bin,和GEC2440_BIOS_V10.bin,将之copy到D盘根目录下(也可以是其他盘根目录,选择根目录是因为在Jlink Commander的命令行下目录都是要手输的,根目录可以好输入一点,呵呵),将GEC2440_BIOS_V10.bin改名为boot.bin(原因同上,输入的时候减少一个一个扣字母的痛苦)。
6.3、启动Jlink Commander,看到附图2所示页面即表示Jlink Commander连接正确。若显示一些其他错误信息,譬如找不到目标Jtag设备、不能挂起CPU等等,请尝试:第一检查硬件连接,开发板是否已经上电,Jlink连线是否正确;第二,键入r命令尝试复位CPU。
6.4、设置Jlink速度。Jlink Commander连接后,默认速度为5KHz,这样下载速度很慢,因此我们使用speed12000 命令将Jlink速度设置为12MHz。
6.4、开始下载armplat.bin到sram运行。首先键入loadbin d:\armplat.bin 0x0,成功后,再次输入setpc 0x0,成功后再次输入g,此时armplat.bin 已经在内部sram内跑起来了,此时开发板上的LED1应该已经在闪烁了。
6.5、键入h命令挂起CPU。注意这一步很重要,如果忽略了此步骤,下面在下载boot.bin时会提示无法挂起CPU。
6.6、键入loadbin d:\boot.bin 0x33000000,成功后再次键入setpc 0x33000000,成功后再次键入g,完成。
注意:在最后一个g之前,一定要提前打开dnw(dnw在光盘/实用工具/串口工具dnw目录下),并且使用串口连接上开发板。因为g命令执行后,SDRAM中的BIOS会立即执行,如果连接了dnw就能在dnw的终端内看到BIOS执行时打印的信息(入附图4.1所示)。此时5s钟内如果不通过dnw做出选择,即会自动进入启动内核(nandflash内烧录的linux或wince内核)流程。那么我们这次辛辛苦苦的把BIOS下载的SDRAM就算是白费了,又得从6.1开始从新来过了。
附图6.1,D:\下置入boot.bin和armplat.bin
附图6.2,r命令复位CPU
附图6.3,speed命令设置Jlink速度为12MHz
附图6.4, 加载并运行armplat.bin
附图6.5,加载并运行boot.bin
7.1、通过dnw烧录BIOS、烧录image等操作请参考开发板光盘下/用户手册/GEC2440开发板用户手册.PDF,文档内说的很详细了。大家刚拿到开发板时都是有预烧录BIOS的,建议先不要破坏BIOS,按照这份文档所说先利用BIOS烧录别的bin做实验,待熟悉BIOS和dnw的使用后再做本实验。
7.2、dnw支持串口和USB两种download方式,但是我们一般会使用USB方式,因为速度会快一些。USB download要求要求连接USB线,并且要安装USB驱动。注意这个USB驱动是在dnw打开并且BIOS运行后选择了0号功能以后,PC端才会跳出发现新硬件的窗口,此时可以手动选择USB驱动文档进行安装。Dnw的USB驱动在开发板光盘内USB驱动程序文件夹内。
7.3、我在做实验时发现,dnw的configuration->Option页面中,USB Port->Download address栏目中输入0x30100000(dnw的默认值)时,linux的image和rootfs下载OK并且可以运行,但是WinCE的NK下载后运行不起来。后来反复实现发现将Download address改为0x30200000即可。猜测应该和SDRAM的空间分配使用有关,暂时没有时间去研究了。
7.4、后来在实验时发现另一个很有意思的问题。我上面列出的例程是我初次实验时写出的,一些初始化代码均使用了c函数,实验是ok的。(但是我实际上刚开始在start.S中是没有 ldr sp, =1024*4的,这个是应该加上的。但是当时没加,结果却是是对的···)
后来在课堂上现写了这个项目的代码,并且使用了汇编代码做初始化。实际就是把上面各个c函数中得内容直接copy到xmain中,结果下载后死活不能成功引导boot。查了几个小时后来发现了两个问题:一是在时钟初始化之后没有加delay,二是在sdram初始化之后没有加delay。
2440手册上有明确提出,当MPLL改变时必须添加至少7个nop以使修改后的时钟稳定。在使用c函数时,因为c调用和返回有一定开销,因此这个细节没有注意并没有造成错误,但是一旦使用汇编代码就会立刻显现出问题。
关于sdram初始化之后要加delay,我倒是没有在手册上找到明确说明。但是实验证明,sdram初始化之后确实也需要加延迟。