NOR Flash 读写的高端操作,你看得懂吗?

2024-05-18 18:08

本文主要是介绍NOR Flash 读写的高端操作,你看得懂吗?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是i.MXRT下改造FlexSPI driver以AHB方式去写入NOR Flash

痞子衡前段时间写过一篇 《串行NAND Flash的两大特性导致其在i.MXRT FlexSPI下无法XiP》,文章里介绍了 NAND Flash 的 Page Read 等待特性(发完 Read 命令后需要回读 Flash 内部状态寄存器 Busy 位来判断 Page 数据是否已准备好)导致其无法像 NOR Flash 那样通过 AHB 方式被便捷访问,仅能在一个 Page 空间里实现 AHB 读(前提是在 IPG 方式发完读命令以及读完状态寄存器确保数据已经准备好后)。

回到 NOR Flash 上,我们可以轻松通过 AHB 方式读取 Flash 数据,但写入 Flash 一般都是调用 FlexSPI 驱动来实现(即 IPG 方式),那么有没有可能也通过 “AHB 方式来写入 Flash” 呢?答案是可以的,但为啥痞子衡加了个引号,且往下看:

本文以恩智浦 MIMXRT1170-EVK 开发板上主芯片 i.MXRT1176 及其配套板载 Flash 芯片 - 芯成 IS25WP128 为例。

一、Flash写操作流程

芯成 IS25WP128 是一颗很典型的四线 QSPI NOR Flash,其写入(编程)时序是符合 JEDEC216 标准的。简单来说,一个完整的写时序包含三个独立子时序:Write Enable 时序 + Page Program 时序 + Read Status 时序。

先来看打头阵的 Write Enable 子时序,NOR Flash 内部的状态寄存器会有一个位叫 WEL (Write Enable Latch),这个位控制着 Flash 的擦写权限,默认值是 0(即不允许擦写)。如果想要写入 Flash,必须先通过 Write Enable 命令将 WEL 位临时设为 1(这个位会随着当前的擦写命令结束后自动恢复到 0)。

4c8a1e7e94f86ad1de5a7adab6a7bbf8.png

2cada8f4502fdd653bb2104141bde684.png

置位了 WEL 后,便可以传输 Page 数据给 Flash,这个子时序便是 Page Program。Page Program 按命令地址和数据传输方式不同分为三种:一线 SPI,四线 SPI,QPI,下面是常用的四线 SPI 的时序图,命令和地址通过 IO0 传输,数据通过 IO[3:0] 传输。

通常来说,在这个时序里,传入的地址应该是 Page 首地址,写入数据长度应该是一个完整的 Page 大小。但从非 Page 首地址处写入小于一个 Page 长度的数据也是可以的,但有一个注意点就是不要在这个时序里出现跨页的现象(如果出现,超出当前页的数据会被放回到该页起始地址处)。

d8960b416329f0e52ef615113439c756.png

Page Program 子时序结束后,数据还并未真正写入 Flash 内存体中,Flash 内部控制器只是开始处理数据,这时候会有一个等待时间(大概0.2ms),Flash 内部的状态寄存器有一个位叫 WIP (Write In Progress),这个位标志着数据写入状态(默认值是 0,当 Page Program 子时序结束后,WIP 立即跳为 1),用户需要通过 Read Status 子时序来实时读取状态寄存器的值从而获知数据处理情况。

当 Flash 内部状态寄存器中的 WIP 位从 1 跳回到 0,便意味着一次完整的写时序结束了,主机可以发起下一次写时序。

ba305a03abc63483c50f10952ec8a190.png

56c2e21551b29d69e6ee388a26120ab0.png

c7aea603e29d8f755e69a840e55bbbe0.png

二、FlexSPI对写时序支持

痞子衡旧文 《从头开始认识i.MXRT启动头FDCB里的lookupTable》 里对 FlexSPI 读时序介绍得非常详细,尤其是对 AHB 方式读支持的实现,现在痞子衡再介绍下 FlexSPI 对于写时序的支持。

第一节里介绍的 Flash 写操作的三个子时序,在 FlexSPI 外设里当然都是支持的,SEQ_CTL 组件里都预先实现了这些子时序,比如下面就是 Page Program 的序列:

3acb69ab56bf716d3a9a6b1b918b2d37.png

因为 Flash 写操作需要三个子序列,比 Flash 读操作单序列要复杂得多,并且最关键的是写操作还包含一个不确定的等待周期(Read Status 子时序与 Flash 交互),这就导致 FlexSPI 外设在 AHB 方式写上没法完美支持,这也是为什么写入 Flash 都是通过 IPG 方式来完成的,因为 IPG 方式下,子序列可以随意组合,由用户代码手动调度。

原则上三个写操作子序列可以放在 LUT 中的任何一个 Sequence 位置,因为即使按序放在一起,我们通过 FlexSPI->FLSHxCR2 寄存器(x可取A1/A2/B1/B2,具体根据Flash引脚连接来定)中的 AWRSEQID 位指明写操作第一个子序列在 LUT 中的位置(index) 也无法自动完成 Page 数据的完整写入操作。

但也不要就此放弃,单独 Page Program 子序列还是可以通过 AHB 方式写来替代的,这样也可以让我们过一下 AHB 方式写入 Flash 的瘾,只是需要在 AHB 写入操作前后辅助 IPG 方式下的 Write Enable 和 Read Status 动作,下一节用代码给大家实际演示。

0c01da681072923ac75a03b71fc5f0c7.png

三、FlexSPI driver用法

例程路径:\SDK_2.10.0_MIMXRT1170-EVK\boards\evkmimxrt1170\driver_examples\flexspi\nor\polling_transfer\cm7\iar
3.1 初始化

先来看一下 FlexSPI 初始化函数 flexspi_nor_flash_init(),这个函数需要三个配置变量:分别是 flexspi_config_t 型面向 FlexSPI 外设层的配置 flexspiconfig,flexspi_device_config_t 型面向 Flash 器件端的配置 deviceconfig,以及很核心的 customLUT(这里只列出了跟 Flash 读写操作相关的时序)。

#define NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD     0
#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE        2
#define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD   4
#define NOR_CMD_LUT_SEQ_IDX_READSTATUSREG      12#define CUSTOM_LUT_LENGTH        60
const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {/* Fast read quad mode - SDR */[4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD] =FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0xEB, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_4PAD, 0x18),[4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD + 1] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_4PAD, 0x06, kFLEXSPI_Command_READ_SDR, kFLEXSPI_4PAD, 0x04),/* Write Enable */[4 * NOR_CMD_LUT_SEQ_IDX_WRITEENABLE] =FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x06, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),/* Page Program - quad mode */[4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD] =FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x32, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),[4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD + 1] =FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_4PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),/* Read status register */[4 * NOR_CMD_LUT_SEQ_IDX_READSTATUSREG] =FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x05, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),
};flexspi_device_config_t deviceconfig = {.flexspiRootClk       = 12000000,.flashSize            = 0x4000, /* 16Mb/KByte */.CSIntervalUnit       = kFLEXSPI_CsIntervalUnit1SckCycle,.CSInterval           = 2,.CSHoldTime           = 3,.CSSetupTime          = 3,.dataValidTime        = 0,.columnspace          = 0,.enableWordAddress    = 0,.AWRSeqIndex          = 0,.AWRSeqNumber         = 0,// 支持 AHB 读的关键配置.ARDSeqIndex          = NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD,.ARDSeqNumber         = 1,.AHBWriteWaitUnit     = kFLEXSPI_AhbWriteWaitUnit2AhbCycle,.AHBWriteWaitInterval = 0,
};void flexspi_nor_flash_init(FLEXSPI_Type *base)
{CLOCK_SetRootClockDiv(kCLOCK_Root_Flexspi1, 2);CLOCK_SetRootClockMux(kCLOCK_Root_Flexspi1, 0);/*Get FLEXSPI default settings and configure the flexspi. */flexspi_config_t flexspiconfig;FLEXSPI_GetDefaultConfig(&flexspiconfig);/*Set AHB buffer size for reading data through AHB bus. */flexspiconfig.ahbConfig.enableAHBPrefetch    = true;flexspiconfig.ahbConfig.enableAHBBufferable  = true;flexspiconfig.ahbConfig.enableReadAddressOpt = true;flexspiconfig.ahbConfig.enableAHBCachable    = true;flexspiconfig.rxSampleClock                  = kFLEXSPI_ReadSampleClkLoopbackFromDqsPad;FLEXSPI_Init(base, &flexspiconfig);/* Configure flash settings according to serial flash feature. */FLEXSPI_SetFlashConfig(base, &deviceconfig, kFLEXSPI_PortA1);/* Update LUT table. */FLEXSPI_UpdateLUT(base, 0, customLUT, CUSTOM_LUT_LENGTH);/* Do software reset. */FLEXSPI_SoftwareReset(base);
}
3.2 一般用法(IPG写)

先来看 IPG 方式的 Flash 写入函数,其中 Page Program 子时序是通过 FLEXSPI_TransferBlocking() 函数来完成的,这个函数就是往大小为 256 bytes 的 IP TX FIFO 写 src 里的数据(默认 FlexSPI->MCR0[ATDFEN] = 0 情况下),SEQ_CTL 组件处理时会将缓存在 IP TX FIFO 里的数据全部发送到 Flash 端。

void flexspi_nor_flash_program(FLEXSPI_Type *base, uint32_t dstAddr, const uint32_t *src, uint32_t size)
{// Write Enable 子时序flexspi_nor_write_enable(base, dstAddr);flexspi_transfer_t flashXfer;flashXfer.deviceAddress = dstAddr;flashXfer.port          = kFLEXSPI_PortA1;flashXfer.cmdType       = kFLEXSPI_Write;flashXfer.SeqNumber     = 1;flashXfer.seqIndex      = NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD;flashXfer.data          = (uint32_t *)(void *)src;flashXfer.dataSize      = size;// page program 子时序FLEXSPI_TransferBlocking(base, &flashXfer);// Read Status 子时序flexspi_nor_wait_bus_busy(base);FLEXSPI_SoftwareReset(base);
}
3.3 特殊用法(AHB写)

我们现在来改造 IPG 方式的 Flash 写入函数,首先要修改 deviceconfig 变量将 AWRSeqIndex 指向 PAGEPROGRAM_QUAD 在 LUT 中的位置,然后将 FLEXSPI_TransferBlocking() 函数换成 AHB 写入代码(memcpy 或者指针操作赋值),这时候 src 里的数据就会被自动放在大小为 64 bytes 的 AHB TX Buffer 里,SEQ_CTL 组件处理时会将缓存在 AHB TX Buffer 里的数据全部发送到 Flash 端。

但这里有一些限制,经实测,利用 memcpy 做 AHB 写,一次仅能写入 1/2/3/4/8 这五种有效长度的数据,其他数据长度不及预期(比如拷贝 5 - 7 字节,实际仅写入前 4 字节;拷贝 8 字节以上,实际仅写入前 8 字节),这其实跟 《i.MXRT中FlexSPI外设对AHB Burst Read特性的支持》 一文里提及的处理器 AHB Burst 策略有关,FlexSPI 每次仅会缓存一次 AHB Burst 写数据进 AHB TX Buffer,而 SEQ_CTL 每工作一次都会使能一次 Flash 器件的数据处理流程(进入 Busy 状态),因此连续的两次 AHB burst 写,后面一次的 burst 行为其实不产生实际 Flash 写入效果。

flexspi_device_config_t deviceconfig = {// 支持 AHB 写的关键配置.AWRSeqIndex          = NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD,.AWRSeqNumber         = 1,// ... 其他省略
};void flexspi_nor_flash_program(FLEXSPI_Type *base, uint32_t dstAddr, const uint32_t *src, uint32_t size)
{while (size){// Write Enable 子时序flexspi_nor_write_enable(base, 0);uint32_t cpyBytes = 0;if (size >= 8)      { cpyBytes = 8; }else if (size >= 4) { cpyBytes = 4; }else                { cpyBytes = size; }memcpy((void *)dstAddr, (void *)src, cpyBytes);__DSB();// Read Status 子时序flexspi_nor_wait_bus_busy(base);dstAddr += cpyBytes;src += cpyBytes / 4;size -= cpyBytes;}FLEXSPI_SoftwareReset(base);
}

上面看似鸡肋的 AHB 方式写入 Flash 到底有什么用?底下痞子衡会讲到 XECC 模块,到时你就知道其用处了。

至此,i.MXRT下改造FlexSPI driver以AHB方式去写入NOR Flash痞子衡便介绍完毕了,掌声在哪里~~~

—— The End ——

推荐好文  点击蓝色字体即可跳转

☞ 干货分享:CAN总线详解 整车的控制只需要一条线

☞ 推荐一个直接用于项目开发的PID库!很好用,很稳定e93fec5e32adc89507cd20c8a7addda8.gif

☞ 精辟,16张图说透Modbus-RTU协议

☞ 推荐一款我私藏已久的串口示波神器

欢迎转发、留言、点赞、分享给你的朋友,感谢您的支持!

点击上方即可关注公众号

分享 💬  点赞 👍  在看 ❤️ 

以“三连”行动支持优质内容!

这篇关于NOR Flash 读写的高端操作,你看得懂吗?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Python正则表达式匹配和替换的操作指南

《Python正则表达式匹配和替换的操作指南》正则表达式是处理文本的强大工具,Python通过re模块提供了完整的正则表达式功能,本文将通过代码示例详细介绍Python中的正则匹配和替换操作,需要的朋... 目录基础语法导入re模块基本元字符常用匹配方法1. re.match() - 从字符串开头匹配2.

Java实现在Word文档中添加文本水印和图片水印的操作指南

《Java实现在Word文档中添加文本水印和图片水印的操作指南》在当今数字时代,文档的自动化处理与安全防护变得尤为重要,无论是为了保护版权、推广品牌,还是为了在文档中加入特定的标识,为Word文档添加... 目录引言Spire.Doc for Java:高效Word文档处理的利器代码实战:使用Java为Wo

ShardingProxy读写分离之原理、配置与实践过程

《ShardingProxy读写分离之原理、配置与实践过程》ShardingProxy是ApacheShardingSphere的数据库中间件,通过三层架构实现读写分离,解决高并发场景下数据库性能瓶... 目录一、ShardingProxy技术定位与读写分离核心价值1.1 技术定位1.2 读写分离核心价值二

sysmain服务可以禁用吗? 电脑sysmain服务关闭后的影响与操作指南

《sysmain服务可以禁用吗?电脑sysmain服务关闭后的影响与操作指南》在Windows系统中,SysMain服务(原名Superfetch)作为一个旨在提升系统性能的关键组件,一直备受用户关... 在使用 Windows 系统时,有时候真有点像在「开盲盒」。全新安装系统后的「默认设置」,往往并不尽编

Python自动化处理PDF文档的操作完整指南

《Python自动化处理PDF文档的操作完整指南》在办公自动化中,PDF文档处理是一项常见需求,本文将介绍如何使用Python实现PDF文档的自动化处理,感兴趣的小伙伴可以跟随小编一起学习一下... 目录使用pymupdf读写PDF文件基本概念安装pymupdf提取文本内容提取图像添加水印使用pdfplum

Python从Word文档中提取图片并生成PPT的操作代码

《Python从Word文档中提取图片并生成PPT的操作代码》在日常办公场景中,我们经常需要从Word文档中提取图片,并将这些图片整理到PowerPoint幻灯片中,手动完成这一任务既耗时又容易出错,... 目录引言背景与需求解决方案概述代码解析代码核心逻辑说明总结引言在日常办公场景中,我们经常需要从 W

使用Python的requests库来发送HTTP请求的操作指南

《使用Python的requests库来发送HTTP请求的操作指南》使用Python的requests库发送HTTP请求是非常简单和直观的,requests库提供了丰富的API,可以发送各种类型的HT... 目录前言1. 安装 requests 库2. 发送 GET 请求3. 发送 POST 请求4. 发送

Python使用python-pptx自动化操作和生成PPT

《Python使用python-pptx自动化操作和生成PPT》这篇文章主要为大家详细介绍了如何使用python-pptx库实现PPT自动化,并提供实用的代码示例和应用场景,感兴趣的小伙伴可以跟随小编... 目录使用python-pptx操作PPT文档安装python-pptx基础概念创建新的PPT文档查看

MySQL 数据库表操作完全指南:创建、读取、更新与删除实战

《MySQL数据库表操作完全指南:创建、读取、更新与删除实战》本文系统讲解MySQL表的增删查改(CURD)操作,涵盖创建、更新、查询、删除及插入查询结果,也是贯穿各类项目开发全流程的基础数据交互原... 目录mysql系列前言一、Create(创建)并插入数据1.1 单行数据 + 全列插入1.2 多行数据

MySQL 临时表与复制表操作全流程案例

《MySQL临时表与复制表操作全流程案例》本文介绍MySQL临时表与复制表的区别与使用,涵盖生命周期、存储机制、操作限制、创建方法及常见问题,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小... 目录一、mysql 临时表(一)核心特性拓展(二)操作全流程案例1. 复杂查询中的临时表应用2. 临时