【i.MX RT 系列】Flexspi LUT 介绍

NXP 的 i.MX RT 系列 MCU 离不开 Flexspi 这个外设,Flexspi 是专为存储设备设计的,以 SPI Flash 举例,它支持单线、 4 线以及 8 线读写,熟悉 Flash 的小伙伴都知道需要一些命令才能对其操作, 对此 Flexspi 设计了一个称为 LUT 的命令查找表,这篇文章就对 LUT 进行一个详细介绍。

 
一、LUT 与 SPI Flash 命令表的联系

1. LUT 简介

LUT  全称是 Look Up Table ,顾名思义就是查找表格,可以自定义一些 Flash 的命令序列,然后提供给 Flexspi 去调用:

 

 

看到这里可能还是很困惑这是个什么东西,我们从大部分人接触的 SPI Flash 说起,以 Winbond W25Q128JVSIQ 为例。



2. 标准 SPI Flash 命令表介绍

如下图,平时接触最多的 SPI 是四条线,也就是标准 SPI:CLK 、CS、 MOSI、 MISO,

 

 

假设现在要读取 Flash 的 ID ,需要怎么操作呢?首先在手册中找到命令表:

 

 

注意这是标准 SPI 的命令表,可以看到读取 Device ID 的命令是 90h ,h 代表十六进制,意思是代码中发送该命令要使用 0x90,发送 0x90 之后是等待两个 byte 的无效时钟(可以在 MOSI 上发送两个 byte 数据,此时 Flash 端不会理会这些数据),接下来再发送 0x00 ,发完之后就可以在 MISO 的数据线上读取 Flash 返回的数据了,详细过程如下图;

 

 以上就是对 Flash 操作的一个完整过程,其它的命令大同小异。



3.  4 线 QSPI Flash 命令表介绍

这里再以一个 QSPI 的命令为例(QSPI 即 4 条数据线 + CS + CLK):

 

快速读取数据的命令是 0xEB :
首先先发送 0xEB ;
再发送 24bit 的读取地址,分 3 个 byte 发送 ;
接着是 3 个 dummy 时钟 ;
注意此时的时钟个数与上一个读取 ID 命令的是有区别的,这里 1 个 dummy 是 2 个时钟周期,读取 ID 的是 8 个 CLK ,这个其实也很好理解,标准 SPI 只有一条数据线发送\接收,那么 1 byte 就需要 8 个时钟周期才能完整读取;
对于QSPI 而言它有 4 跟数据线,1 个时钟周期可以读取 4bit 数据,2 个时钟周期就可以读取 8bit 也就是 1 byte,所以 dummy 的时钟周期也就减少了,等待 3 个dummy 周期后就可以接收数据了。

 

4. SPI Flash 命令对应到 LUT 

以上两个命令我们可以称它们为 2 个命令序列,一个是读取 Device ID 的命令序列,另外一个是快速读取数据的命令序列,可以看到每个命令序列都能以 byte 为单位分解,里面有操作命令(0x90,0xEB 等 )、dummy、地址等 ;

回到 Flexspi 的 LUT ,它的作用就是把常用的 Flash 命令序列提前放在了 LUT 的寄存器里面,方便后续 Flesxspi 调用,只需要给命令序列编号,就能按照里面编辑的命令顺序执行了,从芯片 RM 中可以看到 LUT 一个序列的组成:

 


1 个命令序列称为 Sequence (对于 i.MX RT1050 来说,它的 LUT 最多可以有 16 个序列,后面会解释为何只有 16 个);
1 个 Sequence 中包含了 8 个Instruction ;( 这个 Instruction 可以将它理解为前面说的—— 1 个 Flash 命令序列能以 byte 为单位进行拆解,1 个 Instruction 就代表了一个 byte 的命令 )

1 个 Instruction 里面有三个参数,分别是 opcode,num_pads,operand,解释一下这三个参数的含义:

opcode:Flexspi 外设的专有命令;
比如现在需要向 Flash 发送读取 ID 的命令 0x90 ,那么此时就需要告诉 Flexspi 要发送操作命令到 Flash ,Flexspi 能识别的命令就是 CMD_SDR / CMD_DDR,关于 SDR 和 DDR 的区别可自行上网查找,大部分使用的是 CMD_SDR ,所以在这里这个参数就需要填写 0x01

 

 

SDK 中已经很贴心的将这些参数定义好了,编写代码的时候只需要调用就可以

 

num_pads:这个参数应该很好理解,就是执行 opcode 需要用到多少数据线,比如刚刚的 0x90 这个命令只需要一根,那么只要给 0x0 即可,快速读取的命令 0xEB 就需要用到 4 根线,给 0x3 即可,同样 SDK 中的定义如下:

 

operand:这个参数代表的是执行 opcode 这个命令所需要的参数,还是以读取 ID 的命令为例,刚刚 opcode 给的是 CMD_SDR 的命令,这个时候 Flexspi 它只知道了要发送命令过去,但是发送什么命令过去它还不知道,这个时候就需要 operand 告诉它,所以这里填参数 0x90 ;


根据以上描述,这里就完成了一个序列的其中一个命令,让我们来看看完成的是哪个部分:




二 、 代码编写

接下来以 SDK 中的例程来介绍怎么将我们熟悉的 Flash 命令序列放进 LUT 当中。

 
1. LUT 表序列号

SDK 例程中用的是板载的 Flash 是 ISSI 的 IS25WP064,这里的命令与 Winbond 有些不一样的,在这个基础上修改成 Winbond 的,修改之前先解释一下为什么前面会有 “4 * 序列号”的操作:

 

 

1 个 Sequence 规定需要有 8 个 Instruction ,规定这么多个是为了适配一些复杂的操作 ;可以看到 1 个 Instruction 是 16bit ,那么2 个Instruction 就是 32bit ,1 个 Sequence 就需要 4 * 32bit ,对于 i.MX RT1050 来说, LUT 寄存器只有 64 个,1 个寄存器 32bit ,那么 1 个 Sequence 就要 4 个寄存器,所以最多只能有 16 个 Sequence ,但是这个已经远远够用了,平时用的无非就是擦数据、写数据、读数据。

FLEXSPI_LUT_SEQ 这个宏是将两个 Instruction 合在一起变成 32bit 的数据,方便放进一个 LUT 寄存器当中

 

 

定义的数组是直接写进 LUT 寄存器的,长度是 64,类型是 uint32_t 的,所以其中一个数据就是 32bit ;将 64 个 LUT 寄存器分成 16 份,1 份就是 4 个寄存器,前面说过 1 个 Sequence 就需要 4 个寄存器,所以出现了 “4 * 序列号” 这个操作,那么接下来的 “4 * 序列号 + 1” 也很好理解了,FLEXSPI_LUT_SEQ 只能合并 2 个 Instruction ,有些 Flash 命令序列 2 个 Instruction是满足不了的,所以需要继续往下加,“4 * 序列号 + 1” 就是往下一个相邻的 LUT 寄存器写,可以自己将序列号代入进去理解,这里不再举例说明。

 

2.  以读取 ID 的命令为例,下图是 ISSI 的:

 

 

序列号的宏定义可以自行定义,这里沿用例程的,根据文章开头说的,Winbond 的命令如下:

(1)发送 0x90 命令

Instruction0

Opcode         = kFLEXSPI Command SDR  

num_pads      = kFLEXSPI 1PAD

operand        = 0x90



(2)等待两个 dummy

Instruction1

Opcode         = kFLEXSPI_Command_DUMMY_SDR

num_pads      = kFLEXSPI 1PAD

operand        = 0x10 ( dummy clock 的个数)

(3)发送 0x00 命令

Instruction2

Opcode         = kFLEXSPI Command SDR  

num_pads      = kFLEXSPI 1PAD

operand        = 0x00

(4)读取数据

Instruction3

Opcode         = kFLEXSPI_Command_READ_SDR

num_pads      = kFLEXSPI 1PAD

operand        = 0x04   (该参数无意义,非 0 即可)

(5)发送 Stop 命令

Instruction4

Opcode         = kFLEXSPI_Command_STOP

num_pads      = kFLEXSPI 1PAD

operand        = 0x00  

以上就是全部步骤,实际上第 5 个步骤不加也不会出问题,因为 LUT 表的数值默认为 0 ,其 3 个参数值都为 0 ,故不影响,为了规范建议还是加上去,该序列如下:

 

 

那么这里就会有人问了,读取数据的长度不用给吗?这个长度是在调用库函数里面给的:

 

 

2. 以快速读取数据的命令为例,下图是 ISSI 的:

 

同样,Winbond 的修改如下:



(1)发送 0xEB 命令

Instruction0

Opcode         = kFLEXSPI Command SDR  

num_pads      = kFLEXSPI 1PAD

operand        = 0xEB

 

(2)发送 24bit 读取地址

Instruction1

Opcode         = kFLEXSPI_Command_RADDR_SDR

num_pads      = kFLEXSPI 1PAD

operand        = 0x18  (读取地址的长度,单位 bit, 这里用 24bit )

 

(3)发送 M7-0 dummy

Instruction2

Opcode         = kFLEXSPI_Command_MODE8_SDR

num_pads      = kFLEXSPI 4PAD

operand        = 0xFF 

(4)等待 2 个 dummy

Instruction3

Opcode         = kFLEXSPI_Command_DUMMY_SDR

num_pads      = kFLEXSPI 4PAD

operand        = 0x04  (这里代表发送 4 个 clock 周期)

 

(5)读取数据

Instruction4

Opcode         = kFLEXSPI_Command_READ_SDR

num_pads      = kFLEXSPI 4PAD

operand        = 0x04  (这里参数同样没有意义,非 0 即可)

 

(6)发送停止命令

Instruction5

Opcode         = kFLEXSPI_Command_STOP

num_pads      = kFLEXSPI 1PAD

operand        = 0x00

如下图,最后一行加上去是为了体现一个完整的 Sequence ,会更规范

 


LUT 表的介绍就到这里,这里分别介绍了标准 SPI 和 QSPI 的命令并用一条命令举例,其它的 Flash 命令序列也是一样,单线、4 线主要根据命令的详细时序编写即可。

 

参考资料:

《IMXRT1050RM》

《W25Q128JVSIQ datasheet-20180930》

★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论