首先说说什么是EEPROM,EEPROM的全称是“电可擦除可编程只读存储器”,即Electrically Erasable Programmable Read-Only Memory,是一种掉电后数据不丢失的存储芯片。总的来说和FLASH芯片提供的功能相似,但是操作方式有些许不同。
1.读取方式
Flash和EEPROM都采用随机读取,可以通过地址直接访问存储器中的数据。
2.写入方式
Flash和EEPROM的写入方式不一样,EEPROM可以按字节进行写入也可按页写入(256字节),而Flash通常需要按块进行写入。还有就是,在Flash中,要写入一个数据,需要先擦除一整个块,然后再将新数据写入该块。
3.擦除方式
Flash和EEPROM的擦除方式不一样,EEPROM可以按字节进行擦除(SST26似乎也只能块擦除,这个可能得根据EEP型号而论),而Flash一般需要按块进行擦除。也就是说,在Flash中,要擦除一个数据,通常需要先擦除一整个块,然后再将该块中需要保留的数据重新写入,比EEPROM操作麻烦一些。
4.擦写速度
EEPROM的擦写速度普遍比Flash慢一些,擦写速度会受到许多因素的影响,包括使用的存储器型号、使用的接口类型、写入和擦除的数据量、芯片温度等等。
一、SST26基本信息
1、如下引脚图可见,该芯片可以使用两种通讯方式,SPI和QSPI,当使用SPI模式时引脚功能如下所示:
2:MISO 6:时钟线
5:MOSI 1:片选
当使用QSPI模式时引脚功能如下所示:
1:片选 2:SIO1 7:SIO3
5:SIO0 3:SIO2 6:时钟线
由这四\六个引脚配合stm32完成SPI\QSPI通讯。
手册上值得注意的地方一定要好好看:
关于HOLD#这个引脚只在SPI模式下不使用,一定要拉高,不然什么都读不到。
上图是EEP的内存划分,一个扇区是4096字节,一页是256字节,至于块划分有8k、32k、64k。
上图是SST26的指令图,就算是使用其他型号的EEP,它的手册里面也会有这样一张表,其中Command Cycle就需要写入EEP的命令注意,如“00H”,这个H不是命令,是表示16进制,实际命令是“00”。
还有一点也要注意,有的指令只在QSPI/SPI上生效,一定要注意辨别。
二、EEP驱动编写
首先说说驱动的意义,所谓驱动其实就是写寄存器命令,但是我们总不能每次向EEP写入数据或者擦除数据,都重复写一次寄存器,这样太过于繁琐,也容易出错,因此我们需要将某个操作,需要写入那些命令,按照顺序封装成函数供自己调用。
说实话驱动编写这一块的C语言逻辑难度一般,稍有些功底就可以完成,就是一定要看明白手册,有时候你写某一命令,可能需要一些前置条件,寄存器命令的写如顺序,时间间隔等等都需要细读手册,所以说驱动开发的难度几乎都在英文手册的阅读这一块了。
我所编写EEP驱动也是根据网上现有的类似型号的EEPROM修改的,话不多说,直接上代码:
EEP.h如下,主要是对指令进行了一些封装以及一些驱动函数声明:
/*
* @Descripttion:
* @version:
* @Author: HuangLR
* @Date: 2023-10-23 11:35:04
* @LastEditors: smile
* @LastEditTime: 2023-10-27 14:31:33
*/
#ifndef _EEP_H_
#define _EEP_H_
#include "main.h"
#include "spi.h"
#define CMD_JEDECID 0x9F //获取芯片ID号
#define CMD_HS_READ 0x0B //高速读
#define CMD_READ 0x03 //读
#define CMD_WREN 0x06 //写使能
#define CMD_WRDISEN 0x04 //写失能
#define CMD_SE 0x20 //扇区擦除
#define CMD_BE 0xD8 //块擦除
#define CMD_CE 0xC7 //芯片擦除
#define CMD_PP 0x02 //页编程(写)
#define CMD_RDSR 0x05 //读状态寄存器(BUSY位)
#define CMD_RBPR 0x72 //读块保护寄存器
#define CMD_ULBPR 0x98 //解锁块保护
#define CMD_SFPD 0x5A //获取SFPD只读区域数据
#define MAC_STATE 0x000260 //MAC编程状态地址
#define ADD_MAC 0x000261 //MAC首地址
#define MF_ID 0xBF
#define DEV_TYPE 0x26
#define DEV_ID 0x41
#define JEDEC_ID (uint32_t)((MF_ID<<16)|(DEV_TYPE<<8)|DEV_ID)
#define MAC_Num 6 //MAC地址长度
/
static void EEPStart();
static void EEPStop();
static uint8_t EEPWriteByte(uint8_t txBuf);
static uint8_t EEPReadByte(void);
uint32_t EEPReadID(void);
uint8_t EEPReadStatusRegister(void);
void EEPHighRead(uint32_t addr, uint8_t *rxBuf, uint32_t rdNum);
void EEPRead(uint32_t addr, uint8_t *rxBuf, uint32_t rdNum);
static void EEPWriteEN(void);
void EEPSectorErase(uint32_t addr);
void EEPBlockErase(uint32_t addr);
void EEPChipErase(void);
void EEPPageProgram(uint32_t addr, uint8_t *txBuf, uint8_t wrNum);
void EEPReadBlockProtectionRegister(uint8_t *rxBuf);
void EEPUnlockBlockProtection(void);
void EEPTASK(void);
void EEPGetSFDP(uint32_t addr, uint8_t *rxBuf, uint32_t rdNum);
#endif
EEP.C如下:
/*
* @Descripttion:
* @version:
* @Author: HuangLR
* @Date: 2023-10-23 11:34:38
* @LastEditors: smile
* @LastEditTime: 2023-10-27 14:32:12
*/
#include "EEP.h"
static void EEPStart()
{
HAL_GPIO_WritePin(EEP_CS_GPIO_Port, EEP_CS_Pin, GPIO_PIN_RESET);
}
static void EEPStop()
{
HAL_GPIO_WritePin(EEP_CS_GPIO_Port, EEP_CS_Pin, GPIO_PIN_SET);
}
static uint8_t EEPWriteByte(uint8_t txBuf)
{
uint8_t rxBuf = 0;
if(HAL_SPI_TransmitReceive(&hspi5, &txBuf, &rxBuf, 1, 500) != HAL_OK)
{
return 0;
}
return rxBuf;
}
static uint8_t EEPReadByte(void)
{
return EEPWriteByte(0xFF);
}
uint32_t EEPReadID(void)
{
uint8_t rxBuf[3];
EEPStart();
EEPWriteByte(CMD_JEDECID);
rxBuf[2] = EEPReadByte();
rxBuf[1] = EEPReadByte();
rxBuf[0] = EEPReadByte();
EEPStop();
return (uint32_t)((rxBuf[2]<<16) | (rxBuf[1]<<8) | rxBuf[0]);
}
uint8_t EEPReadStatusRegister(void)
{
uint8_t rxBuf;
EEPStart();
EEPWriteByte(CMD_RDSR);
rxBuf = EEPReadByte();
EEPStop();
return rxBuf;
}
void EEPHighRead(uint32_t addr, uint8_t *rxBuf, uint32_t rdNum)
{
EEPStart();
EEPWriteByte(CMD_HS_READ);
EEPWriteByte((uint8_t)((addr>>16)&0xff));
EEPWriteByte((uint8_t)((addr>>8)&0xff));
EEPWriteByte((uint8_t)(addr&0xff));
EEPWriteByte(0xff);
while(rdNum--)
{
*rxBuf++ = EEPReadByte();
}
EEPStop();
}
void EEPRead(uint32_t addr, uint8_t *rxBuf, uint32_t rdNum)
{
EEPStart();
EEPWriteByte(CMD_READ);
EEPWriteByte((uint8_t)((addr>>16)&0xff));
EEPWriteByte((uint8_t)((addr>>8)&0xff));
EEPWriteByte((uint8_t)(addr&0xff));
while(rdNum--)
{
*rxBuf++ = EEPReadByte();
}
EEPStop();
}
static void EEPWriteEN(void)
{
EEPStart();
EEPWriteByte(CMD_WREN);
EEPStop();
}
void EEPSectorErase(uint32_t addr)
{
EEPWriteEN();
EEPStart();
EEPWriteByte(CMD_SE);
EEPWriteByte((uint8_t)((addr>>16)&0xff));
EEPWriteByte((uint8_t)((addr>>8)&0xff));
EEPWriteByte((uint8_t)(addr&0xff));
EEPStop();
while((EEPReadStatusRegister() & 0x01) != 0);
}
void EEPBlockErase(uint32_t addr)
{
EEPWriteEN();
EEPStart();
EEPWriteByte(CMD_BE);
EEPWriteByte((uint8_t)((addr>>16)&0xff));
EEPWriteByte((uint8_t)((addr>>8)&0xff));
EEPWriteByte((uint8_t)(addr&0xff));
EEPStop();
while((EEPReadStatusRegister() & 0x01) != 0);
}
void EEPChipErase(void)
{
EEPWriteEN();
EEPStart();
EEPWriteByte(CMD_CE);
EEPStop();
while((EEPReadStatusRegister() & 0x01) != 0);
}
void EEPPageProgram(uint32_t addr, uint8_t *txBuf, uint8_t wrNum)
{
EEPWriteEN();
EEPStart();
EEPWriteByte(CMD_PP);
EEPWriteByte((uint8_t)((addr>>16)&0xff));
EEPWriteByte((uint8_t)((addr>>8)&0xff));
EEPWriteByte((uint8_t)(addr&0xff));
while(wrNum--)
{
EEPWriteByte(*txBuf++);
}
EEPStop();
while((EEPReadStatusRegister() & 0x01) != 0);
}
void EEPReadBlockProtectionRegister(uint8_t *rxBuf)
{
uint8_t rdNum = 10;
EEPStart();
EEPWriteByte(CMD_RBPR);
while(rdNum--)
{
*rxBuf++ = EEPReadByte();
}
EEPStop();
}
void EEPUnlockBlockProtection(void)
{
EEPWriteEN();
EEPStart();
EEPWriteByte(CMD_ULBPR);
EEPStop();
}
void EEPGetSFDP(uint32_t addr, uint8_t *rxBuf, uint32_t rdNum)
{
EEPStart();
EEPWriteByte(CMD_SFPD);
EEPWriteByte((uint8_t)((addr>>16)&0xff));
EEPWriteByte((uint8_t)((addr>>8)&0xff));
EEPWriteByte((uint8_t)(addr&0xff));
EEPWriteByte(0xff);
while(rdNum--)
{
*rxBuf++ = EEPReadByte();
}
EEPStop();
}
在此之前其实还有一个步骤,那就是初始化SPI接口,我习惯使用STM32CUBEX生成,减少新建工程的麻烦:
三、初始化SPI
我的配置如下,使用SPI模式0,时钟分频根据主时钟来,一般把SPI频率控制在4-8M即可。
四、读写EEP
void EEPTASK()
{
uint32_t i,ID = 0;
uint32_t addr = 0x100000; //这个地址用户自己指定就行
uint8_t txBuf[256], rxBuf[256], blockProtection[10], num;
uint8_t ip1[2] = {0};
uint8_t ip2[2] = {0};
ID = EEPReadID(); //首先读取ID,验证通讯是否成功
printf("EEPid =0x%x\r\n",ID);
if(ID != JEDEC_ID )
{
ID = EEPReadID();
while((ID != JEDEC_ID));
}
EEPReadBlockProtectionRegister(blockProtection); //取消写保护,一定要取消,不然无法写入
//数据,具体写入那些命令驱动有详细讲。
for(num = 0; num < 10; num++)
{
if(blockProtection[num] != 0)
{
EEPUnlockBlockProtection();
break;
}
}
EEPBlockErase(addr); //擦除
for(i = 0; i < 100; i++)
{
txBuf[i] = 4;
}
EEPPageProgram(addr, txBuf, 100); //页写入,写入100(自动从该地址向后偏移)字节,单字节
//写入不会偏移
EEPHighRead(addr, rxBuf, 100); //高速读取该地址100(会自动偏移)字节。
}