硬件平台和工具:mini2440, jlink
为了移植uboot做准备,这里仅仅的裸机实验,仅仅包括LED灯,SDRAM和NAND FLASH的操作。
1 方案设计
该实验主要是控制SDRAM和NANDFLASH,LED仅仅作为调试使用。因此,在mini2440的4k的stepping stone(加载自nandflash的前4k字节)中,执行关看门狗、初始化堆栈、初始化时钟等操作。然后跳入以0x30000000为基地址的区域(即SDRAM中)运行,然后在该代码段中操作nandflash,这样验证nandflash操作正常与否的同时,又验证了SDRAM是否初始化成功。
2 程序结构
该工程除了Makefile还有三个文件:boot.s, lib.c, main.c。其中boot.s是第一阶段启动代码,main.c是第二阶段启动代码,lib.c有前两个文件调用的内容。
Makefile如下:
CROSS_COMPILE =arm-none-linux-gnueabi-
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
OBJCOPY =$(CROSS_COMPILE)objcopy
OBJDUMP =$(CROSS_COMPILE)objdump
main.bin: boot.s lib.cmain.c
$(CC) -g -c -o boot.o boot.s
$(CC) -g -c -o lib.o lib.c
$(CC) -g -c -o main.o main.c
$(LD) -Ttext 0x30000000 -g boot.o lib.o main.o -o main_elf
//上一行链接的main_elf是elf格式的文件,不是可执行文件。需要使用objcopy转换为可执行文件
$(OBJCOPY)-O binary -S main_elf main.bin
$(OBJDUMP) -D -m arm main_elf > main.dis
clean:
rm -f *.o
rm -f main.bin
rm -f main.dis
rm -f main_elf
3 head.s
首先关闭看门狗,设置堆栈,设置存储控制器,初始化时钟,将0x0(stepping stone)开始的代码拷贝到0x30000000(SDRAM)中。跳到0x30000000基地址的main中执行。
.text
.global _start
_start:
@关看门口狗
ldr r0,= 0x53000000
mov r1,#0x0
str r1,[r0]
@设置堆栈,堆栈指向4K地址,
ldr sp,=0x1000
@初始化存储控制器
bl memoryControllerInit
@设置时间
bl clock_init
@拷贝代码到0x30000000中
bl copy_to_sdram
@进入SDRAM之前重新设置堆栈
ldr sp,=0x34000000
@之前一直运行在初始地址为0的stepping中,使用的都是bl跳转都是基于相对地址的,所以只在stepping范围内跳转
@要跳转到0x34000000,不再使用bl,直接给PC赋值跳转。由于链接地址是0x30000000,所以这里跳转到SDRAM的main中
@跳转到SDRAM的main函数
ldr pc,=main
loop:
b loop
copy_to_sdram:
ldr r0,=0x0
ldr r1,=0x30000000
c_loop:
ldr r3,[r0],#4
str r3,[r1],#4
cmp r0,#0x1000
bne c_loop
mov pc,lr
4 main.c
读nandflash从0开始的地址,然后通过led显示。由于这里没有输出串口,这里用led灯判断读书是否为偶数,觉得不保险,可以使用jlink直接查看寄存器的数据。
void main()
{
//nand flash操作
unsigned long buf[2048];
buf[0]=0;buf[1]=0;buf[2]=0;buf[3]=0;
read_bytes(0,buf,2048);
led1_on_off(buf[0]%2);
led2_on_off((buf[0]>>8)%2);
led3_on_off((buf[0]>>16)%2);
led4_on_off((buf[0]>>24)%2);
while(1);
}
5 lib.c
主要包括时钟相关代码,存储控制器的代码,LED相关的GPIO代码,NANDFLASH相关的代码。其中LED相关的GPIO代码和时钟相关代码较为简单,不详细说明,这里需要记住的是PCLK=200kHz,HCLK=100kHz,FCLK=50kHz。
5.1 存储控制器的设置。
通过查阅mini2440的原理图,可以知道Bank0接nandflash或norflash(通过开关选择),Bank4接DM9000,Bank6和Bank7接SDRAM。这里不考虑网卡,因此暂时忽略Bank4。
存储控制器主要就是配置寄存器。其中SDRAM配置完成便可以使用。
(1) BWSCON
BANK6,7接SDRAM,DW7=10(32位),WS7=0,ST7=0。BANK0有硬件跳线决定,不用设置。
(2) BANKCON0 .. BANKCON5
默认设置即可
(3) BANKCON6, BANKCON7
MT=0x11表示SDRAM。查看芯片手册,Trcd最小值是20ns,FCLK=100HM情况下,20ns对应两个clk,这使用2clk,Trcd=1(3 clolcks)。SCAN=0x01,列地址为9。(注:芯片为HY57V561620)
(4) REFRESH
REFEN=1使能刷新。TREFMD=0自动刷新。根据芯片手册,trp最小值20ns,对应于2个clk,所以设置Trp=1。Trc最小值是65ns,Tsrc=Trc-Trp,最小值是45ns,对应于4.5clock,这里放宽设置为7clocks,所以Tsrc=3
(5) BANKSIZE
开启burst mode,开启省电模式,仅当内存进行数据操作才输入SCLK频率,根据硬件BK76MAP设为1
(6) MRSRB6和MRSRB7
CAS latency设置为3,具体设置大一点。具体手册有建议。
配置存储控制器的代码如下:
//BWSCON配置项
#define ST7 0
#define WS7 0
#define DW7 2
#define ST6 0
#define WS6 0
#define DW6 2
//BANK6,7配置项
#define MT 3
#define Trcd 1
#define SCAN 1
//REFRESH配置项
#define REFEN 1
#define TREFMD 0
#define Trp 0
#define Tsrc 3
#define Refresh_Counter 0x4f4 //7.8125us*100MHz(HCLK)-2049= 1267.75,刷新周期应该更快一点,所以使用1268=0x4f4
//BANKSIZE配置项
#define BURST_EN 1
#define SCKE_EN 1
#define SCLK_EN 1
#define BK76MAP 1
void memoryControllerInit()
{
S3C24X0_MEMCTL* memctl = S3C24X0_GetBase_MEMCTL();
//mini2440中BANCK0接NOR FLASH或NAND FLASh,由硬件跳线决定,这不用设置。BANK3接网卡,这里不做网卡实验,暂时忽略。BANK6,7接SDRAM,DW7=10(32位),WS7=0,ST7=0。
memctl->BWSCON= (ST7<<31) | (WS7<<30) | (DW7<<28) | (ST6<<27) |(WS6<<26) | (DW6<<24);
//BANK0-5使用默认值
memctl->BANKCON[0]= 0x0700;
memctl->BANKCON[1]= 0x0700;
memctl->BANKCON[2]= 0x0700;
memctl->BANKCON[3]= 0x0700;
memctl->BANKCON[4]= 0x0700;
memctl->BANKCON[5]= 0x0700;
//MT=0x11表示SDRAM。Trcd最小值是20ns,FCLK=100HM情况下,20ns对应两个clk,这使用2clk,Trcd=1(3 clolcks)。SCAN=0x01,列地址为9。
memctl->BANKCON[6]= (MT<<15) | (Trcd<<2) | (SCAN<<0);
memctl->BANKCON[7]= (MT<<15) | (Trcd<<2) | (SCAN<<0);
//REFEN=1使能刷新。TREFMD=0自动刷新。根据芯片手册,trp最小值20ns,对应于2个clk,所以设置Trp=1。
//Trc最小值是65ns,Tsrc=Trc-Trp,最小值是45ns,对应于4.5clock,这里放宽设置为7clocks,所以Tsrc=3
memctl->REFRESH= (REFEN<<23) | (TREFMD<<22) | (Trp<<20) | (TREFMD<<18)| (Refresh_Counter<<0);
//开启burst mode,开启省电模式,仅当内存进行数据操作才输入SCLK频率,根据硬件BK76MAP设为1
memctl->BANKSIZE= (BURST_EN<<7) | (SCKE_EN<<5) | (SCLK_EN<<4) |(BK76MAP<<0);
//设置CAS latency为3
memctl->MRSRB6= 0x30;
memctl->MRSRB7= 0x30;
}
5.2 nandflash
使用的NANDFLASH型号是K9F2G08U0B。
5.2.1nandflash寄存器设置
(1) NFCONF
选用的8bit总线的NandFlash,所以BusWidth设为0。
TWRPH1,TWRPH0以及TACLS根据手册设置。如下图。
NAND FLASH命令锁存时序图
NAND FLASH地址锁存时序图
2440关于时序要求。
对比上面各图,锁存命令时, TACLS=tCLS-tWP, TWRPH0=tWP, TWRPH1=tCLH。
对比上面各图,锁存地址时, TACLS=tALS-tWP, TWRPH0=tWP, TWRPH1=tALH。
查询电器参数特性表可知:
tCLS最小值是12ns,tWP最小值是12n,tCLH最小值是5ns。
tALS最小值是12ns,tWP最小值是12n,tALH最小值是5ns。
Nand Flash使用HCLK,这里设置为100MHz,对应周期是10ns。所以,NFCONF中TACLS设置为2(个周期), TWRPH0设置为1(1+1个周期),TWRPH1设置为0(0+1个周期)。
(2) NFCONT
InitECC设置为1,初始化ECC。
Reg_nCE设置为0,使能片选信号。
MODE设置为1,使能NAND FLASH。
(3) NFCMMD NFADDR NFDATA
用于处理命令地址和数据。
(4) NFMECCD0 NFMECCD1 NFMECCD NFMECC0NFMECC1 NFSECC暂时不考虑
(5) NFSTAT
IllegalAccess不合法操作会置位
RnB_TransDetect RnB从0变为1的时候,这个值被置位。写1清除该位。
nCE (Read-only)反应了片选信号nCE的状态。
RnB (Read-only)为0表示NAND FLASH忙,1表示空闲可操作。
5.2.2 nandflash操作
由于移植uboot的时候需要将nandflash读取数据然后移到SDRAM中,所以这里需要介绍一下读操作。
读操作时序图
从时序图可以看出首先发送0x00命令,然后发送5个字节的地址,然后发送命令0x30命令。这时候R/B会拉低,待R/B重新拉高后就可以从串行数据接口中读数据了。
S3C2440的nandflash控制器帮着完成具体的时序,其他的我们只需要读写寄存器就可以。先介绍下各个操作。
(1)写命令
写命令只需要向nandflash的NFCMD寄存器中直接写入值便可以。代码如下:
static voidwrite_cmd(uint8_t cmd)
{
S3C2440_NAND * nand =S3C2440_GetBase_NAND();
nand->NFCMD= cmd;
int i;
for(i=0;i<10;i++);
}
(2)写地址
写地址只需要向nandflash寄存器中的NFADDR寄存器中写入值便可以。
nandflash的地址需要特殊的说明一下。实质上,nandflash不存在绝对的地址,下面的代码为了便于寻址,仅仅是提出了一个绝对地址的概念。
通过下面两个图可以看出,该nandflash每也有2Kbyte再加上64Byte的OOB,每个块有64页,每个设备有2048块。这里把页内索引字节看成了column,至于索引哪个块中的哪个页统一看成row。IO0-IO10是页内用于索引页内的某个字节。IO11表示是否选中了spare filed(OOB),如果改为为0,IO0-IO11可以访问常规地址区域(页内的前面2K字节)。如果为1,IO0-IO11可以访问spare field区域(页内的2K字节之后的64字节)。IO12-IO28用于选择是哪个块中的那个页。
通过上面的分析,我们可以把常规的地址区域看成一块连续的地址空间,忽略spare field(OOB),即第一页内前2K常规区域地址是从0到2047,第二页内的常规地址是从2048-4095。则套地址范围仅仅能够访问常规地址区域,由write_addr实现。同时,在用另一个函数write_addr_spare(该函数尚未实验验证)访问spare filed。
注:当然可以用同一个连续的地址区域同时访问常规区域和spare field。但是这样譬如2112-4095这段的地址空间就会被浪费,地址空间范围也扩大到512M(该nandflash为256M)。
static void write_addr(uint32_t addr)
{
inti;
S3C2440_NAND* nand = S3C2440_GetBase_NAND();
nand->NFADDR= addr&0xff;
for(i=0;i<10;i++);
nand->NFADDR= (addr>>8)&0x07; //这里忽略A11
for(i=0;i<10;i++);
nand->NFADDR= (addr>>11)&0xff; //从A12开始,对应于地址的第11位
for(i=0;i<10;i++);
nand->NFADDR= (addr>>19)&0xff;
for(i=0;i<10;i++);
nand->NFADDR= (addr>>27)&0x01;
for(i=0;i<10;i++);
}
static void write_addr_spare(uint32_trowNum)
{
inti;
S3C2440_NAND* nand = S3C2440_GetBase_NAND();
nand->NFADDR= 0;
for(i=0;i<10;i++);
nand->NFADDR= 1<<3; //选择使用spare
for(i=0;i<10;i++);
nand->NFADDR= ((rowNum*PAZE_SIZE)>>11)&0xff; //从A12开始,对应于地址的第11位
for(i=0;i<10;i++);
nand->NFADDR= ((rowNum*PAZE_SIZE)>>19)&0xff;
for(i=0;i<10;i++);
nand->NFADDR= ((rowNum*PAZE_SIZE)>>27)&0x01;
for(i=0;i<10;i++);
}
(3)读数据
写命令只需要从nandflash的NFDATA寄存器读数据便可以。代码如下:
static uint8_t read_data()
{
S3C2440_NAND* nand = S3C2440_GetBase_NAND();
returnnand->NFDATA;
}
(4)等待命令完成
该过程对应于时序图中等待R/B重新被拉高的过程。
static void wait_read_ok()
{
inti;
S3C2440_NAND* nand = S3C2440_GetBase_NAND();
while(!(nand->NFSTAT&0x1))
for(i=0;i<10;i++);
}
(5) 打开nandflash
设置nandflash寄存器,并打开片选,时能nandflash等。并发送0xff复位命令。
static void open_nand()
{
S3C2440_NAND* nand = S3C2440_GetBase_NAND();
nand->NFCONF= (BusWidth<<0) | (TWRPH1<<4) | (TWRPH0<<8) |(TACLS<<12);
//初始化ECC,使能片选信号,使能NANDFLASH
nand->NFCONT&= ~((1<<4) | (1<<1) | (1<<0)) ;
nand->NFCONT|= ((1<<4) | (0<<1) | (1<<0));
//发reset命令
write_cmd(0xff);
wait_read_ok();
}
(7) 关闭nandflash
取消片选,关闭nandflash
static void close_nand()
{
S3C2440_NAND* nand = S3C2440_GetBase_NAND();
//关闭使能片选信号,关闭NANDFLASH
nand->NFCONT&= ~((1<<1) | (1<<0)) ;
nand->NFCONT|= ((1<<1) | (0<<0));
}
(7) 读操作
专门读常规区域,不考虑sparefield。对应于读操作时序图。
void read_bytes(uint32_t addr,uint8_t *data,uint32_t len)
{
open_nand();
write_cmd(0x00);
write_addr(addr);
write_cmd(0x30);
wait_read_ok();
inti,j;
intpage_num = len/PAZE_SIZE;
for(i=0;i<page_num;)
{
write_cmd(0);
write_addr(addr+i*PAZE_SIZE);
write_cmd(0x30);
wait_read_ok();
for(j=0;j<PAZE_SIZE;j++)
{
*data= read_data();
data++;
}
i++;
}
close_nand();
}
6 实验流程
(1) 使用jink链接jtag接口,并打开j-link Commander
(2) 输入r
(3) Speed 1200
(4) Loadbin e:\main.bin 0
(5) Setpc 0
(6) g
(7) h
(8) loadbin e:\u-boot.bin_opnjtag0x33f80000
(9) setpc 0x33f80000 //保证串口调试终端打开并连接
(10) g
(11) loadbin e:\main.bin 0x30000000
(12) 串口调试终端输入 nanderase 0 0x3000
(13) 串口调试终端输入 nandwrite 0x30000000 0 0x30000
(14) 拔掉jtag,重启。
注:程序源码存在于下载资源处。