请选择 进入手机版 | 继续访问电脑版
查看: 1022|回复: 1

[分享] 从头开始认识i.MXRT启动头FDCB里的lookupTable

[复制链接]
  • TA的每日心情
    奋斗
    前天 08:00
  • 签到天数: 554 天

    [LV.9]以坛为家II

    34

    主题

    5904

    帖子

    2

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    5606
    最后登录
    2024-3-28
    发表于 2021-5-8 18:29:13 | 显示全部楼层 |阅读模式
    大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是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设定写法其实并不标准,没有显式地写出模式序列和停止序列,后面痞子衡会细说):
    1.png
    当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要求的读访问时序。
    2.png
    二、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(序列参数值)三部分。
    3.png
    每个instruction,你可以理解为一个Flash访问传输子序列(比如命令序列、地址序列、模式序列,dummy序列,读/写数据序列,停止序列等),在FlexSPI外设模块里面预先实现了很多个基础instruction,instruction中的opcode即是那些预实现的序列编号。opcode全部编号如下:
    1. 命令序列:
    2.     CMD_SDR   - 0x01, CMD_DDR   - 0x21
    3. 地址序列:
    4.     RADDR_SDR - 0x02, RADDR_DDR - 0x22, CADDR_SDR - 0x03, CADDR_DDR - 0x23
    5. 模式序列:
    6.     MODE1_SDR - 0x04, MODE1_DDR - 0x24, MODE2_SDR - 0x05, MODE2_DDR - 0x25
    7.     MODE4_SDR - 0x06, MODE4_DDR - 0x26, MODE8_SDR - 0x07, MODE8_DDR - 0x27
    8. 写数据序列:
    9.     WRITE_SDR - 0x08, WRITE_DDR - 0x28
    10. 读数据序列:
    11.     READ_SDR  - 0x09, READ_DDR  - 0x29
    12. LEARN序列:
    13.     LEARN_SDR - 0x0A, LEARN_DDR - 0x2A
    14. 数据长度设置序列(适用FPGA):
    15.     DATSZ_SDR - 0x0B, DATSZ_DDR - 0x2B
    16. 空指令序列::
    17.     DUMMY_SDR - 0x0C, DUMMY_DDR - 0x2C, DUMMY_RWDS_SDR - 0x0D, DUMMY_RWDS_DDR - 0x2D
    18. JMP序列:
    19.     JMP_ON_CS - 0x1F
    20. 停止序列:
    21.     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策略灵活决定的。
    4.png
    从引脚信号上来看,完整Quad I/O Read SDR传输时序如下图所示。注意有一处要特别说明,从FlexSPI外设本身而言,MODE8_SDR序列和DUMMY_SDR序列是互相独立的,但在不少Flash芯片上,MODE8_SDR所占的2个时钟周期也被算在了总Dummy时钟周期数里。
    5.png
    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)即可。
    6.png
    但是毕竟应用程序是由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,这个痞子衡后面另有文章再聊。
    1. status_t flexspi_nor_flash_init(uint32_t instance, flexspi_nor_config_t *config)
    2. {
    3.     status_t status = kStatus_InvalidArgument;

    4.     status = flexspi_init(instance, (flexspi_mem_config_t *)config);
    5.     if (status != kStatus_Success)
    6.     {
    7.         break;
    8.     }

    9.     // Configure Lookup table for Read
    10.     // 将config->memConfig.lookupTable里的第一个sequence放到FlexSPI->LUT[0] - FlexSPI->LUT[3]里
    11.     flexspi_update_lut(instance, 0, config->memConfig.lookupTable, 1);

    12.     return status;
    13. }
    复制代码
    四、设定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值即可。
    7.png
    基于上面的真实Flash读数据传输时序图,我们在FDCB中lookupTable里的对应设定应如下:

    1. #define CMD_LUT_SEQ_IDX_READ        0

    2. #define FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1)                                  \
    3.     (FLEXSPI_LUT_OPERAND0(op0) | FLEXSPI_LUT_NUM_PADS0(pad0) | FLEXSPI_LUT_OPCODE0(cmd0) | \
    4.      FLEXSPI_LUT_OPERAND1(op1) | FLEXSPI_LUT_NUM_PADS1(pad1) | FLEXSPI_LUT_OPCODE1(cmd1))

    5. #define FLEXSPI_1PAD 0
    6. #define FLEXSPI_2PAD 1
    7. #define FLEXSPI_4PAD 2
    8. #define FLEXSPI_8PAD 3

    9. const flexspi_nor_config_t qspiflash_config = {
    10.     .memConfig =
    11.         {
    12.             .lookupTable =
    13.                 {
    14.                     // Quad I/O Fast Read LUTs
    15.                     // 第1个instruction是CMD_SDR,参数值为0xEB,即Quad I/O Fast Read命令
    16.                     // 第2个instruction是RADDR_SDR,参数值为0x18,即24bits地址(三字节)
    17.                     [4*CMD_LUT_SEQ_IDX_READ + 0] = FLEXSPI_LUT_SEQ(CMD_SDR,   FLEXSPI_1PAD, 0xEB, RADDR_SDR, FLEXSPI_4PAD, 0x18),

    18.                     // 第3个instruction是MODE8_SDR,参数值为0x00。注意对于IS25WP064AJBLE它同时也算2个Dummy时钟周期!!!
    19.                     // 第4个instruction是DUMMY_SDR,参数值为0x04,加上上面一共6个时钟周期
    20.                     [4*CMD_LUT_SEQ_IDX_READ + 1] = FLEXSPI_LUT_SEQ(MODE8_SDR, FLEXSPI_4PAD, 0x00, DUMMY_SDR, FLEXSPI_4PAD, 0x04),

    21.                     // 第5个instruction是READ_SDR,参数值为0x04,设定并不生效,随便写个非0值都行
    22.                     // 第6个instruction是STOP
    23.                     [4*CMD_LUT_SEQ_IDX_READ + 2] = FLEXSPI_LUT_SEQ(READ_SDR,  FLEXSPI_4PAD, 0x04, STOP,      FLEXSPI_1PAD, 0x00),
    24.                     [4*CMD_LUT_SEQ_IDX_READ + 3] = 0,
    25.                 },
    26.         },
    27. <font face="微软雅黑" size="3">};
    28. </font>
    复制代码
    五、对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。
    8.png
    六、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寄存器里:
    9.png
    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)。
    1. status_t flexspi_config_ahb_buffers(FLEXSPI_Type *base, flexspi_mem_config_t *config)
    2. {
    3.     uint32_t temp;
    4.     uint32_t index;
    5.     status_t status = kStatus_InvalidArgument;

    6.     do
    7.     {
    8.         if ((base == NULL) || (config == NULL))
    9.         {
    10.             break;
    11.         }

    12.         if (config->deviceType == kFlexSpiDeviceType_SerialNOR)
    13.         {
    14.             // Configure AHBCR
    15.             temp = base->AHBCR & (~FLEXSPI_AHBCR_APAREN_MASK);
    16.             // Remove alignment limitation when Flash device works under DDR mode.
    17.             temp |= FLEXSPI_AHBCR_READADDROPT_MASK;
    18. #if FLEXSPI_FEATURE_HAS_PARALLEL_MODE
    19.             if (flexspi_is_parallel_mode(config))
    20.             {
    21.                 temp |= FLEXSPI_AHBCR_APAREN_MASK;
    22.             }
    23. #endif // FLEXSPI_FEATURE_HAS_PARALLEL_MODE
    24.             base->AHBCR = temp;
    25.         }

    26.         // Enable prefetch feature
    27.         base->AHBCR |= FLEXSPI_AHBCR_PREFETCHEN_MASK;

    28.         // Skip AHB buffer configuration if corresponding bit is set
    29.         if ((config->controllerMiscOption & (1<<kFlexSpiMiscOffset_SkipAhbBufConfig)))
    30.         {
    31.             status = kStatus_Success;
    32.             break;
    33.         }

    34.         // Configure AHB RX buffer
    35.         for (index = 0; index < FLEXSPI_AHBRXBUFCR0_COUNT - 1; index++)
    36.         {
    37.             base->AHBRXBUFCR0[index] &=
    38.                 ~(FLEXSPI_AHBRXBUFCR0_BUFSZ_MASK | FLEXSPI_AHBRXBUFCR0_MSTRID_MASK | FLEXSPI_AHBRXBUFCR0_PRIORITY_MASK);
    39.         }
    40.         status = kStatus_Success;

    41.     } while (0);

    42.     return status;
    43. }
    复制代码
    至此,i.MXRT启动头FDCB里的lookupTable痞子衡便介绍完毕了,掌声在哪里~~~

    哎...今天够累的,签到来了~
    回复

    使用道具 举报

  • TA的每日心情
    开心
    6 天前
  • 签到天数: 1334 天

    [LV.10]以坛为家III

    88

    主题

    4290

    帖子

    12

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    9022
    最后登录
    2024-3-29
    发表于 2021-5-9 10:20:41 | 显示全部楼层
    玩 i.mx RT的学习成本还是有的
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /4 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2024-3-29 19:50 , Processed in 0.109846 second(s), 21 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表