W25Qxx - FLASH大容量存储芯片

W25Qxx - FLASH大容量存储芯片

芯片介绍/引脚介绍

W25Q64 (64M-bit),W25Q16(16M-bit)和 W25Q32(32M-bit)是为系统提供一个最小的空间、引脚和功耗的存储器解决方案的串行 Flash 存储器。25Q 系列比普通的串行 Flash 存储器更灵活,性能更优越。基于双倍/四倍的 SPI,它们能够可以立即完成提供数据给 RAM,包括存储声音、文本和数据。芯片支持的工作电压 2.7V 到 3.6V,正常工作时电流小于 5mA,掉电时低于 1uA。

W25Q64/16/32 由每页 256 字节组成。每页的 256 字节用一次页编程指令即可完成。每次可以擦 除 16 页(1 个扇区)、128 页(32KB 块)、256 页(64KB 块)和全片擦除。

W25Q64 的内存空间结构:一页 256 字节,4K(4096 字节)为一个扇区,16 个扇区为 1 块,容量为 8M 字节,共有 128 个块,2048 个扇区。

W25Q64/16/32 支持标准串行外围接口(SPI),和高速的双倍/四倍输出,双倍/四倍用的引脚:串行时钟、片选端、串行数据 I/O0(DI)、I/O1(DO)、I/O2(WP)和 I/O3(HOLD)。SPI 最高支持 80MHz,当用快读双倍/四倍指令时,相当于双倍输出时最高速率 160MHz,四倍输出时最高速率 320MHz。这个传输速率比得上 8 位和 16 位的并行 Flash 存储器。

HOLD 引脚和写保护引脚可编程写保护。此外,芯片支持 JEDEC 标准,具有唯一的 64 位识别序列号。

该存储芯片由SPI通信驱动,关于SPI通信相关内容,详见协议使用笔记SPI章节。

引脚排列(以 SOIC 208-mil 为例)

引脚图

CS - 片选

DO - SPI数据输出,当状态寄存器2的QE位设置为四倍I/O时,此时这个引脚被用为IO1

WP - 写保护输入,当状态寄存器2的QE位设置为四倍I/O时,则WP(硬件写保护)功能不可用,因为此时这个引脚被用为IO2

HOLD - Hold输入,当状态寄存器2的QE位设置为四倍I/O时,则HOLD引脚功能不可用,因为此时这个引脚被用为IO3

CLK - 时钟

DI - SPI数据输入

**注意:**本笔记采用标准SPI模式,设备数据传输是从高位开始,数据传输的格式为 8bit,数据采样从第二个时间边沿开始,空闲状态时,时钟线 clk 为高电平。

部分命令

常用指令

常用指令由上图所示,不带括号表示主机发送,带括号表示主机接收,下面解释几个命令。

  • 写使能 - 06H

    在进行写相关操作(如写数据、擦除(写0xFF))时,必须完成:拉低片选 - 发送写使能命令 - 拉高片选 后才可进行写操作。

  • 读数据 - 03H(表中未列出)

    指令格式:| 03h | A23-A16 | A15-A8 | A7-A0 | (D7-D0)… |

    使用该指令在读取字节数据后,其地址会自动增1,因此只需要在开始时设置一次地址便可连续读取数据。

  • 读状态寄存器1 - 05H

    寄存器内容:

    76543210
    SRP0SECTBBP2BP1BP0WELBUSY

    我们主要关注该寄存器第0位 - 忙状态位:

    当该位为1时,表示芯片忙,此时无法对其进行任何操作,需等待忙结束时,才可继续操作

  • 页编程 - 02H

    页编程指令允许从一个字节到 256 字节的数据编程(一页)(编程之前必须保证内存空间是 0XFF)

    **注意:**使用页写时,写入字节数最少为1个,最多为256个。每次写入至多一页(256字节)数据,若写入数据量大于该页内剩余空间,则其将从该页头部继续写入,直至写完数据为止,因此,在页写过程中务必检查剩余字节数是否足够写入!

  • 扇区擦除 - 20H

    该芯片一次至少擦除一个扇区(4KB),擦除即将其中的内容写为0xFF,为写入数据做准备,由于其一次擦除数据偏大,因此在擦除前应检查该扇区内是否存在有用数据,以此进行备份保护。

代码示例

变量介绍

#define W25QXX_CS 		//片选
#define W25QXX_MOSI 	//主出从入
#define W25QXX_MISO 	//主入从出
#define W25QXX_SCK 		//时钟
u8 W25QXX_SendReceive_Byte(u8 tdata) //SPI发送接收

写使能

void W25Q64_Write_Enable(void)
{
    SPI_CS = 0;
    W25QXX_SendReceive_Byte(0x06);		//命令0x06
    SPI_CS = 1;
}

设备探忙

u8 W25QXX_DeviceBusy(void)
{
    u8 deviceInfo = 0;
    W25QXX_CS = 0;
    W25QXX_SendReceive_Byte(0x05);		//命令0x05
    deviceInfo = W25QXX_SendReceive_Byte(None);	//返回值,我们需要判断返回值最低位
    W25QXX_CS = 1;
    return (deviceInfo & 1);
}

字符读取

void W25QXX_Read(u32 StartADDR,u32 length,u8 *str)
{
    u32 i;

    while(W25QXX_DeviceBusy());

    W25QXX_CS = 0;
    W25QXX_SendReceive_Byte(0x03);				//读取命令0x03
    W25QXX_SendReceive_Byte(StartADDR >> 16);	//24位地址,从高位开始
    W25QXX_SendReceive_Byte(StartADDR >> 8);
    W25QXX_SendReceive_Byte(StartADDR >> 0);

    for(i=0;i<length;i++)					//读取length长的字节
    {
        *str = W25QXX_SendReceive_Byte(None);
        str++;
    }
    W25QXX_CS = 1;
}

扇区擦除

void W25QXX_Sector_Erase(u32 StartADDR)
{

    W25Q64_Write_Enable();
    while (W25QXX_DeviceBusy());

    W25QXX_CS = 0;

    W25QXX_SendReceive_Byte(0x20);		//擦除命令
    W25QXX_SendReceive_Byte(StartADDR >> 16);
    W25QXX_SendReceive_Byte(StartADDR >> 8);
    W25QXX_SendReceive_Byte(StartADDR >> 0);

    W25QXX_CS = 1;

    while (W25QXX_DeviceBusy());
}

字符写入

//页内写
void W25QXX_Write_Page(u32 StartADDR,u32 length,const u8 *str)
{
    u16 i;

    W25Q64_Write_Enable();          //写使能要单独拎出来写一次,在片选拉低之前写
    while (W25QXX_DeviceBusy());

    W25QXX_CS = 0;

    W25QXX_SendReceive_Byte(0x02);
    W25QXX_SendReceive_Byte(StartADDR >> 16);
    W25QXX_SendReceive_Byte(StartADDR >> 8);
    W25QXX_SendReceive_Byte(StartADDR >> 0);


    for(i=0;i<length;i++)
    {
        W25QXX_SendReceive_Byte(*str);
        str++;
    }

    W25QXX_CS = 1;

    while (W25QXX_DeviceBusy());
}

//跨页写
void W25QXX_Write_CrossPage(u32 StartADDR,u32 length,const u8 *str)
{
    u16 offset = StartADDR % 256;
    u16 remain = 256 - offset;

    if(remain > length) remain = length;

    while (1)
    {
        W25QXX_Write_Page(StartADDR,remain,str);
        if(remain == length) break;

        StartADDR += remain;
        str += remain;
        length -= remain;
        if(length > 256) remain = 256;
        else remain = length;
    }
    
}

//写字符串
u8 tempData[4096];
void W25QXX_Write_String(u32 StartADDR,u32 length,const u8 *str)
{
    u32 i;
    u16 SecOffset = StartADDR % 4096;           //在当前扇区内的偏移量 
    u16 SectorRemain = 4096 - SecOffset;        //计算扇区剩余容量
    u16 SecAddr = StartADDR - SecOffset;        //找到扇区起始位置
    
    if(SectorRemain > length) SectorRemain = length; 

    while (1)
    {
        W25QXX_Read(StartADDR,SectorRemain,(tempData + SecOffset));
        for(i = 0; i < SectorRemain; i++)
        {
            if(tempData[SecOffset + i] != 0xff) break; //有数据
        }
        if(i != SectorRemain)
        {
            //把当前扇区的前半截数据保存缓冲区
            W25QXX_Read(SecAddr, SecOffset, tempData);
            //把当前扇区的后半截数据保存缓冲区
            W25QXX_Read(StartADDR+SectorRemain, 4096-(SecOffset+SectorRemain), tempData+(SecOffset+SectorRemain));
            //擦除扇区
            W25QXX_Sector_Erase(SecAddr); 

            for(i=0;i<SectorRemain;i++)
            {
                tempData[SecOffset+i] = str[i];
            }
            W25QXX_Write_CrossPage(SecAddr, 4096, tempData);
            SecOffset = 0;    //扇区内偏移量归零
        }
        else W25QXX_Write_CrossPage(StartADDR, SectorRemain, str);

        if(SectorRemain == length) break;
        StartADDR += SectorRemain;
        str += SectorRemain;
        length -= SectorRemain;
        SecAddr = StartADDR;   //更新扇区起始地址
        if(length > 4096) SectorRemain = 4096;
        else SectorRemain = length;
    }
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值