手册中的指令

  1. 第一列代表指令名(写使能,读状态,写状态,擦除)
  2. 第二列代表指令码,程序中使用此进行操作
  3. 第三列至N列,
    带括号的字节参数,方向为 FLASH 向主机传输,即命令响应。
    不带括号的则为主机向 FLASH 传输;
    “A0~A23” 指 FLASH 芯片内部存储器组织的地址;
    “M0~M7” 为厂商号( MANUFACTURER ID); “ID0-ID15”为 FLASH 芯片的ID;
    “dummy”指该处可为任意数据;
    “D0~D7” 为 FLASH 内部存储矩阵的内容

在这里插入图片描述

固定的厂商编号(M7-M0)和不同类型 FLASH 芯片独有的编号(ID15-ID0) 手册中提供的值如下:
在这里插入图片描述
0x9F紧跟指令编码的三个字节“(M7-M0)”、“(ID15-ID8)”及“(ID7-ID0)” 时序图如下:
在这里插入图片描述

主机首先通过 MOSI 线向 FLASH 芯片发送第一个字节数据为“9F h” ,当 FLASH 芯片收到该数据后,它会解读成主机向它发送了“JEDEC 指令”,然后它就作出该命令的响应: 通过 MISO 线把它的厂商 ID(M7-M0)及芯片类型(ID15-0)发送给主机,主机接收到指令响应后可进行校验。 常见的作用是主机端通过读取设备 ID 来测试硬件是否连接正常,或用于识别设备

SPI使用的主要读写函数,发送一个字节的数据

u8 SPI_FLASH_SendByte(u8 byte)
{
	 SPITimeout = SPIT_FLAG_TIMEOUT;
  //等待发送缓冲区为空,TXE
  while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_TXE) == RESET)
	{
    if((SPITimeout--) == 0) return   SPI_TIMEOUT_UserCallback(0);
   }
  //把要写入的数据写到发送缓冲区
  SPI_I2S_SendData(FLASH_SPIx , byte);
	//设置超时时间
	SPITimeout = SPIT_FLAG_TIMEOUT;
  //等待接收缓冲区非空 RXNE
  while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_RXNE) == RESET)
  {
    if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);
   }

  //从缓冲区中读取数据
  return SPI_I2S_ReceiveData(FLASH_SPIx );
}

读取Flash ID函数

发送命令码后需要读三个字节
在这里插入图片描述
函数实现如下:用于验证读出的是否是0XEF4017(手册中)

u32 SPI_FLASH_ReadID(void)
{
  u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
  //CS拉低,产生起始信号
  SPI_FLASH_CS_LOW();
  //发送0x9F
  SPI_FLASH_SendByte(0X9F);
  //读取一个字节
  Temp0 = SPI_FLASH_SendByte(Dummy_Byte);
  //读取一个字节
  Temp1 = SPI_FLASH_SendByte(Dummy_Byte);
  //读取一个字节
  Temp2 = SPI_FLASH_SendByte(Dummy_Byte);
 //CS拉高,停止通讯
  SPI_FLASH_CS_HIGH();
  //计算出返回值
  Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
  return Temp;
}

读取Device ID

中间有三个dumpy 在变成的时候也需要空出来
在这里插入图片描述
函数实现如下

u32 SPI_FLASH_ReadDeviceID(void)
{
  u32 Temp = 0;
  SPI_FLASH_CS_LOW();
  /* Send "RDID " instruction */
  SPI_FLASH_SendByte(0xAB);
  //根据手册命令需要Dummy_Byte三次
  SPI_FLASH_SendByte(Dummy_Byte);
  SPI_FLASH_SendByte(Dummy_Byte);
  SPI_FLASH_SendByte(Dummy_Byte);
  
  //在第四次时读取ID
  Temp = SPI_FLASH_SendByte(Dummy_Byte);

  /* Deselect the FLASH: Chip Select high */
  SPI_FLASH_CS_HIGH();
  return Temp;
}

只要向 FLASH 芯片发送了读状态寄存器的指令, FLASH 芯片就会持续向主机返回最新的状态寄存器内容,直到收到 SPI 通讯的停止信号。

//写使能命令
void SPI_FLASH_WriteEnable(void)
{
  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(0x06);
  SPI_FLASH_CS_HIGH();
}
#define WIP_Flag                  0x01
//写等待停止信号
void SPI_FLASH_WaitForWriteEnd(void)
{
  u8 FLASH_Status = 0;
  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(0x05);//ReadStatusReg	
  do
  {
    FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);	 
  }
  while ((FLASH_Status & WIP_Flag) == SET);
  
  SPI_FLASH_CS_HIGH();
}

扇区擦除

FLASH 存储器的特性决定了它只能把原来为“ 1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念,在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的
时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。

在这里插入图片描述
FLASH 芯片的最小擦除单位为扇区(Sector),而一个块(Block)包含 16 个扇区。如下所示
在这里插入图片描述

删除4K扇区,程序源码如下:

//读写统一地址
#define  FLASH_WriteAddress     0x00000
#define  FLASH_ReadAddress      FLASH_WriteAddress
#define  FLASH_SectorToErase    FLASH_WriteAddress

void SPI_FLASH_SectorErase(u32 SectorAddr)
{
  //需要开写使能
  SPI_FLASH_WriteEnable();
  SPI_FLASH_WaitForWriteEnd();

  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(0x20);
  //三字节地址需要分三次发送,先高位字节,再中间,后低
  SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);
  SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);
  SPI_FLASH_SendByte(SectorAddr & 0xFF);
  SPI_FLASH_CS_HIGH();
  SPI_FLASH_WaitForWriteEnd();
}

对Flash写操作

整页写

void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
  //写使能
  SPI_FLASH_WriteEnable();
  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(0x02);
  SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);
  SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);
  SPI_FLASH_SendByte(WriteAddr & 0xFF);
  //错误处理
  if(NumByteToWrite > SPI_FLASH_PerWritePageSize)
  {
     NumByteToWrite = SPI_FLASH_PerWritePageSize;
     FLASH_ERROR("SPI_FLASH_PageWrite too large!"); 
  }

  while (NumByteToWrite--)
  {
    SPI_FLASH_SendByte(*pBuffer);
    pBuffer++;
  }
  SPI_FLASH_CS_HIGH();
  SPI_FLASH_WaitForWriteEnd();
}

不固定数据量

Flash 数据写入示意图

数据写入流程图
在这里插入图片描述

程序源码

#define SPI_FLASH_PageSize              256

//pBuffer 写地址指针;WriteAddr写地址;NumByteToWrite写长度
//需要判断WriteAddr写地址是否在首地址还是中间地址
//NumByteToWrite写大小,是整页还是非整页
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
  u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
	
  //取模,检查有几页
  Addr = WriteAddr % SPI_FLASH_PageSize;
  //差count 个数值,刚好对齐到页地址
  count = SPI_FLASH_PageSize - Addr;
  //整数页
  NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
  //不满一页的字节数
  NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
	
  //Addr=0 刚好按页对齐
  if (Addr == 0)
  {
	// NumByteToWrite < SPI_FLASH_PageSize 
    if (NumOfPage == 0) 
    {
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
    }
    else //一页写步下 NumByteToWrite > SPI_FLASH_PageSize 
    { 
      while (NumOfPage--)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }
	  //剩余不满的写完
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
    }
  }
  //地址不对齐
  else 
  {
		/* NumByteToWrite < SPI_FLASH_PageSize */
    if (NumOfPage == 0)
    {
      //当前剩余的count比NumOfSingle 小,一页写不完
      if (NumOfSingle > count) 
      {
        temp = NumOfSingle - count;
		//先写完当页		
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
				
        WriteAddr +=  count;
        pBuffer += count;
		//写剩余的
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
      }
      else//当前的剩余count 能写完NumByteToWrite
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
      }
    }
    else /* NumByteToWrite > SPI_FLASH_PageSize */
    {
	  //地址不对齐多出的count分开处理,重新计算页数
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
      NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
	  //先写完count个数据
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
			
	  //
      WriteAddr +=  count;
      pBuffer += count;
	  //写整页
      while (NumOfPage--)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }
	  //多余不满一页的情况
      if (NumOfSingle != 0)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
      }
    }
  }
}

读数据

//pBuffer读到的缓冲区; ReadAddr 读取Flash地址;NumByteToRead 读数据量
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(0x03);
  SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
  SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);
  SPI_FLASH_SendByte(ReadAddr & 0xFF);
  
  while (NumByteToRead--)
  {
    *pBuffer = SPI_FLASH_SendByte(Dummy_Byte);
    pBuffer++;
  }
  SPI_FLASH_CS_HIGH();
}

参考资料:
W25Q64 datasheet