SST25F080B是美国微芯科技推出的一款8Mbit SPI串行FLASH,今天我们来简单了解下如何用STM32单片机进行FLASH的读写操作。首先我们打开STM32CubeMX软件进行SPI的硬件基本配置:
SPI工作方式我们配置为全双工主模式:
基本参数配置保持默认即可:
然后生成代码即可,这里不在过多的赘述关于STM32CubeMX的配置部分,有需要的可以参考我的其他文章。
这里需要注意的是:时钟极性CPOL和时钟相位CPHA这两个参数需要根据FLASH数据手册的SPI协议来配置,根据SST25VF080B芯片数据手册得知,该芯片工作于模式0或者模式3,我们这里配置为CPOL:0,CPHA:0,
在写驱动程序之前,我们首先了解下该FLASH芯片的区块页概念:对于该芯片来说总共是8Mbit,也就是1Mbyte,众所周知FLASH闪存是由区块页组成的,但是这些区块页之间的换算关系很多人却搞不明白,我在这里给出计算公式,就拿我们这个芯片SST25VF080B举例说下,简单直观:
对于我们这个FLASH芯片来说:总共是8Mbit,大家都知道1byte=8bit,1K=1024byte,1page=256byte,1sector=4Kbyte,1block=64Kbyte,故我们的芯片SST25VF080B:
总位数bit:8Mbit = 8*1024*1024 = 8388608bit
总字节数byte:8388608bit/8=1048576byte=1M字节
总页数page:1048576byte/256=4096页
总扇区sector:4096page/4=1024扇区
总块block:1024sector/64=16块
所以,对我们这个芯片来说::总共有16个块(64Kbyte*16),每块又有16个扇区(4Kbyte*16),每个扇区又有16页(256byte*16).
SST25VF080B的基本指令表:
根据该表我们在SST25VFxx.h文件中我们做如下定义:
#include "main.h"
#define SST25VF_CSL HAL_GPIO_WritePin(SPI2_CE_GPIO_Port,SPI2_CE_Pin,GPIO_PIN_RESET); /*配置spi-cs引脚*/
#define SST25VF_CSH HAL_GPIO_WritePin(SPI2_CE_GPIO_Port,SPI2_CE_Pin,GPIO_PIN_SET);
//SST25VF指令表
#define SST25VF_WrStrEnable 0x50 //写状态使能
#define SST25VF_WriteEnable 0x06 //写使能
#define SST25VF_WriteDisable 0x04 //写禁止
#define SST25VF_ReadStatusReg 0x05 //读状态寄存器
#define SST25VF_WriteStatusReg 0x01 //写状态寄存器
#define SST25VF_ReadData 0x03 //读数据
#define SST25VF_FastReadData 0x0B //快读
#define SST25VF_PageProgram 0x02 //页编程
#define SST25VF_AutoAddress 0xAD //AAI编程
#define SST25VF_BlockErase 0xD8 //块擦除(64K)
#define SST25VF_SectorErase 0x20 //扇区擦除(4K)
#define SST25VF_ChipErase 0xC7 //芯片擦除
#define SST25VF_DeviceID 0xAB //读器件ID
#define SST25VF_ManufactDeviceID 0x90 //读制造ID+器件ID
#define SST25VF_JedecDeviceID 0x9F //读JEDEC ID
void SPI_SST25VF_Write_SR(unsigned char sr);
void SPI_SST25VF_Write_Enable(void);
void SPI_SST25VF_Write_Disable(void);
void SPI_SST25VF_Read(unsigned char *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);
void SPI_SST25VF_Write_severalbyte(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void SPI_SST25VF_Erase_Chip(void);
void SPI_SST25VF_Erase_Sector(uint32_t Dst_Addr);
void SPI_SST25VF_Wait_Busy(void);
void SPI_SST25VF_WrStr_Enable(void);
void SPI_SST25VF_DisProtection(void);
uint8_t SPI_SST25VF_Read_onebyte(unsigned int ReadAddr);
void SPI_SST25VF_Write_onebyte(uint32_t WriteAddr, uint8_t data);
接下来我们来详细讲解下这些函数的意思:
首先我们封装一个读写操作的函数(下面所有功能性的函数都是基于该函数完成的):
//TxData:要写入的字节
//返回值:读取到的字节
*/
uint8_t SPI_ReadWriteByte(uint8_t TxData)
{
uint8_t Rxdata;
HAL_SPI_TransmitReceive(&hspi2, &TxData, &Rxdata, 1, 1000);
return Rxdata; //返回收到的数据
}
一、写使能,写失能(06H,04H):
由图可以看出,我们先拉低CE,然后发送命令0x06,就可以对FLASH进行写数据了,反之发送命令0x04后,将不能对FLASH进行写操作。
//写使能
void SPI_SST25VF_Write_Enable(void)
{
SST25VF_CSL /*enable chip selection*/
SPI_ReadWriteByte(SST25VF_WriteEnable); /*send write enable*/
SST25VF_CSH /*disable chip selection*/
}
//写失能
void SPI_SST25VF_Write_Disable(void)
{
SST25VF_CSL /*enable chip selection*/
SPI_ReadWriteByte(SST25VF_WriteDisable); /*send write disable*/
SST25VF_CSH /*disable chip selection*/
}
二、读数据(03H):
由图我们可以看出,在读数据时我们需要先发送命令0x03,后边紧跟三个字节的24位地址,便可以得到我们想要的数据了。
//读数据
void SPI_SST25VF_Read(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
uint16_t i;
SST25VF_CSL
SPI_ReadWriteByte(SST25VF_ReadData);
SPI_ReadWriteByte((uint8_t)((ReadAddr) >> 16));
SPI_ReadWriteByte((uint8_t)((ReadAddr) >> 8));
SPI_ReadWriteByte((uint8_t)ReadAddr);
for (i = 0; i < NumByteToRead; i++)
{
pBuffer[i] = SPI_ReadWriteByte(0XFF);
}
SST25VF_CSH
}
三、写数据(02H):
1.写单个字节数据:
//写单个字节数据
void SPI_SST25VF_Write_onebyte(uint32_t WriteAddr, uint8_t data)
{
SPI_SST25VF_Write_Enable(); //使能写
SST25VF_CSL
SPI_ReadWriteByte(SST25VF_PageProgram);
SPI_ReadWriteByte((uint8_t)((WriteAddr) >> 16));
SPI_ReadWriteByte((uint8_t)((WriteAddr) >> 8));
SPI_ReadWriteByte((uint8_t)WriteAddr);
SPI_ReadWriteByte(data);
SST25VF_CSH //关闭片选
SPI_SST25VF_Wait_Busy(); //等待写入结束
}
2.写多个字节数据:(写多个字节数据时,相对来说会比较麻烦,由下图我们可以看出,在允许写数据的情况下,我们需要先拉低CE,然后发送0xAD命令,紧跟着是3个字节的24位地址然后是两个字节的数据,拉高CE,随后就是有规律的0xAD命令两个字节数据,如此循环,最后我们发送WRDI命令来退出AAI(地址自动增加)模式)。
//地址自动增加,写多个字节
void SPI_SST25VF_Write_severalbyte(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint16_t i;
SPI_SST25VF_Write_Enable(); //使能写
SST25VF_CSL
SPI_ReadWriteByte(SST25VF_AutoAddress);
SPI_ReadWriteByte((uint8_t)((WriteAddr) >> 16));
SPI_ReadWriteByte((uint8_t)((WriteAddr) >> 8));
SPI_ReadWriteByte((uint8_t)WriteAddr);
SPI_ReadWriteByte(*pBuffer++);
SPI_ReadWriteByte(*pBuffer++);
SST25VF_CSH//关闭片选
SPI_SST25VF_Wait_Busy(); //等待写入结束
for (i = 2; i < NumByteToWrite-1; i+=2)
{
SST25VF_CSL
SPI_ReadWriteByte(SST25VF_AutoAddress);
SPI_ReadWriteByte(*pBuffer++);
SPI_ReadWriteByte(*pBuffer++);
SST25VF_CSH
SPI_SST25VF_Wait_Busy(); //等待写入结束
}
SPI_SST25VF_Write_Disable();
//如果NumByteToWrite为奇数
if(NumByteToWrite%2!=0)
{
//SPI_SST25VF_Write_Enable();
SPI_SST25VF_Write_onebyte(WriteAddr+NumByteToWrite-1, *pBuffer);
}
//SPI_SST25VF_Write_Disable();
}
四、读状态寄存器(05H):
//读状态寄存器
uint8_t SPI_SST25VF_ReadSR(void)
{
uint8_t byte = 0;
SST25VF_CSL /*enable chip selection*/
SPI_ReadWriteByte(SST25VF_ReadStatusReg); /*send read status register command */
byte = SPI_ReadWriteByte(0Xff); /*read one byte */
SST25VF_CSH /*disable chip selection*/
return byte;
}
五、使能写状态寄存器(50H或06H)并写入数据(01H):
这里需要注意的是:该芯片具有写保护功能,我们在上电后需要对芯片的状态寄存器写入数据0x00(并非绝对,也可以是其他值,未验证,感兴趣的小伙伴可以深入研究一下)来解除芯片的写保护,才可以对芯片进行写操作,包括写数据,擦除等。
//使能写状态寄存器
void SPI_SST25VF_WrStr_Enable(void)
{
SST25VF_CSL
SPI_ReadWriteByte(SST25VF_WrStrEnable);
SST25VF_CSH
}
//写一个字节到状态寄存器
void SPI_SST25VF_Write_SR(uint8_t sr)
{
SST25VF_CSL
SPI_ReadWriteByte(SST25VF_WriteStatusReg);
SPI_ReadWriteByte(sr);
SST25VF_CSH
}
//解除写保护
void SPI_SST25VF_DisProtection(void)
{
SPI_SST25VF_WrStr_Enable();
SST25VF_CSL
SPI_ReadWriteByte(0x01);
SPI_ReadWriteByte(0x00);
SST25VF_CSH
六、擦除4k扇区(20H):
//擦除一个扇区
void SPI_SST25VF_Erase_Sector(uint32_t Dst_Addr)
{
Dst_Addr *= 4096;
SPI_SST25VF_Write_Enable(); //SET WEL
SPI_SST25VF_Wait_Busy();
SST25VF_CSL //使能片选
SPI_ReadWriteByte(SST25VF_SectorErase); //发送扇区擦除指令
SPI_ReadWriteByte((uint8_t)((Dst_Addr) >> 16)); //发送24bit地址
SPI_ReadWriteByte((uint8_t)((Dst_Addr) >> 8));
SPI_ReadWriteByte((uint8_t)Dst_Addr);
SST25VF_CSH //关闭片选
SPI_SST25VF_Wait_Busy(); //等待擦除完成
}
七、擦除整个芯片(60H或C7H):
//擦除整个芯片
void SPI_SST25VF_Erase_Chip(void)
{
SPI_SST25VF_Write_Enable(); //SET WEL
SPI_SST25VF_Wait_Busy();
SST25VF_CSL //使能片选
SPI_ReadWriteByte(SST25VF_ChipErase); //发送片擦除命令
SST25VF_CSH //关闭片选
SPI_SST25VF_Wait_Busy(); //等待芯片擦除结束
}
八、判忙函数:
通过读状态寄存器的BUSY位来判断是否内部写入操作是否完成。
//等待空闲
void SPI_SST25VF_Wait_Busy(void)
{
while ((SPI_SST25VF_ReadSR() & 0x01) == 0x01)
; // 等待BUSY位清空
}
最后附上简单的测试程序:
uint8_t LCD_WriteRAM[14] = {0xBF,0xF0,0xFC,0xC8,0x00,0xFA,0xFF,0xFA,0xFE,0xFA,0xFE,0x8F,0xF8,0x08};
//解除写保护
SPI_SST25VF_DisProtection();
//读状态寄存器
status = SPI_SST25VF_ReadSR();
//读数据
SPI_SST25VF_Read(Flash_RxBuf,0x00,20);
HAL_Delay(100);
//写入数据
SPI_SST25VF_Write_severalbyte(LCD_WriteRAM,0x00,10);
//读数据
SPI_SST25VF_Read(Flash_RxBuf,0x00,20);
HAL_Delay(100);
上电后我们先解除芯片的写保护,读状态寄存器status的值应该为0x00,也就是我们写进去的值,保证所有扇区都解除了写保护;第一次读数据所有的值应该都是0xFF,然后写入值从地址0x00开始,写入10个字节,然后重新读20个字节数据,Flash_RxBuf此时接收到的数据应该数组LCD_WriteRAM的前10个字节和10个0xFF,如下图所示: