gnu ucos 加入nandflash 操作并支持nand启动

来源:互联网 发布:ubuntu换语言 编辑:程序博客网 时间:2024/05/21 13:59

        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中去执行主要的程序!


这里使用的是mini2440上的K9F1G08,跟赵春江老师的K9F2G08U0A 几乎无多大差别。 这里的1G代表1Gbit 也就是128Mbyte。关于nandflash命名规则可见如下链接:blog.sina.com.cn/s/blog_604c12520100tqil.html

K9F2G08U0A的一页为(2K64)字节(加号前面的2K表示的是main区容量,加号后面的64表示的是spare区容量),它的一块为64页,而整个设备包括了2048个块。这样算下来一共有2112M位容量,如果只算main区容量则有256M字节(即256M×8位)。



    要实现用8IO口来要访问这么大的容量,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! */adrr0, _start/* r0 <- current position of code   */cmp     r0, #0x0/* don't reloc during debug         */blnecpu_init_critblmemsetup/* we run from nandflash*/@ copy ucos to RAMmov     r0, #0x0ldrr1, =TEST_RAMmovr2, #0x100000@ get read to call C functions (for copy2ram)ldrsp, DW_STACK_START@ setup stack pointerblcopy2ramcpu_init_crit:movr1, #GPIO_CTL_BASEaddr1, r1, #oGPIO_F
   这里的adr是相对寻址,也就是从相对于当前 pc 的位置得到标号_start的位置,可见如下链接:

blog.csdn.net/sukhoi27smk/article/details/8876201

上面前3条指令反汇编形式是:

300000c0:e24f00c8 subr0, pc, #200; 0xc8300000c4:e3500000 cmpr0, #0300000c8:1b000005 blne300000e4 <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 之类的都会拿到循环外面来,这是肯定不对的。
所以对于这种函数要禁止编译器优化需要在函数前面加个声明:
void __attribute__((optimize("O0"))) nand_read_ll(unsigned char *buf,unsigned long start_addr, int size)
还有其它一些函数也需要 禁止优化。

关于优化的可见下面文章:

http://forum.andestech.com/viewtopic.php?f=16&t=225



   最后就是 跳转到主程序开始执行了,注意这里要是绝对跳转指令才行(因为这条跳转指令有可能在前4K中,也有可能在0x30000000 以后的某个地址处)!

ldrpc,__main__main:.word main@blmain@ call mainb .

反汇编形式如下:

30000104:e51ff004 ldrpc, [pc, #-4]; 30000108 <__main>30000108 <__main>:30000108:300005c4 andccr0, r0, r4, asr #113000010c:eafffffe b3000010c <__main+0x4>

看到了正好跳转到 0x300005c4 ,也就是main函数起始地址。

前面的函数 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(){UINT8buff[2048];inti;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 完整的代码。





0 0
原创粉丝点击