22 FlexSPI—读写外部 SPI NorFlash

文章目录

  • 22.1 SPI 协议简介
    • 22.1.1 SPI物理层
    • 22.1.2 协议
    • 22.1.3 CPOL/CPHA 及通讯模式
    • 22.1.4 扩展 SPI 协议
    • 22.1.5 SDR 和 DDR 模式
  • 22.2 RT1052 的 FlexSPI 特性及架构
    • 22.2.1 RT1052 的 FlexSPI 外设简介
    • 22.2.2 RT1052 的 FlexSPI 架构剖析
      • 22.2.2.1 通讯引脚
      • 22.2.2.2 指令查找表 LUT
      • 22.2.2.3 命令仲裁器
      • 22.2.2.4 IP 命令控制逻辑
      • 22.2.2.5 AHB 命令控制逻辑
      • 22.2.2.6 驱动时钟
  • 22.3 FlexSPI 初始化配置结构体
    • 22.3.1 rxSampleClock
    • 22.3.2 enableSckFreeRunning
    • 22.3.3 enableCombination
    • 22.3.4 enableDoze
    • 22.3.5 enableHalfSpeedAccess
    • 22.3.6 enableSckBDiffOpt
    • 22.3.7 enableSameConfigForAll
    • 22.3.8 seqTimeoutCycle
    • 22.3.9 ipGrantTimeoutCycle
    • 22.3.10 txWatermark
    • 22.3.11 rxWatermark
    • 22.3.12 ahbConfig 结构体
      • 22.3.12.1 enableAHBWriteIpTxFifo
      • 22.3.12.2 enableAHBWriteIpRxFifo
      • 22.3.12.3 ahbGrantTimeoutCycle
      • 22.3.12.4 ahbBusTimeoutCycle
      • 22.3.12.5 resumeWaitCycle
      • 22.3.12.6 buffer
      • 22.3.12.7 enableClearAHBBufferOpt
      • 22.3.12.8 enableAHBPrefetch
      • 22.3.12.9 enableAHBBufferable
      • 22.3.12.10 enableAHBCachable
    • 22.3.13 配置
      • 22.3.13.1 FLEXSPI_GetDefaultConfig
      • 22.3.13.2 FLEXSPI_Init
  • 22.4 FlexSPI 传输结构体详解
    • 22.4.1 deviceAddress
    • 22.4.2 port
    • 22.4.3 cmdType
    • 22.4.4 seqIndex
    • 22.4.5 SeqNumber
    • 22.4.6 data
    • 22.4.7 dataSize
    • 22.4.8 FLEXSPI_TransferBlocking
  • 22.5 FlexSPI 外部设备配置结构体
    • 22.5.1 flexspiRootClk
    • 22.5.2 isSck2Enabled
    • 22.5.3 flashSize
    • 22.5.4 CSIntervalUnit
    • 22.5.5 CSInterval
    • 22.5.6 CSHoldTime
    • 22.5.7 CSSetupTime
    • 22.5.8 dataValidTime
    • 22.5.9 columnspace
    • 22.5.10 ***enableWordAddress***
    • 22.5.11 AWRSeqIndex
    • 22.5.12 AWRSeqNumber
    • 22.5.13 ARDSeqIndex
    • 22.5.14 ARDSeqNumber
    • 22.5.15 AHBWriteWaitUnit
    • 22.5.16 AHBWriteWaitInterval
    • 22.5.17 enableWriteMask
    • 22.5.18 FLEXSPI_SetFlashConfig
  • 22.6 读写串行 FLASH
    • 22.6.1 硬件
    • 22.6.2 控制 FLASH 的命令
      • 22.6.2.1 FLASH 命令表
      • 22.6.2.2 读 ID 命令“JEDEC ID”
      • 22.6.2.3 3 字节地址和 4 字节地址命令
      • 22.6.2.4 Single/Dual/Quad 模式命令
    • 22.6.3 软件设计
      • 22.6.3.1 硬件相关宏定义
      • 22.6.3.2 FLASH 命令编码表
      • 22.6.3.4 IOMUXC 相关配置
      • 22.6.3.5 NorFlash_FlexSPI_ModeInit
        • 22.6.3.5.1 配置时钟
        • 22.6.3.5.2 关闭 DCache 功能
        • 22.6.3.5.3 初始化 FlexSPI 外设
        • 22.6.3.5.4 配置闪存功能参数
        • 22.6.3.5.5 更新查找表
      • 22.6.3.6 FlexSPI 初始化函数
        • 22.6.3.6.1 读取 FLASH 芯片的 Jedec Device ID
        • 22.6.3.6.2 FLASH 写使能
        • 22.6.3.6.3 读取 FLASH 当前状态
        • 22.6.3.6.4 FLASH 扇区擦除
        • 22.6.3.6.5 FLASH 的 Quad 模式页写入
        • 22.6.3.6.6 不定量数据写入
        • 22.6.3.6.7 FLASH 的 Quad 模式读取数据
      • 22.6.3.7 编写 IP 命令访问方式测试
      • 22.6.3.8 AHB 命令访问的查找表配置
      • 22.6.3.9 AHB命令访问方式相对应的测试代码
      • 22.6.3.10 main 函数

22.1 SPI 协议简介

22.1.1 SPI物理层

SPI 通讯使用 3 条总线及片选线,3 条总线分别为 SCK、MOSI、MISO。

SS( Slave Select)片选信号线,也称为 NSS、CS

  • 每个从设备都有独立的这一条 NSS 信号线
  • 当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平

SCK (Serial Clock):时钟信号线,用于通讯数据同步。

  • 它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样

22.1.2 协议

22 FlexSPI—读写外部 SPI NorFlash_第1张图片SPI 使用 MOSI 及 MISO 信号线来传输数据,使用 SCK 信号线进行数据同步。

  • MOSI 及 MISO 数据线在 SCK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。
  • 一般都会采用MSB 先行模式。
  • 数据在 SCK 的上升沿期间变化输出,在 SCK 的下降沿时被采样。
  • SPI 每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制。

22.1.3 CPOL/CPHA 及通讯模式

SPI 一共有四种通讯模式,它们的主要区别是总线空闲时 SCK 的时钟状态以及数据采样时刻。

  • 时钟极性 CPOL 是指 SPI 通讯设备处于空闲状态时,SCK 信号线的电平信号。CPOL=0 时,SCK 在空闲状态时为低电平,CPOL=1 时,则相反。
  • 时钟相位 CPHA 是指数据的采样的时刻
    • 当 CPHA=0 时,MOSI 或 MISO 数据线上的信号将会在SCK 时钟线的“奇数边沿”被采样。
    • 当 CPHA=1 时,数据线在 SCK 的“偶数边沿”采样。
      22 FlexSPI—读写外部 SPI NorFlash_第2张图片22 FlexSPI—读写外部 SPI NorFlash_第3张图片由 CPOL 及 CPHA 的不同状态,SPI 分成了四种模式。实际中采用较多的是“模式 0”与“模式 3”。
      22 FlexSPI—读写外部 SPI NorFlash_第4张图片

22.1.4 扩展 SPI 协议

为了适应更高速率的通讯需求,半导体厂商扩展 SPI 协议,主要发展出了 Dual/Quad/Octal SPI 协议,加上标准 SPI 协议(Single SPI),这四种协议的主要区别是数据线的数量及通讯方式
22 FlexSPI—读写外部 SPI NorFlash_第5张图片
扩展的三种 SPI 协议都是半双工的通讯方式,也就是说它们的数据线是分时进行收发数据的。

  • 双线 SPI(Dual SPI)的两根线都具有收发功能,但在同一时刻只能是发送或者是接收
  • 四线 SPI(Quad SPI)和八线 SPI(Octal SPI)与双线 SPI(Dual SPI)类似,只是数据线量的区别。

22.1.5 SDR 和 DDR 模式

SDR 模式(单倍速率 Single Data Rate)和 DDR 模式(双倍速率 DoubleData Rate)。

  • 在标准 SPI 协议的 SDR 模式下,只在 SCK 的单边沿进行数据传输,即一个 SCK时钟只传输一位数据
  • 在它 DDR 模式下,会在 SCK 的上升沿和下降沿都进行数据传输,即一个 SCK 时钟能传输两位数据,传输速率提高一倍。

22.2 RT1052 的 FlexSPI 特性及架构

RT1052 芯片也集成了专门用于 SPI 协议通讯的外设 FlexSPI、LPSPI

  • LPSPI 主要定位于通用的 SPI 通讯
  • FlexSPI 外设除了支持 SPI 通讯外还提供了很多与存储器相关的特性,所以 FlexSPI 外设通常用于与使用 SPI 协议的存储设备进行通讯。

22.2.1 RT1052 的 FlexSPI 外设简介

NXP 以 Flex 形容它的 SPI 外设是因为它使用起来非常灵活

  • Single/Dual/Quad/Octal 模式的传输(即 1/2/4/8 根数据线的传输)
  • 支持 SDR/DDR 通讯模式
    • 在 SDR 模式下,它仅支持 SPI 中的模式 0(CPOL=0,CPHA=0)
  • 支持控制 SPI 接口的串行 NOR/NAND Flash 设备、HyperBus 协议设备以及 FPGA 设备。
  • 支持读写单个串行 FLASH 以及读写多个并联的串行 FLASH 的模式
  • 支持把存储器地址映射至通过 AHB 总线读写,即后面说明的 AHB 命令模式。
  • 支持使用 DMA 进行访问,从而减少 CPU 的介入。
  • 支持以下多种模式以适配不同的功耗状态:模块关闭模式(Module Disable mode)、打盹儿模式(Doze mode)、停止模式(Stop mode)以及正常模式(Normal mode)。
  • 最多支持 4 个存储设备,每个存储设备最大容量为 4GB,连接多个存储设备时容量的总和也不能超过 4GB。

22.2.2 RT1052 的 FlexSPI 架构剖析

22 FlexSPI—读写外部 SPI NorFlash_第6张图片

22.2.2.1 通讯引脚

FlexSPI 外设包含有 A/B 两组 SPI 通讯接口,每组接口最多可外接 2 个设备,即 A1、A2、B1 和 B2。
RT1052 的 FlexSPI A 组引脚:
22 FlexSPI—读写外部 SPI NorFlash_第7张图片
这些引脚与外部 SPI Flash 设备的连接的方式
22 FlexSPI—读写外部 SPI NorFlash_第8张图片
当 FlexSPI 工作于 Octal 模式(八线 SPI)时,它会以 SIOB[3:0] 数据信号线作为高 4 位的数据线,此时仅 A 组可以工作,B 组不能使用。
22 FlexSPI—读写外部 SPI NorFlash_第9张图片

22.2.2.2 指令查找表 LUT

FlexSPI 外设中包含有一个指令查找表 LUT(Look Up Table),上图第 ② 部分 SEQ_CTL(序列控制逻辑)的主要内容

  • 用来预存储访问外部设备时可能使用到的指令
  • 对 FLASH 进行访问时,FlexSPI 会从查找表 LUT 中获取相应的指令然后通过 SPI 接口对 FLASH 发起通讯。

查找表 LUT 的构成:
22 FlexSPI—读写外部 SPI NorFlash_第10张图片

  • 第 ① 部分是查找表 LUT 视图,它表示查找表 LUT 有 0~N 个序列;
  • 第 ② 部分是序列视图,它表示 1 个序列中包含有 8 个指令
  • 第 ③ 部分是指令视图,表示指令由 opcode(指令编码)、num_pads(数据线的数目)、operand(指令参数) 三个寄存器域构成。

这些指令的存储位置是 FlexSPI 外设中的寄存器 LUT0~LUT63

  • 每个 LUT 寄存器可以缓存 2 个指令
  • 1 个指令序列(8 个指令)由 4 个寄存器构成
  • 这些寄存器构成了一个完整的 LUT 表

LUT寄存器
22 FlexSPI—读写外部 SPI NorFlash_第11张图片 OPCODE:指令编码

  • 由 FlexSPI 定义的一些基本指令码
    • 向 FLASH 发送控制命令的 CMD_SDR 指令 OPCODE 为 0x01;
    • 发送行地址到 FLASH 的指令 OPCODE 为 0x02等

NUM_PADS:进行 SPI 通讯时使用的数据线的数目

    1. 0x0:Single 模式
    1. 0x1:Dual 模式
    1. 0x2:Quad 模式
    1. 0x3:Octal 模式

OPERAND:指令参数

  • 部分 OPCODE 指令包含参数,这些参数就由 OPERAND 设定
  • 参数的具体作用由相应的 OPCODE 决定。

查找表 LUT 的常用指令
22 FlexSPI—读写外部 SPI NorFlash_第12张图片22 FlexSPI—读写外部 SPI NorFlash_第13张图片
查找表支持两套有同功能不同模式的指令

  • 如 CMD_SDR 和 CMD_DDR 的 OPCODE 为0x01 和 0x21,它们分别表示使用 SDR 模式和 DDR 模式的 CMD 指令,它们的功能一样,都是向 FLASH 发送命令代码。
  • 其它指令类似,大都有 SDR 和 DDR 模式。

数据线的数目由 NUM_PADS 指定。

  • 不同指令可以使用不同的数据线数目。
    • 一些 FLASH 存储器的命令只使用一根数据线(Single 模式)
    • 快速读写的命令则可支持 Dual、Quad 模式

OPERAND 参数在不同指令下作用不同:

  • 对于 CMD_SDR 指令,此时要发送的 FLASH 命令代码就是 CMD_SDR 指令的参数

    • 例如 W25Q256 型号的 FLASH 的读取 ID 命令代码为 0xAB,当 RT1052 要读取FLASH 的 ID 时,利用 CMD_SDR 指令同时把命令代码 0xAB 赋予到 OPERAND
  • 对于 RADDR_SDR 指令,功能是向 FLASH 发送要读写的存储单元地址,该地址由IPCR0 寄存器指定,同时 OPERAND 域用于指定地址的长度。

    • 例如部分 FLASH 的空间比较小,只使用 16 位来表示地址,那么该命令的 OPERAND 域的值就应为 16,对于 W25Q256这种地址为 24 或 32 位的存储器,OPERAND 域的值就应设置为 24 或 32。
  • 对于 DUMMY_SDR 指令,它的功能是释放 FlexSPI 对数据线的控制,在这种情况下可通过 OPERAND 指定该过程要占多少个 SCK的周期数。

数据传输指令的数据缓冲区位置分两种情况。WRITE_SDR 和 READ_SDR 指令分别用于向FLASH 写入和读取数据

  • 在 AHB 命令模式下:数据缓存在 AHB_TX_BUF(发送缓冲区)以及AHB_RX_BUF(接收缓冲区)中,此时要传输的字节数由 AHB 突发传输的大小和类型决定。
  • 在 IP 命令模式下:数据缓存在 IP_TX_FIFO(发送缓冲区)以及 IP_RX_FIFO(接收缓冲区)中,此时要传输的字节数可通过寄存器 IPCR1 的 DATSZ 域指定。

操作通常使用序列的形式并配合 STOP 指令(停止指令)使用

  • 以上说明的各个指令通常不会单独执行,而是组成一个指令序列,对于指令数不满 8 个的序列,需要使用 STOP 指令表示结束。

如一个使用 IP 命令模式的读取操作:

    1. 使用 CMD_SDR 指令向发送 FLASH 的读取命令,如 W25Q256 的 Quad 模式读取命令编码为 0x6B,此时 CMD_SDR 指令的 OPERAND 域为 0x6B;
    1. 使用 RADDR_SDR 指令发送要读取的 FLASH 存储单元地址,OPERAND 域的值为 24 表示使用 24 位的地址,而地址具体的值由寄存器 IPCR0 设定;
    1. 按照 FLASH 的 Quad 模式读取命令的要求发送占 8 个 SCK 时钟的 DUMMY 操作,此时使用 DUMMY_SDR 指令且 OPERAND 域的值设置为 8;
    1. 使用 READ_SDR 指令,开始接收 FLASH 的数据到 IP_RX_FIFO 中,要读取的字节数由寄存器 IPCR1 的 DATSZ 域指定;
    1. 由于使用的指令不足 8 个字节,在该序列的最后使用 STOP 指令表示停止,当 FlexSPI 执行到 STOP 指令时,会释放 SPI 的片选信号 CS,结束通讯。

22.2.2.3 命令仲裁器

上图第 ③ 部分是 ARB_CTL(仲裁器逻辑),它主要用来决定执行哪一套命令。

  • 其后有一个AHB_CTL(AHB 命令控制逻辑)和 IP_CTL(IP 命令控制逻辑),它们分别代表了内核对 FlexSPI的两种控制方式。
  • 仲裁器逻辑就是决定它们谁拥有对前面逻辑单元(SEQ_CTL 和 IO_CTL)的控制权。

22.2.2.4 IP 命令控制逻辑

第 ④ 部分 IP_CTL 是 IP 命令控制逻辑,它包含 IP_RX_FIFO 和 IP_TX_FIFO 用来缓冲收发的数据,它们均为 16*64Bits 大小。

  • IP_CTL 连接至 32 位的 ARM IP 总线,通过它可以向ARB_CTL(仲裁器逻辑)发送控制命令,从而利用 FlexSPI 访问外部 SPI 设备

IP 命令的控制流程如下:

  • (1) 往 IP_TX_FIFO 填充要传输的数据;
  • (2) 通过 IPCR0 寄存器设置要写入的 FLASH 内部存储单元的首地址,要传输的数据大小以及要执行的 LUT 命令序列的编号;
  • (3) 对寄存器 IPCMD 的 TRG 位置 1 触发 FlexSPI 访问;
  • (4) 检查寄存器 INTR 的 IPCMDDONE 位以等待至 FlexSPI 外设执行完该指令;
  • (5) 若执行的命令序列有会接收数据,那么接收到的数据会被缓存至 IP_RX_FIFO 中

22.2.2.5 AHB 命令控制逻辑

第⑤部分AHB_CTL是AHB命令控制逻辑

  • 它包含有128×64Bits大小的AHB_RX_BUF和 8×64Bits 大小的 AHB_TX_BUF 用来缓冲收发的数据
  • AHB_CTL 连接至 64 位的 AHBP 总线,通过它可以向 ARB_CTL(仲裁器逻辑)发送控制命令,从而 FlexSPI 访问外部 SPI 设备。

使用 AHB 命令的方式是直接访问 RT1052 内部的 0x600 0000-0x1000 0000 地址

  • 这些地址的读写访问会触发 FlexSPI 产生 SPI 控制时序,然后对连接的 FLASH 内部存储单元进行读写,这种功能称为地址映射。

例如

  • 把外部 NOR Flash 存储器的内部地址 0x0 映射到 RT1052 的 0x60000000 地址
  • 初始化好FlexSPI 后,我们直接使用指针读取 RT1052 的 0x60000000 地址
  • 自动触发 FlexSPI外设访问外部的 NOR Flash 存储器的 0x0 地址获得数据
  • 访问时它会自动使用 AHB_RX_BUF 及AHB_TX_BUF 缓冲数据。

AHB 命令仅支持对 FLASH 存储单元的读写访问,对 FLASH 存储器的工作模式或状态寄存器的读取需要使用 IP 命令实现。

22.2.2.6 驱动时钟

FlexSPI 外设的驱动时钟,它的 SCK 线的时钟信号是由 ipg_clk_sfck 提供的,即 FlexSPI 根时钟 FLEXSPI_CLK_ROOT
在这里插入图片描述FlexSPI 根时钟有 4 个可选输入来源:

  • semc_clk_root_pre:这是未经过 SEMC“时钟门”的 SEMC 根时钟 SEMC_CLK_ROOT
    • 如果选择本输入源的话,不打开 SEMC 的时钟门它也是可以正常输入到 FlexSPI 的。
  • pll3_sw_clk:该时钟来源即为 PLL3,常规配置为 480MHz。
  • PLL2 PFD2:该时钟常规配置为 396MHz。
  • PLL3 PFD0:该时钟常规配置为 720MHz。

选择的时钟源经过 FlexSPI 的时钟门之后,还有一个 3 位的分频器,它可对时钟源进行 1~8 分频,分频后得到 FlexSPI 根时钟 FLEXSPI_CLK_ROOT。

22.3 FlexSPI 初始化配置结构体

FlexSPI 的初始化结构体及函数定义在库文件“fsl_flexspi.h”及“fsl_flexspi.c”中

 /*! @brief FLEXSPI 初始化配置结构体 */
 typedef struct _flexspi_config {
 /*!< 选择读取 FLASH 使用的采样时钟源 */
 flexspi_read_sample_clock_t rxSampleClock;
 /*!< 是否使能 SCK 自由运行输出 */
 bool enableSckFreeRunning;
 /*!< 是否使能 PORT A 和 PORT B 的数据引脚组合 (SIOA[3:0] 和 SIOB[3:0]) 以支持FLASH 的 8 位模式 */
 bool enableCombination;

 /*!< 是否使能 doze 模式 */
 bool enableDoze;
 /*!< 是否使能 为半速率命令而对时钟 2 分频 的功能 */
 bool enableHalfSpeedAccess;
 /*!< 是否使能 SCKB 用作 SCKA 的差分时钟,当使能时, PORT B 的 FLASH 无法访问 */
 bool enableSckBDiffOpt;
 /*!< 是否使能对所有连接的设备使用同样的配置,当使能时,FLSHA1CRx 寄存器的配置会应用到所有设备 */
 bool enableSameConfigForAll;
 /*!< 命令序列执行的等待超时周期, ahbGrantTimeoutCyle*1024 个串行根时钟周期后超时 */
 uint16_t seqTimeoutCycle;
 /*!< IP 命令授予等待超时周期, ipGrantTimeoutCycle*1024 个 AHB 时钟周期后超时  */
 uint8_t ipGrantTimeoutCycle;
 /*!< FLEXSPI IP 发送水印值 */
 uint8_t txWatermark;
 /*!< FLEXSPI 接收水印值 */
 uint8_t rxWatermark;
		 struct {
				 /*!< 使能 AHB 总线对 IP TX FIFO 的写访问 */
				 bool enableAHBWriteIpTxFifo;
				 /*!< 使能 AHB 总线对 IP RX FIFO 的写访问 */
				 bool enableAHBWriteIpRxFifo;
				 /*!< AHB 命令授予等待超时周期,在 ahbGrantTimeoutCyle*1024 个 AHB 时钟周期后超时 */
				 uint8_t ahbGrantTimeoutCycle;
				 /*!< AHB 读写访问超时周期, ahbBusTimeoutCycle*1024 个 AHB 时钟后超时 */
				 uint16_t ahbBusTimeoutCycle;
				 /*!< 在暂停命令序列恢复之前空闲状态的等待周期, ahbBusTimeoutCycle 个 AHB时钟后超时 */
				 uint8_t resumeWaitCycle;
				 /*!< AHB 缓冲区信息 */
				 flexspi_ahbBuffer_config_t buffer[FSL_FEATURE_FLEXSPI_AHB_BUFFER_COUNT];
				 /*!< 是否使能当 FLEXSPI 返回停止模式响应时自动清除 AHB RX 和 TX 缓冲 */
				 bool enableClearAHBBufferOpt;
				 /*!< 是否使能 AHB 预读取特性,当使能时, FLEXSPI 会读取比当前 AHB 突发读取更多的数据 */
				 bool enableAHBPrefetch;
				 /*!< 是否使能 AHB 缓冲写访问的功能,当使能时, FLEXSPI 会在等待命令执行完成前就返回 */
				 bool enableAHBBufferable;
				 /*!< 是否使能 AHB 总线缓冲读访问的功能 */
				 bool enableAHBCachable;
		 } ahbConfig;
 } flexspi_config_t;

22.3.1 rxSampleClock

  • 寄存器位 MCR0[RXCLKSRC],本成员用于配置读取 FLASH 使用的采样时钟源,它是一个枚举类型变量
 /*! @brief 选择给读取 FLASH 使用的 FlexSPI 采样时钟源 */
 typedef enum _flexspi_read_sample_clock {
		 /*!< 伪读选通脉冲由 FlexSPI 控制器产生并在内部回环 */
		 kFLEXSPI_ReadSampleClkLoopbackInternally = 0x0U,
		 /*!< 伪读选通脉冲由 FlexSPI 控制器产生并从 DQS 引脚回环 */
		 kFLEXSPI_ReadSampleClkLoopbackFromDqsPad = 0x1U,
		 /*!< 使用 SCK 输出时钟以并从 SCK 引脚回环 */
		 kFLEXSPI_ReadSampleClkLoopbackFromSckPad = 0x2U,
		 /*!< Flash 提供读选通脉冲并从 DQS 引脚输入 */
		 kFLEXSPI_ReadSampleClkExternalInputFromDqsPad = 0x3U,
 } flexspi_read_sample_clock_t;

22.3.2 enableSckFreeRunning

  • 寄存器位 MCR0[SCKFREERUNEN],本成员用于配置是否使能 SCK 的输出自由运行(free-running)的功能
  • 该功能通常应用在与 FPGA 设备相关的应用中,它会以 SCK 作为参考时钟自己内部 PLL 的输入。

22.3.3 enableCombination

  • 寄存器位 MCR0[COMBINATIONEN],本成员用于配置是否使能图 22‑11 中的PORT A 和 PORT B 组合模式
  • 在组合模式中会同时使用 PORTA[3:0] 及 PORTB[3:0] 的数据线,通过这种方式,可以对有 8 根数据信号线的 FLASH 进行读写。

22.3.4 enableDoze

  • 寄存器位 MCR0[DOZEEN],本成员配置是否使能 Doze 模式
  • 使能了 doze 模式后,即使 RT1052 芯片处于低功耗 stop 运行状态时 FlexSPI 也能正常工作。

22.3.5 enableHalfSpeedAccess

  • 寄存器位 MCR0[HSEN],本成员配置是否使能对 SCK 时钟进行 2 分频以降低访问速率。

22.3.6 enableSckBDiffOpt

  • 寄存器位 MCR2[SCKBDIFFOPT],本成员配置是否把 SCKB 用作 SCKA 的差分时钟,使用 2 线差分的形式可以提高抗干扰的能力。
  • 不过此时 PORT B 就不能再用于 FLASH 的时钟信号,也就是无法使用了。

22.3.7 enableSameConfigForAll

  • 寄存器位 MCR2[SAMEDEVICEEN],本成员配置是否使能所有连接的设备都用同样的配置
  • 如果使能的话,FLASHA1CRx 寄存器的配置会被应用到所有的设备。

22.3.8 seqTimeoutCycle

  • 寄存器位 MCR1[SEQWAIT],本成员设置命令序列执行的等待超时周期
  • 设置结果为 seqTimeoutCycle*1024 个 FlexSPI 根时钟周期后超时,超时后可产生中断且会忽略 AHB 命令。若本成员设置为 0 则不使用本功能。

22.3.9 ipGrantTimeoutCycle

  • 寄存器位 MCR0 [IPGRANTWAIT],本成员配置 IP 命令授予等待超时周期
  • 触发 IP 命令后,若命令仲裁器在 ipGrantTimeoutCycle*1024 个 AHB 时钟周期后还不允许执行该命令,则可触发中断。
  • 本功能仅支持调试模式,使用时直接设置为默认值即可,且不能设置为 0!

22.3.10 txWatermark

  • 寄存器位 IPTXFCR [TXWMRK],本成员配置 IP 发送的水印值,单位为字节。
  • 当IP_TX_FIFO 的空余的程度大于或等于该水印值时,可触发 IPTXWE 中断
  • 同时也可触发 DMA 请求从而往 IP_TX_FIFO 填充要传输的数据。

22.3.11 rxWatermark

  • 寄存器位 IPRXFCR [RXWMRK],本成员配置接收的水印值,单位为字节。
  • 当IP_RX_FIFO 接收的数据超过该水印值时,可触发 IPRXWA 中断
  • 同时也可触发DMA 请求把数据从 IP_RX_FIFO 转移出去

22.3.12 ahbConfig 结构体

在 flexspi_config_t 中的一个 ahbConfig 结构体类型的成员,它主要用于配置 AHB 总线的各项参数:

22.3.12.1 enableAHBWriteIpTxFifo

  • 寄存器位 MCR0 [ATDFEN],配置是否使能 AHB 总线通过映射地址对 IP_TX_FIFO 的写访问。
    • 0x7F800000-0x11000400
  • 使能后,可通过 AHB 总线映射的地址向 IP_TX_FIFO 写入内容,此时通过 IP 总线写入 IP_TX_FIFO 的操作会被忽略,但不会出现错误标志
  • 若不使能,可通过 IP 总线写入 IP_TX_FIFO,此时通过 AHB 总线的写入操作会被忽略,且会产生错误标志。

22.3.12.2 enableAHBWriteIpRxFifo

  • 寄存器位 MCR0 [ARDFEN],配置是否使能 AHB 总线通过映射地址对 IP_RX_FIFO 的读访问。
    • 0x7FC00000-0x10000200
  • 与 enableAHBWriteIpTxFifo 的类似,用于控制使用 AHB 还是 IP 进行读操作。

22.3.12.3 ahbGrantTimeoutCycle

  • 寄存器位MCR0[AHBGRANTWAIT],与ipGrantTimeoutCycle成员类似,ahbGrantTimeoutCycle 用于配置 AHB 命令授予等待超时周期
  • 触发 AHB 命令后,若命令仲裁器在在 ahbGrantTimeoutCyle*1024 个 AHB 时钟周期后还不允许执行该命令,会触发中断。
    • 注意本功能同样仅支持调试模式,使用时直接设置为默认值即可,且不能设置为 0

22.3.12.4 ahbBusTimeoutCycle

  • 寄存器位 MCR1 [AHBBUSWAIT],配置 AHB 读写访问超时周期
  • 在超过 ahbBus-TimeoutCycle*1024 个 AHB 时钟周期后,仍没接收到数据或没发送出数据则表示访问超时,触发时可产生 AHBBUSTIMEOUT 中断。

22.3.12.5 resumeWaitCycle

  • 寄存器位 MCR2[RESUMEWAIT],配置在暂停命令序列恢复之前,等待空闲状态的周期
  • 等待超过 resumeWaitCycle 个 AHB 时钟后超时。

22.3.12.6 buffer

寄存器位 AHBRXBUFxCR0 [PRIORITY、MSTRID、BUFSZ](其中 x 可以为 0~3)。配置 AHB 缓冲区的一些信息,它是一个 flexspi_ahbBuffer_config_t 结构体

  • 包含缓冲区的优先级、主索引号以及缓冲区的大小
1 typedef struct _flexspi_ahbBuffer_config {
2 uint8_t priority; /* 优先级 */
3 uint8_t masterIndex; /* 主索引号 */
4 uint16_t bufferSize; /* 缓冲区大小 */
5 } flexspi_ahbBuffer_config_t;

buffer[FSL_FEATURE_FLEXSPI_AHB_BUFFER_COUNT];

  • 该语句中的宏 FSL_FEATURE_FLEXSPI_AHB_BUFFER_COUNT 值为 4(即 RT1052芯片 AHB Rx Buffer 的数量)

22.3.12.7 enableClearAHBBufferOpt

寄存器位 MCR2[CLRAHBBUFOPT],配置是否使能自动清除 AHB RX 和 TX 缓冲的功能

  • 使能后,当 FLEXSPI 退出停止模式时自动清除。

22.3.12.8 enableAHBPrefetch

寄存器位 AHBCR[PREFETCHEN],配置是否使能 AHB 预读取特性,

  • 当使能时,FLEXSPI 会读取比当前 AHB 突发读取方式更多的数据,提高效率。

22.3.12.9 enableAHBBufferable

寄存器位AHBCR[BUFFERABLEEN],配置是否使能AHB缓冲写访问的功能

  • 使能时,FLEXSPI 会在命令执行完成前就返回
  • 否则会等待至 AHB 总线准备好并把数据传输至外部设备且命令执行完成后才返回。

22.3.12.10 enableAHBCachable

寄存器位 AHBCR [CACHABLEEN],配置是否使能 AHB 总线缓冲读访问的功能。

22.3.13 配置

先直接调用库函数FLEXSPI_GetDefaultConfig 赋予常用默认配置,然后再针对性地把初始化配置结构体修改成自己需要的内容。

22.3.13.1 FLEXSPI_GetDefaultConfig

FLEXSPI_GetDefaultConfig 函数的实现:

/*!
2 * @brief 获取 FLEXSPI 的常用默认配置
3 *
4 * @param FLEXSPI 配置结构体
5 */
6 void FLEXSPI_GetDefaultConfig(flexspi_config_t *config)
7 {
8 config->rxSampleClock = kFLEXSPI_ReadSampleClkLoopbackInternally;
9 config->enableSckFreeRunning = false;
10 config->enableCombination = false;
11 config->enableDoze = true;
12 config->enableHalfSpeedAccess = false;
13 config->enableSckBDiffOpt = false;
14 config->enableSameConfigForAll = false;
15 config->seqTimeoutCycle = 0xFFFFU;
16 config->ipGrantTimeoutCycle = 0xFFU;
17 config->txWatermark = 8;
18 config->rxWatermark = 8;
19 config->ahbConfig.enableAHBWriteIpTxFifo = false;
20 config->ahbConfig.enableAHBWriteIpRxFifo = false;
21 config->ahbConfig.ahbGrantTimeoutCycle = 0xFFU;
22 config->ahbConfig.ahbBusTimeoutCycle = 0xFFFFU;
23 config->ahbConfig.resumeWaitCycle = 0x20U;
24 memset(config->ahbConfig.buffer, 0, sizeof(config->ahbConfig.buffer));
25 config->ahbConfig.enableClearAHBBufferOpt = false;
26 config->ahbConfig.enableAHBPrefetch = false;
27 config->ahbConfig.enableAHBBufferable = false;
28 config->ahbConfig.enableAHBCachable = false;

调用以上函数对初始化配置结构体赋予默认值并针对自己的需求修改后,可以通过调用FLEXSPI_Init 函数根据结构体的配置值向寄存器写入配置,完成 FlexSPI 外设的初始化

22.3.13.2 FLEXSPI_Init

1 /*!
2 * @brief 初始化 FLEXSPI 外设和内部状态
3 *
4 * 本函数使能了 FLEXSPI 的时钟并根据输入的参数配置 FLEXSPI 外设
5 * 在使用 FLEXSPI 操作前应调用本函数
6 *
7 * @param base FLEXSPI 外设基地址
8 * @param config FLEXSPI 初始化配置结构体
9 */
10 void FLEXSPI_Init(FLEXSPI_Type *base, const flexspi_config_t *config);

本函数时直接通过 base 参数指定要初始化哪个 FlexSPI

  • 不过在 RT1052 中只有一个 FLexSPI外设
  • 函数保留本参数主要是为了兼容以后可能推出的、具有多个该外设的芯片

函数的第 2个参数即为 flexspi_config_t 初始化配置结构体

  • 调用时把赋值好的结构体变量作为参数输入即可

22.4 FlexSPI 传输结构体详解

NXP 的软件库中还提供了传输结构体flexspi_transfer_t 和传输函数以简化 FlexSPI 的数据通讯

  • fsl_flexspi.h 文件
  • 使用这样的函数即可控制 FlexSPI 执行查找表 LUT 里的指令
1 /*! @brief FLEXSPI 传输结构体 */
2 typedef struct _flexspi_transfer {
3 uint32_t deviceAddress; /*!< 要操作的设备地址 */
4 flexspi_port_t port; /*!< 要操作的端口 */
5 flexspi_command_type_t cmdType; /*!< 要执行的命令类型 */
6 uint8_t seqIndex; /*!< 命令的序列 ID */
7 uint8_t SeqNumber; /*!< 命令序列的数量 */
8 uint32_t *data; /*!< 数据缓冲区 */
9 size_t dataSize; /*!< 传输数据的字节数 */
10 } flexspi_transfer_t;

22.4.1 deviceAddress

寄存器位 IPCR0[SFAR],这用于设置 IP 指令要访问的 FLASH 设备的内部地址。

  • 如读命令中要读取地址 0x1000 的内容,那么控制时这个 deviceAddress 的值就应赋值为 0x1000

22.4.2 port

指定要操作的 FLASH 端口,这是一个 flexspi_port_t 枚举类型

1 /*! @brief 选择要操作的 FLEXSPI 端口 */
2 typedef enum _flexspi_port {
3 kFLEXSPI_PortA1 = 0x0U, /*!< 使用 A1 端口访问 FLASH */
4 kFLEXSPI_PortA2 = 0x1U, /*!< 使用 A2 端口访问 FLASH */
5 kFLEXSPI_PortB1 = 0x2U, /*!< 使用 B1 端口访问 FLASH */
6 kFLEXSPI_PortB2 = 0x3U, /*!< 使用 B2 端口访问 FLASH */
7 } flexspi_port_t;

22.4.3 cmdType

用于告知要执行的命令是什么类型,它是一个 flexspi_command_type_t 枚举类型

  • 可选值分别为命令操作、配置设备模式操作、读操作以及写操作
1 typedef enum _flexspi_command_type {
2 kFLEXSPI_Command, /*!< 命令操作 , TX 和 Rx buffer 都被忽略 */
3 kFLEXSPI_Config, /*!< 配置设备模式 , TX fifo 的大小由 LUT 设定 */
4 kFLEXSPI_Read, /*!< 读操作 , 只有 RX Buffer 有效 */
5 kFLEXSPI_Write, /*!< 写操作 , 只有 TX Buffer 有效 */
6 } flexspi_command_type_t;

22.4.4 seqIndex

寄存器位 IPCR1[ISEQID],本成员是指令的序列 ID,即要执行的指令序列在 LUT中的编号

  • FlexSPI 外设通过该配置在 LUT 中找到对应的指令序列执行。

22.4.5 SeqNumber

寄存器位 IPCR1[ISEQNUM],本成员指要执行 LUT 指令序列的数量。

22.4.6 data

本成员是一个指针,它指向要发送的数据或缓存接收到数据的缓冲区

  • 定义一个数组用于缓冲。
  • 对于要发送的数据,指针中的数据最终会被写入到 TFDR0-TFDR31 寄存器(IP_TX_FIFO 寄存器)
  • 接收数据时,会从 RFDR0-RFDR31 寄存器(IP_RX_FIFO 寄存器)中获取数据存储到指针

22.4.7 dataSize

寄存器位 IPCR1[IDATSZ],本成员用于指定要传输数据的字节数

  • 该寄存器配置域是 16 位的,所以一次传输最多不能超过 65535 个字节。

22.4.8 FLEXSPI_TransferBlocking

设置好传输结构体后,可以调用库函数 FLEXSPI_TransferBlocking 或FLEXSPI_TransferNonBlocking 开始数据传输。

  • 前者在通讯的整个过程中会阻塞代码,当整个传输过程完成时才退出函数
  • 后者会开启中断传输,调用函数后它会根据结构体配置寄存器,然后就退出函数

无论使用哪个函数,这都是通过 IP 命令的形式来控制 FlexSPI 外设。

  • 使用 AHB 命令对 FLASH 存储单元进行读写时不需要使用这些函数,直接对映射的 RT1052 内部地址读写即可
  • 不过 AHB命令不支持对存储单元读写外的其它操作。

22.5 FlexSPI 外部设备配置结构体

FlexSPI 与外部设备通讯时常常需要与设备协调通讯的时序

  • 如时钟频率、数据有效时间等内容
  • NXP 软件库提供了结构体类型 flexspi_device_config_t 专门用于配置这些参数
/*! @brief 外部设备配置事项 */
2 typedef struct _flexspi_device_config {
3 uint32_t flexspiRootClk; /*!< FLEXSPI 串行根时钟频率 */
4 bool isSck2Enabled; /*!< FLEXSPI 是否使用 SCK2 */
5 uint32_t flashSize; /*!< Flash 的大小,单位为 KByte */
6 /*!< CS 间隔单位 , 1 或 256 个周期 */
7 flexspi_cs_interval_cycle_unit_t CSIntervalUnit;
8 /*!< CS 线断言间隔,通过多个 CS 间隔单位来获取 CS 线断言间隔周期 */
9 uint16_t CSInterval;
10 uint8_t CSHoldTime; /*!< CS 线保持时间 */
11 uint8_t CSSetupTime; /*!< CS 线建立时间 */
12 uint8_t dataValidTime; /*!< 对外部设备的数据有效时间 */
13 uint8_t columnspace; /*!< 列空间大小 */
14 bool enableWordAddress; /*!< 是否使能字( 4 字节)地址 */
15 uint8_t AWRSeqIndex; /*!< AHB 写命令的 AHB 序列 ID */
16 uint8_t AWRSeqNumber; /*!< AHB 写命令的序列数目 */
17 uint8_t ARDSeqIndex; /*!< AHB 读命令序列 ID */
18 uint8_t ARDSeqNumber; /*!< AHB 读命令的序列数目 */
19 /*!< AHB 写等待单位 */
20 flexspi_ahb_write_wait_unit_t AHBWriteWaitUnit;
21 /*!< AHB 写等待间隔,通过多个 AHB 写间隔单位来完成 AHB 写等待周期 */
22 uint16_t AHBWriteWaitInterval;
23 /*!< 是否使能 FLEXSPI 写外部设备时驱动 DQS 位作为写掩码 */
24 bool enableWriteMask;
25 } flexspi_device_config_t;

22.5.1 flexspiRootClk

用于告知库函数通讯中使用的 FlexSPI 的根时钟频率

  • 用于库函数根据该频率的大小调整其它与时钟相关的参数而已

22.5.2 isSck2Enabled

配置是否使能 FlexSPI 的 SCK2 时钟,在 RT1052 中没有 SCK2 引脚,所以本成员值直接设置成 false

22.5.3 flashSize

寄存器位 FLSHxxCR0[FLSHSZ](其中 xx 可为 A1、A2、B1、B2,下同)

  • 本成员用于配置外接 FLASH 存储器的容量大小,单位为 Kbyte
  • 单个 FLASH 存储器容量不超过 4GB

22.5.4 CSIntervalUnit

寄存器位 FLSHxxCR1[CSINTERVALUNIT]

  • 用于配置 CS 信号线间隔的时间单位(即下面 CSInterval 成员的单位)
  • 枚举变量,可取的值为 1 个 SCK 周期(kFLEXSPI_CsIntervalUnit1SckCycle)和 256 个 SCK 周期(kFLEXSPI_CsIntervalUnit256SckCycle)。

22.5.5 CSInterval

寄存器位 FLSHxxCR1[CSINTERVAL]

  • 用于配置 CS 信号线有效与无效切换的最小时间间隔,单位为上面 CSIntervalUnit 成员的配置。
  • 某些 FLASH 会对 CS信号线有效与无效切换的最小时间作出要求,若无该要求的直接配置成 0 即可

22.5.6 CSHoldTime

寄存器位 FLSHxxCR1[TCSH],本成员用于设定 CS 信号线的保持时间,单位为FlexSPI 根时钟周期。

22.5.7 CSSetupTime

寄存器位 FLSHxxCR1[TCSS],本成员用于设定 CS 信号线的建立时间,单位为FlexSPI 根时钟周期。

22.5.8 dataValidTime

寄存器 DLLACR 和 DLLBCR,本成员用于配置通讯中的数据有效时间,单位为纳秒。

22.5.9 columnspace

寄存器位 FLSHxxCR1[CAS],本成员用于配置列地址的宽度,单位为 bit。

  • 控制具有行地址和列地址的 FLASH 存储器时,要使用本成员设置列地址的宽度,若 FLASH 不含列地址则把它设置为 0。

22.5.10 enableWordAddress

寄存器位 FLSHxxCR1[WA],配置是否使能 4 字节可寻址功能,使能后会以 16 位的数据格式对 FLASH 进行访问。

22.5.11 AWRSeqIndex

寄存器位 FLSHxxCR2[AWRSEQID]

  • 配置 AHB 写命令在查找表 LUT 中的序列 ID
  • 通过向映射的地址写操作触发 AHB 写命令时,它会执行查找表 LUT 中的哪个序列

22.5.12 AWRSeqNumber

寄存器位 FLSHxxCR2[AWRSEQNUM],本成员配置 AHB 写命令要执行的序列数目。

22.5.13 ARDSeqIndex

寄存器位 FLSHxxCR2[ARDSEQID],本成员配置 AHB 读命令在查找表 LUT 中的序列 ID,即 AHB 命令读触发时执行的序列。

22.5.14 ARDSeqNumber

寄存器位 FLSHxxCR2[ARDSEQNUM],本成员配置 AHB 读命令的序列数目。

22.5.15 AHBWriteWaitUnit

寄存器位 FLSHxxCR2[AWRWAITUNIT],本成员配置 AHB 写等待时间的单位

  • 即后面结构体成员 AHBWriteWaitInterval 数值的单位
  • AHBWriteWaitUnit 是一个枚举变量
1 /*! @brief FLEXSPI 写等待的时间单位(寄存器位 AWRWAIT 的单位) */
2 typedef enum _flexspi_ahb_write_wait_unit {
3 /*!< 单位为 2 、 8 、 32 、 128 、 512 、 2048 、 8192 、 32768 个 AHB 时钟周期 */
4 kFLEXSPI_AhbWriteWaitUnit2AhbCycle = 0x0U,
5 kFLEXSPI_AhbWriteWaitUnit8AhbCycle = 0x1U,
6 kFLEXSPI_AhbWriteWaitUnit32AhbCycle = 0x2U,
7 kFLEXSPI_AhbWriteWaitUnit128AhbCycle = 0x3U,
8 kFLEXSPI_AhbWriteWaitUnit512AhbCycle = 0x4U,
9 kFLEXSPI_AhbWriteWaitUnit2048AhbCycle = 0x5U,
10 kFLEXSPI_AhbWriteWaitUnit8192AhbCycle = 0x6U,
11 kFLEXSPI_AhbWriteWaitUnit32768AhbCycle = 0x7U,
12 } flexspi_ahb_write_wait_unit_t;

22.5.16 AHBWriteWaitInterval

寄存器位 FLSHxxCR2[AWRWAIT],本成员配置 AHB 写等待时间值

  • 执行 AHB写指令给 FLASH 传输完要写入的数据后,通常都必须等待一定的时间让 FLASH存储器把数据写入到它内部的存储单元,在此期间 FLASH 会忽略除读状态寄存器外的所有访问.
  • 使用 IP 命令时可通过读 FLASH 的状态寄存器确认写入完成, AHB 命令没有这样的功能,所以需要控制好写入操作后要等待的时间。
  • 总的等待时间会被设置为 AHBWriteWaitInterval*AHBWriteWaitUnit 个AHB 时钟周期

22.5.17 enableWriteMask

寄存器 FLSHCR4,本成员用于设置 FlexSPI 写外部设备时是否使能驱动 DQS 位作为掩码,这种功能在访问数据宽度为 16 位时用于地址对齐。

22.5.18 FLEXSPI_SetFlashConfig

对结构体赋值完后,调用库函数 FLEXSPI_SetFlashConfig,它会根据以上参数把配写入到特定的FlexSPI 的端口的 FLSH xxCR 0、FLSH xxCR 1、FLSH xxCR 2 和 DLLA CR 等寄存器中

1 /*!
2 * @brief 配置连接设备的参数
3 *
4 * 本函数配置连接设备相关的参数,例如大小,命令等,
5 * @param base FLEXSPI 外设基地址
6 * @param config Flash 外部设备配置结构体
7 * @param port 要操作的 FLEXSPI 端口
8 */
9 void FLEXSPI_SetFlashConfig(FLEXSPI_Type *base,
10 flexspi_device_config_t *config,
11 flexspi_port_t port);

22.6 读写串行 FLASH

本节以一种使用 QSPI(四线 SPI)通讯的串行 FLASH W25Q256 存储芯片进行读写

22.6.1 硬件

22 FlexSPI—读写外部 SPI NorFlash_第14张图片相关信号与 RT1052 的引脚连接
22 FlexSPI—读写外部 SPI NorFlash_第15张图片 FLASH 的信号连接表
22 FlexSPI—读写外部 SPI NorFlash_第16张图片FLASH 芯片的 IO0~IO3 引脚在某些模式下会有第二功能

  • 在标准 SPI 模式下,SIO_IO0 用于MOSI;SO_IO1 用于 MISO;WP_IO2 可控制写保护功能,当该引脚为低电平时,禁止写入数据;HOLD/RESET_IO3 可用于保持或恢复通讯

22.6.2 控制 FLASH 的命令

22.6.2.1 FLASH 命令表

22 FlexSPI—读写外部 SPI NorFlash_第17张图片如上

  • 带括号的字节参数,方向为 FLASH 向主机传输,即命令响应
  • 不带括号的则为主机向 FLASH 传输
  • “A0~A23”指 FLASH 芯片内部存储器组织的地址;
  • “M0~M7”为厂商号(MANUFACTURER ID);
  • ID0-ID15”为 FLASH 芯片的 ID;
  • “D0~D7”为 FLASH 内部存储矩阵的内容。

22.6.2.2 读 ID 命令“JEDEC ID”

通过读取设备 ID 来测试硬件是否连接正常,或用于识别设备。

  • 命令执行正常时,W25Q256 会返回“0xEF4019”这样的内容

22 FlexSPI—读写外部 SPI NorFlash_第18张图片
使用 FlexSPI 控制时,我们会把以上“JEDEC 命令”的执行过程定义成查找表 LUT 中的序列,过程如下
定义查找表 LUT0 中的指令 0

  • 向 LUT 寄存器写入指令,它的 OPCOD 域为 CMD_SDR,即 0x01
  • 寄存器的NUM_PADS 域使用 Single 模式,即赋值 0x01
  • 指令参数寄存器域 OPERAND 的内容为0x9F,即 FLASH 存储器“JEDEC 命令”定义的编码;

定义查找表LUT0中的指令1

  • 向LUT寄存器写入后续指令,它的OPCOD域为READ_SDR,即 0x09
  • 寄存器的NUM_PADS 域使用 Single 模式,即赋值 0x01
  • 该指令参数寄存器域 OPERAND 的内容可为任意值

定义 FlexSPI 传输结构体

  • 对该结构体的成员 seqIndex 赋值,该值指向为上面“JEDEC 命令”序列在 LUT 查找表中的位置
  • 对结构体成员 dataSize 赋值为 3,表示可接收 3 个字节内容

执行 FLEXSPI_TransferNonBlocking 函数开始传输

  • 传输完成后 FLASH 返回的内容会被存储在 FlexSPI 传输结构体的 data 成员指向的缓冲区中

22.6.2.3 3 字节地址和 4 字节地址命令

“Read Data”和“Read Data with 4-Byte Address”,这是 W25Q256 中典型的 3 字节(24 位)和 4 字节(32 位)地址宽度模式的命令,功能都是读取数据
22 FlexSPI—读写外部 SPI NorFlash_第19张图片
进行数据读写或擦除时,需要给 FLASH 指定目标地址,以上是Read Data的时序图,命令编码分别为 0x03 和 0x13。

  • 传输的地址宽度分别为 3 字节和 4 字节

FLASH 中每个地址编号表示 1 个字节的存储单元

  • 3 字节的地址可以表示 2 24 =2 4 2 10 2 10 =1610241024=16M 字节的存储空间
  • W25Q256 型号的 FLASH 总存储空间为32MB,使用 3 字节长度的地址是无法访问 W25Q256 超过 16M 字节后的存储单元
  • 4 字节长度的地址则没有这个问题,它支持访问最大 32GB 的存储空间

W25Q256型号的FLASH支持3字节访问主要是为了兼容它的低型号芯片

  • 如W25Q128、W25Q64型号的芯片容量分别为16M字节和8M字节,它们的访问命令都使用3字

22.6.2.4 Single/Dual/Quad 模式命令

不同的模式对应有不同的访问命令,如页写操作:

  • 4字节 Single 模式的“Page Program”命令编码为 0x12
  • Quad 模式下该命令编码为 0x34

22 FlexSPI—读写外部 SPI NorFlash_第20张图片
二者区别
22 FlexSPI—读写外部 SPI NorFlash_第21张图片
Quad模式:
22 FlexSPI—读写外部 SPI NorFlash_第22张图片两种模式下的命令编码和存储单元的地址都是使用 1 根数据信号线传输的

  • 区别在传输数据时 Quad 模式下使用 4 根数据线,它在两个 CLK 时钟驱动下就传输完了一个字节的内容

22.6.3 软件设计

22.6.3.1 硬件相关宏定义

/*! @brief FlexSPI 引脚定义 */
2 #define NORFLASH_SS_IOMUXC IOMUXC_GPIO_SD_B1_06_FLEXSPIA_SS0_B
3 #define NORFLASH_SCLK_IOMUXC IOMUXC_GPIO_SD_B1_07_FLEXSPIA_SCLK
4 #define NORFLASH_DATA00_IOMUXC IOMUXC_GPIO_SD_B1_08_FLEXSPIA_DATA00
5 #define NORFLASH_DATA01_IOMUXC IOMUXC_GPIO_SD_B1_09_FLEXSPIA_DATA01
6 #define NORFLASH_DATA02_IOMUXC IOMUXC_GPIO_SD_B1_10_FLEXSPIA_DATA02
7 #define NORFLASH_DATA03_IOMUXC IOMUXC_GPIO_SD_B1_11_FLEXSPIA_DATA03

22.6.3.2 FLASH 命令编码表

1 /* 使用的 FLASH 地址宽度,单位: bit */
2 #define FLASH_ADDR_LENGTH 32
3
4 /* FLASH 常用命令 */
5 #define W25Q_WriteEnable 0x06
6 #define W25Q_WriteDisable 0x04
7 #define W25Q_ReadStatusReg 0x05
8 #define W25Q_WriteStatusReg 0x01
9 #define W25Q_ReadData 0x03
10 #define W25Q_ReadData_4Addr 0x13
11 #define W25Q_FastReadData 0x0B
12 #define W25Q_FastReadData_4Addr 0x0C
13 #define W25Q_FastReadDual 0x3B
14 #define W25Q_FastReadDual_4Addr 0x3C
15 #define W25Q_FastReadQuad 0x6B
16 #define W25Q_FastReadQuad_4Addr 0x6C
17 #define W25Q_PageProgram 0x02
18 #define W25Q_PageProgram_4Addr 0x12
19 #define W25Q_PageProgramQuad 0x32
20 #define W25Q_PageProgramQuad_4Addr 0x34
21 #define W25Q_BlockErase 0xD8
22 #define W25Q_BlockErase_4Addr 0xDC
23 #define W25Q_SectorErase 0x20
24 #define W25Q_SectorErase_4Addr 0x21
25 #define W25Q_ChipErase 0xC7
26 #define W25Q_PowerDown 0xB9
27 #define W25Q_ReleasePowerDown 0xAB
28 #define W25Q_DeviceID 0xAB
29 #define W25Q_ManufactDeviceID 0x90
30 #define W25Q_JedecDeviceID 0x9F
31 /* 其它 */
32 #define FLASH_ID 0X18
33 #define FLASH_JEDECDEVICE_ID 0XEF4019

本文访问FLASH 全部直接采用 4 字节地址的 Quad 模式命令

  • 即带“Quad”和“4Addr”后缀的宏
  • 定义的宏 FLASH_ADDR_LENGTH用于表示地址的宽度,其值为 32 位,即 4 字节

22.6.3.4 IOMUXC 相关配置

 1 /********************* 第 2 部分中使用的宏 **********************/
2 /* FLEXSPI 的引脚使用同样的 PAD 配置 */
3 #define FLEXSPI_PAD_CONFIG_DATA (SRE_1_FAST_SLEW_RATE| \
4 DSE_6_R0_6| \
5 SPEED_3_MAX_200MHz| \
6 ODE_0_OPEN_DRAIN_DISABLED| \
PKE_1_PULL_KEEPER_ENABLED| \
8 PUE_0_KEEPER_SELECTED| \
9 PUS_0_100K_OHM_PULL_DOWN| \
10 HYS_0_HYSTERESIS_DISABLED)
11 /* 配置说明 : */
12 /* 转换速率 : 转换速率快
13 驱动强度 : R0/6
14 带宽配置 : max(200MHz)
15 开漏配置 : 关闭
16 拉 / 保持器配置 : 使能
17 拉 / 保持器选择 : 保持器
18 上拉 / 下拉选择 : 100K 欧姆下拉 ( 选择了保持器此配置无效 )
19 滞回器配置 : 禁止 */
20
21 /************************ 第 1 部分 ****************************/
22 /**
23 * @brief 初始化 NORFLASH 相关 IOMUXC 的 MUX 复用配置
24 * @param 无
25 * @retval 无
26 */
27 static void FlexSPI_NorFlash_IOMUXC_MUX_Config(void)
28 {
29 /* FlexSPI 通讯引脚 */
30 IOMUXC_SetPinMux(NORFLASH_SS_IOMUXC, 1U);
31 IOMUXC_SetPinMux(NORFLASH_SCLK_IOMUXC, 1U);
32 IOMUXC_SetPinMux(NORFLASH_DATA00_IOMUXC, 1U);
33 IOMUXC_SetPinMux(NORFLASH_DATA01_IOMUXC, 1U);
34 IOMUXC_SetPinMux(NORFLASH_DATA02_IOMUXC, 1U);
35 IOMUXC_SetPinMux(NORFLASH_DATA03_IOMUXC, 1U);
36 }
37
38 /************************ 第 2 部分 ****************************/

39 /**
40 * @brief 初始化 NORFLASH 相关 IOMUXC 的 PAD 属性配置
41 * @param 无
42 * @retval 无
43 */
44 static void FlexSPI_NorFlash_IOMUXC_PAD_Config(void)
45 {
46 /* FlexSPI 通讯引脚使用同样的属性配置 */
47 IOMUXC_SetPinConfig(NORFLASH_SS_IOMUXC, FLEXSPI_PAD_CONFIG_DATA);
48 IOMUXC_SetPinConfig(NORFLASH_SCLK_IOMUXC, FLEXSPI_PAD_CONFIG_DATA);
49 IOMUXC_SetPinConfig(NORFLASH_DATA00_IOMUXC, FLEXSPI_PAD_CONFIG_DATA);
50 IOMUXC_SetPinConfig(NORFLASH_DATA01_IOMUXC, FLEXSPI_PAD_CONFIG_DATA);
51 IOMUXC_SetPinConfig(NORFLASH_DATA02_IOMUXC, FLEXSPI_PAD_CONFIG_DATA);
52 IOMUXC_SetPinConfig(NORFLASH_DATA03_IOMUXC, FLEXSPI_PAD_CONFIG_DATA);
53 }

第1部分

  • 在本代码中开启了引脚的 SION 功能

第 2 部分
配置控制 FLASH的 FlexSPI 外设相关引脚的 PAD 属性

  • 使用了“快转换速率”以及“200MHz 带宽”的配置

22.6.3.5 NorFlash_FlexSPI_ModeInit

模式初始化函数

  • 对 FlexSPI 外设模式的时钟、模式、FLASH 参数以及查找表 LUT 的配置的内容我们都编写进了 NorFlash_FlexSPI_ModeInit 函数
/**
2 * @brief 初始化 NorFlash 使用的 FlexSPI 外设模式及时钟
3 * @param 无
4 * @retval 无
5 */
6 static void NorFlash_FlexSPI_ModeInit(void)
7 {
8 flexspi_config_t config;
9 /************************ 第 1 部分 ****************************/
10 const clock_usb_pll_config_t g_ccmConfigUsbPll = {.loopDivider = 0U};
11
12 /* 初始化 USB1PLL ,即 PLL3 , loopDivider=0 ,
13 所以 USB1PLL=PLL3 = 24*20 = 480MHz */
14 CLOCK_InitUsb1Pll(&g_ccmConfigUsbPll);
15 /* 设置 PLL3 PFD0 频率为: PLL3*18/24 = 360MHZ. */
16 CLOCK_InitUsb1Pfd(kCLOCK_Pfd0, 24);
17 /* 选择 PLL3 PFD0 作为 flexspi 时钟源
18 00b derive clock from semc_clk_root_pre
19 01b derive clock from pll3_sw_clk
20 10b derive clock from PLL2 PFD2
21 11b derive clock from PLL3 PFD0 */
22 CLOCK_SetMux(kCLOCK_FlexspiMux, 0x3);
23 /* 设置 flexspiDiv 分频因子,
24 得到 FLEXSPI_CLK_ROOT = PLL3 PFD0/(flexspiDiv+1) = 120M. */
25 CLOCK_SetDiv(kCLOCK_FlexspiDiv, 2);
26
27 /************************ 第 2 部分 ****************************/
28 /* 关闭 DCache 功能 */
29 SCB_DisableDCache();
30
31 /************************ 第 3 部分 ****************************/
32 /* 获取 FlexSPI 常用默认设置 */
33 FLEXSPI_GetDefaultConfig(&config);
34 /* 允许 AHB 预读取的功能 */
35 config.ahbConfig.enableAHBPrefetch = true;
36 /* 写入配置 */
37 FLEXSPI_Init(FLEXSPI, &config);
38
39 /************************ 第 4 部分 ****************************/
40 /* 根据串行闪存功能配置闪存设置 */
41 FLEXSPI_SetFlashConfig(FLEXSPI, &deviceconfig, kFLEXSPI_PortA1);
42
43 /************************ 第 5 部分 ****************************/
44 /* 更新查找表 */
45 FLEXSPI_UpdateLUT(FLEXSPI, 0, customLUT, CUSTOM_LUT_LENGTH);
46 }
22.6.3.5.1 配置时钟

CLOCK_InitUsb1Pll

  • 配置 USB1 PLL 即 PLL3 的频率,调用时的输入参数g_ccmConfigUsbPll.loopDivider = 0,该设置把 PLL3 的频率定为 480MHz

CLOCK_InitUsb1Pfd

  • 配置 PFD0 的频率,调用时的输入参数 pfdFrac 为 24,得到 PFD0 的频率为: f P F D 0 = f P L L 3 × 18 / p f d F r a c = 480 ∗ 18 / 24 = 360 M H z f_ {PFD0} =f_ {PLL3} ×18/pfdFrac = 480*18/24 = 360MHz fPFD0=fPLL3×18/pfdFrac=48018/24=360MHz

CLOCK_SetMux

  • 选 择 PLL3 PFD0 作 为 FlexSPI 的 时 钟 源

CLOCK_SetDiv

  • 配 置 分 频 因 子 flexspiDiv 为 2

最 终 FlexSPI 的 根 时 钟: f F L E X S P I C L K R O O T = f P L L 3 P F D 0 / ( f l e x s p i D i v + 1 ) = 120 M H z f_{FLEXSPI_CLK_ROOT} =f_{ PLL3 PFD0} /( flexspiDiv + 1)=120MHz fFLEXSPICLKROOT=fPLL3PFD0/(flexspiDiv+1)=120MHz

  • 进行 SPI 通讯时,SCK 信号直接由 FlexSPI 根时钟 FLEXSPI_CLK_ROOT 提供的
  • 通讯频率不应超过 FLASH 存储器支持的极限,该 FLASH最高支持的通讯频率为 133MHz。
22.6.3.5.2 关闭 DCache 功能

第 2 部分调用了库函数 SCB_DisableDCache 关闭了 DCache 功能

  • 开启该功能时程序会进行 DCache 操作导致竞争 SPI 总线而出错。
22.6.3.5.3 初始化 FlexSPI 外设

第 3 部分主要是调用了库函数 FLEXSPI_Init 向 FlexSPI 外设写入配置
-针对 AHB 预读取功能使用了不同的配置,把config.ahbConfig.enableAHBPrefetch 赋值为 true

22.6.3.5.4 配置闪存功能参数

第 4 部分主要是调用了库函数 FLEXSPI_SetFlashConfig 向 FlexSPI 外设写入闪存参数相关的配置

  • 写入的参数是 deviceconfig 变量,它的类型是外部设备配置结构体 flexspi_device_config_t
/* 设备特性相关的参数 */
2 flexspi_device_config_t deviceconfig = {
3 /************************ 第 1 部分 ****************************/
4 .flexspiRootClk = 120000000,
5 .flashSize = FLASH_SIZE,
6 .CSIntervalUnit = kFLEXSPI_CsIntervalUnit1SckCycle,
7 .CSInterval = 2,
8 .CSHoldTime = 1,
9 .CSSetupTime = 1,
10 .dataValidTime = 2,
11 .columnspace = 0,
12 .enableWordAddress = false,
13 /************************ 第 2 部分 ****************************/
14 .AWRSeqIndex = NOR_CMD_LUT_SEQ_IDX_AHB_PAGEPROGRAM_QUAD_1,
15 .AWRSeqNumber = 2,
16 .ARDSeqIndex = NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD,
17 .ARDSeqNumber = 1,
18 /* W25Q256 typical time=0.7ms,max time=3ms
19 * fAHB = 528MHz,T AHB = 1/528us
20 * unit = 32768/528 = 62.06us
21 * 取延时时间为 1ms ,
22 * AHBWriteWaitInterval = 1*1000/62.06 = 17
23 */
24 .AHBWriteWaitUnit = kFLEXSPI_AhbWriteWaitUnit32768AhbCycle,
25 .AHBWriteWaitInterval = 17,
26 };

flexspiRootClk

  • 本成员被赋值为 FlexSPI 的根时钟频率 120000000

flashSize

  • 用于设置FLASH的大小,单位为KB
  • 在本代码中被赋值为宏FLASH_SIZE(宏值为 321024), 321024KB,即 32MB

CSIntervalUnit 和 CSInterval

  • 用于配置 CS 信号线有效与无效切换的最小时间间隔
  • 本代码随意设置了一个比较小的时间值,即 2 个 CLK时钟周期

CSHoldTime、CSSetupTime 和 dataValidTime

  • 三个参数分别表示 CS 信号线的保持时间、建立时间以及数据有效时间

22 FlexSPI—读写外部 SPI NorFlash_第23张图片时序参数(摘自《W25Q256》数据手册)
如上图, CS 保持时间和建立时间最长的要求为 5ns

  • 由于 CSHoldTime 和 CS-SetupTime 成员值的以 FlexSPI 根时钟周期为单位,而 1 个 FlexSPI 时钟周期 T F L E X S P I − C L K − R O O T = 1 / 120 M = 8.4 n s T _{FLEXSPI-CLK-ROOT} =1/120M = 8.4ns TFLEXSPICLKROOT=1/120M=8.4ns,所以这两个成员值都赋值为 1 即可

数据有效时间约等于数据建立时间(Data in Setup Time),表中要求为 2ns

  • 结构体成员值 dataValidTime 的单位也是 ns,所以代码中该成员被赋值为 2

columnspace

  • W25Q256 存储器不包含列地址,所以本成员直接赋值为 0

enableWordAddress

  • 设置是否使用字的方式访问 FLASH,此处禁用该功能
22.6.3.5.5 更新查找表
  • base:要初始化哪个 FlexSPI 外设,RT1052 仅有一个,所以只能输入FLEXSPI;
  • index:从查找表的哪个 LUT 寄存器开始更新,此处输入为 0 表示从头开始;
  • cmd:要更新的查找表数组,此处调用输入为自定义的变量 customLUT;
  • count: 表 示 cmd 数 组 的 长 度, 此 处 输 入 为 customLUT 变 量 的 长 度, 宏 CUS-TOM_LUT_LENGTH 值为 60。

定义查找表

 1 /************************ 第 1 部分 ****************************/
2 /* 定义指令在查找表中的编号 */
3 #define NOR_CMD_LUT_SEQ_IDX_READ_NORMAL 0
4 #define NOR_CMD_LUT_SEQ_IDX_READ_FAST 1
5 #define NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD 2
6 #define NOR_CMD_LUT_SEQ_IDX_READSTATUS 3
7 #define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE 4
8 #define NOR_CMD_LUT_SEQ_IDX_ERASESECTOR 5
9 #define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_SINGLE 6
10 #define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD 7
11 #define NOR_CMD_LUT_SEQ_IDX_READID 8
12 #define NOR_CMD_LUT_SEQ_IDX_READJEDECID 9
13 #define NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG 10
14 #define NOR_CMD_LUT_SEQ_IDX_READSTATUSREG 11
15 #define NOR_CMD_LUT_SEQ_IDX_ERASECHIP 12
16
17 /************************ 第 2 部分 ****************************/
18 /* 查找表的长度 */
19 #define CUSTOM_LUT_LENGTH 60
20
21 /* 定义查找表 LUT
22 * 下表以 [4 * NOR_CMD_LUT_SEQ_IDX_xxxx] 表示 1 个序列,
23 *
24 1 个序列最多包含 8 条指令,使用宏 FLEXSPI_LUT_SEQ 可以一次定义 2 个指令。
25 *
26 一个 FLEXSPI_LUT_SEQ 占一个 LUT 寄存器,端口 A 和端口 B 各有 64 个 LUT 寄存器,
27 * 所以 CUSTOM_LUT_LENGTH 最大值为 64 
*
30 FLEXSPI_LUT_SEQ 格式如下( LUT 指令 0 ,使用的数据线数目,指令的参数,
31 LUT 指令 1 ,使用的数据线数目,指令的参数)
32 *
33 * 不满 8 条指令的序列应以 STOP 指令结束,即 kFLEXSPI_Command_STOP ,
34 * 不过因为 STOP 指令中的所有参数均为 0 ,而数组的初始值也都为 0 ,
35 * 所以部分序列的末尾忽略了 STOP 指令也能正常运行。
36 */
37 const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {
38 /* 普通读指令, Normal read mode -SDR */
39 [4 * NOR_CMD_LUT_SEQ_IDX_READ_NORMAL] =
40 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, W25Q_ReadData_
,4Addr,
41 kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, FLASH_ADDR_
,→ LENGTH),
42
43 [4 * NOR_CMD_LUT_SEQ_IDX_READ_NORMAL + 1] =
44 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04,
45 kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),
46
47 /* 快速读指令, Fast read mode - SDR */
48 [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST] =
49 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, W25Q_FastReadData_
,4Addr,
50 kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, FLASH_ADDR_
,→ LENGTH),
51
52 [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST + 1] =
53 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_1PAD, 0x08,
54 kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),
55
56 /* QUAD 模式快速读指令, Fast read quad mode - SDR */
57 [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD] =
58 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, W25Q_FastReadQuad_
,4Addr,
59 kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, FLASH_ADDR_
,→ LENGTH),
60 [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD + 1] =
61 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_4PAD, 0x08,
62 kFLEXSPI_Command_READ_SDR, kFLEXSPI_4PAD, 0x04),
63
64 /* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
65 };

第 1 部分

  • 查找表中定义的序列编号宏,在定义查找表和执行序列指令时使用这些宏会方便操作。
  • FlexSPI 执行的时候是以序列为单位的,一个序列最多可支持 8 个 LUT 指令

第 2 部分

  • 定义了查找表 customLUT
  • 一个有 CUSTOM_LUT_LENGTH(宏值为 60)个 32 位元素的数组
  • 每个元素对应一个 LUT 寄存器,即可存储两个指令
  • 对这些元素进行赋值时可以使用库中定义的宏 FLEXSPI_LUT_SEQ
1 /*! @breif Formula to form FLEXSPI instructions in LUT table. */
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))

宏 FLEXSPI_LUT_SEQ 可以一次定义两个指令,它包含有 6 个参数

  • cmd0:指令 0 的编码, LUT 寄存器的 OPCODE
/*! @brief FLEXSPI 外设的指令编码定义 , 用于填写 LUT 表 */
2 enum _flexspi_command {
3 /*!< STOP 停止指令,释放 CS 信号线 */
4 kFLEXSPI_Command_STOP = 0x00U,
5 /*!< 发送命令代码到 FLASH, 使用 SDR 模式 */
6 kFLEXSPI_Command_SDR = 0x01U,
7 /*!< 发送行地址到 FLASH, 使用 SDR 模式 */
8 kFLEXSPI_Command_RADDR_SDR = 0x02U,
9 /*!< 发送列地址到 FLASH, 使用 SDR 模式 */
10 kFLEXSPI_Command_CADDR_SDR = 0x03U,
11
12 /* ⋯部分内容省略⋯ */
13 /*!< 发送命令代码到 FLASH, 使用 DDR 模式 */
14 kFLEXSPI_Command_DDR = 0x21U,
15 /*!< 发送行地址到 FLASH, 使用 DDR 模式 */
16 kFLEXSPI_Command_RADDR_DDR = 0x22U,
17 /*!< 发送列地址到 FLASH, 使用 DDR 模式 */
18 kFLEXSPI_Command_CADDR_DDR = 0x23U,
19 /* ⋯部分内容省略⋯ */
20 };
  • pad0:指令0的要使用的数据线数目,LUT寄存器的NUM_PADS,可设定该指令使用SINGLE/DUAL/QUAD/OCTAL 模式通讯
1 /*! @brief FLEXSPI 的数据线数目定义,用于填写 LUT 表 */
2 enum _flexspi_pad {
3 /*!< SINGLE 模式,传输命令、地址以及数据只使用 DATA0 或 DATA1 信号线 */
4 kFLEXSPI_1PAD = 0x00U,
5 /*!< DUAL 模式,传输命令、地址以及数据只使用 DATA[1:0] 信号线 */
6 kFLEXSPI_2PAD = 0x01U,
7 /*!< QUAD 模式,传输命令、地址以及数据只使用 DATA[3:0] 信号线 */
8 kFLEXSPI_4PAD = 0x02U,
9 /*!< OCTAL 模式,传输命令、地址以及数据只使用 DATA[7:0] 信号线 */
10 kFLEXSPI_8PAD = 0x03U,
11 };
  • op0:指令 0 包含的参数, LUT 寄存器的 OPERAND;
  • cmd1、pad1、op0:这是第二条指令的编码、数据线数目以及参数。

以该表中的第 1 个序列“普通读序列,Normal read mode -SDR”为例进行讲解

  • 该序列能完成对 FLASH 的“Read Data with 4-Byte Address”命令的功能,用于读取数据,它是一个 4 字节地址的 Single 模式命令,该命令时序

22 FlexSPI—读写外部 SPI NorFlash_第24张图片查找表中第 1 个序列的代码:

/* 定义查找表 LUT
2 * 下表以 [4 * NOR_CMD_LUT_SEQ_IDX_xxxx] 表示 1 个序列,
3 *
4 1 个序列最多包含 8 条指令,使用宏 FLEXSPI_LUT_SEQ 可以一次定义 2 个指令。
5 *
6 *
7 FLEXSPI_LUT_SEQ 格式如下( LUT 指令 0 ,使用的数据线数目,指令的参数,
8 LUT 指令 1 ,使用的数据线数目,指令的参数)
9 *
10 * 不满 8 条指令的序列应以 STOP 指令结束,即 kFLEXSPI_Command_STOP ,
11 * 不过因为 STOP 指令中的所有参数均为 0 ,而数组的初始值也都为 0 ,
12 * 所以部分序列的末尾忽略了 STOP 指令也能正常运行。
13 */
14 const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {
15 /* 普通读指令, Normal read mode -SDR */
16 [4 * NOR_CMD_LUT_SEQ_IDX_READ_NORMAL] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, W25Q_ReadData_,4Addr,kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, FLASH_ADDR_,LENGTH),
20 [4 * NOR_CMD_LUT_SEQ_IDX_READ_NORMAL + 1] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),
23 /* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
24 };

序列说明:
序列包含4条指令,存储在查找表的“[4*NOR_CMD_LUT_SEQ_IDX_READ_NORMAL]” 和 “[4 * NOR_CMD_LUT_SEQ_IDX_READ_NORMAL+1] ” 的 位 置

  • 宏NOR_CMD_LUT_SEQ_IDX_READ_NORMAL 的值为 0
  • 后面需要控制 FlexSPI执行本序列的时候,使用宏 NOR_CMD_LUT_SEQ_IDX_READ_NORMAL来指定即可。

指令 0

  • 使用宏 FLEXSPI_LUT_SEQ 来一次定义两条指令并赋值给查找表 customLUT。
    • 该宏的第 1 个参数表示指令 0,这里赋值为 FlexSPI 查找表的“kFLEXSPI_Command_SDR”指令。该指令表示通过 FlexSPI 向 FLASH 发送要执行的 FLASH 命令编码,模式为 SDR
    • 第 2 个参数“kFLEXSPI_1PAD”表示使用 1 根数据线发送该编码
    • 第 3 个参数赋值为宏“W25Q_ReadData_4Addr”,这就是要被发送的 FLASH 命令编码“0x13”

指令 1

  • 宏 FLEXSPI_LUT_SEQ 中的第 4 个参数为指令 1。这里赋值为“kFLEXSPI_Command_RADDR_SDR”,表示通过 FlexSPI 向 FLASH 发送要读取的FLASH 内部存储单元的地址;
  • 第 5 个参数表示“kFLEXSPI_1PAD”表示使用 1 根数据线发送
  • 第 6 个参数表示 FLASH 使用多少位来表示存储单元的地址,此处赋值为宏FLASH_ADDR_LENGTH,即使用 32 位地址。

指令2

  • 指令2和指令3存储在查找表的“[4*NOR_CMD_LUT_SEQ_IDX_READ_NORMAL+1]”位置中
  • 第 1 个参数表示指令 2,这里赋值为“kFLEXSPI_Command_READ_SDR”,表示要从FLASH 中读取内容
  • 第 2 个参数“kFLEXSPI_1PAD”表示使用 SINGLE 模式即 1 根数据线来接收数据;
  • 第 3 个参数此处赋值为“0x04”,实际上这个参数为任意值都不会有影响
  • “kFLEXSPI_Command_READ_SDR”指令要读取的字节数由后面调用时通过 FlexSPI 传输结构体指定

指令 3

  • 这里赋值为“kFLEXSPI_Command_STOP”指令,表示这个执行完前面 3 个指令后,序列就结束了
  • 在STOP 指令中,它的数据线数和参数分别赋值为 kFLEXSPI_1PAD 和 0 即可

22.6.3.6 FlexSPI 初始化函数

以上定义的 FlexSPI 引脚配置函数,模式初始化函数都被封装到函数FlexSPI_NorFlash_Init 中,用时只要调用该函数就可以完成 FlexSPI 外设的初始化

* @retval 无
5 */
6 void FlexSPI_NorFlash_Init(void)
7 {
8 FlexSPI_NorFlash_IOMUXC_MUX_Config();
9 FlexSPI_NorFlash_IOMUXC_PAD_Config();
10 NorFlash_FlexSPI_ModeInit();
11 }
22.6.3.6.1 读取 FLASH 芯片的 Jedec Device ID

初始化好 FlexSPI 后,我们通常会采用读取设备 ID 的方式来确认通讯是否正常

/************************ 第 1 部分 ****************************/
2 /* 查找表(部分) */
3 const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {
4 /* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
5 /* 读 JedecDeviceID,MF7-MF0+ID15-ID0 */
6 [4 * NOR_CMD_LUT_SEQ_IDX_READJEDECID] =
7 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, W25Q_
,→ JedecDeviceID,
8 kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),
9 /* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
10 };
11
12 /************************ 第 2 部分 ****************************/
/**
14 * @brief 读取 FLASH 芯片的 JedecDevice ID
15 * @param base: 使用的 FlexSPI 端口
16 * @param vendorID[out]:
17 存储接收到的 ID 值的缓冲区,大小为 1 字节,正常时该值为 0xEF4019
18 * @retval FlexSPI 传输返回的状态值,正常为 0
19 */
20 status_t FlexSPI_NorFlash_Get_JedecDevice_ID (FLEXSPI_Type *base,
21 uint32_t *vendorID)
22 {
23 /************************ 第 3 部分 ****************************/
24 uint32_t temp;
25 flexspi_transfer_t flashXfer;
26 /* 要读写的 FLASH 内部存储单元地址,本命令不带地址 */
27 flashXfer.deviceAddress = 0;
28 /* 要使用的端口 */
29 flashXfer.port = kFLEXSPI_PortA1;
30 /* 本命令的类型 */
31 flashXfer.cmdType = kFLEXSPI_Read;
32 /* 要执行的序列个数 */
33 flashXfer.SeqNumber = 1;
34 /* 要执行的序列号 */
35 flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_READJEDECID;
36 /* 接收到的数据先缓存到 temp */
37 flashXfer.data = &temp;
38 /* 要接收的数据个数 */
39 flashXfer.dataSize = 3;
40
41 /************************ 第 4 部分 ****************************/
42 /* 开始阻塞传输 */
43 status_t status = FLEXSPI_TransferBlocking(base, &flashXfer);
/************************ 第 5 部分 ****************************/
46 /* 调整高低字节,结果赋值到 vendorId */
47 *vendorID = ((temp&0xFF)<<16) | (temp&0xFF00) | ((temp&0xFF0000)>>16);
48
49 return status;
50 }

第 1 部分

  • 读取 Jedec Device ID 过程在查找表 LUT 中的定义
    • 首先是发送命令编码的指令“kFLEXSPI_Command_SDR” ,该指令发送 FLASH 的“W25Q_JedecDeviceID”命令(0x03)
    • 然后是读取内容的指令“kFLEXSPI_Command_READ_SDR”,用于接收 FLASH 返回的ID,这个读取指令的参数是无意义的

第 2 部分

  • 此处定义了 FlexSPI_NorFlash_Get_JedecDevice_ID 函数
    • base:用于设置使用哪个 FlexSPI 外设,由于 RT1052 只有一个,所以调用时只能使用宏“FLEXSPI”作为输入参数
    • vendorID:这是一个指针,用于存储函数执行完毕后它接收到的 ID 值,是函数的输出。一个 32 位的变量地址
  • status_t 是直接返回内部库函数 FLEXSPI_TransferBlocking 的执行结果。执行正常时返回值为 0

第 3 部分

  • 定义了一个 FlexSPI 传输结构体 flashXfer 并针对本命令执行的需要对其成员进行赋值。
  • deviceAddress:本成员用于指定要读取的 FLASH 内部存储单元的地址,由于“JEDEC”命令不需要发送地址,所以本成员是无效的,它只有在查找表 LUT 的序列中加入发送地址指令“kFLEXSPI_Command_RADDR_SDR”才有效
  • port:设置要使用的 FlexSPI 端口,根据硬件连接,此处使用kFLEXSPI_PortA1 即 A1 端口
  • cmdType:设定本序列的类型,由于读取 ID 也属于读命令的范畴,所以此处赋值为kFLEXSPI_Read。
  • SeqNumber:设定要执行的序列数目,本命令一个序列已经足够,所以赋值为 1。
  • seqIndex: 设 定 要 执 行 的 序 列 索 引 值, 这 是 传 输 结 构 体 中 的 核 心 内 容, 此 处 赋值 为 宏 NOR_CMD_LUT_SEQ_IDX_READJEDECID,查 找 表LUT 中“JEDEC”序列所在的索引
  • data:这是执行命令序列时数据的存储位置,是一个指针,此处赋值为缓冲的变量 temp 的地址,即接收到的内容先存储到该变量中。
  • dataSize:设定要接收的数据个数,本命令中 FLASH 会输出 MF7-MF0 以及 ID15-ID0 共 3个字节,所以此处赋值为 3。

第 4 部分

  • 有了 FlexSPI 传输结构体,就可以执行传输函数了,本代码中使用阻塞传输

第 5 部分

  • 执行完 FLEXSPI_TransferBlocking 函数后,temp 变量中会存储好接收到的数据,接收内容时按字节存储
  • 先接收到的内容会被存储到 temp 变量的低位字节
22.6.3.6.2 FLASH 写使能

确认 FlexSPI 与 FLASH 通讯正常后,可以继续编写对 FLASH 的控制操作函数。向 FLASH 芯片存储矩阵写入数据前,还要先进行写使能操作,通过“Write Enable(编码 0x06)”命令即可写使能。

/************************ 第 1 部分 ****************************/
2 /* 查找表(部分) */
3 const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {
4 /* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
5 /* 写使能, Write Enable */
6 [4 * NOR_CMD_LUT_SEQ_IDX_WRITEENABLE] =
7 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, W25Q_WriteEnable,
8 kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),
9 /* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
10 };
11
12 /************************ 第 2 部分 ****************************/
13 /**
14 * @brief 写使能
15 * @param base: 使用的 FlexSPI 端口
16 * @retval FlexSPI 传输返回的状态值,正常为 0
17 */
18 status_t FlexSPI_NorFlash_Write_Enable(FLEXSPI_Type *base)
19 {
20 flexspi_transfer_t flashXfer;
21 status_t status;
22
23 /* 写使能 */
24 flashXfer.deviceAddress = 0;
25 flashXfer.port = kFLEXSPI_PortA1;
26 flashXfer.cmdType = kFLEXSPI_Command;
27 flashXfer.SeqNumber = 1;
28 flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_WRITEENABLE;
30 status = FLEXSPI_TransferBlocking(base, &flashXfer);
31
32 return status;
33 }

写使能的执行过程也被定义到了查找表LUT中,该执行序列非常简单,仅需要发送写使能命令的编码即可。

  • 较为特别的是由于本指令序列不包含数据传输,所以 cmdType 结构体成员被赋值值为 kFLEXSPI_Command。最后调用库函数FLEXSPI_TransferBlocking 执行
22.6.3.6.3 读取 FLASH 当前状态

FLASH 芯片向内部存储单元写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在擦除和写操作后需要确认 FLASH 芯片“空闲”时才能进行再次写入或读取数据
22 FlexSPI—读写外部 SPI NorFlash_第25张图片

关注这个状态寄存器的第 0 位“BUSY”,当这个位为“1”时,表明 FLASH 芯片处于忙碌状态,它可能正在对内部的存储单元进行“擦除”或“数据写入”的操作

  • 利用命令表中的“Read Status Register(编码 0x05)”命令可以获取 FLASH 芯片状态寄存器的内容

22 FlexSPI—读写外部 SPI NorFlash_第26张图片
只要向 FLASH 芯片发送了读状态寄存器的命令,FLASH 芯片就会持续向主机返回最新的状态寄存器内容,直到收到 SPI 通讯的停止信号。

#define FLASH_BUSY_STATUS_OFFSET 0
2 /************************ 第 1 部分 ****************************/
3 /* 查找表(部分) */
4 const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {
5 /* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
6 /* 读状态寄存器, Read status register */
7 [4 * NOR_CMD_LUT_SEQ_IDX_READSTATUSREG] =
8 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, W25Q_
,→ ReadStatusReg,
9 kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),
10 /* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
11 };
12
13 /************************ 第 2 部分 ****************************/
14 /**
15 * @brief 等待至 FLASH 空闲状态
* @param base: 使用的 FlexSPI 端口
17 * @retval FlexSPI 传输返回的状态值,正常为 0
18 */
19 status_t FlexSPI_NorFlash_Wait_Bus_Busy(FLEXSPI_Type *base)
20 {
21 /* 等待至空闲状态 */
22 bool isBusy;
23 uint32_t readValue;
24 status_t status;
25 flexspi_transfer_t flashXfer;
26
27 /* 读状态寄存器 */
28 flashXfer.deviceAddress = 0;
29 flashXfer.port = kFLEXSPI_PortA1;
30 flashXfer.cmdType = kFLEXSPI_Read;
31 flashXfer.SeqNumber = 1;
32 flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_READSTATUSREG;
33 flashXfer.data = &readValue;
34 flashXfer.dataSize = 1;
35 /************************ 第 3 部分 ****************************/
36 do {
37 status = FLEXSPI_TransferBlocking(base, &flashXfer);
38
39 if (status != kStatus_Success) {
40 return status;
41 }
42
43 /* 判断状态寄存器的 busy 位 */
44 if (readValue & (1U << FLASH_BUSY_STATUS_OFFSET)) {
45 isBusy = true;
46 } else {
47 isBusy = false;
48 }
49 } while (isBusy);
50
51 return status;
52 }

第 1 部分

  • 发送的编码即FLASH的读状态寄存器命令编码W25Q_ReadStatusReg
    (宏值为 0x05)。

第 2 部分

  • 定义读状态寄存器操作的函数
  • 函数内部设置 FlexSPI 传输结构体执行读状态寄存器序列 NOR_CMD_LUT_SEQ_IDX_READSTATUSREG
  • 并且把读取到的状态寄存器内容存储到变量 readValue 中,长度为 1 字节。

第 3 部分

  • 在 do⋯while 循环中调用库函数 FLEXSPI_TransferBlocking 执行序列并判断其返回值 status,若返回不正常则退出函数;
  • 若执行正常,判断 readValue 的FLASH_BUSY_STATUS_OFFSET(第 0 位,Busy 位)位是否为 1,为 1 表示忙碌,否则为空闲并记录该状态到标志 isBusy 中
22.6.3.6.4 FLASH 扇区擦除

由于 FLASH 存储器的特性决定了它的写入操作只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。

  • 擦除操作会把存储单元中的数据位全擦除为“1”,在其后的写入数据操作中,如果要存储数据“1”,那就不修改存储单元,在要存储数据“0”时,才更改该数据位。
  • FLASH 中对存储单元擦除的基本操作单位都是多个字节进行,如本例子中的 FLASH 芯片支持“扇区擦除”、“块擦除”以及“整片擦除”

22 FlexSPI—读写外部 SPI NorFlash_第27张图片
22 FlexSPI—读写外部 SPI NorFlash_第28张图片使用扇区擦除命令“Sector Erase with 4-Byte Address(编码 0x21)”可控制 FLASH 芯片开始擦写
22 FlexSPI—读写外部 SPI NorFlash_第29张图片具体代码

/************************ 第 1 部分 ****************************/
2 /* 查找表(部分) */
3 const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {
4 /* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
5 /* 擦除扇区, Erase Sector */
6 [4 * NOR_CMD_LUT_SEQ_IDX_ERASESECTOR] =
7 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, W25Q_SectorErase_
,4Addr,
8 kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, FLASH_ADDR_
,→ LENGTH),
/* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
10 };
11
12 /************************ 第 2 部分 ****************************/
13 /**
14 * @brief 擦除扇区
15 * @param base: 使用的 FlexSPI 端口
16 * @param address: 要擦除扇区的起始地址
17 * @retval FlexSPI 传输返回的状态值,正常为 0
18 */
19 status_t FlexSPI_NorFlash_Erase_Sector(FLEXSPI_Type *base,
20 uint32_t dstAddr)
21 {
22 status_t status;
23 flexspi_transfer_t flashXfer;
24 /************************ 第 3 部分 ****************************/
25 /* 写使能 */
26 status = FlexSPI_NorFlash_Write_Enable(base);
27
28 if (status != kStatus_Success) {
29 return status;
30 }
31 /************************ 第 4 部分 ****************************/
32 /* 擦除扇区指令 */
33 flashXfer.deviceAddress = dstAddr;
34 flashXfer.port = kFLEXSPI_PortA1;
35 flashXfer.cmdType = kFLEXSPI_Command;
36 flashXfer.SeqNumber = 1;
37 flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_ERASESECTOR;
38
39 status = FLEXSPI_TransferBlocking(base, &flashXfer);
if (status != kStatus_Success) {
42 return status;
43 }
44 /************************ 第 5 部分 ****************************/
45 /* 等待 FLASH 至空闲状态 */
46 status = FlexSPI_NorFlash_Wait_Bus_Busy(base);
47
48 return status;
49 }

第 1 部 分

  • 包 含 一 个发 送 命 令 编 码 的 指 令 和 发 送 地 址 的 指 令。
  • 发 送 的 编 码 即 FLASH 的 扇 区擦 除 命 令 编 码 W25Q_SectorErase_4Addr(宏 值 为 0x21)
  • 发 送 地 址 的 指 令kFLEXSPI_Command_RADDR_SDR 它附带的参数为宏 FLASH_ADDR_LENGTH,表示我们要发送的地址是 32 位的。

第 2 部分

  • 定义执行擦除操作的函数 FlexSPI_NorFlash_Erase_Sector

第 3 部分

  • 调用前面定义的写使能函数 FlexSPI_NorFlash_Write_Enable 使 FLASH 允许写入

第 4 部分

  • 执行库函数 FLEXSPI_TransferBlocking
  • 它与前面各种命令最大的区别是此处 deviceAddress 成员是有效的,它被赋值为函数的输入参数dstAddr

第 5 部分

  • 调用前面定义的 FlexSPI_NorFlash_Wait_Bus_Busy 函数等待 FLASH 完成擦除操作。

特别注意,根据 FLASH 的要求,调用扇区擦除命令时注意输入的地址要对齐到 4KB。

22.6.3.6.5 FLASH 的 Quad 模式页写入

目标扇区被擦除完毕后,就可以向它写入数据了

  • 使用页写入命令最多可以一次向 FLASH 传输 256 个字节的数据,我们把这个单位称为页大小。
  • FLASH 的页写入有 Single/Dual/Quad 模式,不同的模式有对应的命令编码
  • Quad 模式页写入指令“Quad Input Page Program with 4-Byte Address”

22 FlexSPI—读写外部 SPI NorFlash_第30张图片从时序图可知,第 1 个字节为“Quad 模式页写入指令”命令的编码 0x34,紧接着是 32 位的“地址 A”,它表示要写入的 FLASH 内部存储单元的起始地址。

要注意的是,在这个 Quad 模式的写入命令中,地址依然是使用单根数据线传输的,在传输数据的时候才使用 4 根数据线

与擦除指令不一样,页写入指令的地址并不要求按 256 字节对齐,只要确认目标存储单元是擦除状态即可

所以,若对“地址 x”执行页写入指令后,发送了 200 个字节数据后终止通讯,下一次再执行页写入指令,从“地址 (x+200)”开始写入 200 个字节也是没有问题的 (小于 256 均可)。

/************************ 第 1 部分 ****************************/
2 /* 查找表(部分) */
3 const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {
4 /* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
5 /* QUAD 模式页写入, Page Program - quad mode */
6 [4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD] =
7 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, W25Q_PageProgramQuad_
,4Addr,
8 kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, FLASH_ADDR_
,→ LENGTH),
9 [4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD + 1] =
10 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_4PAD, 0x04,
11 kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),
12 /* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
13 };
/************************ 第 2 部分 ****************************/
16 /**
17 * @brief 页写入
18 * @param base: 使用的 FlexSPI 端口
19 * @param dstAddr: 要写入的起始地址
20 * @param src: 要写入的数据的指针
21 * @param dataSize: 要写入的数据量,不能大于 256
22 * @retval FlexSPI 传输返回的状态值,正常为 0
23 */
24 status_t FlexSPI_NorFlash_Page_Program(FLEXSPI_Type *base,
25 uint32_t dstAddr,
26 uint8_t *src,
27 uint16_t dataSize)
28 {
29 status_t status;
30 flexspi_transfer_t flashXfer;
31 /************************ 第 3 部分 ****************************/
32 /* 写使能 */
33 status = FlexSPI_NorFlash_Write_Enable(base);
34
35 if (status != kStatus_Success) {
36 return status;
37 }
38 /************************ 第 4 部分 ****************************/
39 /* 设置传输结构体 */
40 flashXfer.deviceAddress = dstAddr;
41 flashXfer.port = kFLEXSPI_PortA1;
42 flashXfer.cmdType = kFLEXSPI_Write;
43 flashXfer.SeqNumber = 1;
44 flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD;
45 flashXfer.data = (uint32_t *)src;
46 flashXfer.dataSize = dataSize;
48 status = FLEXSPI_TransferBlocking(base, &flashXfer);
49 if (status != kStatus_Success) {
50 return status;
51 }
52 /************************ 第 5 部分 ****************************/
53 /* 等待写入完成 */
54 status = FlexSPI_NorFlash_Wait_Bus_Busy(base);
55
56 return status;
57 }

第 1 部分

  • 发送命令编码指令kFLEXSPI_Command_SDR,编码为W25Q_PageProgramQuad_4Addr,其值为0x34,即FLASH的Quad模式页写入命令
  • 此命令编码和下面的地址都是使用一根数据线传输的,所以数据线的数目赋值为枚举值 kFLEXSPI_1PAD
  • 发 送 地 址 的 指 令 kFLEXSPI_Command_RADDR_SDR, 附 带 的 参 数 为 宏FLASH_ADDR_LENGTH(值为 32),要发送的地址是 32 位的
  • 写入数据指令 kFLEXSPI_Command_WRITE_SDR,要注意该指令使用的数据线数目为kFLEXSPI_4PAD,即传输数据时使用 Quad 四根数据线的模式
    • 本指令中的参数 0x04 是无意义的
  • 明确指定使用 STOP 指令停止传输

第 2 部分

  • 定义执行擦除操作的函数 FlexSPI_NorFlash_Page_Program,包含三个参数
    • 指定要使用的 FlexSPI 外设(base)
    • 要写入的 FLASH 内部存储单元的地址(dstAddr)
    • 要写入数据的指针(src)
    • 要写入的数据量(dataSize)
  • 注意页写入命令最多只能写入 256 个数据

第 3 部分

  • 调用前面定义的写使能函数 FlexSPI_NorFlash_Write_Enable 使 FLASH 允许写入

第 4 部分

  • 给 FlexSPI 传输结构体赋值并执行库函数 FLEXSPI_TransferBlocking
  • deviceAddress 被被赋值为函数的输入参数 dstAddr

第 5 部 分

  • 与 擦 除 操 作 类 似, 传 输 完 成 后 需 要 调 用 前 面 定 义 的FlexSPI_NorFlash_Wait_Bus_Busy 函数等待 FLASH 完成写入操作

本函数中集成了写使能及传输后的等待操作,应用时直接调用即可

22.6.3.6.6 不定量数据写入

“不定量数据写入”的函数

/**
2 * @brief 向 FLASH 写入不限量的数据
3 * @note 写入前要确保该空间是被擦除的状态
4 * @param base: 使用的 FlexSPI 端口
5 * @param dstAddr: 要写入的起始地址
6 * @param src: 要写入的数据的指针
7 * @param dataSize:
8 要写入的数据量,没有限制,确认空间是已被擦除过的即可
9 * @retval 写入的返回值,为 0 是表示正常
10 */
11 status_t FlexSPI_NorFlash_Buffer_Program(FLEXSPI_Type *base,
12 uint32_t dstAddr,
13 uint8_t *src,
uint16_t dataSize)
15 {
16 status_t status = kStatus_Success;
17 uint16_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
18 /* 后续要处理的字节数,初始值为 NumByteToWrite*/
19 uint16_t NumByteToWriteRest = dataSize;
20 /* 根据以下情况进行处理:
21 1. 写入的首地址是否对齐
22 2. 最后一次写入是否刚好写满一页 */
23 Addr = dstAddr % FLASH_PAGE_SIZE;
24 count = FLASH_PAGE_SIZE - Addr;
25
26 /* 若 NumByteToWrite > count :
27 第一页写入 count 个字节,对其余字节再进行后续处理,
28 所以用 (NumByteToWriteRest = dataSize - count)
29 求出后续的 NumOfPage 和 NumOfSingle 进行处理。
30
31 若 NumByteToWrite < count :
32 即不足一页数据,直接用 NumByteToWriteRest = NumByteToWrite
33 求出 NumOfPage 和 NumOfSingle 即可 */
34 NumByteToWriteRest = (dataSize > count) ? (dataSize - count) :,→ dataSize;
35
36 /* 要完整写入的页数(不包括前 count 字节) */
37 NumOfPage = NumByteToWriteRest / FLASH_PAGE_SIZE;
38 /* 最后一页要写入的字节数(不包括前 count 字节) */
39 NumOfSingle = NumByteToWriteRest % FLASH_PAGE_SIZE;
40
41 /* dataSize > count 时,需要先往第一页写入 count 个字节
42 dataSize < count 时无需进行此操作 */
43 if (count != 0 && dataSize > count) {
44 status = FlexSPI_NorFlash_Page_Program(base, dstAddr, src, count);
if (status != kStatus_Success) return status;
46
47 dstAddr += count;
48 src += count;
49 }
50
51 /* 处理后续数据 */
52 if (NumOfPage== 0 ) {
53 status = FlexSPI_NorFlash_Page_Program(base, dstAddr, src,,→ NumOfSingle);
54 if (status != kStatus_Success) return status;
55 } else {
56 /* 后续数据大于一页 */
57 while (NumOfPage--) {
58 status = FlexSPI_NorFlash_Page_Program(base, dstAddr, src, FLASH_PAGE_
,→ SIZE);
59 if (status != kStatus_Success) return status;
60
61 dstAddr += FLASH_PAGE_SIZE;
62 src += FLASH_PAGE_SIZE;
63 }
64 /* 最后一页 */
65 if (NumOfSingle != 0) {
66 status = FlexSPI_NorFlash_Page_Program(base, dstAddr, src,,→ NumOfSingle);
67 if (status != kStatus_Success) return status;
68
69 }
70 }
71
72 return status;
73 }
  • 在实际数据写入的时候使用的 FLASH 页写入函数
  • 在实际调用这个“FlexSPI_NorFlash_Buffer_Program”函数时,要注意确保目标扇区处于擦除状态。
22.6.3.6.7 FLASH 的 Quad 模式读取数据

FLASH 芯片的数据读取要简单得多

  • 直接使用 Quad 模式读取命令“Fast Read Quad Output with 4-Byte Address(编码 0x6C)”即可

22 FlexSPI—读写外部 SPI NorFlash_第31张图片代码

/************************ 第 1 部分 ****************************/
2 /* 查找表(部分) */
3 const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {
4 /* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
5 /* QUAD 模式快速读指令, Fast read quad mode - SDR */
6 [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD] =
7 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, W25Q_FastReadQuad_
,4Addr,
8 kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, FLASH_ADDR_
,→ LENGTH),
9 [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD + 1] =
10 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_4PAD, 0x08,
11 kFLEXSPI_Command_READ_SDR, kFLEXSPI_4PAD, 0x04),
12 /* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
13 };
14
15 /************************ 第 2 部分 ****************************/
16 /**
17 * @brief 读取数据
18 * @param base: 使用的 FlexSPI 端口
19 * @param address: 要读取的起始地址
20 * @param dst[out]: 存储接收到的数据的指针
21 * @param dataSize: 要读取的数据量,不能大于 65535
22 * @retval FlexSPI 传输返回的状态值,正常为 0
23 */
24 status_t FlexSPI_NorFlash_Buffer_Read(FLEXSPI_Type *base,
25 uint32_t address,
26 uint8_t *dst,
27 uint16_t dataSize)
{
29 status_t status;
30 flexspi_transfer_t flashXfer;
31
32 /* 设置传输结构体 */
33 flashXfer.deviceAddress = address;
34 flashXfer.port = kFLEXSPI_PortA1;
35 flashXfer.cmdType = kFLEXSPI_Read;
36 flashXfer.SeqNumber = 1;
37 flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD;
38 flashXfer.data = (uint32_t *)dst;
39 flashXfer.dataSize = dataSize;
40
41 status = FLEXSPI_TransferBlocking(base, &flashXfer);
42
43 return status;
44 }

特别的是查找表中使用了发送 DUMMY 操作的指“kFLEXSPI_Command_DUMMY_SDR”,且该指令的参数为 0x08。

FLASH 存储器没有限制读取的数据量,但传输结构体的 flashXfer.dataSize 变量以及它对应的寄存器配置域 IPCR1[IDATSZ] 是 16 位的,所以接收的数据量不应超过 65535

22.6.3.7 编写 IP 命令访问方式测试

使用 IP 命令访问的方式进行测试

/************************ 第 1 部分 ****************************/
2 #define EXAMPLE_SECTOR 1000 /* 要进行读写测试的扇区号 */
3 #define EXAMPLE_SIZE (4*1024) /* 读写测试的数据量,单位为字节 */
4 /************************ 第 2 部分 ****************************/
5 /* 读写测试使用的缓冲区 */
6 static uint8_t s_nor_program_buffer[EXAMPLE_SIZE];
7 static uint8_t s_nor_read_buffer[EXAMPLE_SIZE];
8
9 /**
10 * @brief 使用 IP 命令的方式进行读写测试
11 * @param 无
12 * @retval 测试结果,正常为 0
13 */
14 int NorFlash_IPCommand_Test(void)
15 {
16 uint32_t i = 0;
17 status_t status;
18 uint32_t JedecDeviceID = 0;
19
20 PRINTF("\r\nNorFlash IP 命令访问测试\r\n");
21 /************************ 第 3 部分 ****************************/
22 /*************************** 读 ID 测试 ************************/
23 /* 获取 JedecDevice ID. */
24 FlexSPI_NorFlash_Get_JedecDevice_ID(FLEXSPI, &JedecDeviceID);
25
26 if (JedecDeviceID != FLASH_JEDECDEVICE_ID) {
27 PRINTF("FLASH 检测错误,读取到的 JedecDeviceID 值为: 0x%x\r\n",
28 JedecDeviceID);
29 return -1;
}
31
32 PRINTF("检测到 FLASH 芯片,JedecDeviceID 值为: 0x%x\r\n", JedecDeviceID);
33 /************************ 第 4 部分 ****************************/
34 /*************************** 擦除测试 ************************/
35 PRINTF("擦除扇区测试\r\n");
36
37 /* 擦除指定扇区 */
38 status = FlexSPI_NorFlash_Erase_Sector(FLEXSPI, EXAMPLE_SECTOR *,→ SECTOR_SIZE);
39 if (status != kStatus_Success) {
40 PRINTF("擦除 flash 扇区失败 !\r\n");
41 return -1;
42 }
43
44 /* 读取内容 */
45 status = FlexSPI_NorFlash_Buffer_Read(FLEXSPI,
46 EXAMPLE_SECTOR * SECTOR_SIZE,
47 s_nor_read_buffer,
48 EXAMPLE_SIZE);
49
50 if (status != kStatus_Success) {
51 PRINTF("读取数据失败 !\r\n");
52 return -1;
53 }
54
55 /* 擦除后 FLASH 中的内容应为 0xFF ,
56 设置比较用的 s_nor_program_buffer 值全为 0xFF */
57 memset(s_nor_program_buffer, 0xFF, sizeof(s_nor_program_buffer));
58 /* 把读出的数据与 0xFF 比较 */
59 if (memcmp(s_nor_program_buffer, s_nor_read_buffer,EXAMPLE_SIZE)){
60 PRINTF("擦除数据,读出数据不正确 !\r\n ");
return -1;
62 } else {
63 PRINTF("擦除数据成功. \r\n");
64 }
65 /************************ 第 5 部分 ****************************/
66 /*************************** 写入数据测试 ********************/
67 PRINTF("写入数据测试 \r\n");
68
69 for (i = 0; i < EXAMPLE_SIZE; i++) {
70 s_nor_program_buffer[i] = (uint8_t) i;
71 }
72
73 /* 写入数据 */
74 status = FlexSPI_NorFlash_Buffer_Program(FLEXSPI,
75 EXAMPLE_SECTOR * SECTOR_SIZE,
76 (void *)s_nor_program_buffer,
77 EXAMPLE_SIZE);
78 if (status != kStatus_Success) {
79 PRINTF("写入失败 !\r\n");
80 return -1;
81 }
82
83 /* 读取数据 */
84 status = FlexSPI_NorFlash_Buffer_Read(FLEXSPI,
85 EXAMPLE_SECTOR * SECTOR_SIZE,
86 s_nor_read_buffer,
87 EXAMPLE_SIZE);
88
89 if (status != kStatus_Success) {
90 PRINTF("读取数据失败 !\r\n");
91 return -1;
92 }
/* 把读出的数据与写入的比较 */
95 if (memcmp(s_nor_program_buffer, s_nor_read_buffer, EXAMPLE_SIZE)) {
96 PRINTF("写入数据,读出数据不正确 !\r\n ");
97 return -1;
98 } else {
99 PRINTF("写入数据成功. \r\n");
100 }
101
102 return 0;
103 }

第 1 部分。

  • 定义两个宏用于指定本测试要读写、擦除的扇区号 EXAMPLE_SECTOR 以及读写测试使用的数据量 EXAMPLE_SIZE。

第 2 部分。

  • 定义两个数组用来缓冲要写入的数据和接收读取的数据,数据量均为上面定义的 EXAMPLE_SIZE 大小。

第 3 部分。

  • 调用 FlexSPI_NorFlash_Get_JedecDevice_ID 函数尝试读取 Jedec Device ID

第 4 部分。

  • 调用 FlexSPI_NorFlash_Erase_Sector 函数擦除宏 EXAMPLE_SECTOR 指定的扇区
  • 代码中通过“EXAMPLE_SECTOR * SECTOR_SIZE”操作来计算出该扇区的地址,如编号为 1000 的扇区,其地址为 1000*4096,计算得到的地址作为参数输入到函数中。
  • 擦除操作完成后调用 FlexSPI_NorFlash_Buffer_Read 函数读取该地址的内容,并存储到读取缓冲区s_nor_read_buffer 中

第 5 部分

  • 代码中先对写缓冲区 s_nor_program_buffer 重新赋值
  • 调用函数 FlexSPI_NorFlash_Buffer_Program 把这些内容写入到前面已擦除的空间中。
  • 写入完毕后调用 FlexSPI_NorFlash_Buffer_Read 函数读取出来并使用 memcmp 函数进行校验

本测试使用我们前面定义的各种操作函数对 FLASH 进行访问,这些函数内部是访问FlexSPI 寄存器实现的,这就是所谓的 IP 命令访问方式

22.6.3.8 AHB 命令访问的查找表配置

本外设还支持使用 AHB 命令的方式来访问 FLASH

/************************ 第 1 部分 ****************************/
2 /* 查找表(部分) */
3 const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {
4 /* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
5 /* 给 AHB 命令访问的 QUAD 模式页写入
6 序列,包含写使能和页写入两条序列 */
7 /* 写使能, Write Enable */
8 [4 * NOR_CMD_LUT_SEQ_IDX_AHB_PAGEPROGRAM_QUAD_1] =
9 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, W25Q_WriteEnable,
10 kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),
11
12 /* QUAD 模式页写入, Page Program - quad mode */
13 [4 * NOR_CMD_LUT_SEQ_IDX_AHB_PAGEPROGRAM_QUAD_2] =
14 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, W25Q_
,→ PageProgramQuad_4Addr,
15 kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, FLASH_ADDR_
,→ LENGTH),
16 [4 * NOR_CMD_LUT_SEQ_IDX_AHB_PAGEPROGRAM_QUAD_2 + 1] =
17 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_4PAD, 0x04,
18 kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),
19 /************************ 第 2 部分 ****************************/
/* QUAD 模式快速读指令, Fast read quad mode - SDR */
21 [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD] =
22 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, W25Q_FastReadQuad_
,4Addr,
23 kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, FLASH_ADDR_
,→ LENGTH),
24 [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD + 1] =
25 FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_4PAD, 0x08,
26 kFLEXSPI_Command_READ_SDR, kFLEXSPI_4PAD, 0x04),
27 /* ⋯⋯以下省略其它命令,具体内容请查看程序源码⋯⋯ */
28 };
29
30 /************************ 第 3 部分 ****************************/
31 /* 设备特性相关的参数 */
32 flexspi_device_config_t deviceconfig = {
33 /*... 部分内容省略 ...*/
34 .AWRSeqIndex = NOR_CMD_LUT_SEQ_IDX_AHB_PAGEPROGRAM_QUAD_1,
35 .AWRSeqNumber = 2,
36 .ARDSeqIndex = NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD,
37 .ARDSeqNumber = 1,
38 /* W25Q256 typical time=0.7ms,max time=3ms
39 * fAHB = 528MHz,T AHB = 1/528us
40 * unit = 32768/528 = 62.06us
41 * 取延时时间为 1ms ,
42 * AHBWriteWaitInterval = 1*1000/62.06 = 17
43 */
44 .AHBWriteWaitUnit = kFLEXSPI_AhbWriteWaitUnit32768AhbCycle,
45 .AHBWriteWaitInterval = 17,
46 };

第 1 部分

  • 部分代码包含了查找表中的两个序列,序列的功能分别是写使能和 QUAD 模式的页写入,它与前面 IP 命令中使用的序列的功能其实并没有 差 别, 只 是 它 们 被 特 意 定 义 到 查 找 表 中 的 连 续 位 置 了
    • 写 使 能 序 列 的 编 号NOR_CMD_LUT_SEQ_IDX_AHB_PAGEPROGRAM_QUAD_1(宏值为 13)
    • QUAD模式页写入序列的编号 NOR_CMD_LUT_SEQ_IDX_AHB_PAGEPROGRAM_QUAD_2(宏值为 14)。

第 2 部分。

  • 这部分是 QUAD 模式的快速读指令,它与 IP 命令访问使用的是同一个查找表序列,此处不再分析。

第 3 部分。

  • 这段内容是前面《配置闪存功能参数》小节中的代码,是外部设备配置结构体的赋值代码
    • AWRSeqIndex 和 AWRSeqNumber: 它 们 用 于 指 定 触 发 AHB 写 访 问 时FlexSPI 外 设 要 执 行 的 序 列 和 序 列 数 目, 要 执 行 的 是 第 1 部 分 代码 定 义 的 包 含 写 使 能 和 QUAD 模 式 页 写 入 的 两 个 序 列, 所 以 分 别 赋 值 为NOR_CMD_LUT_SEQ_IDX_AHB_PAGEPROGRAM_QUAD_1 和 2。
    • ARDSeqIndex 和 ARDSeqNumber:类似地,它们用于指定触发 AHB 读访问时 FlexSPI 外设要执行的序列和数目,代码中正是指向查找表中 QUAD 模式的页写入序列。
    • AHBWriteWaitUnit 和 AHBWriteWaitInterval:它们用于指定写入操作后要等待的时间。在IP 命令访问方式中,可以看到我们可通过读取 FLASH 的 BUSY 状态寄存器位来确认写入操作是否完成

22 FlexSPI—读写外部 SPI NorFlash_第32张图片该表中表示的页写入需要的时间典型值为0.7ms,最大值为3ms,由于使用AHB命令写入时通常每次只写入不超过80个数据,不足页写入的256个,因而本代码中的配置选择了延时1ms,具体还可根据自己的需求进行调整

22.6.3.9 AHB命令访问方式相对应的测试代码

/************************ 第 1 部分 ****************************/
2 /* FlexSPI_AMBA_BASE 是 AHB 命令使用的映射地址,值为 0x6000 0000 */
3 /* 把对 FLASH 访问的地址封装成指针 */
4 #define NORFLASH_AHB_POINTER(addr) (void*)( FlexSPI_AMBA_BASE + addr)
5 /************************ 第 2 部分 ****************************/
6 /**
7 * @brief 使用 IP 命令的方式进行读写测试
8 * @param 无
9 * @retval 测试结果,正常为 0
10 */
11 int NorFlash_AHBCommand_Test(void)
12 {
13 uint32_t i = 0;
14 status_t status;
15 uint32_t JedecDeviceID = 0;
16
17 PRINTF("\r\nNorFlash AHB 命令访问测试\r\n");
18
19 /*************************** 读 ID 测试 ****************************/
20 /* 获取 JedecDevice ID. */
21 FlexSPI_NorFlash_Get_JedecDevice_ID(FLEXSPI, &JedecDeviceID);
if (JedecDeviceID != FLASH_JEDECDEVICE_ID) {
24 PRINTF("FLASH 检测错误,读取到的 JedecDeviceID 值为: 0x%x\r\n",
25 JedecDeviceID);
26 return -1;
27 }
28
29 PRINTF("检测到 FLASH 芯片,JedecDeviceID 值为: 0x%x\r\n", JedecDeviceID);
30
31 /*************************** 擦除测试 ****************************/
32 PRINTF("擦除扇区测试\r\n");
33
34 /* 擦除指定扇区 */
35 status = FlexSPI_NorFlash_Erase_Sector(FLEXSPI, EXAMPLE_SECTOR *,→ SECTOR_SIZE);
36 if (status != kStatus_Success) {
37 PRINTF("擦除 flash 扇区失败 !\r\n");
38 return -1;
39 }
40 /************************ 第 3 部分 ****************************/
41 /* 读取数据 */
42 memcpy(s_nor_read_buffer,
43 NORFLASH_AHB_POINTER(EXAMPLE_SECTOR * SECTOR_SIZE),
44 EXAMPLE_SIZE);
45
46 /* 擦除后 FLASH 中的内容应为 0xFF ,
47 设置比较用的 s_nor_program_buffer 值全为 0xFF */
48 memset(s_nor_program_buffer, 0xFF, EXAMPLE_SIZE);
49 /* 把读出的数据与 0xFF 比较 */
50 if (memcmp(s_nor_program_buffer, s_nor_read_buffer, EXAMPLE_SIZE)) {
51 PRINTF("擦除数据,读出数据不正确 !\r\n ");
52 return -1;
53 } else {
PRINTF("擦除数据成功. \r\n");
55 }
56 /************************ 第 4 部分 ****************************/
57 /***************** 一次写入少量数据测试 ************/
58 PRINTF("8、16、32 位写入数据测试:0x12、0x3456、0x789abcde \r\n");
59 /* 使用 AHB 命令方式写入数据 */
60 *(uint8_t *)NORFLASH_AHB_POINTER(EXAMPLE_SECTOR * SECTOR_SIZE) = 0x12;
61 *(uint16_t *)NORFLASH_AHB_POINTER(EXAMPLE_SECTOR * SECTOR_SIZE + 4) =,0x3456;
62 *(uint32_t *)NORFLASH_AHB_POINTER(EXAMPLE_SECTOR * SECTOR_SIZE + 8) =,0x789abcde;
63 /* 使用 AHB 命令方式读取数据 */
64 PRINTF("8 位读写结果 = 0x%x\r\n",
65 *(uint8_t *)NORFLASH_AHB_POINTER(EXAMPLE_SECTOR * SECTOR_SIZE));
66 PRINTF("16 位读写结果 = 0x%x\r\n",
67 *(uint16_t *)NORFLASH_AHB_POINTER(EXAMPLE_SECTOR * SECTOR_SIZE+4));
68 PRINTF("32 位读写结果 = 0x%x\r\n",
69 *(uint32_t *)NORFLASH_AHB_POINTER(EXAMPLE_SECTOR * SECTOR_SIZE+8));
70
71 /************** 一次写入一个扇区数据测试 ********************/
72
73 PRINTF("\r\n大量数据写入和读取测试 \r\n");
74
75 for (i = 0; i < EXAMPLE_SIZE; i++) {
76 s_nor_program_buffer[i] = (uint8_t)i;
77 }
78
79 /* 擦除指定扇区 */
80 status = FlexSPI_NorFlash_Erase_Sector(FLEXSPI, EXAMPLE_SECTOR *,→ SECTOR_SIZE);
81 if (status != kStatus_Success) {
82 PRINTF("擦除 flash 扇区失败 !\r\n");
return -1;
84 }
85 /************************ 第 5 部分 ****************************/
86 /* 写入一个扇区的数据 */
87 status = FlexSPI_NorFlash_Buffer_Program(FLEXSPI,
88 EXAMPLE_SECTOR * SECTOR_SIZE,
89 (void *)s_nor_program_buffer,
90 EXAMPLE_SIZE);
91 if (status != kStatus_Success) {
92 PRINTF("写入失败 !\r\n");
93 return -1;
94 }
95
96 /* 使用软件复位来重置 AHB 缓冲区 . */
97 FLEXSPI_SoftwareReset(FLEXSPI);
98
99 /* 读取数据 */
100 memcpy(s_nor_read_buffer,
101 NORFLASH_AHB_POINTER(EXAMPLE_SECTOR * SECTOR_SIZE),
102 EXAMPLE_SIZE);
103 /* 把读出的数据与写入的比较 */
104 if (memcmp(s_nor_program_buffer, s_nor_read_buffer,EXAMPLE_SIZE)) {
105 PRINTF("写入数据,读出数据不正确 !\r\n ");
106 return -1;
107 } else {
108 PRINTF("大量数据写入和读取测试成功. \r\n");
109 }
110
111 PRINTF("NorFlash AHB 命令访问测试完成。\r\n");
112
113 return 0;
114 }

第 1 部分

  • 定义使用 AHB 命令访问的地址和指针。
  • 这个映射的内部首地址就是 0x6000 0000,在库文件中它被定义成了宏FlexSPI_AMBA_BASE(值为 0x6000 0000)

第 2 部分

  • 这部分是类似前面 IP 命令访问的测试函数,其中的读取 Jedec Device ID 和擦除操作使用的仍然都是 IP 命令的访问方式
    • 因为这样的操作并不支持使用 AHB 命令方式访问

第 3 部分

  • C 库函数 memcpy 操作应用了 AHB 命令的读访问操作,它以指针NORFLASH_AHB_POINTER(EXAMPLE_SECTOR * SECTOR_SIZE) 作为第 2 个输入参数

第 4 部分

  • 这部分内容使用了 AHB 命令对 FLASH 使用了 8、16 以及 32 位的方式进行读写
  • 读写时直接通过“(uint8_t)”、“*(uint16_t )”和“(uint32_t *)”配合对指针宏NORFLASH_AHB_POINTER 进行赋值

第 5 部 分

  • 这 部 分 内 容 在 擦 除 扇 区 后 依 然 使 用 IP 命 令 的 写 入 操 作 函 数FlexSPI_NorFlash_Buffer_Program 来写入一个扇区内容,然后通过与第 3 部分相同的AHB 命令方式把内容读取回来然后进行数据校验。

对整个扇区写入时没有使用 AHB 命令,使用 AHB 命令写入大量数据时仍然不正常,在靠后的数据总是无法写入

  • 考虑到写入操作还要对扇区进行擦除操作(AHB 命令是不支持擦除操作的),所以我们不建议使用 AHB 命令对FLASH 进行写入
  • 使用 AHB 命令对 FLASH 进行读取还是比较推荐的

22.6.3.10 main 函数

/**
2 * @brief 主函数
3 * @param 无
4 * @retval 无
5 */
6 int main(void)
7 {
8 /* 初始化内存保护单元 */
9 BOARD_ConfigMPU();
10 /* 初始化开发板引脚 */
11 BOARD_InitPins();
12 /* 初始化开发板时钟 */
13 BOARD_BootClockRUN();
14 /* 初始化调试串口 */
15 BOARD_InitDebugConsole();
16
17 /* 初始化 FlexSPI 外设 */
18 FlexSPI_NorFlash_Init();
19
20 /* 使用 IP 命令访问 FLASH 的测试 */
21 NorFlash_IPCommand_Test();
22
23 /* 使用 AHB 命令访问 FLASH 的测试 */
24 NorFlash_AHBCommand_Test();
25 while (1) {
26 }
27 }

调用 FlexSPI_NorFlash_Init 初始化了 FlexSPI 后,通过 Nor-Flash_IPCommand_Test 和NorFlash_AHBCommand_Test 函数对 FLASH 完成了 IP 命令和 AHB 命令的测试。

你可能感兴趣的:(NXP,单片机)