STM32通过SPI软件读写W25Q64

目录

STM32通过SPI软件读写W25Q64

1. W25Q64简介

2. W25Q64硬件电路图

3. W25Q64芯片框图

先看右边:存储部分

SPI控制逻辑部分:SPICommand &Control Logic

SPI状态寄存器 StatusRegister

SPI写控制逻辑 Write Control Logic

SPI高电压生成器High Voltage Generators

SPI页地址锁存/计数器、字节地址锁存/计数器

4. Flash操作注意事项

5. W25Q64手册精简

(11.1 )STATUS REGISTER 状态寄存器

(11.2.1)Manufacturer and Device Identification 制造商和设备标识

(11.2.2) Instruction Set Table SPI 指令集 (常用标记)

6. W25Q64读写存储器编写

7.1编写时需要注意的点

7.2程序文件简要说明:

W25Q64.c

W25Q64.h

W25Q64_Ins.h

main.c

7. 验证Flash注意事项

1. Flash操作注意事项

2. 验证Flash擦除之后变为全1

3. 验证每个数据位只能由1改写为0,不能由0改写为1

4.最多写入一页的数据,超过页尾位置的数据会回到页首覆盖写入


SPI通信详解可以看我的这篇博客:SPI通信详解-CSDN博客

STM32硬件SPI通信详解可以看我的这篇博客:STM32通过SPI硬件读写W25Q64-CSDN博客

STM32通过SPI软件读写W25Q64

1. W25Q64简介

  • W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景

  • 存储介质:Nor Flash(闪存)

  • 时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI) 可以在只发或者只收的时候,把不用的引脚(比如写保护、或者不用的发送、接收引脚)暂时当做数据传输用的。(这里只做了解)

  • 支持SPI模式0和模式3

  • 存储容量(24位地址):

    • W25Q40: 4Mbit / 512KByte
    • W25Q80: 8Mbit / 1MByte
    • W25Q16: 16Mbit / 2MByte
    • W25Q32: 32Mbit / 4MByte
    • W25Q64: 64Mbit / 8MByte
    • W25Q128: 128Mbit / 16MByte
    • W25Q256: 256Mbit / 32MByte

2. W25Q64硬件电路图

这里的小括号IO1、2、3、4就是介绍时所说的双重SPI和四重SPI

这个电路图没啥好说的

引脚功能
VCC、GND电源(2.7~3.6V)
CS(SS)SPI片选
CLK(SCK)SPI时钟
DI(MOSI)SPI主机输出从机输入
DO(MISO)SPI主机输入从机输出
WP写保护
HOLD数据保持

3. W25Q64芯片框图

先看右边:存储部分

  • W25Q64的地址宽度是24位。也就是三个字节。
    • 右边这一整块是所有的存储器。存储器以字节为单位。每个字节都有一个对应的地址,左下角为起始地址,右上角为结束地址
  • 右上角的最大地址为7FFFFFh(因为W25Q64的芯片最大容量为8M,只用了一半的地址空间)
  • 整个存储器分为128块(块0~块7F),以64KB为一块。
    • 两个例子↓
    • 块0 起始地址:000000 结束地址:00FFFF
    • 块15 起始地址:0F0000 结束地址:0FFFFF
  • 一个块又分为16个扇区(扇区0~扇区F),以4KB为一扇区。
    • 两个例子↓
    • 块0 ,扇区2 起始地址:002000 结束地址:002FFF
    • 块15 , 扇区14 起始地址:0FE000 结束地址:0FEFFF
  • 一个扇区又分为16页(页0~页F),以256字节为一页
    • 两个例子↓
    • 块0 ,扇区2 ,页12 起始地址:002C00 结束地址:002CFF
    • 块15 , 扇区14 ,页3 起始地址:0FE300 结束地址:0FE3FF

SPI控制逻辑部分:SPICommand &Control Logic

芯片内部可以进行地址锁存、数据读写等操作。引出的引脚是我们用来操作的。

SPI状态寄存器 StatusRegister

检测芯片是否处于忙状态、是否写使能、是否写保护等等。都是在状态寄存器中体现

SPI写控制逻辑 Write Control Logic

与外部WP引脚相连。可以用来进行写保护

SPI高电压生成器High Voltage Generators

配合Flash进行编程用的。为了使芯片掉电不丢失

SPI页地址锁存/计数器、字节地址锁存/计数器

这两个寄存器就是用来指定地址的。

我们发的前两个字节会进入页地址锁存计数器中。最后一个字节会进入字节锁存计数器中。

  • 页地址通过写保护和行解码决定操作那一页。
  • 字节地址通过列解码和256页缓存决定指定地址的读写操作
    • 页缓存区是一个256字节的RAM存储器
    • 数据写入就是通过这个RAM缓存区来进行的。
    • 因为Flash存储信息较慢,而RAM读写信息很快。
    • 所以需要RAM先临时保存,Flash根据RAM存储器来进行存储操作。
    • 所以也有一个规定:一次写的字节数不能超过256字节
    • 在在写入时序之后,芯片会进入一段忙的状态(忙着把RAM中的信息存储到Flash中)这个忙的状态会通过一条线,连接SPI状态寄存器。给状态寄存器的BUSY位置1。
    • 因为读取只是看一眼状态,不需要改变,所以读取时很快的。基本不会受到限制
  • 并且因为这两个寄存器都有计数器,所以在读写之后会指针+1

4. Flash操作注意事项

写入操作时:

  • 写入操作前,必须先进行写使能

  • 每个数据位只能由1改写为0,不能由0改写为1

    所以写入数据前必须先擦除,擦除后,所有数据位变为1

  • 擦除必须按最小擦除单元进行(整个芯片、按块、按扇区)

  • 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入 写入操作结束后,芯片进入忙状态,不响应新的读写操作

读取操作时:

  • 直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取

5. W25Q64手册精简

这里只精简出一般需要的。其他位请详看手册。括号内是对应手册的章节

(11.1 )STATUS REGISTER 状态寄存器

  1. BUSY位
    • 忙碌(BUSY)是状态寄存器(S0)中的一个只读位,
    • 当设备正在执行页编程、扇区擦除、块擦除、芯片擦除或写状态寄存器指令时,该位被设置为 1 状态。
    • 在此期间,设备将忽略其他指令,除了读状态寄存器和擦除暂停指令(请参阅交流特性中的 tW、tPP、tSE、tBE 和 tCE)。
    • 当编程、擦除或写状态寄存器指令完成时,BUSY位将被清除为 0 状态,表示设备已准备好接受进一步的指令。
  2. 写使能锁存器(WEL)
    • 写使能锁存器(WEL)是状态寄存器(S1)中的一个只读位,
    • 在执行写使能指令后被设置为 1。
    • 当设备被写禁止时,WEL 状态位被清除为 0。
    • 在上电或执行以下任何指令后会出现写禁止状态:写禁止、页编程、扇区擦除、块擦除、芯片擦除和写状态寄存器。 这表明我们在每次写入操作时都要写使能一下
  3. 这两位在寄存器中是这样的。我们要掌握的在最低的两位

(11.2.1)Manufacturer and Device Identification 制造商和设备标识

(11.2.2) Instruction Set Table SPI 指令集 (常用标记)

6. W25Q64读写存储器编写

7.1编写时需要注意的点

一定要先看懂时序图再去编写存储器 的 读写时序

需要注意Flash的操作注意事项

写入操作时:

  • 写入操作前,必须先进行写使能

  • 每个数据位只能由1改写为0,不能由0改写为1

    所以写入数据前必须先擦除,擦除后,所有数据位变为1

  • 擦除必须按最小擦除单元进行(整个芯片、按块、按扇区)

  • 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入 写入操作结束后,芯片进入忙状态,不响应新的读写操作

读取操作时:

  • 直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取

7.2程序文件简要说明:

  • MySPI:为了防止与stm32函数重名,所以添加了My前缀。 主要是为了完成SPI的三个基本时序
  • MySPI.h:函数声明
  • W25Q64.c:初始化W25Q64存储寄存器。完成读写、擦除存储器的时序

  • W25Q64.h:函数声明、数据结构体声明
  • W25Q64_Ins.h:控制存储器时需要用到的指令集
  • main.c:测试SPI读取存储器结果。

MySPI.c

#include "stm32f10x.h"                  // Device header

/*所用引脚列表*/
#define RCCPeriph       RCC_APB2Periph_GPIOA
#define SCK_Port        GPIOA
#define SCK_Pin         GPIO_Pin_6
#define SS_Port         GPIOA
#define SS_Pin          GPIO_Pin_5
#define MOSI_Port       GPIOA
#define MOSI_Pin        GPIO_Pin_4
#define MISO_Port       GPIOA
#define MISO_Pin        GPIO_Pin_3

/**
  * 函    数:写片选信号SS
  * 参    数:BitValue:输入1片选信号SS为高电平
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_W_SS (uint8_t BitValue)
{
    GPIO_WriteBit(SS_Port,SS_Pin,(BitAction)BitValue);
}

/**
  * 函    数:写时钟信号SCK
  * 参    数:BitValue:输入1时钟信号SCK为高电平
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_W_SCK (uint8_t BitValue)
{
    GPIO_WriteBit(SCK_Port,SCK_Pin,(BitAction)BitValue);
}

/**
  * 函    数:写 主机输出,从机输入信号MOSI
  * 参    数:BitValue:输入1时主机输出高电平
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_W_MOSI (uint8_t BitValue)
{
    GPIO_WriteBit(MOSI_Port,MOSI_Pin,(BitAction)BitValue);
}

/**
  * 函    数:读 主机输入,从机输出信号MISO
  * 参    数:无
  * 返 回 值:BitValue
  * 注意事项:无
  */
uint8_t MySPI_R_MISO (void)
{
    return GPIO_ReadInputDataBit(MISO_Port,MISO_Pin);
}

/**
  * 函    数:MySPI初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_Init(void)
{   
    /*配置时钟与引脚*/
    RCC_APB2PeriphClockCmd(RCCPeriph,ENABLE);
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = SCK_Pin | SS_Pin | MOSI_Pin;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(MOSI_Port,&GPIO_InitStructure);   //时钟、片选、MOSI都是推挽输出
    
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = MISO_Pin;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(MISO_Port,&GPIO_InitStructure);   //MISO 为上拉输入
    
    MySPI_W_SS(1);  //片选默认为高
    MySPI_W_SCK(0); //时钟默认为低
}

/*******************/
/*SPI的三个时序单元*/
/*******************/

/**
  * 函    数:起始信号
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_Start(void)
{   
    MySPI_W_SS(0);
}

/**
  * 函    数:终止条件
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_Stop(void)
{   
    MySPI_W_SS(1);
}

/**
  * 函    数:交换一个字节(模式0)(方法1)
  * 参    数:SendByte      待发送的字节
  * 返 回 值:ReceiveByte   接收到的字节
  * 注意事项:这是使用掩码依次提取数据中的每一位保存或发送
              好处是不会改变传入参数本身,但是效率不高
              如果要改为模式1,则先上升沿再发送。先下降沿再接收(2、3则直接改时钟极性就ok了)
  */
//uint8_t MySPI_WarpByte(uint8_t SendByte)
//{
//    uint8_t ReceiveByte = 0x00;
//    
//    uint8_t i = 0;
//    for(i = 0; i < 8; i++)
//    {
//        MySPI_W_MOSI(SendByte & (0x80 >> i));  //发送第一个bit
//        MySPI_W_SCK(1);//第上升沿来临
//        if (MySPI_R_MISO() == 1)
//        {
//            ReceiveByte |= (0x80 >> i);    //按照从高往低接收数据
//        }
//        MySPI_W_SCK(0); //下降沿来临
//    }
//    return ReceiveByte;
//} 

/**
  * 函    数:交换一个字节(模式0)(方法2)
  * 参    数:SendByte      待发送的字节
  * 返 回 值:ReceiveByte   接收到的字节
  * 注意事项:这是使用了移位模型的方式。效率更快
              如果要改为模式1,则先上升沿再发送。先下降沿再接收(2、3则直接改时钟极性就ok了)
  */
uint8_t MySPI_WarpByte(uint8_t SendByte)
{
    uint8_t i = 0;
    for(i = 0; i < 8; i++)
    {
        MySPI_W_MOSI(SendByte & 0x80);  //发送第一个bit
        SendByte <<= 1; //发送数据左移一位
        MySPI_W_SCK(1); //第上升沿来临
        if (MySPI_R_MISO() == 1)
        {
            SendByte |= 0x01;    //保存收到的数据到发送寄存器的最低位
        }
        MySPI_W_SCK(0); //下降沿来临
    }
    return SendByte;
} 

MySPI.h

#ifndef __MYSPI_H
#define __MYSPI_H

//初始化
void MySPI_Init(void);
//起始
void MySPI_Start(void);
//终止
void MySPI_Stop(void);
//交换
uint8_t MySPI_WarpByte(uint8_t SendByte);

#endif

W25Q64.c

#include "stm32f10x.h"                  // Device header
#include "W25Q64.h"
#include "W25Q64_Ins.h"
#include "MySPI.h"

/**
  * 函    数:初始化W25Q64
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void W25Q64_Init(void)
{
    MySPI_Init();
}

/********************/
/*拼接完整的通信时序*/
/********************/

/**
  * 函    数:查看W25Q64的厂商号和设备号
  * 参    数:ID* Str   存放了ID结构体的指针
  * 返 回 值:无
  * 注意事项:接收第八位时是|=
  */
ID W25Q64_ID;//存放设备ID号的结构体
void W25Q64_ReadID(ID* Str)
{
    MySPI_Start();//起始
    MySPI_WarpByte(W25Q64_JEDEC_ID);//发送读取设备号指令。返回值不要
    Str->MID = MySPI_WarpByte(W25Q64_DUMMY_BYTE);//接收厂商ID 从机发来的设备号。发送值随便
    Str->DID = MySPI_WarpByte(W25Q64_DUMMY_BYTE);//接收设备ID高八位
    Str->DID <<= 8;//把接收到的数据放到高八位
    Str->DID |= MySPI_WarpByte(W25Q64_DUMMY_BYTE);//接收设备ID低八位
    MySPI_Stop();//停止
}

/**
  * 函    数:写使能
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void W25Q64_WriteEnable(void)
{
    MySPI_Start();//起始
    MySPI_WarpByte(W25Q64_WRITE_ENABLE);//发送
    MySPI_Stop();//停止
}

/**
  * 函    数:写失能
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void W25Q64_WriteDisable(void)
{
    MySPI_Start();//起始
    MySPI_WarpByte(W25Q64_WRITE_DISABLE);//发送
    MySPI_Stop();//停止
}

/**
  * 函    数:等待忙函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void W25Q64_WaitBusy(void)
{
    MySPI_Start();//起始
    MySPI_WarpByte(W25Q64_READ_STATUS_REGISTER_1);  //发送
    
    uint32_t TimeOut = 100000;
    while((MySPI_WarpByte(W25Q64_DUMMY_BYTE)&0x01) == 0x01)    //读取状态寄存器1的Busy位是否为1,为1则等待
    {
        TimeOut--;
        if(TimeOut == 0)
        {
            break;  //超时退出
        }
    }
    MySPI_Stop();   //停止
}

/**
  * 函    数:页编程
  * 参    数:Address       要写入那个页地址
              *DataArray    存储字节所用的数组
              Count         一次写入多少字节
  * 返 回 值:无
  * 注意事项:一次只能写入最多0-256个字节
  */
void W25Q64_PageProgram(uint32_t Address, uint8_t* DataArray,uint16_t Count)//(0-256,所以要16位)
{
    W25Q64_WaitBusy();
    //事前等待。(事后等待是先等待再退出,比较保险。 事前等待可以先做别的事,再进去。效率高)
    
    W25Q64_WriteEnable();//写使能(每次写时都要先写使能)
    
    MySPI_Start();//起始
    MySPI_WarpByte(W25Q64_PAGE_PROGRAM);//发送页编程指令
    MySPI_WarpByte(Address >> 16 );
    MySPI_WarpByte(Address >> 8 );//(接收只能接收8位。会自动舍弃)
    MySPI_WarpByte(Address >> 0);//发送页地址
    uint16_t i = 0;
    for(i = 0; i < Count; i++)
    {
        MySPI_WarpByte(DataArray[i]);//发送Count个数组的第i位
    }
    MySPI_Stop();//停止
}

/**
  * 函    数:页擦除
  * 参    数:Address       要擦除那一页
  * 返 回 值:无
  * 注意事项:最小的擦除单位。4kb 1扇区
  */
void W25Q64_PageErase(uint32_t Address)
{
    W25Q64_WaitBusy();
    //事前等待。(事后等待是先等待再退出,比较保险。 事前等待可以先做别的事,再进去。效率高)
    
    W25Q64_WriteEnable();//写使能(每次写时都要先写使能)
    
    MySPI_Start();//起始
    MySPI_WarpByte(W25Q64_SECTOR_ERASE_4KB);//发送页编程指令
    MySPI_WarpByte(Address >> 16 );
    MySPI_WarpByte(Address >> 8 );//发送地址
    MySPI_WarpByte(Address >> 0);
    MySPI_Stop();//停止
}

/**
  * 函    数:读取数据
  * 参    数:Address       要读取个地址
              *DataArray    存储字节所用的数组
              Count         一次读取多少字节
  * 返 回 值:无
  * 注意事项:读取可以无限制读取
  */
void W25Q64_ReadData(uint32_t Address, uint8_t* DataArray,uint32_t Count)//读取没有限制
{
    W25Q64_WaitBusy();
    //事前等待。(事后等待是先等待再退出,比较保险。 事前等待可以先做别的事,再进去。效率高)
    
    MySPI_Start();//起始
    MySPI_WarpByte(W25Q64_READ_DATA);//发送页编程指令
    MySPI_WarpByte(Address >> 16 );
    MySPI_WarpByte(Address >> 8 );//(接收只能接收8位。会自动舍弃)
    MySPI_WarpByte(Address >> 0);//发送页地址
    uint32_t i = 0;
    for(i = 0; i < Count; i++)
    {
        DataArray[i] = MySPI_WarpByte(W25Q64_DUMMY_BYTE);//接收Count个字节,放到数组的第i位
    }
    MySPI_Stop();//停止
}

W25Q64.h

#ifndef __W25Q64_H
#define __W25Q64_H

//初始化W25Q64
void W25Q64_Init(void);

/*厂商和设备ID号*/
typedef struct ID
{
    uint8_t MID;//8位厂商ID
    uint16_t DID;//16位设备ID
}ID;
extern ID W25Q64_ID;

//获取厂商和设备号ID
void W25Q64_ReadID(ID* Str);
//页编程
void W25Q64_PageProgram(uint32_t Address, uint8_t* DataArray,uint16_t Count);
//页擦除
void W25Q64_PageErase(uint32_t Address);
//读取
void W25Q64_ReadData(uint32_t Address, uint8_t* DataArray,uint32_t Count);
#endif

W25Q64_Ins.h

#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H

#define W25Q64_WRITE_ENABLE	                        0x06
#define W25Q64_WRITE_DISABLE                        0x04
#define W25Q64_READ_STATUS_REGISTER_1               0x05
#define W25Q64_READ_STATUS_REGISTER_2               0x35
#define W25Q64_WRITE_STATUS_REGISTER                0x01
#define W25Q64_PAGE_PROGRAM                         0x02
#define W25Q64_QUAD_PAGE_PROGRAM                    0x32
#define W25Q64_BLOCK_ERASE_64KB	                    0xD8
#define W25Q64_BLOCK_ERASE_32KB	                    0x52
#define W25Q64_SECTOR_ERASE_4KB	                    0x20
#define W25Q64_CHIP_ERASE                           0xC7
#define W25Q64_ERASE_SUSPEND                        0x75
#define W25Q64_ERASE_RESUME                         0x7A
#define W25Q64_POWER_DOWN                           0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE                0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET           0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID	    0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID               0x90
#define W25Q64_READ_UNIQUE_ID                       0x4B
#define W25Q64_JEDEC_ID                             0x9F
#define W25Q64_READ_DATA                            0x03
#define W25Q64_FAST_READ                            0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT                0x3B
#define W25Q64_FAST_READ_DUAL_IO                    0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT                0x6B
#define W25Q64_FAST_READ_QUAD_IO                    0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO              0xE3
            
#define W25Q64_DUMMY_BYTE                           0xFF

#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "oled.h"
#include "Delay.h"
#include "key.h"
#include "W25Q64.h"

/**
  * 函    数:验证SPI控制W25Q64存储器
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */

uint8_t ArrWrite[] = {0x01,0x02,0x03,0x04};
uint8_t ArrRead[4];

int main()
{
    Delay_Init();//初始化演示
    OLED_Init();//初始化OLED;
    W25Q64_Init();//初始化W25Q64存储器

    OLED_ShowString(1,1,"MID:  ,DID:   ");
    
    W25Q64_ReadID(&W25Q64_ID);//读ID放到这个结构体中
    OLED_ShowHexNum(1,5,W25Q64_ID.MID,2);
    OLED_ShowHexNum(1,12,W25Q64_ID.DID,4);//显示MID DID
    
    OLED_ShowString(2,1,"W:");
    OLED_ShowString(3,1,"R:");
    
    W25Q64_PageErase(0x000000);               //擦除地址。写入前需要(最好定位到扇区的起始地址(后三位为0))
    W25Q64_PageProgram(0x000000,ArrWrite,4);  //写入数组中数据到存储器
    W25Q64_ReadData(0x000000,ArrRead,4);      //读取存储器中数据到数组
    
    OLED_ShowHexNum(2, 3, ArrWrite[0], 2);
    OLED_ShowHexNum(2 ,6, ArrWrite[1], 2);
    OLED_ShowHexNum(2, 9, ArrWrite[2], 2);
    OLED_ShowHexNum(2, 12, ArrWrite[3], 2);
    
    OLED_ShowHexNum(3, 3, ArrRead[0], 2);
    OLED_ShowHexNum(3 ,6, ArrRead[1], 2);
    OLED_ShowHexNum(3, 9, ArrRead[2], 2);
    OLED_ShowHexNum(3, 12, ArrRead[3], 2);

    while(1)
    {

    }
}

7. 验证Flash注意事项

1. Flash操作注意事项

写入操作时:

  • 写入操作前,必须先进行写使能

  • 每个数据位只能由1改写为0,不能由0改写为1

    所以写入数据前必须先擦除,擦除后,所有数据位变为1

  • 擦除必须按最小擦除单元进行(整个芯片、按块、按扇区)

  • 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据会回到页首覆盖写入 写入操作结束后,芯片进入忙状态,不响应新的读写操作

读取操作时:

  • 直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取

2. 验证Flash擦除之后变为全1

只擦除,不写入,直接读取

    W25Q64_PageErase(0x000000);               //擦除地址。写入前需要(最好定位到扇区的起始地址(后三位为0))
//    W25Q64_PageProgram(0x000000,ArrWrite,4);  //写入数组中数据到存储器
    W25Q64_ReadData(0x000000,ArrRead,4);      //读取存储器中数据到数组

3. 验证每个数据位只能由1改写为0,不能由0改写为1

当前0x000000地址存储为 0xAA 0xBB 0xCC 0xDD

不擦除直接写入

    //W25Q64_PageErase(0x000000);               //擦除地址。写入前需要(最好定位到扇区的起始地址(后三位为0))
    W25Q64_PageProgram(0x000000,ArrWrite,4);  //写入数组中数据到存储器
    W25Q64_ReadData(0x000000,ArrRead,4);      //读取存储器中数据到数组
  1. 直接写入0xFF 0xFF 0xFF 0xFF

    结果。字节中0并未改变

  2. 直接写入0x00 0x00 0x00 0x00

    结果。字节中1全部改变为0

4.多写入一页的数据,超过页尾位置的数据会回到页首覆盖写入

一页的范围是xxxx00到xxxxFF。因此他能存放(0-255)256个字节

现在指定写入的地址为0x0000FF。写入0xAA 0xBB 0xCC 0xDD

那么按照规则来说,就会在页尾写入0xAA、页首写入 0xBB 0xCC 0xDD

而另一页并不会写入数据。而读取可以跨页

从页尾开始读取结果如下:

从页首开始读取结果如下:

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值