目录
1. 初始化
2. CS控制例子
3. 读ID
3.1 制造商
3.2 容量大小
3.3 设置IO类型
3.3.1 setQSPIWinbond
3.3.2 setQSPIMxic
3.3.3 setQSPIMicrochip
3.3.4 setQSPIMicron
4. 写保护
5. 等待空闲
6. 擦除扇区
7. 页编程
8. 页读
9. 写
10. 读
11. 验证
基于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 2048uint8_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");