nandflash 因为造价低在对大容量的数据存储中发挥着重要的作用。nandflash没有地址或数据总线,如果是8位 nandflash(我见过的最多的情况),那么它只有8个IO口还有一些控制口,控制口在不同状态切换这8个IO口分别用于传输命令、地址和数据。nandflash主要以page(页)为单位进行读写,以block(块)为单位进行擦除。Nand Flash芯片每一位只能从1写为0,而不能从0写为1,所以在对其进行写入操作之前一定要将相应块擦除(擦除就是将相应块的位全部变为1)
s3c2440处理器集成了 nandflash 控制器,还有一个特殊的功能:在系统刚上电之后不依赖于任何代码 把nandflash前4K的内容搬运到“Steppingstone”的内部SRAM缓存。
然后把该Steppingstone映射为Bank0,最后从这4K代码开始执行。这个功能很有用,可以把系统最必要的初始化代码和搬运的代码放到这4K当中--4K已经够多了,记得以前
看《自己动手写操作系》那个bios只是把512字节的东西搬运到内存中。搬运代码就负责把剩余的代码从nandflash中搬运到ram中,然后跳转到ram中去执行主要的程序!
K9F2G08U0A的一页为(2K+64)字节(加号前面的2K表示的是main区容量,加号后面的64表示的是spare区容量),它的一块为64页,而整个设备包括了2048个块。这样算下来一共有2112M位容量,如果只算main区容量则有256M字节(即256M×8位)。
要实现用8个IO口来要访问这么大的容量,K9F2G08U0A规定了用5个周期来实现。第一个周期访问的地址为A0~A7;第二个周期访问的地址为A8~A11,它作用在IO0~IO3上,而此时IO4~IO7必须为低电平;第三个周期访问的地址为A12~A19;第四个周期访问的地址为A20~A27;第五个周期访问的地址为A28,它作用在IO0上,而此时IO1~IO7必须为低电平。前两个周期传输的是列地址,后三个周期传输的是行地址。通过分析可知,列地址是用于寻址页内空间,行地址用于寻址页,如果要直接访问块,则需要从地址A18开始。
下面重点介绍下如何实现从nandflash中启动。
首先判断是从仿真器启动还是从nandflash启动:
/* * we do sys-critical inits only at reboot, * not when booting from ram! */ adr r0, _start /* r0 <- current position of code */ cmp r0, #0x0 /* don't reloc during debug */ blne cpu_init_crit bl memsetup /* we run from nandflash*/ @ copy ucos to RAM mov r0, #0x0 ldr r1, =TEST_RAM mov r2, #0x100000 @ get read to call C functions (for copy2ram) ldr sp, DW_STACK_START @ setup stack pointer bl copy2ram cpu_init_crit: mov r1, #GPIO_CTL_BASE add r1, r1, #oGPIO_F这里的adr是相对寻址,也就是从相对于当前 pc 的位置得到标号_start的位置,可见如下链接:
blog.csdn.net/sukhoi27smk/article/details/8876201
上面前3条指令反汇编形式是:
300000c0: e24f00c8 sub r0, pc, #200 ; 0xc8 300000c4: e3500000 cmp r0, #0 300000c8: 1b000005 blne 300000e4 <cpu_init_crit>如果是从jtag仿真器 启动那么 _start 被放在0x30000000 位置处r0与0不等,跳转到cpu_init_crit执行。
如果是被烧写到nandflash中并被2440加载到前4K 那么得到的r0寄存器值也就是0。 接下来就执行memsetup 和 copy2ram 。
注意前面在仿真器加载的时候首先load并运行了一个初始化ram的bin文件,这里从 nandflash 启动需要先把ram初始化 然后才能拷贝flash中数据进来。
然后就是copy2ram :
int copy2ram(unsigned long start_addr, unsigned char *buf, int size)
从左到右 第一个参数是 start_addr :
对应着 mov r0, #0x0 也就是从 nandflash 偏移为0出开始读,当然也包括开始的那4K
第二个参数 buf 对应着 ldr r1, =TEST_RAM 前面宏定义 #define TEST_RAM 0x30000000 把nandflash数据拷贝到这个位置。
第三个参数 size 对应着 mov r2, #0x100000 也就是拷贝1M 大小的数据,这个对于我们的小项目来说已经够了。
整个 nandflash 拷贝操作代码是从 u-boot 里面拷贝出来的,直接上代码没必要讲解 。值得注意一点就是函数 nand_read_ll 有一个如下的循环:
for(i=start_addr; i < (start_addr + size);) { /* ·¢³öREAD0ÃüÁî */ write_cmd(0); /* Write Address */ write_addr_lp(i); write_cmd(0x30); wait_idle(); for(j=0; j < NAND_SECTOR_SIZE_LP; j++, i++) { *buf = read_data(); buf++; } }如果程序用最高优化等级编译,那么编译器会把上面循环优化的,像 write_cmd read_data 之类的都会拿到循环外面来,这是肯定不对的。
关于优化的可见下面文章:
http://forum.andestech.com/viewtopic.php?f=16&t=225
最后就是 跳转到主程序开始执行了,注意这里要是绝对跳转指令才行(因为这条跳转指令有可能在前4K中,也有可能在0x30000000 以后的某个地址处)!
ldr pc,__main __main: .word main @bl main @ call main b .
30000104: e51ff004 ldr pc, [pc, #-4] ; 30000108 <__main> 30000108 <__main>: 30000108: 300005c4 andcc r0, r0, r4, asr #11 3000010c: eafffffe b 3000010c <__main+0x4>
前面的函数 copy2ram 把前4K的内容也拷贝到了 0x30000000 位置处,这里的跳转跳转到了 0x300005c4 。
这里还有一些约束条件那就是拷贝 nandflash 的c 文件要被连接器链接进前 4K 里面,这个可以在 ucos.lds 里面指定。 在跳转到main函数之前的代码可能在
两个地址执行(0x00,0x30000000)所以需要是位置无关的,关于位置无关代码可见如下链接:
blog.csdn.net/ustc_dylan/article/details/6965330
示例代码中在Task0 里面有个函数关于nandflash验证的代码如下:
void nand_test() { UINT8 buff[2048]; int i; for(i = 0;i < 2048;i ++) buff[i] = i & 0xff; nand_init_ll(); printf("erase result is %x\r\n",nand_eraseblock(8)); printf("write result is %x\r\n",nand_writepage(515,buff,2048)); for(i = 0;i < 2048;i ++) buff[i] = 0x0; printf("read result is %x\r\n",nand_read_3(515 * 2048,buff,2048)); for(i = 0;i < 4;i ++){ printf("%2x ",buff[i]); } nand_ramdom_write(514,2048 - 33,0xf4); printf("result is :%x \r\n",nand_ramdom_read(514,2048 - 33)); nand_readID(buff); printf("nand ID is :"); for(i = 0;i < 5;i ++) printf("%x ",buff[i]); printf("\r\n"); }
上面功能依次是
擦除一块nandflash
向擦除的flash 某个页写一页数据,读出来刚才写的数据;
nand随机写然后再随机读刚才写的数值
读出nandflash的ID
另外还有一个大小端的问题,arm架构支持大小端模式切换,但默认的是小端模式; nandflash 是8位的, 字访问,半字访问,字节访问 读出来字节序列不一样的!! 为了加快速度,一般都选择字访问! 当 nand_readpage 调用函数接口是 nand_read_page
当按照字节读的时候:
for(j=0; (j < NAND_SECTOR_SIZE_LP) && (j < size); j++ ) {
*buf = read_data8();
buf++;
}
打印结果如下:
erase result is 66
write result is 66
read result is 66
0 1 2 3
当按照字读取的时候:
for(j=0; (j < NAND_SECTOR_SIZE_LP) && (j < size); j+=4 ) {
*(UINT32 *)buf = read_data();
buf+=4;
}
erase result is 66
write result is 66
read result is 66
0 1 2 3
可见是小端模式访问!
nandflash操作很多控制细节 都是是参考 zhaocj 的讲解来的,这里就不多废话了。参考链接:
blog.csdn.net/zhaocj/article/details/5803699
blog.csdn.net/zhaocj/article/details/5795254
可以去我的github clone 完整的代码。