先看下nor flash和nand flash之间的区别
NOR FLASH 可以直接进行读操作,但写操作是按块而不是按字节来进行,不能直接写,需要通过命令来做控制;NAND FLASH 即不能直接进行读操作,也不可以直接进行写,读写都是按块来进行,都需要通过命令来做控制。另外nand flash价格便宜,但是存储时可能发生位反转,可能会导致坏块的产生,所以通常用于存储数据量比较大同时可以容忍出一些小错误的信息,而nor flash不会发生位反转,但是价格昂贵,通常用于存储核心程序代码。
我们的开发板可以选则Nor flash启动,也可以选择nand flash启动
就是Uboot最开始装载哪个地方上。
我们先来体验一下nor flash
首先我们要将uboot烧写到nor flash,烧写成功后,我们就根据如下指令来读取信息了
怎么来看这张图呢?如果想执行command对应的命令,需要向对应的Addr中写入Data数据
然后我们可以从如下接线图看出,cpu和nor flash之间错开一位来接线,所以为什么要这样设计呢?我找了网上一些大神的分析,我没有看懂,所以我就不在这里说了
因而我们在往uboot中写的地址和nor上收到的地址是不一样的,uboot提供的地址中的第一位将会给nor的第0位对应线,所以我们要对手册上提供的addr向左移动一位
安装这样的方法我们就可以执行手册提供的command,command不仅可以读id,还可以sector erase(扇区擦除)
这被称为jedec标准, 是不是觉得很麻烦,如果我们可以通过直接读某个地址就可以对应的数据,而不是需要像这样向某个地址写入对应固定的数据才能返回我们需要的数据
我们可以看command中的最后一个cfi query,可以进入cfi查询模式。这个模式主要是给内核来获取Nor flash的信息
具体操作
进入cfi模式 往AAH里写入98 mw . w aa 98
读数据 读20H得到0051 md . d 20 1
读22H得到0052 md . d 22 1
读24H得到0059 md . d 24 1
读4EH得到容量 md . d 4e 1
退出cfi模式 往任意地址空间写入f0 md . w 0 f0
如果不理解md,mw这两个命令,可以查一下相关的资料
mw后的参数可以有三种选择b(bit 8位)w(word 16位) l(32位)
所以在command中有两种选择bit和word
输入这些命令就可以查询到Nor的容量,cfi模式的query就是这样
现在我们来看下如何写nor
我们可能会想啊,上面不是已经告诉我们了吗,写的命令就是mw (b\w\l)addr data
这个命令正是uboot的写命令,然而当真的在Nor上写的时候发现根本就写不了
所以我们要读nor写,也需要类似于进入cfi的query一样,属于输入一些“密码”做验证,验证成功了,nor才会对我们开放写模式
密码是什么呢?就是command中对应的program(烧写),按照要求输入即可
当然nor的写还有非常重要的特点
我们只能将nor中1写为0,而不能反过来,如果数据里面不是全为1呢?我们必须要先执行擦除操作,才能执行nor写
擦除的方法就是执行command中的sector erase
我们来试一下通过uart串口打印nor的硬件信息
首先要进入cfi的query模式,根据上面的command得知是往55H里写入98。可以预想到后面我们将会频繁对nor上的特定地址进行频繁的读和写操作来执行command,所以对读和写操作进行封装成函数
#define NOR_FLASH_BASE 0
void nor_write_word(unsigned int base, unsigned int offset, unsigned int val)
{
/*我们是需要向base + offset 地址中写入 val,需要用指针操作*/
volatile unsigned short *p = (volatile unsigned short *)(base + (offset << 1)) //offset左移一位是nor和cpu之间关键链接的关系造成的
*p = val;
}
void nor_cmd(unsigned int offset, unsigned int cmd)
{
nor_write_word(NOR_FLASH_BASE, offset, cmd);
}
unsigned int nor_read_word(unsigned int base, unsigned int offset)
{
volatile unsigned short *p = (volatile unsigned short *)(base + (offset << 1));
return *p;
}
unsigned int nor_data(unsigned int offset)
{
return nor_read_word(NOR_FLASH_BASE, offset);
}
打印信息存储空间大小和扇区的相关信息,我们需要进入cfi模式,进入cfi后,我们做先做一些测试,如看是否可以打印出"Q R Y" 以验证是否成功进入cfi模式
void do_scan_nor_flash(void)
{
char str[4];
unsigned int size;
int regions, i;
int region_info_base;
int block_addr, blocks, block_size, j;
int cnt;
int vendor, device;
/* 打印厂家ID、设备ID */
nor_cmd(0x555, 0xaa); /* 解锁 */
nor_cmd(0x2aa, 0x55);
nor_cmd(0x555, 0x90); /* read id */
vendor = nor_dat(0);
device = nor_dat(1);
nor_cmd(0, 0xf0); /* reset */
nor_cmd(0x55, 0x98); /* 进入cfi模式 */
str[0] = nor_dat(0x10);
str[1] = nor_dat(0x11);
str[2] = nor_dat(0x12);
str[3] = '\0';
printf("str = %s\n\r", str); /* 测试是否进入cfi模式 */
/* 打印容量 */
size = 1<<(nor_dat(0x27));
printf("vendor id = 0x%x, device id = 0x%x, nor size = 0x%x, %dM\n\r", vendor, device, size, size/(1024*1024));
/* 打印各个扇区的起始地址 */
/* 名词解释:
* erase block region : 里面含有1个或多个block, 它们的大小一样
* 一个nor flash含有1个或多个region
* 一个region含有1个或多个block(扇区)
* Erase block region information:
* 前2字节+1 : 表示该region有多少个block
* 后2字节*256 : 表示block的大小
*/
regions = nor_dat(0x2c);
region_info_base = 0x2d;
block_addr = 0;
printf("Block/Sector start Address:\n\r");
cnt = 0;
for (i = 0; i < regions; i++)
{
blocks = 1 + nor_dat(region_info_base) + (nor_dat(region_info_base+1)<<8);
block_size = 256 * (nor_dat(region_info_base+2) + (nor_dat(region_info_base+3)<<8));
region_info_base += 4;
// printf("\n\rregion %d, blocks = %d, block_size = 0x%x, block_addr = 0x%x\n\r", i, blocks, block_size, block_addr);
for (j = 0; j < blocks; j++)
{
/* 打印每个block的起始地址 */
//printf("0x%08x ", block_addr);
printHex(block_addr);
putchar(' ');
cnt++;
block_addr += block_size;
if (cnt % 5 == 0)
printf("\n\r");
}
}
printf("\n\r");
/* 退出CFI模式 */
nor_cmd(0, 0xf0);
}
如果对代码有疑问,欢迎和我交流