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

        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!
	 */
	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 之类的都会拿到循环外面来,这是肯定不对的。
所以对于这种函数禁止编译器优化需要在函数前面加个声明:
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 以后的某个地址处)!

	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>

看到了正好跳转到 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()
{
	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,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 完整的代码

相关文章

  译序:JWMediaPlayer是开源的网页使用的Flash播放器。本...
    Flash编程原理都是只能将1写为0,而不能将0写成1.所...
 上传setenvgatewayip192.168.1.1;setenvserverip192.168.1...
Error:FlashDownloadFailed-"Cortex-M3"出现一般有...
jPlayer是一个用于控制和播放mp3文件的jQuery插件。它在后台...
#ifndef__FONTUPD_H__#define__FONTUPD_H__#include"sy...