Nand Flash编程实现读地址信息
/*
*硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
*软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
*参考资料:开发版原理图,S3C2440A datasheet,K9F2G08U0C datasheet
*/
目录
一、基础知识
1、Nand Flash 存储结构介绍
在《韦东山嵌入式Linux学习----015 Nand Flash(1)》这一篇中简单的从电路图与芯片手册介绍了一下K9F2G08U0C这块芯片,下面讲具体分析下它的存储结构。
分析:
①、Nand Flash 的数据:以bit的方式保存在memory cell。
②、Nand Device的位宽:一般来说,一个cell 中只能存储一个bit。这些cell 以8个或者16个为单位,连成bit line,形成所谓的byte(x8)/word(x16)。图中可以看出芯片采用的时x8。
③、Nand Flash的Page:多条的Line会再组成Page。图中可以看出1 Page = 2048 Bytes + 64 Bytes。
④、Nand Flash的Block:多个的page形成一个Block。图中可以看出1 Block = 64 Page。
⑤、可以看到,每条公式以(xK + yK)的形式表示,yK代表的是OOB块的大小。
2、行列地址的提取
NAND flash以页为单位读写数据,而以块为单位擦除数据。按照这样的组织方式可以形成所谓的三类地址:列地址 :Column Address 、 页地址 :Page Address、块地址 :Block Address。
在具体操作中,我们发送地址时是一次性输入操作的内存的地址,如0x100000。但是由于我们使用的地址和命令只能在I/O[7:0]上传递,数据宽度是8位,所在传输写读地址时,程序设计需要单独提取行列地址。
结合上图,可以得出如下提取方式:
col = (addr & 0xfff); //取列数
page = (addr >> 12); //取页数
1st Cycle | Column Address | nand_addr(col & 0xff); |
---|---|---|
2st Cycle | Column Address | nand_addr((col >> 8) & 0x0f); |
3st Cycle | Row Address | nand_addr((page & 0xff)); |
4st Cycle | Row Address | nand_addr((page >> 8) & 0xff); |
5st Cycle | Row Address | nand_addr(page>> 16); |
3、Nand Flash OOB块介绍
①、为什么会有OOB区?
由于NAND Flash的工艺不能保证NAND的Memory Array在其生命周期中保持性能的可靠,因此,在NAND的生产中及使用过程中会产生坏块。当编程/擦除这个块时,会造成Page Program和Block Erase操作时的错误,相应地反映到OOB区的相应位。
②、OOB区多大,在哪里?
如上图所示,每个Page都会有OOB区,大小为64 Btyes,位置在每个Page的2048位。
③、注意
当读写擦除Nand Flash时,需要避免破坏OOB区。
4、位反转
CPU在寻址的时候看不到OOB区,所以当CPU读到2048个数据时,此时访问的是下一个Page的第0个数据。
这里也可以看出,OOB的存在,是为了解决Nand Flash的缺陷。对于CPU而已,只关心数据,不需要看OOB。
二、实现功能
功能:通过串口输入指定的地址,并读取该地址的信息。
三、编程原理
1 Page = (2K(数据)+ 64 (OOB区))Bytes
查K9F2G08U0C 芯片手册,读操作的时序图如下
1、编写读地址函数
/* Nand Flash 读地址
* addr:地址 buf:存储信息 len:读取的信息长度
*/
void nand_read(unsigned int addr,unsigned char *buf ,int len)
{
int i =0 ;
int col = (addr & 0xfff); //取列数
int page = (addr >> 12); //取页数
// 1、片选
nand_select();
while(i<len)
{
// 2、发00命令
nand_cmd(0x00);
// 3、发五个周期的地址
/* 横坐标 */
nand_addr(col & 0xff);
nand_addr((col >> 8) & 0x0f);
/* 纵坐标 */
nand_addr((page & 0xff));
nand_addr((page >> 8) & 0xff);
nand_addr(page>> 16);
// 4、发30命令
nand_cmd(0x30);
// 等待时间
nand_wait_ready();
// 5、读数据
/* 保证列数不超过2047,i<len */
for(; (col < 2048) && (i < len);col++)
{
buf[i++] = nand_data();
}
/* 完成读取 */
if (i == len)
break;
/* 未完成读取,位反转 */
col = 0;
page++;
}
// 6、取消片选
nand_disselect();
}
2、进一步封装读地址函数
/* 读某个地址
* 1、获取地址
* 2、打印地址信息
*/
void do_read_nand_flash(void)
{
int i,j;
unsigned int addr;
unsigned char c;
unsigned char buf[64];
unsigned char str[16];
volatile unsigned char *tmpbuf;
// 1、获取地址
printf("Enter the address to read: ");
addr = get_uint();
// 2、执行读函数
nand_read(addr,buf,64);
tmpbuf = (volatile unsigned char *)buf;
// 3、打印地址信息
/* 固定长度64字节
* 按照市面上的标准以16个字节为一行,前为数值,后为字符
* 分辨字符是否可视
*/
printf("Data: \n\r");
for(i=0;i<4;i++)
{
for(j=0;j<16;j++)
{
c = *tmpbuf++;
str[j] = c;
printf("%02x ",c);
}
printf(" ;");
for(j=0;j<16;j++)
{
if((str[j] < 0x20) || (str[j] > 0x7E))
printf(".");
else
printf("%c",str[j]);
}
printf("\n\r");
}
}
3、实现从Nand Flash启动
由于开发版上电时,Nand Flash中的4K会自动被复制到4K的RAM中,之前我们已经实现了SDRAM重定位,将这4K代码完全复制到SDRAM中。同时,为了确保4K代码中包含nand_flash.c文件,需要修改Makefile文件与sdram.c文件。
3.1 修改sdram_init.c文件
/* 判断NOR启动还是NAND启动
* 先保存0地址中的数据,后往0地址里面写入0x123
* 判断0地址中是否成功被修改
* --修改成功为NOR启动,恢复0地址信息
* --修改失败为NAND启动
*/
int IsBootNorFlash(void)
{
volatile unsigned int *p = (volatile unsigned int *)0;
unsigned int val = *p;
*p = 0x123;
if(*p == 0x123)
{
*p = val;
return 0;
}
else
{
return 1;
}
}
/*功能:复制整个text,rodata,data段到SDRAM中*/
void copy_to_sdram(void)
{
extern int start,__bss_start; //建立外部变量,方便获取lds文件中的量
int len;
volatile unsigned int *text =(volatile unsigned int *) &start; //text指向程序开头地址
volatile unsigned int *end = (volatile unsigned int *)&__bss_start; //end指向bss段开头的地址
volatile unsigned int *src = (volatile unsigned int *)0; //src指向0地址.即FLASH的开头地址
len = ((int)&__bss_start) - ((int)&start);
if(IsBootNorFlash())
{
while(text < end)
{
*text++ = *src++;
}
}
else
{
nand_init();
nand_read(src,text,len);
}
}
3.2 修改Makefile文件
确保复制的前4K代码中包含nand_flash.c文件,把start.o sdram_init.o nand_flash.o 放前面
all: start.o sdram_init.o nand_flash.o uart.o main.o uart.o led.o exception.o eint.o timer.o my_printf.o string_utils.o lib1funcs.o
四、编程文件
1、修改后的Makefile文件
all: start.o sdram_init.o nand_flash.o uart.o main.o uart.o led.o exception.o eint.o timer.o my_printf.o string_utils.o lib1funcs.o
arm-linux-ld -T sdram.lds $^ -o sdram.elf
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis
clean:
rm *.bin *.o *.elf *.dis
%.o : %.c
arm-linux-gcc -march=armv4 -c -o $@ $<
%.o : %.S
arm-linux-gcc -march=armv4 -c -o $@ $<
2、修改后的nand_init.c文件
#include "s3c2440_soc.h"
#include "my_printf.h"
void nand_init(void)
{
#define TACLS 0
#define TWRPH0 1
#define TWRPH1 0
/* 初始化时序 TACLS = 1、TWRPH0 = 1、TWRPH1 = 0 */
NFCONF = (0<<12)|(1<<8)|(0<<4);
/*使能NAND FLASH控制器,初始化ECC,禁止片选*/
NFCONT = (1<<4) | (1<<1) | (1<<0);
}
void nand_select(void)
{
/* 片选信号 */
NFCONT &=~ (1<<1);
}
void nand_disselect(void)
{
/* 禁止片选信号 */
NFCONT |= (1<<1);
}
/* 写命令 */
void nand_cmd(unsigned char cmd)
{
volatile int i;
NFCCMD = cmd;
for(i=0; i<10; i++);
}
/* 写地址 */
void nand_addr(unsigned char addr)
{
volatile int i;
NFADDR = addr;
for(i=0; i<10; i++);
}
/* 读数据 */
unsigned char nand_data(void)
{
return NFDATA;
}
void nand_wait_ready(void)
{
while (!(NFSTAT & 1));
}
void nand_write_data(unsigned char val)
{
NFDATA = val;
}
/* Nand Flash 读ID */
void do_scan_nand_flash(void)
{
unsigned char buf[5];
// 1、片选信号
nand_select();
// 2、写读ID命令
nand_cmd(0x90);
// 3、写地址
nand_addr(0x00);
// 4、读5次数据
buf[0] = nand_data(); //厂商ID
buf[1] = nand_data(); //设备ID
buf[2] = nand_data(); //3rd
buf[3] = nand_data(); //4th
buf[4] = nand_data(); //5th
// 5、打印数据
/* 打印5次读取的数据 */
printf("Maker Code :0x%x\n\r",buf[0]);
printf("Device Code :0x%x\n\r",buf[1]);
printf("3rd Cycle :0x%x\n\r",buf[2]);
printf("4rd Cycle :0x%x\n\r",buf[3]);
printf("5rd Cycle :0x%x\n\r",buf[4]);
// 6、禁止片选
NFCONT |= (1<<1);
/* 通过提取,打印页大小和块大小 */
printf("Page Size :%d KB\n\r",1 << (buf[3]&0x03));
printf("Block Size :%d KB\n\r",64 << ((buf[3]>>4)&0x03));
}
/* Nand Flash 读地址 */
void nand_read(unsigned int addr,unsigned char *buf ,int len)
{
int i =0 ;
int col = (addr & 0xfff); //取列数
int page = (addr >> 12); //取页数
// 1、片选
nand_select();
while(i<len)
{
// 2、发00命令
nand_cmd(0x00);
// 3、发五个周期的地址
/* 横坐标 */
nand_addr(col & 0xff);
nand_addr((col >> 8) & 0x0f);
/* 纵坐标 */
nand_addr((page & 0xff));
nand_addr((page >> 8) & 0xff);
nand_addr(page>> 16);
// 4、发30命令
nand_cmd(0x30);
// 等待时间
nand_wait_ready();
// 5、读数据
/* 保证列数不超过2047,i<len */
for(; (col < 2048) && (i < len);col++)
{
buf[i++] = nand_data();
}
if (i == len)
break;
col = 0;
page++;
}
// 6、取消片选
nand_disselect();
}
/* 读某个地址
* 1、获取地址
* 2、打印地址信息
*/
void do_read_nand_flash(void)
{
int i,j;
unsigned int addr;
unsigned char c;
unsigned char buf[64];
unsigned char str[16];
volatile unsigned char *tmpbuf;
// 1、获取地址
printf("Enter the address to read: ");
addr = get_uint();
// 2、执行读函数
nand_read(addr,buf,64);
tmpbuf = (volatile unsigned char *)buf;
// 3、打印地址信息
/* 固定长度64字节
* 按照市面上的标准以16个字节为一行,前为数值,后为字符
* 分辨字符是否可视
*/
printf("Data: \n\r");
for(i=0;i<4;i++)
{
for(j=0;j<16;j++)
{
c = *tmpbuf++;
str[j] = c;
printf("%02x ",c);
}
printf(" ;");
for(j=0;j<16;j++)
{
if((str[j] < 0x20) || (str[j] > 0x7E))
printf(".");
else
printf("%c",str[j]);
}
printf("\n\r");
}
}
void nand_flash_test(void)
{
char c;
while (1)
{
/* 打印菜单, 供我们选择测试内容 */
printf("[s] Scan nand flash\n\r");
printf("[e] Erase nand flash\n\r");
printf("[w] Write nand flash\n\r");
printf("[r] Read nand flash\n\r");
printf("[q] Quit\n\r");
printf("Enter selection: ");
c = getchar();
printf("%c\n\r", c);
/* 测试内容:
* 1. 识别nand flash
* 2. 擦除nand flash某个扇区
* 3. 编写某个地址
* 4. 读某个地址
*/
switch (c)
{
case 'q':
case 'Q':
return;
break;
case 's':
case 'S':
do_scan_nand_flash();
break;
case 'e':
case 'E':
break;
case 'w':
case 'W':
break;
case 'r':
case 'R':
do_read_nand_flash();
break;
default:
break;
}
}
}
3、main.c文件
#include "s3c2440_soc.h"
#include "uart.h"
#include "sdram_init.h"
#include "nand_flash.h"
char g_Char = 'A';
char g_Char2 = 'a';
char i ='0';
int main(void)
{
unsigned char c;
int flag;
//interrupt_init();
key_eint_int();
// timer0_init();
uart0_init();
//sdram_init();
nand_init();
nand_flash_test();
puts("uart0 init success!\n\r''");
putchar(i);
while(1)
{
putchar(g_Char);
g_Char++;
delay(1000000);
putchar(g_Char2);
g_Char2++;
delay(1000000);
}
return 0;
}
五、运行结果
通过两个图的对比可知,代码成功烧写到了Nand Flash,并进行了重定位,读地址功能实现成功。