W25Q128JV 128Mbit 串行Flash存储器
1. 信息
该存储器先划分为256个可擦除块。每个块可以划分为16个扇区,每个扇区4KB。每个扇区可以划分为16个页。每页256字节。
即可以知道,共计65536可编程页, 4096个扇区,256个块。
存储框图
2. 写入数据
一次最多写入256字节。
3. 擦除数据
页被擦除时,可以按照16页一组(4KB的扇区大小),
或者按照128页一组(32KB块大小),
或者256页一组(64KB块),
或者整个芯片
4. SPI
标准SPI通信(模式0和模式3),时钟频率133MHz。标准SPI指令使用DI输入引脚在CLK的上升沿将指令,地址或数据串行写入器件。DO输出引脚用于在CLK的下降沿从器件读取数据或状态。
当SPI总线主机处于待机状态且数据未传输至串行闪存时,模式0与模式3之间的主要区别在于CLK信号的正常状态。
(1)对于模式0,CLK信号通常在/ CS的下降沿和上升沿为低电平。
(2)对于模式3,CLK信号通常在/ CS的下降沿和上升沿为高电平
5. 片选
SPI片选(/ CS)引脚启用和禁用器件操作。
(1) / CS为高电平时,取消选择器件,并且串行数据输出(DO或IO0,IO1,IO2,IO3)引脚处于高阻态。取消选择时,除非正在进行内部擦除,编程或写入状态寄存器周期,否则设备的功耗将处于待机状态。
(2)将/ CS调低时,将选择设备,功耗将增加到活动水平,并且可以将指令写入设备或从设备读取数据。
上电后,/ CS必须从高电平转换为低电平,然后才能接受新指令。
/ CS输入必须在加电和断电时跟踪VCC电源电平(请参见“写保护”和图58)。
如果需要,可以使用/ CS引脚上的上拉电阻器来完成此操作。
6. 状态和配置寄存器
W25Q128JV提供了三个状态和配置寄存器。
【读取状态寄存器-1/2/3指令】 可用于提供有关闪存阵列可用性的状态,无论该设备是启用写操作还是禁用写操作,写保护状态,Quad SPI设置,安全寄存器锁定状态, 擦除/程序挂起状态,输出驱动器强度,上电。
【写状态寄存器指令】 可用于配置设备写保护功能,Quad SPI设置,安全寄存器OTP锁定和输出驱动器强度。对状态寄存器的写访问由非易失性状态寄存器保护位(SRL)的状态,写使能指令以及在标准/双SPI操作期间控制
状态寄存器1
状态寄存器2
状态寄存器3
当擦除和编写的动作涉及到处于保护状态的区域数据时,该命令会被忽略。
7. 制造商和设备ID
8. 指令集
Data Input Output Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 Byte 7
Number of Clock(1-1-1) 8 8 8 8 8 8 8
Write Enable 06h 【写保护】
Volatile SR Write Enable 50h 【易失性 状态寄存器写使能】
Write Disable 04h 【写失能】
Read Status Register-1 05h (S7-S0)(2) 【读 状态寄存器1】
Write Status Register-1(4) 01h (S7-S0)(4) 【写 状态寄存器1】
Read Status Register-2 35h (S15-S8)(2) 【读 状态寄存器2】
Write Status Register-2 31h (S15-S8) 【写 状态寄存器2】
Read Status Register-3 15h (S23-S16)(2)【读 状态寄存器3】
Write Status Register-3 11h (S23-S16) 【写 状态寄存器3】
Release Power-down / ID ABh Dummy Dummy Dummy (ID7-ID0)(2)
Manufacturer/Device ID 90h Dummy Dummy 00h (MF7-MF0) (ID7-ID0)
JEDEC ID 9Fh (MF7-MF0) (ID15-ID8) (ID7-ID0)
Read Unique ID 4Bh Dummy Dummy Dummy Dummy (UID63-0)
Read Data 03h A23-A16 A15-A8 A7-A0 (D7-D0) 【读数据】
Fast Read 0Bh A23-A16 A15-A8 A7-A0 Dummy (D7-D0) 【读数据 快速】
Fast Read Dual Output 3Bh A23-A16 A15-A8 A7-A0 Dummy Dummy (D7-D0)(7) 【读数据 快速 双输出】
Page Program 02h A23-A16 A15-A8 A7-A0 D7-D0 D7-D0(3) 【页编程】1~256个字节
Sector Erase (4KB) 20h A23-A16 A15-A8 A7-A0 【擦除 扇区】
Block Erase (32KB) 52h A23-A16 A15-A8 A7-A0 【擦除 块 32KB】
Block Erase (64KB) D8h A23-A16 A15-A8 A7-A0 【擦除 块 64KB】
Chip Erase C7h/60h 【擦除 整片】
Read SFDP Register 5Ah 00 00 A7-A0 Dummy (D7-D0) 【串行闪存可发现参数(SFDP)寄存器】一个 256字节
Erase Security Register(5) 44h A23-A16 A15-A8 A7-A0 【安全寄存器】三个 256字节
Program Security Register(5) 42h A23-A16 A15-A8 A7-A0 D7-D0 D7-D0(3)
Read Security Register(5) 48h A23-A16 A15-A8 A7-A0 Dummy (D7-D0)
Global Block Lock 7Eh
Global Block Unlock 98h
Read Block Lock 3Dh A23-A16 A15-A8 A7-A0 (L7-L0)
Individual Block Lock 36h A23-A16 A15-A8 A7-A0
Individual Block Unlock 39h A23-A16 A15-A8 A7-A0
Erase / Program Suspend 75h 【擦除/编程暂停】
Erase / Program Resume 7Ah 【擦除/编程恢复】
Power-down B9h
Enable Reset 66h 【使能复位】
Reset Device 99h 【复位设备】//---------------------------------------------------------------------------------------------------//
Number of Clock(1-2-2) 8 4 4 4 4 4 4 4 4
Fast Read Dual I/O BBh A23-A16(6) A15-A8(6) A7-A0(6) Dummy(11) (D7-D0)(7) 【读数据 快速 双IO输出】
Mftr./Device ID Dual I/O 92h A23-A16(6) A15-A8(6) 00(6) Dummy(11) (MF7-MF0) (ID7-ID0)(7)//---------------------------------------------------------------------------------------------------//
Number of Clock(1-1-4) 8 8 8 8 2 2 2 2 2
Quad Input Page Program 32h A23-A16 A15-A8 A7-A0 (D7-D0)(9) (D7-D0)(3) … 【页编程 四输入】
Fast Read Quad Output 6Bh A23-A16 A15-A8 A7-A0 Dummy Dummy Dummy Dummy (D7-D0)(10) 【读数据 快速 四输出】//---------------------------------------------------------------------------------------------------//
Number of Clock(1-4-4) 8 2(8) 2(8) 2(8) 2 2 2 2 2
Mftr./Device ID Quad I/O 94h A23-A16 A15-A8 00 Dummy(11) Dummy Dummy (MF7-MF0) (ID7-ID0)
Fast Read Quad I/O EBh A23-A16 A15-A8 A7-A0 Dummy(11) Dummy Dummy (D7-D0) 【读数据 快速 四IO输出】
Set Burst with Wrap 77h Dummy Dummy Dummy W8-W0 【设置自动换行突发】
9. 代码实现
w25qxx驱动代码,已封装好,只需要改下SPI接口,就直接调用各种函数-其它文档类资源-CSDN下载
bool W25qxx_Init(void);void W25qxx_EraseChip(void);void W25qxx_EraseSector(uint32_t SectorAddr);void W25qxx_EraseBlock(uint32_t BlockAddr);uint32_t W25qxx_PageToSector(uint32_t PageAddress);uint32_t W25qxx_PageToBlock(uint32_t PageAddress);uint32_t W25qxx_SectorToBlock(uint32_t SectorAddress);uint32_t W25qxx_SectorToPage(uint32_t SectorAddress);uint32_t W25qxx_BlockToPage(uint32_t BlockAddress);bool W25qxx_IsEmptyPage(uint32_t Page_Address, uint32_t OffsetInByte, uint32_t NumByteToCheck_up_to_PageSize);bool W25qxx_IsEmptySector(uint32_t Sector_Address, uint32_t OffsetInByte, uint32_t NumByteToCheck_up_to_SectorSize);bool W25qxx_IsEmptyBlock(uint32_t Block_Address, uint32_t OffsetInByte, uint32_t NumByteToCheck_up_to_BlockSize);void W25qxx_WriteByte(uint8_t pBuffer, uint32_t Bytes_Address);void W25qxx_WritePage(uint8_t *pBuffer, uint32_t Page_Address, uint32_t OffsetInByte, uint32_t NumByteToWrite_up_to_PageSize);void W25qxx_WriteSector(uint8_t *pBuffer, uint32_t Sector_Address, uint32_t OffsetInByte, uint32_t NumByteToWrite_up_to_SectorSize);void W25qxx_WriteBlock(uint8_t *pBuffer, uint32_t Block_Address, uint32_t OffsetInByte, uint32_t NumByteToWrite_up_to_BlockSize);void W25qxx_ReadByte(uint8_t *pBuffer, uint32_t Bytes_Address);void W25qxx_ReadBytes(uint8_t *pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);void W25qxx_ReadPage(uint8_t *pBuffer, uint32_t Page_Address, uint32_t OffsetInByte, uint32_t NumByteToRead_up_to_PageSize);void W25qxx_ReadSector(uint8_t *pBuffer, uint32_t Sector_Address, uint32_t OffsetInByte, uint32_t NumByteToRead_up_to_SectorSize);void W25qxx_ReadBlock(uint8_t *pBuffer, uint32_t Block_Address, uint32_t OffsetInByte, uint32_t NumByteToRead_up_to_BlockSize);
10. 使用W25Q记录数据以及日志
单片机+Flash芯片并不能使用数据库来记录数据。为了使用Flash芯片记录数据,需要对FLASH的空间进行合理的划分。
(1)FLASH芯片不能频繁擦写,擦写次数预估在10000次。
(2)设备存在突然断电的情况,一秒钟向FLASH写一个数据,每次写入时,需要明确偏移地址。这个地址不能通过FLASH记录(考虑到寿命)。
(3)如果设备拥有铁电FRAM,可以使用铁电记录这种频繁修改的参数。铁电的擦写次数可以达到上亿次。
(4)如果不存在铁电FRAM,推荐使用RTC时间,用来计算上电时的数据记录偏移地址。
(5)上电时,从FLASH中读取起始时间点t0,则偏移地址offset = rtc实时时间 - t0 。当记满FLASH之后,修改FLASH中记录得到t0。
extern uint32_t RTC_Get_RawSec(void);
extern void _WriteStratSec(uint32_t rawSec);//#define PRECISE_TIME 0x100000 //整个Flash可以记录104 8576秒的数据,约为12.13天
#define PRECISE_TIME 0x0FD200 //1036800秒 ,12天整//支持跨页读取,跨扇区读取,跨块读取
static void _ReadData(uint8_t* buf, uint32_t byteAddress,uint8_t len)
{//一页256个字节,一次读8条数据,共128字节if(byteAddress%0x100 +len <=256)//可以在一页里面读完{W25qxx_ReadPage(buf,byteAddress>>8,byteAddress%0x100,len);}else //需要分成两页读取{uint32_t pageaddress = byteAddress>>8;uint8_t pageoffaddress=byteAddress%0x100;uint8_t frontLen = 256-pageoffaddress;uint8_t behindLen = len-frontLen;//前段W25qxx_ReadPage(buf,pageaddress,pageoffaddress,frontLen);if(pageaddress>=0xFD1F)pageaddress=0x0000;//后段 溢出回到地址开头W25qxx_ReadPage(buf+frontLen,pageaddress,0,behindLen);}}static void _ReadLog(uint8_t* buf, uint32_t byteAddress,uint8_t len)
{//一页256个字节,一次读8条数据,共64字节if(byteAddress%0x100 +len <=256)//可以在一页里面读完{W25qxx_ReadPage(buf,byteAddress>>8,byteAddress%0x100,len);}else //需要分成两页读取{uint32_t pageaddress = byteAddress>>8;uint8_t pageoffaddress=byteAddress%0x100;uint8_t frontLen = 256-pageoffaddress;uint8_t behindLen = len-frontLen;//前段W25qxx_ReadPage(buf,pageaddress,pageoffaddress,frontLen);if(pageaddress>=0xFFFF)pageaddress=0xFD20;//后段 溢出回到地址开头W25qxx_ReadPage(buf+frontLen,pageaddress,0,behindLen);}}/*
将时间秒数转化为地址的函数
*/
static uint32_t getAddressFromTime(uint32_t rawSec)
{uint32_t Dsec = 0;//如果现在的时间比起始时间早,或者记满一圈,则需要修改起始时间(以当前时间为新的起始时间)if(rawSec<g_MI.W25Q.startSec || rawSec>=PRECISE_TIME+g_MI.W25Q.startSec){//写入到EEPROM_WriteStratSec(rawSec);//保存给全局变量g_MI.W25Q.startSec =rawSec;//秒数差值//Dsec=0;}else{//秒数差值Dsec = rawSec- g_MI.W25Q.startSec;}return Dsec*sizeof(S_FlashData);//16
}//传入的地址 为 字节地址(页地址+偏移量)
static void _CheckSector(uint32_t ByteAddress)
{//根据扇区中字节的偏移量,可知是否进入新页if(ByteAddress % 0x1000==0)//页字节偏移量(第0页第00字节){//擦除当前扇区编号W25qxx_EraseSector(ByteAddress>>12);//传入第几扇区0x000~0xFFFreturn;}if(g_MI.INIT.flashCheck ==1){//通过读取本扇区的第一条数据,判断是否擦除过//第一条数据 的时间戳 如果不等于正确的时间点 且 不等于0xFFFFFFFF,则需要擦除uint32_t startByteAddress = (ByteAddress>>12)<<12;uint32_t startSec = (startByteAddress>>4)+g_MI.W25Q.startSec;S_FlashData data;_ReadData((uint8_t*) &data,startByteAddress,16);if(data.rawSec!=0xFFFFFFFF && data.rawSec!=startSec){W25qxx_EraseSector(ByteAddress>>12);}g_MI.INIT.flashCheck = 0;}
}//写数据,每秒写16个字节
static void _WriteData(uint32_t ByteAddress,uint32_t rawSec)
{//准备数据S_FlashData data;data.flag.a = 0xFFFFFFFF;data.flag.b.Te = e_Temp_UNUSE;data.flag.b.obj= e_Obj_pH; data.flag.b.CalNum=1;data.rawSec=rawSec; //原始时间戳data.data = g_MI.RTD.RT_pH_ORP; //数据data.temp=g_MI.RTD.RT_Tempeature;//数据//写入数据W25qxx_WritePage((uint8_t *)&data ,ByteAddress>>8,ByteAddress%0x100,sizeof(S_FlashData));
}//存在SPI复用的情况,每次操作FLASH前,需要将SPI切换为mode0
extern void reInitSPI1(uint8_t mode);
/*
保存数据,写入到flash
*/
void W25Q128_RecordData(void)
{uint32_t rawSec,ByteAddress;reInitSPI1(0);rawSec = RTC_Get_RawSec(); //得到当前的时间秒数//1. 得到字节地址 ByteAddress = getAddressFromTime(rawSec); //2. 检查扇区_CheckSector(ByteAddress);//4. 向指定地址写入数据_WriteData(ByteAddress,rawSec);reInitSPI1(1);
}/*
读数据,接口仅供历史记录界面使用传入的是指定时间点thisSec(向后共8个数据)
*/
void W25Q128_ReadData(uint32_t thisSec,S_FlashData* buff,uint8_t num)
{uint32_t CurrentSec=RTC_Get_RawSec();uint32_t ByteAddress=0;//1. 首先对指定的时间点进行限制(修正)if(thisSec>CurrentSec-num) thisSec=CurrentSec-num;if(thisSec<CurrentSec+256-PRECISE_TIME) thisSec=CurrentSec+256-PRECISE_TIME;//2. 根据修正后的指定时间点和起始时间点作比较if(thisSec>=g_MI.W25Q.startSec)//正向段的8条数据,数据段为1块{ByteAddress = (thisSec-g_MI.W25Q.startSec)<<4; }else{ByteAddress = (PRECISE_TIME+thisSec-g_MI.W25Q.startSec)<<4;}//3. 开始读数据reInitSPI1(0);_ReadData((uint8_t*) buff,ByteAddress,num*16);//支持跨页reInitSPI1(1);
}#include "define.h"
extern void EEPROM_Read(uint16_t readAddr,uint8_t* Data,uint8_t num);
extern void EEPROM_Write(uint16_t writeAddr,uint8_t* pData,uint8_t num);void W25Q128_init(void)
{reInitSPI1(0);W25qxx_Init();reInitSPI1(1);//检查Flash区域,扇区是否已被擦除g_MI.INIT.flashCheck =1;//读取到日志索引{uint16_t index=0;EEPROM_Read(EEPROM_ADDR_LOG_INDEX,(uint8_t*)&index,2);if(index >= 23552){index =0;//EEPROM_Write(EEPROM_ADDR_LOG_INDEX,(uint8_t*)&index,2);}g_MI.W25Q.logIndex = index;}}//记录日志
//日志记录在最后的46个扇区内
//起始地址为 4050*4096 = 16588800 0xFD 2000
//剩余字节大小 0x2E000 (188416字节),单条记录8字节,可记 23552 条记录
#define LOG_BASE_ADDRESS 0xFD2000void W25Q128_RecordLog(uint8_t L1,uint8_t L2,uint8_t L3,uint8_t data1,uint8_t data2,uint8_t data3)
{uint32_t ByteAddress;s_LogData data;//准备数据data.flag.a.L1 = L1;data.flag.a.L2 = L2;data.flag.a.L3 = L3;data.data1 = data1;data.data2 = data2;data.data3 = data3;data.rawSec = RTC_Get_RawSec();//根据 index ,得到地址ByteAddress = g_MI.W25Q.logIndex* sizeof(s_LogData)+LOG_BASE_ADDRESS;reInitSPI1(0);//判断扇区是否需要清空if(ByteAddress % 0x1000==0)//页字节偏移量(第0页第00字节){//擦除当前扇区编号W25qxx_EraseSector(ByteAddress>>12);//传入第几扇区0x000~0xFFF}//写入数据W25qxx_WritePage((uint8_t*)&data ,ByteAddress>>8,ByteAddress%0x100,sizeof(s_LogData));//保存索引+1g_MI.W25Q.logIndex++;if(g_MI.W25Q.logIndex >= 23552){g_MI.W25Q.logIndex =0;}EEPROM_Write(EEPROM_ADDR_LOG_INDEX,(uint8_t*)&g_MI.W25Q.logIndex,2);reInitSPI1(1);
}void W25Q128_ReadLog(uint8_t* buf,uint16_t startIndex , uint16_t num)
{uint32_t ByteAddress = startIndex* sizeof(s_LogData)+LOG_BASE_ADDRESS;reInitSPI1(0);_ReadLog(buf, ByteAddress,num*sizeof(s_LogData));reInitSPI1(1);}