STM32CubeMX+HAL库采用SPI方式驱动FLASH芯片-SST25VF080B

        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,如下图所示:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值