Nor Flash读写方法

1、物理连接 (AM29LV160DB的NORFLASH)

2、读操作
读操作不需要命令序列,默认会进入读取状态

 U16 read_en29lv160ab(U32 addr)
{
       return *((volatile U16 *)(addr));
}

3、获取ID

    #define FLASH_base 0x00000000 //FLASH接到bank0上

    #define CMD_ADDR0 *((volATIle U16 *)(0x555*2+FLASH_base))
    #define CMD_ADDR1 *((volaTIle U16 *)(0x2aa*2+FLASH_base))
    #define CMD_ADDR2 *((volatile U16 *)(0xaaa*2+FLASH_base))
    /*** 读取 Am29LV800D 的ID ***/
    U32 Get_Am29LV800D_ID(void)
  {
   U32 i="0";
   U32 j="0";    CMD_ADDR0 = 0xaa; CMD_ADDR1 = 0x55; CMD_ADDR0 = 0x90;
   i = (*(U16 *)(0*2+FLASH_base))<<16;//Manufacturer ID = 01
   CMD_ADDR0 = 0xaa; CMD_ADDR1 = 0x55; CMD_ADDR0 = 0x90;
 
  j = *(U16 *)(1*2+FLASH_base);//device ID = 2249
   return 0;
  }
 

为什么对16bit的flash,在处理地址的时候要乘以2?
答:注意到16bit的flash的地址线,输入地址的A1连接到flash的A0引脚。例如16位的FLASH芯片,对于CPU,0x00和0x01表示2个不同的字节,但是从硬件连线上看,最低一位实际上是被丢掉了,所以在FLASH引脚端得到的都是0,也就是都指向FLASH的第一个WORD。
0x555到cpu端实际上是0x555>>1(右移动了一位),为了抵消这个,原0x555必须左移一位,也就是乘以2。


硬件为什么这么连接呢?

为什么用到volatile限定符?
答:volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。也就是对实时的硬件操作还是很有必要的。

4、擦除操作

编程前一定要先擦除.由于编程只能将'1'改写为'0',通过擦写可以将数据全部擦写为'1'.

 void Am29LV800D_SectorErase(U32 SA)
    {
    CMD_ADDR0 = 0xAA; CMD_ADDR1 = 0x55; CMD_ADDR0 = 0x80;
    CMD_ADDR0 = 0xAA; CMD_ADDR1 = 0x55;//Word 模式命令序列
    *((volatile U16 *)(SA)) = 0x30;
    Waitfor_endofprg();//状态查询
    }

5、编程操作(写操作)

 

    int Am29LV800D_WordProg (U32 PA,U16 PD)
    {
    CMD_ADDR0 = 0xAA; CMD_ADDR1 = 0x55; CMD_ADDR0 = 0xA0;
    *((volatile U16 *)(PA)) = PD;// word模式,以上为4个命令周期
    return(Waitfor_endofprg());//状态查询
    }
6、查询状态(判断写操作是否完成)

NorFLASH 提供几个数据位来确定一个写操纵的状态,它们分别是: DQ2, DQ3, DQ5, DQ6,DQ7, and RY/BY#. 如上图所示.其中DQ7, RY/BY#引脚, 和 DQ6 中的每一个都提供了一种方法来判定一个编程或者擦除操纵是否已经完成或正在进行中.实际编程中只需要使用其中的一种.

DQ7:Data# Polling bit,DQ7在编程时的状态变化.
    在编程过程中从正在编程的地址中读出的数据的DQ7为要写进数据的补码.比如写进的数据为0x0000,及输进的DQ7为'0',则在编程中读出的数据为'1';当编程完成时读出的数据又变回输进的数据即'0'.
    在擦除过程中DQ7输出为'0';擦除完成后输出为'1';留意读取的地址必须是擦除范围内的地址.
RY/BY#:高电平表示'停当',低电平表示'忙'.
DQ6:轮转位1(Toggle Bit 1).
    在编程和擦除期间,读任意地址都会导致DQ6的轮转(0,1间相互变换)当操纵完成后,DQ6停止转换.
DQ2:轮转位2(Toggle Bit 2).当某个扇区被选中擦除时,读有效地址(地址都在擦除的扇区范围内)会导致DQ2的轮转.
    留意:DQ2只能判定一个特定的扇区是否被选中擦除.但不能区分这个快是否正在擦除中或者正处于擦除暂停状态.相比之下,DQ6可以区分NorFLASH是否处于擦除中或者擦除状态,但不能区分哪个快被选中擦除.因此需要这2个位来确定扇区和模式状态信息.
DQ5: 超时位(Exceeded Timing Limits),当编程或擦除操纵超过了一个特定内部脉冲计数是DQ5=1;这表明操纵失败.当编程时把'0'改为'1'就会导致DQ5=1,由于只有擦除擦做才能把'0'改为'1'.当错误发生后需要执行复位命令(见图1-1)才能返回到读数据状态.
DQ3: (扇区擦除计时位)Sector Erase Timer,只在扇区擦除指令时起作用.当擦除指令真正开始工作是DQ3=1,此时输进的命令(除擦除暂停命令外)都被忽略.DQ3=0,是可以添加附加的扇区用于多扇区擦除.
    以上讲了这些状态为,实际只需要使用几个就行,比较简单的就是选择DQ5,DQ6/DQ2.如下例.

int Waitfor_endofprg(void)
    {
    volatile U16 FLASHStatus,old;
    old=*((volatile U16 *)0x0);//先读一次状态
    while(1)
    {
    FLASHStatus=*((volatile U16 *)0x0);//再读一次状态
    if( (old&0x40) == (FLASHStatus&0x40) ) break;//比较DQ6.相同说明设备已经停当
    if( FLASHStatus&0x20 ) //判定DQ5,为'1'则超时
    {
    old=*((volatile U16 *)0x0);
    FLASHStatus=*((volatile U16 *)0x0);//在读取比较一次.由于可能在超时前恰好完成操纵.
    if( (old&0x40) == (FLASHStatus&0x40) )
    return 1;
    else return 0;
    }
    old=FLASHStatus;
    }
    return 1;
    }
7、内核虚拟地址

如果在Linux内核驱动中使用,而不是arm中使用,需要对物理地址进行虚拟转换

iomap_label_addr=(BYTE*)ioremap(FLASH_base,SECT_SIZE);

8、nor flash应用

nor flash可以直接写入二进制代码,在嵌入式的uboot中有很好的应用

9、遗留问题

为了保证读写过程不被打断,在内核驱动中要不要关闭中断?

local_irq_disable()
local_irq_enable()

原文地址:https://www.cnblogs.com/mingziday/p/2271159.html