痞子衡嵌入式:其實i.MXRT下改造FlexSPI driver同樣支援AHB方式去寫入NOR Flash

痞子衡發表於2021-08-26

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是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_READ_FAST_QUAD     0
#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] = {
    /* Fast read quad mode - SDR */
    [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD] =
        FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0xEB, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_4PAD, 0x18),
    [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD + 1] = 
        FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_4PAD, 0x06, kFLEXSPI_Command_READ_SDR, kFLEXSPI_4PAD, 0x04),

    /* 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,
    // 支援 AHB 讀的關鍵配置
    .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 = {
    // 支援 AHB 寫的關鍵配置
    .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主頁知乎主頁微信公眾號 平臺上。

微信搜尋"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。

相關文章