libftdi1学习笔记 5 - SPI Nor Flash

2024-04-17 21:44
文章标签 学习 笔记 flash spi libftdi1

本文主要是介绍libftdi1学习笔记 5 - SPI Nor Flash,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

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");

这篇关于libftdi1学习笔记 5 - SPI Nor Flash的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/912936

相关文章

利用Python快速搭建Markdown笔记发布系统

《利用Python快速搭建Markdown笔记发布系统》这篇文章主要为大家详细介绍了使用Python生态的成熟工具,在30分钟内搭建一个支持Markdown渲染、分类标签、全文搜索的私有化知识发布系统... 目录引言:为什么要自建知识博客一、技术选型:极简主义开发栈二、系统架构设计三、核心代码实现(分步解析

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学