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

相关文章

10. 文件的读写

10.1 文本文件 操作文件三大类: ofstream:写操作ifstream:读操作fstream:读写操作 打开方式解释ios::in为了读文件而打开文件ios::out为了写文件而打开文件,如果当前文件存在则清空当前文件在写入ios::app追加方式写文件ios::trunc如果文件存在先删除,在创建ios::ate打开文件之后令读写位置移至文件尾端ios::binary二进制方式

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2

什么是 Flash Attention

Flash Attention 是 由 Tri Dao 和 Dan Fu 等人在2022年的论文 FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness 中 提出的, 论文可以从 https://arxiv.org/abs/2205.14135 页面下载,点击 View PDF 就可以下载。 下面我

STM32内部闪存FLASH(内部ROM)、IAP

1 FLASH简介  1 利用程序存储器的剩余空间来保存掉电不丢失的用户数据 2 通过在程序中编程(IAP)实现程序的自我更新 (OTA) 3在线编程(ICP把整个程序都更新掉) 1 系统的Bootloader写死了,只能用串口下载到指定的位置,启动方式也不方便需要配置BOOT引脚触发启动  4 IAP(自己写的Bootloader,实现程序升级) 1 比如蓝牙转串口,

动手学深度学习【数据操作+数据预处理】

import osos.makedirs(os.path.join('.', 'data'), exist_ok=True)data_file = os.path.join('.', 'data', 'house_tiny.csv')with open(data_file, 'w') as f:f.write('NumRooms,Alley,Price\n') # 列名f.write('NA

线程的四种操作

所属专栏:Java学习        1. 线程的开启 start和run的区别: run:描述了线程要执行的任务,也可以称为线程的入口 start:调用系统函数,真正的在系统内核中创建线程(创建PCB,加入到链表中),此处的start会根据不同的系统,分别调用不同的api,创建好之后的线程,再单独去执行run(所以说,start的本质是调用系统api,系统的api

Java IO 操作——个人理解

之前一直Java的IO操作一知半解。今天看到一个便文章觉得很有道理( 原文章),记录一下。 首先,理解Java的IO操作到底操作的什么内容,过程又是怎么样子。          数据来源的操作: 来源有文件,网络数据。使用File类和Sockets等。这里操作的是数据本身,1,0结构。    File file = new File("path");   字

MySQL——表操作

目录 一、创建表 二、查看表 2.1 查看表中某成员的数据 2.2 查看整个表中的表成员 2.3 查看创建表时的句柄 三、修改表 alter 3.1 重命名 rename 3.2 新增一列 add 3.3 更改列属性 modify 3.4 更改列名称 change 3.5 删除某列 上一篇博客介绍了库的操作,接下来来看一下表的相关操作。 一、创建表 create

STM32 ADC+DMA导致写FLASH失败

最近用STM32G070系列的ADC+DMA采样时,遇到了一些小坑记录一下; 一、ADC+DMA采样时进入死循环; 解决方法:ADC-dma死循环问题_stm32 adc dma死机-CSDN博客 将ADC的DMA中断调整为最高,且增大ADCHAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_Buffer_Size); 的ADC_Bu

封装MySQL操作时Where条件语句的组织

在对数据库进行封装的过程中,条件语句应该是相对难以处理的,毕竟条件语句太过于多样性。 条件语句大致分为以下几种: 1、单一条件,比如:where id = 1; 2、多个条件,相互间关系统一。比如:where id > 10 and age > 20 and score < 60; 3、多个条件,相互间关系不统一。比如:where (id > 10 OR age > 20) AND sco