目录
基于MPSSE SPI实现Nor Flash的读写。也定义两组flash。
typedef enum
{
SFLASH_PORT_0 = 0,
SFLASH_PORT_1,
SFLASH_PORT_MAX,
}sflash_port_e;
定义一个结构体记录flash的属性
typedef struct
{
sflash_io_e ioType;
uint8_t spiPort;
void (*pCSEnable)(uint8_t port, bool enable);
sflash_manufactor_e manufactor;
sflash_size size;
sflash_addr_size_e addrSize;
}sflash_s;
ioType:表示flash的IO类型,一般支持3种接口方式:
typedef enum
{
SFLASH_SPI = 0,
SFLASH_DSPI,
SFLASH_QSPI,
}sflash_io_e;
spiPort:表示该flash使用哪组SPI
pCSEnable:对应CS脚控制的接口函数
manufactor:该flash的生产商
typedef enum
{
SFLASH_Winbond = 0,
SFLASH_Cypress,
SFLASH_ESMT,
SFLASH_GigaDevice,
SFLASH_MXIC,
SFLASH_Micron,
SFLASH_ISIS,
SFLASH_Microchip,
SFLASH_ZBit,
SFLASH_FuDan,
SFLASH_BOYA,
SFLASH_Unknown,
}sflash_manufactor_e;
size:该flash的大小
typedef enum
{
SFLASH_SIZE_256KB = 0,
SFLASH_SIZE_512KB,
SFLASH_SIZE_1MB,
SFLASH_SIZE_2MB,
SFLASH_SIZE_4MB,
SFLASH_SIZE_8MB,
SFLASH_SIZE_16MB,
SFLASH_SIZE_32MB,
}sflash_size;
addrSize:该flash的地址宽度
typedef enum
{
SFLASH_ADDR_24BIT = 3,
SFLASH_ADDR_32BIT,
}sflash_addr_size_e;
1. 初始化
void sflashInit(uint8_t port, sflash_s init)
{
if(port >= SFLASH_PORT_MAX)
return;
sflash[port] = init;
}
2. CS控制例子
初始化spi要根据实际情况配置CS的控制函数,例如:
#define SFLASH_CS_PIN 3
void sflash0CS(uint8_t port, bool enable)
{
spiCPOL(port);
mpsseGpioWrite(SFLASH_CS_PIN, !enable);
}
3. 读ID
通过命令0x9F读取flash的Jedec ID,其函数原型为:
uint32_t sflashReadJedecID(uint8_t port)
返回值即是id。
uint8_t cmd[4] = {CMD_READ_ID, 0xff, 0xff, 0xff};
uint32_t id;
sflash[port].pCSEnable(port, true);
spiWriteBytes(sflash[port].spiPort, cmd, 1);
spiReadBytes(sflash[port].spiPort, cmd + 1, 3);
sflash[port].pCSEnable(port, false);
id = ((uint32_t)cmd[1] << 16) | (uint32_t)(cmd[2] << 8) | (uint32_t)(cmd[3]);
由于flash的QSPI方式和品牌商有关,这里根据ID做判断(注意,这套规则并不是所有厂商都符合)
3.1 制造商
由ID的16~23位确定品牌商
switch (id & 0xff0000)
{
case 0xEF0000:
sflash[port].manufactor = SFLASH_Winbond;
printf("sflash Winbond\n");
break;
case 0x010000:
sflash[port].manufactor = SFLASH_Cypress;
printf("sflash Cypress\n");
break;
case 0x8C0000:
sflash[port].manufactor = SFLASH_ESMT;
printf("sflash ESMT\n");
break;
case 0xC80000:
sflash[port].manufactor = SFLASH_GigaDevice;
printf("sflash GigaDevice\n");
break;
case 0xC20000:
sflash[port].manufactor = SFLASH_MXIC;
printf("sflash MXIC\n");
break;
case 0x200000:
sflash[port].manufactor = SFLASH_Micron;
printf("sflash Micron\n");
break;
case 0x5E0000:
sflash[port].manufactor = SFLASH_ZBit;
printf("sflash ZBit\n");
break;
case 0x9D0000:
sflash[port].manufactor = SFLASH_ISIS;
printf("sflash ISIS\n");
break;
case 0xA10000:
sflash[port].manufactor = SFLASH_FuDan;
printf("sflash FuDan\n");
break;
case 0xBF0000:
sflash[port].manufactor = SFLASH_Microchip;
printf("sflash Microchip\n");
break;
case 0x680000:
sflash[port].manufactor = SFLASH_BOYA;
printf("sflash BOYA\n");
break;
default:
sflash[port].manufactor = SFLASH_Unknown;
printf("sflash Unknown\n");
break;
}
3.2 容量大小
根据ID的位0~7确定容量大小
switch(id & 0xff)
{
case 0x12:
sflash[port].size = SFLASH_SIZE_256KB;
sflash[port].addrSize = SFLASH_ADDR_24BIT;
printf("sflash size 256KB, 24bits address\n");
break;
case 0x13:
sflash[port].size = SFLASH_SIZE_512KB;
sflash[port].addrSize = SFLASH_ADDR_24BIT;
printf("sflash size 512KB, 24bits address\n");
break;
case 0x14:
sflash[port].size = SFLASH_SIZE_1MB;
sflash[port].addrSize = SFLASH_ADDR_24BIT;
printf("sflash size 1MB, 24bits address\n");
break;
case 0x15:
sflash[port].size = SFLASH_SIZE_2MB;
sflash[port].addrSize = SFLASH_ADDR_24BIT;
printf("sflash size 2MB, 24bits address\n");
break;
case 0x16:
sflash[port].size = SFLASH_SIZE_4MB;
sflash[port].addrSize = SFLASH_ADDR_24BIT;
printf("sflash size 4MB, 24bits address\n");
break;
case 0x17:
sflash[port].size = SFLASH_SIZE_8MB;
sflash[port].addrSize = SFLASH_ADDR_24BIT;
printf("sflash size 8MB, 24bits address\n");
break;
case 0x18:
sflash[port].size = SFLASH_SIZE_16MB;
sflash[port].addrSize = SFLASH_ADDR_24BIT;
printf("sflash size 16MB, 24bits address\n");
break;
case 0x19:
sflash[port].size = SFLASH_SIZE_32MB;
sflash[port].addrSize = SFLASH_ADDR_32BIT;
printf("sflash size 32MB, 32bits address\n");
break;
}
3.3 设置IO类型
if (sflash[port].ioType == SFLASH_QSPI)
{
sflashSetQSPI(true);
}
else
{
sflashSetQSPI(false);
}
根据不同制造商设置flash的IO类型,这里只实现SPI和QSPI的方式,部分制造商的设定方式是一样的。
bool sflashSetQSPI(uint8_t port, bool enable)
{
bool ret = false;
switch(sflash[port].manufactor)
{
case SFLASH_Winbond:
case SFLASH_GigaDevice:
case SFLASH_Cypress:
case SFLASH_ZBit:
case SFLASH_BOYA:
case SFLASH_FuDan:
ret = setQSPIWinbond(port, enable);
break;
case SFLASH_MXIC:
case SFLASH_ESMT:
case SFLASH_ISIS:
ret = setQSPIMxic(port, enable);
break;
case SFLASH_Microchip:
ret = setQSPIMicrochip(port, enable);
break;
case SFLASH_Micron:
ret = setQSPIMicron(port, enable);
break;
default:
return false;
}
return ret;
}
3.3.1 setQSPIWinbond
第一种方式以Winbond为代表,QSPI的使能位在Status2寄存器中。
通过命令0x35读入该寄存器值,通过0x31写即可。
bool setQSPIWinbond(uint8_t port, bool enable)
{
uint8_t cmd[2] = {0x35, 0xff};
sflash[port].pCSEnable(port, true);
if(spiWriteBytes(sflash[port].spiPort, cmd, 1) < 0)
return false;
if(spiReadBytes(sflash[port].spiPort, cmd + 1, 1) < 0)
return false;
sflash[port].pCSEnable(port, false);
if(enable == true)
{
if ((cmd[1] & 0x02) == 0x02) //QE == 1
{
return true;
}
cmd[1] |= 0x02;
}
else
{
if ((cmd[1] & 0x02) == 0x00) //QE == 0
{
return true;
}
cmd[1] &= (uint8_t)0xFD;
}
cmd[0] = 0x31;
sflash[port].pCSEnable(port, true);
if(spiWriteBytes(sflash[port].spiPort, cmd, 2) < 0)
return false;
sflash[port].pCSEnable(port, false);
return true;
}
3.3.2 setQSPIMxic
第二种方式以Mxic为代表,QSPI的使能位在Status寄存器的位6
通过命令0x05读入该寄存器,0x01写。
bool setQSPIMxic(uint8_t port, bool enable)
{
uint8_t cmd[2] = {0x05, 0xff};
sflash[port].pCSEnable(port, true);
if(spiWriteBytes(sflash[port].spiPort, cmd, 1) < 0)
return false;
if(spiReadBytes(sflash[port].spiPort, cmd + 1, 1) < 0)
return false;
sflash[port].pCSEnable(port, false);
if(enable == true)
{
if ((cmd[1] & 0x40) == 0x40) //QE == 1
{
return true;
}
cmd[1] |= 0x40;
}
else
{
if ((cmd[1] & 0x40) == 0x00) //QE == 0
{
return true;
}
cmd[1] &= (uint8_t)0xBF;
}
cmd[0] = 0x01;
sflash[port].pCSEnable(port, true);
if(spiWriteBytes(sflash[port].spiPort, cmd, 2) < 0)
return false;
sflash[port].pCSEnable(port, false);
return true;
}
3.3.3 setQSPIMicrochip
Microchip的QSPI是 通过命令的方式使能(0x38)和禁止(0xFF)的。以SST26VF040A为例:
bool setQSPIMicrochip(uint8_t port, bool enable)
{
uint8_t cmd[1] = {0x38};
if(enable == true)
{
sflash[port].pCSEnable(port, true);
if(spiWriteBytes(sflash[port].spiPort, cmd, 1) < 0)
return false;
sflash[port].pCSEnable(port, false);
}
else
{
cmd[0] = 0xff;
sflash[port].pCSEnable(port, true);
if(spiWriteBytes(sflash[port].spiPort, cmd, 1) < 0)
return false;
sflash[port].pCSEnable(port, false);
}
return true;
}
3.3.4 setQSPIMicron
Micron(以MT25QL256为例)的QSPI通过读写易失性配置寄存器的位7配置。
通过命令0x85读入该寄存器,0x81写。
bool setQSPIMicron(uint8_t port, bool enable)
{
uint8_t cmd[2] = {0x85, 0xff};
sflash[port].pCSEnable(port, true);
if(spiWriteBytes(sflash[port].spiPort, cmd, 1) < 0)
return false;
if(spiReadBytes(sflash[port].spiPort, cmd + 1, 1) < 0)
return false;
sflash[port].pCSEnable(port, false);
if(enable == true)
{
if ((cmd[1] & 0x80) == 0x80) //QE == 1
{
return true;
}
cmd[1] |= 0x80;
}
else
{
if ((cmd[1] & 0x80) == 0x00) //QE == 0
{
return true;
}
cmd[1] &= (uint8_t)0x7F;
}
cmd[0] = 0x81;
sflash[port].pCSEnable(port, true);
if(spiWriteBytes(sflash[port].spiPort, cmd, 2) < 0)
return false;
sflash[port].pCSEnable(port, false);
return true;
}
4. 写保护
使能写的命令为:
#define CMD_WRITE_ENABLE 0x06
对应的函数:
void sflashWriteEnable(uint8_t port)
{
uint8_t cmd[1] = {CMD_WRITE_ENABLE};
sflash[port].pCSEnable(port, true);
spiWriteBytes(sflash[port].spiPort, cmd, 1);
sflash[port].pCSEnable(port, false);
}
禁止写的命令为:
#define CMD_WRITE_DISABLE 0x04
对应的函数:
void sflashWriteDisable(uint8_t port)
{
uint8_t cmd[1] = {CMD_WRITE_DISABLE};
sflash[port].pCSEnable(port, true);
spiWriteBytes(sflash[port].spiPort, cmd, 1);
sflash[port].pCSEnable(port, false);
}
5. 等待空闲
通过读状态寄存器等待设备空闲。
void sflashWaitFree(uint8_t port)
{
while(true)
{
uint8_t cmd[2] = {CMD_READ_STATUS, 0xFF};
sflash[port].pCSEnable(port, true);
spiWriteBytes(sflash[port].spiPort, cmd, 1);
spiReadBytes(sflash[port].spiPort, cmd + 1, 1);
sflash[port].pCSEnable(port, false);
if((cmd[1] & 0x01) == 0)
break;
usleep(1000);
}
}
6. 擦除扇区
一般扇区大小为4096个字节,擦除扇区的命令
#define CMD_SECTOR_ERASE 0x20
命令后面接需要擦除扇区的起始地址。
在擦除命令前必须先设置写使能,擦除根据芯片不同可能需要几十ms才能完成(例如W25Q128FVxxIQ需要45ms,SST26VF032B需要25ms),等待一段时间后判断擦除是否完成,最后恢复写保护。
void sflashEraseSector(uint8_t port, uint32_t addr)
{
uint8_t cmd[5] = {CMD_SECTOR_ERASE, 0, 0, 0, 0};
uint8_t offset = 1;
if(sflash[port].addrSize == SFLASH_ADDR_32BIT)
cmd[offset++] = (uint8_t)(addr >> 24);
cmd[offset++] = (uint8_t)(addr >> 16);
cmd[offset++] = (uint8_t)(addr >> 8);
cmd[offset++] = (uint8_t)(addr >> 0);
sflashWriteEnable(port);
sflash[port].pCSEnable(port, true);
spiWriteBytes(sflash[port].spiPort, cmd, offset);
sflash[port].pCSEnable(port, false);
usleep(1000 * 20);
sflashWaitFree(port);
sflashWriteDisable(port);
}
7. 页编程
void sflashPageProgram(uint8_t port, uint32_t addr, uint8_t *buf, uint16_t len)
norflash的写不能按字节写,最小单位为页,一般页的大小为256字节。但是实际上页内是可以按照字节写的。比如可以在地址16的位置写10个字节数据。
地址的计算方式和擦除一样
uint8_t cmd[5] = {CMD_PAGE_PROGRAM, 0, 0, 0, 0};
uint8_t offset = 1;
if(sflash[port].addrSize == SFLASH_ADDR_32BIT)
cmd[offset++] = (uint8_t)(addr >> 24);
cmd[offset++] = (uint8_t)(addr >> 16);
cmd[offset++] = (uint8_t)(addr >> 8);
cmd[offset++] = (uint8_t)(addr >> 0);
然后不同的接口有不同的命令,SPI的命令是0x02,QSPI的命令是0x32(这里有个特例,MXIC的芯片QPI的命令是0x38)。
#define CMD_PAGE_PROGRAM 0x02
#define CMD_PAGE_PROGRAM_QIO 0x32
注意,QPI的接口模式下,命令字是SPI方式发送的。
sflashWriteEnable(port);
sflash[port].pCSEnable(port, true);
if(sflash[port].ioType == SFLASH_QSPI)
{
cmd[0] = CMD_PAGE_PROGRAM_QIO;
if(sflash[port].manufactor == SFLASH_MXIC)
{
cmd[0] = 0x38;
}
spiWriteBytes(sflash[port].spiPort, cmd, 1);
qspiWriteBytes(sflash[port].spiPort, cmd + 1, offset);
qspiWriteBytes(sflash[port].spiPort, buf, len);
}
else
{
cmd[0] = CMD_PAGE_PROGRAM;
spiWriteBytes(sflash[port].spiPort, cmd, offset);
spiWriteBytes(sflash[port].spiPort, buf, len);
}
sflash[port].pCSEnable(port, false);
最后等待编程结束
sflashWaitFree(port);
sflashWriteDisable(port);
8. 页读
对于flash来说,没有页读的概念,这里定义页读的方式是因为FTDI设备每笔最大通信是64K字节,每次读最好设置为256B,这是因为FTDI设备的最大传输字节数为64KB,由于GPIO模拟的SPI协议特别费字节,大了会超过64KB的大小,另外,太大了有时候会出现通讯错误
对于SPI接口,使用Fast Read命令0x0B,对于QSPI,使用0xEB命令。
#define CMD_FASTREAD 0x0B
#define CMD_FASTREAD_QIO 0xEB
地址的计算等同其他
uint8_t cmd[5] = {CMD_FASTREAD, 0, 0, 0, 0};
uint8_t offset = 1;
if(sflash[port].addrSize == SFLASH_ADDR_32BIT)
cmd[offset++] = (uint8_t)(addr >> 24);
cmd[offset++] = (uint8_t)(addr >> 16);
cmd[offset++] = (uint8_t)(addr >> 8);
cmd[offset++] = (uint8_t)(addr >> 0);
写完地址后需要空读1-2个字节(QSPI是空读2个字节,SPI读空1个字节)
sflash[port].pCSEnable(port, true);
if(sflash[port].ioType == SFLASH_QSPI)
{
uint8_t dummy[2];
cmd[0] = CMD_FASTREAD_QIO;
spiWriteBytes(sflash[port].spiPort, cmd, 1);
qspiWriteBytes(sflash[port].spiPort, cmd + 1, offset);
qspiReadBytes(sflash[port].spiPort, dummy, 2);
qspiReadBytes(sflash[port].spiPort, buf, len);
}
else
{
uint8_t dummy[1];
cmd[0] = CMD_FASTREAD;
spiWriteBytes(sflash[port].spiPort, cmd, offset);
spiReadBytes(sflash[port].spiPort, dummy, 1);
spiReadBytes(sflash[port].spiPort, buf, len);
}
sflash[port].pCSEnable(port, false);
9. 写
void sflashWrite(uint8_t port, uint32_t addr, uint8_t *buf, uint32_t len)
在写之前要确保flash已经擦除。
首先判断一下地址是不是页对齐,不是就先把不齐的部分编程。
uint32_t offset = 0;
uint16_t count;
if(port >= SFLASH_PORT_MAX)
if(len == 0)
return;
if((addr % sflashPageSize) > 0)
{
count = (len > (sflashPageSize - (addr % sflashPageSize))) ?
(uint16_t)(sflashPageSize - (addr % sflashPageSize)) : (uint16_t)len;
sflashPageProgram(port, addr, buf, count);
offset += count;
len -= count;
addr += count;
}
剩余的数据写完
while (len > 0)
{
count = (len > sflashPageSize) ? (uint16_t)sflashPageSize : (uint16_t)len;
sflashPageProgram(port, addr, buf + offset, count);
offset += count;
len -= count;
addr += count;
}
10. 读
void sflashRead(uint8_t port, uint32_t addr, uint8_t *buf, uint32_t len)
读可以任意的地址读,没有特别的处理。
uint32_t offset = 0;
uint32_t readPageSize = 256;
while(len > 0)
{
uint16_t count;
count = (len > readPageSize) ? (uint16_t)readPageSize : (uint16_t)len;
sflashPageRead(port, addr, buf + offset, count);
offset += count;
len -= count;
addr += count;
}
11. 验证
配置和SPI验证一样。这里只验证SPI、Mode0的方式。
擦除扇区->写随机数据->读入数据->比较读写的数据是否相等。
#define EX_SFLASH_SIZE 2048
uint8_t wrBuf[EX_SFLASH_SIZE];
uint8_t rdBuf[EX_SFLASH_SIZE];
int i;
uint8_t port = 0;
然后产生随机数据
srand(time(NULL));
for(i = 0; i < EX_SFLASH_SIZE; i++)
{
wrBuf[i] = (uint8_t)rand();
rdBuf[i] = 0;
}
擦除一个扇区
sflashEraseSector(port, 0 * 4096);
将随机数据写入flash
sflashWrite(port, 0, wrBuf, EX_SFLASH_SIZE);
再从相同地址读回
sflashRead(port, 0, rdBuf, EX_SFLASH_SIZE);
最后比较
for(i = 0; i < EX_SFLASH_SIZE; i++)
{
if(wrBuf[i] != rdBuf[i])
{
printf("sflash test fail %d: %x!=%x\n", i, wrBuf[i], rdBuf[i]);
break;
}
}
printf("sflash test finish\n");