大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是i.MXRT启动头FDCB里的lookupTable

  一个MCU内部通常有很多外设模块,这些外设模块是各MCU厂商做差异化产品的本质,也是各厂商核心竞争力所在(这里特指那些生产ARM Cortex-M内核MCU的厂商)。在做MCU开发时有时候并不需要了解全部的外设,因为有些外设在项目里不一定会用到,但是要想把恩智浦i.MXRT系列MCU玩起来,有一个外设是必须要有所了解的,它就是FlexSPI,这个外设负责与外部串行NOR Flash连接,实现外部NOR Flash里的应用程序指令与数据的读取,而串行NOR Flash正是i.MXRT首选的启动设备。

  那么在FlexSPI外设模块里究竟是什么机制实现了Flash中应用程序指令与数据的读取功能呢?痞子衡从i.MXRT启动头FDCB里的lookupTable设定开始说起:

一、为何i.MXRT能从外部Flash XIP启动?

  关于在串行NOR Flash XIP执行原理,痞子衡其实在之前一篇文章 《在串行NOR Flash XIP调试原理》 的第二小节 i.MXRT FlexSPI外设特性 介绍过,是FlexSPI这个外设实现了从串行Flash任意地址取指令的功能,这是先决条件。

  有了从Flash任意地址取指的先决条件基础,在i.MXRT芯片上电后,BootROM便只需要将FlexSPI外设配置到指定工作状态(这里详见 《深入i.MXRT1050系列ROM中串行NOR Flash启动初始化流程》 一文,尤其是文中最后一节提到的第二次FlexSPI初始化,本文讨论的内容其实属于第二次初始化后的状态),FlexSPI外设配置信息完全来自于启动头FDCB(一共512bytes),FlexSPI配置完成后,BootROM再把CPU控制权交给应用程序,这就完成了启动任务。

  下面的 qspiflash_config 便是i.MXRT SDK包里使用的一个典型的适用符合JEDEC SFDP标准且容量为8MB的QSPI NOR Flash的FDCB头。这个启动头将FlexSPI配置成了四线模式,100MHz时钟频率,Quad I/O Fast Read时序模式(注意这个头里lookupTable设定写法其实并不标准,没有显式地写出模式序列和停止序列,后面痞子衡会细说):

  当PC开始指向FlexSPI映射空间(0x60000000 - 0x607FFFFF)去执行用户程序时,FlexSPI便在背后一直默默为CPU送上指定的指令数据,如下图绿色箭头流向所示。指令数据从外部Flash中通过IO_CTL且按照SEQ_CTL指定的时序送入RX_FIFO,再到AHB_RX_BUF,最后经过AHB_CTL送到系统AHB总线上,以被CPU无障碍获取。整个过程中最重要的自动化环节其实是黄色框内的SEQ_CTL,是这个SEQ_CTL在时刻驱动着FlexSPI发送符合Flash要求的读访问时序。

二、FlexSPI外设的SEQ_CTL是如何工作的?

  经过上一节的分析,我们知道了是FlexSPI中的SEQ_CTL组件实现了核心的Flash访问时序控制,那么SEQ_CTL我们该怎么控制它?别急,这时候该LUT登场,LUT是Look Up Table的简称,它其实是FlexSPI内部的一块存储区(即FlexSPI->LUTx寄存器),它的组织结构如下,LUT由多个Sequence组成(比如i.MXRT1050上是16个),每个Sequence由最多8个instruction组成,每个instruction大小为16bits,分为opcode(序列编号) + num_pads(管脚模式) + operand(序列参数值)三部分。

  每个instruction,你可以理解为一个Flash访问传输子序列(比如命令序列、地址序列、模式序列,dummy序列,读/写数据序列,停止序列等),在FlexSPI外设模块里面预先实现了很多个基础instruction,instruction中的opcode即是那些预实现的序列编号。opcode全部编号如下:

命令序列:
CMD_SDR - 0x01, CMD_DDR - 0x21
地址序列:
RADDR_SDR - 0x02, RADDR_DDR - 0x22, CADDR_SDR - 0x03, CADDR_DDR - 0x23
模式序列:
MODE1_SDR - 0x04, MODE1_DDR - 0x24, MODE2_SDR - 0x05, MODE2_DDR - 0x25
MODE4_SDR - 0x06, MODE4_DDR - 0x26, MODE8_SDR - 0x07, MODE8_DDR - 0x27
写数据序列:
WRITE_SDR - 0x08, WRITE_DDR - 0x28
读数据序列:
READ_SDR - 0x09, READ_DDR - 0x29
LEARN序列:
LEARN_SDR - 0x0A, LEARN_DDR - 0x2A
数据长度设置序列(适用FPGA):
DATSZ_SDR - 0x0B, DATSZ_DDR - 0x2B
空指令序列::
DUMMY_SDR - 0x0C, DUMMY_DDR - 0x2C, DUMMY_RWDS_SDR - 0x0D, DUMMY_RWDS_DDR - 0x2D
JMP序列:
JMP_ON_CS - 0x1F
停止序列:
STOP - 0x00

  有了这些基础instruction,我们便可以自由组合它们(最多8个),得到我们想要的完整传输Sequence。比如最常见的Quad I/O Read SDR传输时序便由CMD_SDR + RADDR_SDR + MODE8_SDR + DUMMY_SDR + READ_SDR + STOP六个子序列组成,如下表所示:

  • Note: 关于READ_SDR的参数值设置(即读取数据长度)需要特别说明一下,这个参数仅对IP CMD方式的访问时序有效;而对于AHB CMD方式的访问时序,这个参数值设定是无效的,实际读取数据长度是由AHB RX Buffer策略灵活决定的。

  从引脚信号上来看,完整Quad I/O Read SDR传输时序如下图所示。注意有一处要特别说明,从FlexSPI外设本身而言,MODE8_SDR序列和DUMMY_SDR序列是互相独立的,但在不少Flash芯片上,MODE8_SDR所占的2个时钟周期也被算在了总Dummy时钟周期数里。

  LUT中最多可以存储16个Sequence,对于XIP执行而言,只需要一个读访问时序(比如最常用的Quad I/O Read SDR传输时序)即可。如果是IAP,那么还需要添加擦除时序,写访问时序,写使能时序,读状态寄存器时序等。这些预先存放在LUT中的Sequence被用户按需触发以实现各种不同类型的Flash访问,这就是SEQ_CTL工作机制。

三、FDCB中的lookupTable是如何配置进FlexSPI->LUT的?

  从FlexSPI外设模块设计上而言,LUT里16个Sequence地位是相同的,对于XIP执行,必要的读访问时序可以放在LUT中的任何一个Sequence位置,只需要在FlexSPI->FLSHxCR2寄存器(x可取A1/A2/B1/B2,具体根据Flash引脚连接来定)中的ARDSEQID位指明读访问时序在LUT中的位置(index)即可。

  但是毕竟应用程序是由BootROM引导的,BootROM有自己的一套配置FlexSPI规则,它定死了CMD_LUT_SEQ_IDX_READ位置,即读访问时序必须是FlexSPI->LUT[]中第一个Sequence,因为FlexSPI->FLSHxCR2[ARDSEQID]被BootROM配置成了0。所以我们在准备FDCB时,lookupTable中第一个Sequence必须放置读访问时序。

  再来看BootROM中的FlexSPI初始化函数,在外设模块基本初始化 flexspi_init() 完成后,然后 flexspi_update_lut() 被调用去更新了一次LUT就直接结束了。这次的LUT更新其实仅仅是将FDCB里的lookupTable[0] - lookupTable[3](第一条Sequence) 填到 FlexSPI->LUT[0] - FlexSPI->LUT[3]里。至于为何有时候你会看到FDCB里lookupTable中不止一条Sequence,这个痞子衡后面另有文章再聊。

status_t flexspi_nor_flash_init(uint32_t instance, flexspi_nor_config_t *config)
{
status_t status = kStatus_InvalidArgument; status = flexspi_init(instance, (flexspi_mem_config_t *)config);
if (status != kStatus_Success)
{
break;
} // Configure Lookup table for Read
// 将config->memConfig.lookupTable里的第一个sequence放到FlexSPI->LUT[0] - FlexSPI->LUT[3]里
flexspi_update_lut(instance, 0, config->memConfig.lookupTable, 1); return status;
}

四、设定FDCB中lookupTable的一个实例

  我们以i.MXRT官方EVK上配套的典型Flash型号IS25WP064AJBLE来实战,下图是该Flash的Fast Read Quad I/O Sequence,这个时序图中命令序列、地址序列、Dummy序列的参数值是明确的,但模式序列、读数据序列参数值并不明确,我们给它明确一下,模式序列中mode bits我们设为0x00(其实只要不是0xAx均可),即 non-continuous read mode;读数据序列中data out byte其实不可设(上面讲过AHB访问下是由RX Buffer策略自动控制的),随便写个非0值即可。

  基于上面的真实Flash读数据传输时序图,我们在FDCB中lookupTable里的对应设定应如下:

#define CMD_LUT_SEQ_IDX_READ        0

#define FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1)                                  \
(FLEXSPI_LUT_OPERAND0(op0) | FLEXSPI_LUT_NUM_PADS0(pad0) | FLEXSPI_LUT_OPCODE0(cmd0) | \
FLEXSPI_LUT_OPERAND1(op1) | FLEXSPI_LUT_NUM_PADS1(pad1) | FLEXSPI_LUT_OPCODE1(cmd1)) #define FLEXSPI_1PAD 0
#define FLEXSPI_2PAD 1
#define FLEXSPI_4PAD 2
#define FLEXSPI_8PAD 3 const flexspi_nor_config_t qspiflash_config = {
.memConfig =
{
.lookupTable =
{
// Quad I/O Fast Read LUTs
// 第1个instruction是CMD_SDR,参数值为0xEB,即Quad I/O Fast Read命令
// 第2个instruction是RADDR_SDR,参数值为0x18,即24bits地址(三字节)
[4*CMD_LUT_SEQ_IDX_READ + 0] = FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0xEB, RADDR_SDR, FLEXSPI_4PAD, 0x18), // 第3个instruction是MODE8_SDR,参数值为0x00。注意对于IS25WP064AJBLE它同时也算2个Dummy时钟周期!!!
// 第4个instruction是DUMMY_SDR,参数值为0x04,加上上面一共6个时钟周期
[4*CMD_LUT_SEQ_IDX_READ + 1] = FLEXSPI_LUT_SEQ(MODE8_SDR, FLEXSPI_4PAD, 0x00, DUMMY_SDR, FLEXSPI_4PAD, 0x04), // 第5个instruction是READ_SDR,参数值为0x04,设定并不生效,随便写个非0值都行
// 第6个instruction是STOP
[4*CMD_LUT_SEQ_IDX_READ + 2] = FLEXSPI_LUT_SEQ(READ_SDR, FLEXSPI_4PAD, 0x04, STOP, FLEXSPI_1PAD, 0x00),
[4*CMD_LUT_SEQ_IDX_READ + 3] = 0,
},
},
};

五、对FlexSPI映射区域进行AHB读访问一定会启动SEQ_CTL工作吗?

  当我们放好了正确的FDCB,BootROM正常配置完FlexSPI,并启动了应用程序后,CPU便开始按部就班从FlexSPI映射区域直接AHB访问去获取应用程序指令,是不是每一次的CPU访问都会让SEQ_CTL组件按LUT里的设定发送一次读访问时序呢?其实并不是!

  我们知道i.MXRT系列会有L1 Cache,如果Flash某地址里的指令内容缓存在L1 Cache里,那么当前CPU访问该Flash地址处的指令并不需要从Flash里重新再获取一次,CPU直接从cache里便可以得到指令,此时SEQ_CTL不会工作。

  即便L1 Cache里没有缓存到CPU所要指令,如果FlexSPI本身的Cacheable和Prefetch功能打开的话,AHB RX/TX Buffer里可能也会缓存CPU所要指令。如果所需指令确实缓存在AHB Buffer里,SEQ_CTL仍然不会工作。

  仅当CPU所要指令是全新的,完全没有缓存,SEQ_CTL才会真正开始工作,按LUT设定去发送读数据访问时序给Flash。

六、AHB读访问下SEQ_CTL工作一次到底获取多长的数据?

  前面讲了,我们在lookupTable里无法有效设置读数据序列中data out byte,因为AHB访问下的一次读取的长度是由RX Buffer策略控制的。在i.MXRT1050中AHB RX Buffer总大小为1KB,分为四个:AHB RX Buffer0 - AHB RX Buffer3,每个Buffer的大小都是可配的。具体配置在如下FlexSPI->AHBRXBUFxCR0寄存器里:

  BootROM使用了如下 flexspi_config_ahb_buffers() 函数配置了AHB Buffer,即开启了FlexSPI的Prefetch功能,并且将四个FlexSPI->AHBRXBUFxCR0[BUFSZ]全部设为了0,根据手册,这种配置意味着仅启用Buffer3作为唯一的RX Buffer,并且Buffer3大小为1KB。那么我们现在知道了,在Prefetch开启的情况下,SEQ_CTL工作一次就会读取1KB数据。当然Prefetch功能是可以在应用程序里被关掉的,如果Prefetch不使能,SEQ_CTL工作一次仅获取最小数据单元(8bytes)。

status_t flexspi_config_ahb_buffers(FLEXSPI_Type *base, flexspi_mem_config_t *config)
{
uint32_t temp;
uint32_t index;
status_t status = kStatus_InvalidArgument; do
{
if ((base == NULL) || (config == NULL))
{
break;
} if (config->deviceType == kFlexSpiDeviceType_SerialNOR)
{
// Configure AHBCR
temp = base->AHBCR & (~FLEXSPI_AHBCR_APAREN_MASK);
// Remove alignment limitation when Flash device works under DDR mode.
temp |= FLEXSPI_AHBCR_READADDROPT_MASK;
#if FLEXSPI_FEATURE_HAS_PARALLEL_MODE
if (flexspi_is_parallel_mode(config))
{
temp |= FLEXSPI_AHBCR_APAREN_MASK;
}
#endif // FLEXSPI_FEATURE_HAS_PARALLEL_MODE
base->AHBCR = temp;
} // Enable prefetch feature
base->AHBCR |= FLEXSPI_AHBCR_PREFETCHEN_MASK; // Skip AHB buffer configuration if corresponding bit is set
if ((config->controllerMiscOption & (1<<kFlexSpiMiscOffset_SkipAhbBufConfig)))
{
status = kStatus_Success;
break;
} // Configure AHB RX buffer
for (index = 0; index < FLEXSPI_AHBRXBUFCR0_COUNT - 1; index++)
{
base->AHBRXBUFCR0[index] &=
~(FLEXSPI_AHBRXBUFCR0_BUFSZ_MASK | FLEXSPI_AHBRXBUFCR0_MSTRID_MASK | FLEXSPI_AHBRXBUFCR0_PRIORITY_MASK);
}
status = kStatus_Success; } while (0); return status;
}

  至此,i.MXRT启动头FDCB里的lookupTable痞子衡便介绍完毕了,掌声在哪里~~~

欢迎订阅

文章会同时发布到我的 博客园主页CSDN主页知乎主页微信公众号 平台上。

微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。

痞子衡嵌入式:从头开始认识i.MXRT启动头FDCB里的lookupTable的更多相关文章

  1. 痞子衡嵌入式:在i.MXRT启动头FDCB里调整Flash工作频率也需同步设Dummy Cycle

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是Flash工作频率与Dummy Cycle的联系. 上一篇文章 <从头开始认识i.MXRT启动头FDCB里的lookupTable ...

  2. 痞子衡嵌入式:在i.MXRT启动头FDCB里使能串行NOR Flash的DTR模式

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是在FDCB里使能串行NOR Flash的DTR模式. 前两篇文章 <IS25WP系列Dummy Cycle设置> 与 < ...

  3. 痞子衡嵌入式:在i.MXRT启动头FDCB里使能串行NOR Flash的Continuous read模式

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是在FDCB里使能串行NOR Flash的Continuous read模式. 前面关于串行Flash传输时序的文章 <Fast R ...

  4. 痞子衡嵌入式:在i.MXRT启动头FDCB里使能串行NOR Flash的QPI/OPI模式

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是在FDCB里使能串行NOR Flash的QPI/OPI模式. 我们知道 Flash 读时序里有五大子序列 CMD + ADDR + MO ...

  5. 痞子衡嵌入式:轻松为i.MXRT设计更新Segger J-Link Flash下载算法文件

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是为i.MXRT设计更新Segger J-Link Flash下载算法文件. 想要在Flash中调试,基本是离不开Flash下载算法的,毕 ...

  6. 痞子衡嵌入式:一种i.MXRT下从App中进入ROM串行下载模式的方法

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT下在App中利用ROM API进ISP/SDP模式的方法. 我们知道i.MXRT系列分为两大阵营:CM33内核的i.MXRT ...

  7. 痞子衡嵌入式:恩智浦MCU安全加密启动一站式工具NXP-MCUBootUtility用户指南

    NXP MCU Boot Utility English | 中文 1 软件概览 1.1 介绍 NXP-MCUBootUtility是一个专为NXP MCU安全加密启动而设计的工具,其特性与NXP M ...

  8. 痞子衡嵌入式:在i.MXRT1170上启动含DQS的Octal Flash可不严格设Dummy Cycle (以MT35XU512为例)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是Octal或Hyper Flash上DQS信号与Dummy Cycle联系. 关于在 i.MXRT 上启动 NOR Flash 时如何设 ...

  9. 痞子衡嵌入式:i.MXRT中FlexSPI外设对AHB Burst Read特性的支持

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是FlexSPI外设对AHB Burst Read特性的支持. 痞子衡之前写过一篇关于FlexSPI LUT的文章 <从头开始认识i ...

随机推荐

  1. CSS flex waterfall layout

    CSS flex waterfall layout https://github.com/YoneChen/waterfall-flexbox https://css-tricks.com/snipp ...

  2. js滚轮事件兼容写法

    /** * 简易的事件添加方法 */ define(function(require, exports, module) { exports.addEvent = (function(window, ...

  3. 创新全球算力生态价值,SPC算力生态强势来袭!

    当前,区块链技术已经到了一个新的时代,即3.0时代.在区块链3.0时代,区块链技术迎来了数字经济革命,各行各业也在积极寻找与区块链能够融合的切入点.而随着区块链的愈加成熟,区块链技术也愈加被更多的人应 ...

  4. Java volatile 关键字底层实现原理解析

    本文转载自Java volatile 关键字底层实现原理解析 导语 在Java多线程并发编程中,volatile关键词扮演着重要角色,它是轻量级的synchronized,在多处理器开发中保证了共享变 ...

  5. winform导出csv

    public void ExportToSvc1(string strFileName) { string strPath = strFileName + ".csv"; Stri ...

  6. 自己的Scrapy框架学习之路

    开始自己的Scrapy 框架学习之路. 一.Scrapy安装介绍 参考网上资料,先进行安装 使用pip来安装Scrapy 在开始菜单打开cmd命令行窗口执行如下命令即可 pip install Scr ...

  7. Java基础语法:static修饰符

    一.静态变量 描述: 在类中,使用'static'修饰的成员变量,就是静态变量,反之为非静态变量. 区别: 静态变量属于类的,可以使用类名来访问:非静态变量是属于对象的,必须使用对象来访问. 静态变量 ...

  8. 用 Hugo 快速搭建博客

    用 Hugo 搭建博客 Hugo 是一个用 Go 编写的静态站点生成器,生成速度很快 下面是具体操作: 1.安装 Hugo Windows 用户 使用 Chocolatey 或者 Scoop 快速安装 ...

  9. 后端程序员之路 59、go uiprogress

    gosuri/uiprogress: A go library to render progress bars in terminal applicationshttps://github.com/g ...

  10. 巧用 -webkit-box-reflect 倒影实现各类动效

    在很久之前的一篇文章,有讲到 -webkit-box-reflect 这个属性 -- 从倒影说起,谈谈 CSS 继承 inherit -webkit-box-reflect 是一个非常有意思的属性,它 ...