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

相关文章

SQL中JOIN操作的条件使用总结与实践

《SQL中JOIN操作的条件使用总结与实践》在SQL查询中,JOIN操作是多表关联的核心工具,本文将从原理,场景和最佳实践三个方面总结JOIN条件的使用规则,希望可以帮助开发者精准控制查询逻辑... 目录一、ON与WHERE的本质区别二、场景化条件使用规则三、最佳实践建议1.优先使用ON条件2.WHERE用

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与

Java Multimap实现类与操作的具体示例

《JavaMultimap实现类与操作的具体示例》Multimap出现在Google的Guava库中,它为Java提供了更加灵活的集合操作,:本文主要介绍JavaMultimap实现类与操作的... 目录一、Multimap 概述Multimap 主要特点:二、Multimap 实现类1. ListMult

Python中文件读取操作漏洞深度解析与防护指南

《Python中文件读取操作漏洞深度解析与防护指南》在Web应用开发中,文件操作是最基础也最危险的功能之一,这篇文章将全面剖析Python环境中常见的文件读取漏洞类型,成因及防护方案,感兴趣的小伙伴可... 目录引言一、静态资源处理中的路径穿越漏洞1.1 典型漏洞场景1.2 os.path.join()的陷

Python使用Code2flow将代码转化为流程图的操作教程

《Python使用Code2flow将代码转化为流程图的操作教程》Code2flow是一款开源工具,能够将代码自动转换为流程图,该工具对于代码审查、调试和理解大型代码库非常有用,在这篇博客中,我们将深... 目录引言1nVflRA、为什么选择 Code2flow?2、安装 Code2flow3、基本功能演示

Python中OpenCV与Matplotlib的图像操作入门指南

《Python中OpenCV与Matplotlib的图像操作入门指南》:本文主要介绍Python中OpenCV与Matplotlib的图像操作指南,本文通过实例代码给大家介绍的非常详细,对大家的学... 目录一、环境准备二、图像的基本操作1. 图像读取、显示与保存 使用OpenCV操作2. 像素级操作3.

ShardingSphere之读写分离方式

《ShardingSphere之读写分离方式》:本文主要介绍ShardingSphere之读写分离方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录ShardingSphere-读写分离读写分离mysql主从集群创建 user 表主节点执行见表语句项目代码读写分

python操作redis基础

《python操作redis基础》Redis(RemoteDictionaryServer)是一个开源的、基于内存的键值对(Key-Value)存储系统,它通常用作数据库、缓存和消息代理,这篇文章... 目录1. Redis 简介2. 前提条件3. 安装 python Redis 客户端库4. 连接到 Re

Java Stream.reduce()方法操作实际案例讲解

《JavaStream.reduce()方法操作实际案例讲解》reduce是JavaStreamAPI中的一个核心操作,用于将流中的元素组合起来产生单个结果,:本文主要介绍JavaStream.... 目录一、reduce的基本概念1. 什么是reduce操作2. reduce方法的三种形式二、reduce

MySQL表空间结构详解表空间到段页操作

《MySQL表空间结构详解表空间到段页操作》在MySQL架构和存储引擎专题中介绍了使用不同存储引擎创建表时生成的表空间数据文件,在本章节主要介绍使用InnoDB存储引擎创建表时生成的表空间数据文件,对... 目录️‍一、什么是表空间结构1.1 表空间与表空间文件的关系是什么?️‍二、用户数据在表空间中是怎么