痞子衡嵌入式:其实i.MXRT下改造FlexSPI driver同样支持AHB方式去写入NOR Flash

时间:2024-02-29 12:34:34

  大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是i.MXRT下改造FlexSPI driver以AHB方式去写入NOR Flash

  痞子衡前段时间写过一篇 《串行NAND Flash的两大特性导致其在i.MXRT FlexSPI下无法XiP》,文章里介绍了 NAND Flash 的 Page Read 等待特性(发完 Read 命令后需要回读 Flash 内部状态寄存器 Busy 位来判断 Page 数据是否已准备好)导致其无法像 NOR Flash 那样通过 AHB 方式被便捷访问,仅能在一个 Page 空间里实现 AHB 读(前提是在 IPG 方式发完读命令以及读完状态寄存器确保数据已经准备好后)。

  回到 NOR Flash 上,我们可以轻松通过 AHB 方式读取 Flash 数据,但写入 Flash 一般都是调用 FlexSPI 驱动来实现(即 IPG 方式),那么有没有可能也通过 “AHB 方式来写入 Flash” 呢?答案是可以的,但为啥痞子衡加了个引号,且往下看:

本文以恩智浦 MIMXRT1170-EVK 开发板上主芯片 i.MXRT1176 及其配套板载 Flash 芯片 - 芯成 IS25WP128 为例。

一、Flash写操作流程

  芯成 IS25WP128 是一颗很典型的四线 QSPI NOR Flash,其写入(编程)时序是符合 JEDEC216 标准的。简单来说,一个完整的写时序包含三个独立子时序:Write Enable 时序 + Page Program 时序 + Read Status 时序。

  先来看打头阵的 Write Enable 子时序,NOR Flash 内部的状态寄存器会有一个位叫 WEL (Write Enable Latch),这个位控制着 Flash 的擦写权限,默认值是 0(即不允许擦写)。如果想要写入 Flash,必须先通过 Write Enable 命令将 WEL 位临时设为 1(这个位会随着当前的擦写命令结束后自动恢复到 0)。

  置位了 WEL 后,便可以传输 Page 数据给 Flash,这个子时序便是 Page Program。Page Program 按命令地址和数据传输方式不同分为三种:一线 SPI,四线 SPI,QPI,下面是常用的四线 SPI 的时序图,命令和地址通过 IO0 传输,数据通过 IO[3:0] 传输。

  通常来说,在这个时序里,传入的地址应该是 Page 首地址,写入数据长度应该是一个完整的 Page 大小。但从非 Page 首地址处写入小于一个 Page 长度的数据也是可以的,但有一个注意点就是不要在这个时序里出现跨页的现象(如果出现,超出当前页的数据会被放回到该页起始地址处)。

  Page Program 子时序结束后,数据还并未真正写入 Flash 内存体中,Flash 内部控制器只是开始处理数据,这时候会有一个等待时间(大概0.2ms),Flash 内部的状态寄存器有一个位叫 WIP (Write In Progress),这个位标志着数据写入状态(默认值是 0,当 Page Program 子时序结束后,WIP 立即跳为 1),用户需要通过 Read Status 子时序来实时读取状态寄存器的值从而获知数据处理情况。

  当 Flash 内部状态寄存器中的 WIP 位从 1 跳回到 0,便意味着一次完整的写时序结束了,主机可以发起下一次写时序。

二、FlexSPI对写时序支持

  痞子衡旧文 《从头开始认识i.MXRT启动头FDCB里的lookupTable》 里对 FlexSPI 读时序介绍得非常详细,尤其是对 AHB 方式读支持的实现,现在痞子衡再介绍下 FlexSPI 对于写时序的支持。

  第一节里介绍的 Flash 写操作的三个子时序,在 FlexSPI 外设里当然都是支持的,SEQ_CTL 组件里都预先实现了这些子时序,比如下面就是 Page Program 的序列:

  因为 Flash 写操作需要三个子序列,比 Flash 读操作单序列要复杂得多,并且最关键的是写操作还包含一个不确定的等待周期(Read Status 子时序与 Flash 交互),这就导致 FlexSPI 外设在 AHB 方式写上没法完美支持,这也是为什么写入 Flash 都是通过 IPG 方式来完成的,因为 IPG 方式下,子序列可以随意组合,由用户代码手动调度。

  原则上三个写操作子序列可以放在 LUT 中的任何一个 Sequence 位置,因为即使按序放在一起,我们通过 FlexSPI->FLSHxCR2 寄存器(x可取A1/A2/B1/B2,具体根据Flash引脚连接来定)中的 AWRSEQID 位指明写操作第一个子序列在 LUT 中的位置(index) 也无法自动完成 Page 数据的完整写入操作。

  但也不要就此放弃,单独 Page Program 子序列还是可以通过 AHB 方式写来替代的,这样也可以让我们过一下 AHB 方式写入 Flash 的瘾,只是需要在 AHB 写入操作前后辅助 IPG 方式下的 Write Enable 和 Read Status 动作,下一节用代码给大家实际演示。

三、FlexSPI driver用法

例程路径:\SDK_2.10.0_MIMXRT1170-EVK\boards\evkmimxrt1170\driver_examples\flexspi\nor\polling_transfer\cm7\iar

3.1 初始化

  先来看一下 FlexSPI 初始化函数 flexspi_nor_flash_init(),这个函数需要三个配置变量:分别是 flexspi_config_t 型面向 FlexSPI 外设层的配置 flexspiconfig,flexspi_device_config_t 型面向 Flash 器件端的配置 deviceconfig,以及很核心的 customLUT(这里只列出了跟 Flash 写操作相关的时序)。

#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE        2
#define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD   4
#define NOR_CMD_LUT_SEQ_IDX_READSTATUSREG      12

#define CUSTOM_LUT_LENGTH        60
const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {
    /* Write Enable */
    [4 * NOR_CMD_LUT_SEQ_IDX_WRITEENABLE] =
        FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x06, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),

    /* Page Program - quad mode */
    [4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD] =
        FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x32, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),
    [4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD + 1] =
        FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_4PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),

    /* Read status register */
    [4 * NOR_CMD_LUT_SEQ_IDX_READSTATUSREG] =
        FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x05, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),
};

flexspi_device_config_t deviceconfig = {
    .flexspiRootClk       = 12000000,
    .flashSize            = 0x4000, /* 16Mb/KByte */
    .CSIntervalUnit       = kFLEXSPI_CsIntervalUnit1SckCycle,
    .CSInterval           = 2,
    .CSHoldTime           = 3,
    .CSSetupTime          = 3,
    .dataValidTime        = 0,
    .columnspace          = 0,
    .enableWordAddress    = 0,
    .AWRSeqIndex          = 0,
    .AWRSeqNumber         = 0,
    .ARDSeqIndex          = NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD,
    .ARDSeqNumber         = 1,
    .AHBWriteWaitUnit     = kFLEXSPI_AhbWriteWaitUnit2AhbCycle,
    .AHBWriteWaitInterval = 0,
};

void flexspi_nor_flash_init(FLEXSPI_Type *base)
{
    CLOCK_SetRootClockDiv(kCLOCK_Root_Flexspi1, 2);
    CLOCK_SetRootClockMux(kCLOCK_Root_Flexspi1, 0);

    /*Get FLEXSPI default settings and configure the flexspi. */
    flexspi_config_t flexspiconfig;
    FLEXSPI_GetDefaultConfig(&flexspiconfig);

    /*Set AHB buffer size for reading data through AHB bus. */
    flexspiconfig.ahbConfig.enableAHBPrefetch    = true;
    flexspiconfig.ahbConfig.enableAHBBufferable  = true;
    flexspiconfig.ahbConfig.enableReadAddressOpt = true;
    flexspiconfig.ahbConfig.enableAHBCachable    = true;
    flexspiconfig.rxSampleClock                  = kFLEXSPI_ReadSampleClkLoopbackFromDqsPad;
    FLEXSPI_Init(base, &flexspiconfig);

    /* Configure flash settings according to serial flash feature. */
    FLEXSPI_SetFlashConfig(base, &deviceconfig, kFLEXSPI_PortA1);

    /* Update LUT table. */
    FLEXSPI_UpdateLUT(base, 0, customLUT, CUSTOM_LUT_LENGTH);

    /* Do software reset. */
    FLEXSPI_SoftwareReset(base);
}

3.2 一般用法(IPG写)

  先来看 IPG 方式的 Flash 写入函数,其中 Page Program 子时序是通过 FLEXSPI_TransferBlocking() 函数来完成的,这个函数就是往大小为 256 bytes 的 IP TX FIFO 写 src 里的数据(默认 FlexSPI->MCR0[ATDFEN] = 0 情况下),SEQ_CTL 组件处理时会将缓存在 IP TX FIFO 里的数据全部发送到 Flash 端。

void flexspi_nor_flash_program(FLEXSPI_Type *base, uint32_t dstAddr, const uint32_t *src, uint32_t size)
{
    // Write Enable 子时序
    flexspi_nor_write_enable(base, dstAddr);

    flexspi_transfer_t flashXfer;
    flashXfer.deviceAddress = dstAddr;
    flashXfer.port          = kFLEXSPI_PortA1;
    flashXfer.cmdType       = kFLEXSPI_Write;
    flashXfer.SeqNumber     = 1;
    flashXfer.seqIndex      = NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD;
    flashXfer.data          = (uint32_t *)(void *)src;
    flashXfer.dataSize      = size;
    // page program 子时序
    FLEXSPI_TransferBlocking(base, &flashXfer);

    // Read Status 子时序
    flexspi_nor_wait_bus_busy(base);

    FLEXSPI_SoftwareReset(base);
}

3.3 特殊用法(AHB写)

  我们现在来改造 IPG 方式的 Flash 写入函数,首先要修改 deviceconfig 变量将 AWRSeqIndex 指向 PAGEPROGRAM_QUAD 在 LUT 中的位置,然后将 FLEXSPI_TransferBlocking() 函数换成 AHB 写入代码(memcpy 或者指针操作赋值),这时候 src 里的数据就会被自动放在大小为 64 bytes 的 AHB TX Buffer 里,SEQ_CTL 组件处理时会将缓存在 AHB TX Buffer 里的数据全部发送到 Flash 端。

  但这里有一些限制,经实测,利用 memcpy 做 AHB 写,一次仅能写入 1/2/3/4/8 这五种有效长度的数据,其他数据长度不及预期(比如拷贝 5 - 7 字节,实际仅写入前 4 字节;拷贝 8 字节以上,实际仅写入前 8 字节),这其实跟 《i.MXRT中FlexSPI外设对AHB Burst Read特性的支持》 一文里提及的处理器 AHB Burst 策略有关,FlexSPI 每次仅会缓存一次 AHB Burst 写数据进 AHB TX Buffer,而 SEQ_CTL 每工作一次都会使能一次 Flash 器件的数据处理流程(进入 Busy 状态),因此连续的两次 AHB burst 写,后面一次的 burst 行为其实不产生实际 Flash 写入效果。

flexspi_device_config_t deviceconfig = {
    // ... 其他省略
    .AWRSeqIndex          = NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD,
    .AWRSeqNumber         = 1,
};

void flexspi_nor_flash_program(FLEXSPI_Type *base, uint32_t dstAddr, const uint32_t *src, uint32_t size)
{
    while (size)
    {
        // Write Enable 子时序
        flexspi_nor_write_enable(base, 0);

        uint32_t cpyBytes = 0;
        if (size >= 8)      { cpyBytes = 8; }
        else if (size >= 4) { cpyBytes = 4; }
        else                { cpyBytes = size; }
        memcpy((void *)dstAddr, (void *)src, cpyBytes);
        __DSB();

        // Read Status 子时序
        flexspi_nor_wait_bus_busy(base);

        dstAddr += cpyBytes;
        src += cpyBytes / 4;
        size -= cpyBytes;
    }

    FLEXSPI_SoftwareReset(base);
}

  上面看似鸡肋的 AHB 方式写入 Flash 到底有什么用?底下痞子衡会讲到 XECC 模块,到时你就知道其用处了。

  至此,i.MXRT下改造FlexSPI driver以AHB方式去写入NOR Flash痞子衡便介绍完毕了,掌声在哪里~~~

欢迎订阅

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

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