#芯片# W25Q128

news/2024/12/27 18:02:35/

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);}


http://www.ppmy.cn/news/114615.html

相关文章

gorm调用beforeUpdate等钩子函数时报错 reflect.Value.Addr of unaddressable value

问题说明 使用下面的结构体在执行gorm的Save、update、updates方法时&#xff0c;会自动调用钩子函数BeforeUpdate,官方文档中的示例也是这么写的。但是出现报错reflect.Value.Addr of unaddressable value。 type ArtworkLockRecord struct {//some fields } func (a *Artwo…

16JS07——数组

目标&#xff1a; 1、数组的概念 2、创建数组 3、获取数组中的元素 4、遍历数组 5、数组中新增元素 6、数组案例 一、数组的概念 数组是指一组数据的集合&#xff0c;其中的每个数据被称作元素&#xff0c;在数组中可以存放任意类型的元素。数组是一种将一组数据存储在单个变…

【03.04】大数据教程--html+css基础

当谈到大数据时&#xff0c;HTML和CSS可能并不是最相关的技术。HTML和CSS主要用于构建网页和应用程序的用户界面&#xff0c;而大数据则涉及处理和分析大规模数据集。但是&#xff0c;如果您想展示有关大数据的信息或结果&#xff0c;并在网页上呈现&#xff0c;那么HTML和CSS可…

Fatal error: Port 9100 is already in use by another process.

解决办法 #查看占用端口的进程id lsof -i:9100 #杀死进行 kill -9 4852

i310100和i39100f对比哪个好 i3 10100和i3 9100f差别大吗

i3-10100基于祖传的14nm制程工艺&#xff0c;拥有4核8线程&#xff0c;默认主频3.6Ghz&#xff0c;最大睿频4.3Ghz&#xff0c;三级缓存为6MB&#xff0c;不支持超频&#xff0c;内置UHD630核显&#xff0c;设计功耗65W 选i3 10100还是i39100f这些点很重要!看完你就知道了 http…

实施AS9100标准的意义

1、市场范围不断扩大 您的认证可为您打开未利用的国内和国际商业的商机之门。另外&#xff0c;AS-认证的质量系统有助于建立商业之间的共同语和期望水平。通过让公司具有一样的标准&#xff0c;可实现提高效率的目的&#xff0c;否则便不能达到此目的或通过个人/所有者的质量系…

关于解决prometheus报错get “http://ip:9100/metrics“:connect:no route to host

linux服务器部署node_exporter完成后 启动node_exporter服务&#xff0c;一切正常&#xff0c;日志也未报错&#xff0c;访问地址&#xff1a;http://ip:9100&#xff0c;就是访问不了 如图 访问Prometheus平台 http://ip:9090,state显示down 其他两台服务器一样的配置均正常…

elk日志分析部署报错,出现9100端口,9200端口不见

在主机上刚开始安装了elasticsearch的时候启动9200端口成功 当安装完 node-v8.2.1.tar.gz和elasticsearch-head.tar.gz之后&#xff0c;对服务再次就行启动时发现 9100端口可以正常启动&#xff0c;而9200端口启动不了&#xff0c; systemctl start elasticsearch时没有任何报…