固定的厂商编号(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 );
}
发送命令码后需要读三个字节
函数实现如下:用于验证读出的是否是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;
}
中间有三个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();
}
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