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 线主要根据命令的详细时序编写即可。
参考资料:
评论