一、W25Q简介
W25Q64是华邦公司推出的大容量SPI FLASH产品,其容量为64Mb。该25Q系列的器件在灵活性和性能方面远远超过普通的串行闪存器件。W25Q64将8M字节的容量分为128个块,每个块大小为64K字节,每个块又分为16个扇区,每个扇区4K个字节。W25Q64的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。所以,这需要给W25Q64开辟一个至少4K的缓存区,这样必须要求芯片有4K以上的SRAM才能有很好的操作。
W25Q64的擦写周期多达10W次,可将数据保存达20年之久,支持2.7~3.6V的电压,支持标准的SPI,还支持双输出/四输出的SPI,最大SPI时钟可达80Mhz。
W25Q64特征:
支持标准、双输出和四输出的SPI
高性能串行闪存
高达普通串行闪存性能的6倍
80Mhz的时钟操作
支持160Mhz的双输出SPI
支持320Mhz的四输出SPI
40MB/S的数据连续传输速率
高效的“连续读取模式”
低指令开销
仅需8个时钟周期处理内存
允许XIP操作
性能优于X16并行闪存
低功耗,温度范围宽
单电源2.7V至3.6V
4mA有源电流
-40°C 至+85°C的正常运行温度范围
灵活的4KB扇区构架
扇区统一擦除(4KB)
块擦除(32KB和64KB)
1到256个字节编程
超过10万次擦除/写循环
超过20年的数据保存
高级的安全功能
软件和硬件写保护
自上至下,扇区或块选择
锁定和保护OTP
每个设备都有唯一的64位ID
有效的空间的包装
8-pin SOIC 208-mil
8-pin PDIP 300-mil
8-pad WSON 8x6-mm
16-pin SOIC 300-mil
W25Q64
CS:片选信号输入
DO(IO1):数据输出(数据输入输出1)
WP(IO2):写保护输入(数据输入输出2)
GND:地信号
DI(IO0):数据输入(数据输入输出0)
CLK:串行时钟输入
HOLD(IO3):Hold输入(数据输入输出3)
VCC:电源
注:
- IO0和IO1用于标准SPI和双输出SPI操作
- IO0-IO3用于四输出SPI操作
片选:
SPI片选引脚能够使能和失能器件的操作。当片选引脚为高电平时,器件没有被选中,串行数据输出引脚(DO或IO0,IO2,IO3,IO4)处于高阻抗状态。当器件没有被选中时,其功耗将会处于待机状态下的水平,除非内部正在擦除、运行程序或状态寄存器周期。当片选引脚置为低时,器件被选中,电源消耗将增至活跃水平,可以进行读写操作。电源上电后,在执行一次操作之前,片选引脚必须由高电平转至低电平。
串行数据输入、输出:
W25Q64支持标准的SPI,双输出SPI和四输出SPI操作。标准的SPI指令利用单向的数据输入引脚在串行时钟输入上升沿串行地向器件写入指令、地址或数据。标准的SPI也利用单向的数据输出引脚在串行时钟输入下降沿串行地从器件读取数据或状态。
双输出和四输出SPI利用双向IO引脚在串行时钟输入上升沿串行地向器件写入指令、地址或数据,在串行时钟输入下降沿串行地从器件读取数据或状态。
写保护:
WP引脚用来防止状态寄存器被写入。用于与状态寄存器的块保护位(SEC、TB、BP2、BP1和BP0)配合,状态寄存器保护位(SRP),部分或整个存储器阵列可以用硬件保护。WP引脚在低电平时有效。当状态寄存器2的QE位设置为四倍I/O时,则WP(硬件写保护)功能不可用,因为此时这个引脚被用为IO2。
HOID:
HOID引脚允许器件在有效选择的情况下被终止。当HOLD引脚被置为低电平时,CS引脚为低,DO引脚将处于高阻态状态,并且DI和CLK引脚上的信号将被忽略。当HOLD引脚被置为高电平时,器件操作将被恢复。HOLD引脚的功能通常用在当多个器件共享同一个SPI信号的情况下。HOLD引脚在低电平时有效。当状态寄存器2的QE位设置为四倍I/O时,则HOLD引脚功能不可用,因为此时这个引脚被用为IO3。
串行时钟(CLK):
SPI串行时钟输入引脚(CLK)为串行输入和输出操作提供时序。
二、硬件连接
三、软件代码
#include "flash.h"
#define LOG_TAG "flash"
#define LOG_LVL ELOG_LVL_DEBUG
u8 const W25X_WriteEnable= 0x06 ;
u8 const W25X_WriteDisable= 0x04 ;
u8 const W25X_ReadStatusReg= 0x05 ;
u8 const W25X_WriteStatusReg= 0x01 ;
u8 const W25X_ReadData= 0x03 ;
u8 const W25X_FastReadData= 0x0B ;
u8 const W25X_FastReadDual= 0x3B ;
u8 const W25X_PageProgram= 0x02 ;
u8 const W25X_BlockErase= 0xD8 ;
u8 const W25X_SectorErase= 0x20 ;
u8 const W25X_ChipErase = 0xC7 ;
u8 const W25X_PowerDown = 0xB9 ;
u8 const W25X_ReleasePowerDown= 0xAB ;
u8 const W25X_DeviceID = 0xAB ;
u8 const W25X_ManufactDeviceID= 0x90 ;
u8 const W25X_JedecDeviceID= 0x9F ;
static struct rt_spi_device *spi_dev_w25q;
//读取W25QXX的状态寄存器
//BIT7 6 5 4 3 2 1 0
//SPR RV TB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
//默认:0x00
u8 flash_read_sr(void)
{
u8 byte=0;
rt_spi_send_then_recv(spi_dev_w25q,&W25X_ReadStatusReg,1,&byte,1);
return byte;
}
//写W25QXX状态寄存器
//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
void flash_write_sr(u8 sr)
{
rt_spi_send_then_send(spi_dev_w25q,&W25X_WriteStatusReg,1,&sr,1); //发送写取状态寄存器命令
}
//W25QXX写使能
//将WEL置位
void flash_write_enable(void)
{
rt_spi_send(spi_dev_w25q,&W25X_WriteEnable,1); //发送写使能
}
//W25QXX写禁止
//将WEL清零
void flash_write_disable(void)
{
rt_spi_send(spi_dev_w25q,&W25X_WriteDisable,1); //发送写禁止指令
}
//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void flash_read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{
u32 ReadCmd = (W25X_ReadData)|(ReadAddr<<8);
rt_spi_send_then_recv(spi_dev_w25q,&ReadCmd,4,pBuffer,NumByteToRead);//发送读取命令
}
//SPI在一页(0~65535)内写入少于255个字节的数据
//在指定地址开始写入最大255字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大255),该数不应该超过该页的剩余字节数!!!
void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u8 NumByteToWrite)
{
u32 WriteCmd = (W25X_PageProgram)|(WriteAddr<<8);
flash_write_enable(); //SET WEL
rt_spi_send_then_send(spi_dev_w25q,&WriteCmd,4,pBuffer,NumByteToWrite%256); //发送写页命令
flash_wait_busy(); //等待写入结束
}
//无检验写SPI FLASH
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void flash_write_no_check(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
u16 pageremain;
pageremain=256-WriteAddr%256; //单页剩余的字节数
if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节
while(1)
{
W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);
if(NumByteToWrite==pageremain)break;//写入结束了
else //NumByteToWrite>pageremain
{
pBuffer+=pageremain;
WriteAddr+=pageremain;
NumByteToWrite-=pageremain; //减去已经写入了的字节数
if(NumByteToWrite>255)pageremain=255; //一次可以写入256个字节
else pageremain=NumByteToWrite; //不够256个字节了
}
};
}
//写SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
void flash_write(u8* pBuffer,u16 sectorNum,u16 NumByteToWrite)
{
flash_erase_sector(sectorNum);//擦除这个扇区
flash_write_no_check(pBuffer,sectorNum,NumByteToWrite);//写入整个扇区
}
//擦除整个芯片
//等待时间超长...
void flash_erase_chip(void)
{
flash_write_enable(); //SET WEL
flash_wait_busy();
rt_spi_send(spi_dev_w25q,&W25X_ChipErase,1); //发送片擦除命令
flash_wait_busy(); //等待芯片擦除结束
}
//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个山区的最少时间:150ms
void flash_erase_sector(u32 Dst_Addr)
{
u32 EraseSectorCmd = (W25X_SectorErase)|(Dst_Addr<<8);
rt_kprintf("Erase_Sector:%x\r\n",Dst_Addr);//监视falsh擦除情况,测试用
Dst_Addr*=4096;
flash_write_enable(); //SET WEL
flash_wait_busy();
rt_spi_send(spi_dev_w25q,&EraseSectorCmd,4); //发送扇区擦除指令
flash_wait_busy(); //等待擦除完成
}
//等待空闲
void flash_wait_busy(void)
{
while((flash_read_sr()&0x01)==0x01); // 等待BUSY位清空
}
//进入掉电模式
void flash_powerdown(void)
{
rt_spi_send(spi_dev_w25q,&W25X_PowerDown,1); //发送掉电命令
rt_hw_us_delay(3); //等待TPD
}
//唤醒
void flash_wakeup(void)
{
rt_spi_send(spi_dev_w25q,&W25X_ReleasePowerDown,1); // send W25X_PowerDown command 0xAB
rt_hw_us_delay(3); //等待TRES1
}
void flash_init()
{
rt_hw_spi_device_attach("spi2", "spi flash", GPIOI, GPIO_PIN_0);
spi_dev_w25q = (struct rt_spi_device *)rt_device_find("spi flash");
if (!spi_dev_w25q)
{
rt_kprintf("spi sample run failed! can't find %s device!\n","spi2");
}
else
{
struct rt_spi_configuration cfg;
cfg.data_width = 8;
cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_3 | RT_SPI_MSB;
cfg.max_hz = 50 * 1000 *1000; /* 50MHz ,no more than 80M */
rt_spi_configure(spi_dev_w25q, &cfg);
}
}
flash.h
#ifndef _FLASH_H_
#define _FLASH_H_
#include "main_value.h"
#include "drv_spi.h"
#define W25Q80 0XEF13
#define W25Q16 0XEF14
#define W25Q32 0XEF15
#define W25Q64 0XEF16
#define W25Q128 0XEF17
void flash_init(void);
u16 flash_read_id(void); //读取FLASH ID
u8 flash_read_sr(void); //读取状态寄存器
void flash_write_sr(u8 sr); //写状态寄存器
void flash_write_enable(void); //写使能
void flash_write_disable(void); //写保护
void flash_write_no_check(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);
void flash_read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead); //读取flash
void flash_write(u8* pBuffer,u16 sectorNum,u16 NumByteToWrite);//写入flash
void flash_erase_chip(void); //整片擦除
void flash_erase_sector(u32 Dst_Addr); //扇区擦除
void flash_wait_busy(void); //等待空闲
void flash_powerdown(void); //进入掉电模式
void flash_wakeup(void); //唤醒
#endif