痞子衡嵌入式:在i.MXRT啟動頭FDCB裡調整Flash工作頻率也需同步設Dummy Cycle

痞子衡發表於2021-04-15

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是Flash工作頻率與Dummy Cycle的聯絡

  上一篇文章 《從頭開始認識i.MXRT啟動頭FDCB裡的lookupTable》,痞子衡帶大家從頭梳理了下i.MXRT下啟動頭FDCB裡lookupTable的設計與實現細節,在這個過程中也簡單跟大家介紹了序列NOR Flash的工作模式(主要是Fast Read Quad I/O),今天痞子衡在FDCB設定範疇下跟大家再進一步地探討Flash工作頻率與Dummy Cycle設定問題。

一、引入Flash工作頻率與Dummy Cycle對應關係問題

  今天我們以i.MXRT1170-EVK上的板載Flash為例,其具體型號為IS25WP128-JBLE,在Flash資料手冊特性介紹裡可以看到這顆Flash最高可以執行在133MHz頻率下(SDR模式),並且其Dummy Cycle也是可選的,那麼Dummy Cycle是不是可以任意配呢?答案既是也不是,痞子衡先賣個關子。

  讓我們再來回顧下這顆Flash的Fast Read Quad I/O時序圖,從時序圖裡可以看到Dummy Cycle預設是6(包括Mode Bits時序一共6個SCK時鐘週期),那麼預設的6個Dummy Cycle是不是適用全部的Flash工作頻率呢?我們們繼續看Flash資料手冊。

  在資料手冊裡找到了下面這張表,Read Dummy Cycle與最大工作頻率的聯絡,從表裡可以看到當Flash工作在Fast Read Quad I/O模式時,預設的6個Dummy Cycle適用的最大工作頻率是104MHz,即104MHz工作頻率及以下均可以使用預設的6個Dummy Cycle。如果工作頻率高於104MHz,Dummy Cycle應相應調大,比如133MHz工作頻率需對應至少9個Dummy Cycle。

二、如何在FDCB裡設定FlexSPI的Dummy Cycle?

  根據上面的分析,如果我們希望i.MXRT1170-EVK上這顆IS25WP128-JBLE啟動後工作在133MHz,那麼我們需要在FDCB裡至少配置9個Dummy Cycle。因此下述FDCB啟動頭裡 FLASH_DUMMY_CYCLES 巨集應設為9, qspiflash_config.memConfig.serialClkFreq 應改為 kFlexSpiSerialClk_133MHz,這樣的設定對於i.MXRT端是足夠的,因為更改後FlexSPI外設確實可以輸出133MHz的SCK,並且按9個Dummy Cycle的時序去讀Flash。

  但是把這樣的FDCB啟動頭直接下載進Flash後發現i.MXRT無法從Flash正常啟動,因為Flash端的Dummy Cycle還是預設的6個SCK週期,上面的FDCB僅僅是調整了FlexSPI輸出,並不會同步調整Flash端,此時主從兩端Dummy Cycle數不匹配,時序錯亂了,傳輸資料發生了錯位,應用程式當然無法啟動。

  所以Flash這邊需要其他的方式設定好Dummy Cycle後,上述方式更改的FDCB啟動頭才能正常使用。

  有一個現象需要特別說明一下,如果我們直接修改 qspiflash_config.memConfig.serialClkFreq 到 kFlexSpiSerialClk_133MHz, 但是 FLASH_DUMMY_CYCLES 依舊保持預設的6,這樣的FDCB頭下載進Flash也可以正常工作的,雖然有點違反Flash資料手冊裡的Dummy Cycle規範,但實測下來似乎是沒問題的,那是不是意味著我們根本不需要動預設的Dummy Cycle?其實不是的,當我們使能Flash的 continuous read mode 的時候,Dummy Cycle不規範就會出問題,關於這點痞子衡會單獨寫一篇文章細聊。

三、如何更改Flash裡的Dummy Cycle?

  那麼Flash裡的Dummy Cycle到底怎麼改呢?這需要我們繼續看Flash資料手冊,IS25WP128-JBLE內部有個8bit的Read Register,其bit6-bit3是Dummy Cycles設定,可設範圍是1-15,並且在暫存器型別裡可以看到其有易失性和非易失性兩種屬性,也就是說我們既可以臨時地改Dummy Cycle(掉電即恢復預設6),也可以永久地改Dummy Cycle(掉電仍保持上一次設定)。

  在IS25WP128-JBLE的指令集表裡,可以看到有專門寫Read Register的指令,SRPNV指令是非易失性方式地寫Read Register,SRPV指令是易失性方式去寫Read Register。

  分析到這裡,問題就變成到底是使用一個額外的小工程(比如藉助SDK裡的flexspi example稍微更改下程式碼)以SRPNV指令去永久性更改下Flash裡的Dummy Cycle再用作i.MXRT啟動,還是i.MXRT每次啟動時直接藉助FDCB啟動頭裡的設定用SRPV指令臨時地更改Flash的Dummy Cycle?從靈活性角度,痞子衡推薦第二種方式,那麼在FDCB裡應該怎麼做?痞子衡直接給答案:

// 設定Dummy Cycle數
#define FLASH_DUMMY_CYCLES      9
// 寫Read Register時序在LUT中的index(可自定義位置,但不要佔BootROM預設的幾個時序位置)
#define CMD_LUT_SEQ_IDX_SET_READ_PARAM 7
// BootROM中預設的LUT命令時序的index
#define CMD_LUT_SEQ_IDX_READ           0
#define CMD_LUT_SEQ_IDX_READSTATUS     1
#define CMD_LUT_SEQ_IDX_WRITEENABLE    3

const flexspi_nor_config_t qspiflash_config = {
    .memConfig =
        {
            .tag              = FLEXSPI_CFG_BLK_TAG,
            .version          = FLEXSPI_CFG_BLK_VERSION,
            .readSampleClkSrc = kFlexSPIReadSampleClk_LoopbackFromDqsPad,
            .csHoldTime       = 3u,
            .csSetupTime      = 3u,
            // Enable Safe configuration
            .controllerMiscOption = 0x10,
            .deviceType           = kFlexSpiDeviceType_SerialNOR,
            .sflashPadType        = kSerialFlash_4Pads,
            .serialClkFreq        = kFlexSpiSerialClk_133MHz,
            .sflashA1Size         = 16u * 1024u * 1024u,
            // 使能Flash暫存器配置操作
            .configCmdEnable = 1u,
            .configModeType[0] = kDeviceConfigCmdType_Generic,
            // 指示Flash暫存器配置時序在LUT中index
            .configCmdSeqs[0] = 
                {
                    .seqNum = 1,
                    .seqId = CMD_LUT_SEQ_IDX_SET_READ_PARAM,
                    .reserved = 0,
                },
            // 設定Flash暫存器配置值(這裡就是寫入Read Register的值)
            .configCmdArgs[0] = FLASH_DUMMY_CYCLES << 3,
            .lookupTable =
                {
                    // Fast Read Quad I/O
                    [4*CMD_LUT_SEQ_IDX_READ]               = FLEXSPI_LUT_SEQ(CMD_SDR,   FLEXSPI_1PAD, 0xEB, RADDR_SDR, FLEXSPI_4PAD, 0x18),
                    [4*CMD_LUT_SEQ_IDX_READ + 1]           = FLEXSPI_LUT_SEQ(MODE8_SDR, FLEXSPI_4PAD, 0x00, DUMMY_SDR, FLEXSPI_4PAD, FLASH_DUMMY_CYCLES-2),
                    [4*CMD_LUT_SEQ_IDX_READ + 2]           = FLEXSPI_LUT_SEQ(READ_SDR,  FLEXSPI_4PAD, 0x04, STOP,      FLEXSPI_1PAD, 0x00),
                   
                    // READ STATUS REGISTER
                    [4*CMD_LUT_SEQ_IDX_READSTATUS]         = FLEXSPI_LUT_SEQ(CMD_SDR,   FLEXSPI_1PAD, 0x05, READ_SDR,  FLEXSPI_1PAD, 0x01),
                    [4*CMD_LUT_SEQ_IDX_READSTATUS + 1]     = FLEXSPI_LUT_SEQ(STOP,      FLEXSPI_1PAD, 0x00, 0, 0, 0),
                   
                    // WRTIE ENABLE
                    [4*CMD_LUT_SEQ_IDX_WRITEENABLE]        = FLEXSPI_LUT_SEQ(CMD_SDR,   FLEXSPI_1PAD, 0x06, STOP,      FLEXSPI_1PAD, 0x00),

                    // Flash暫存器配置時序(這個時序需要上面READ STATUS, WRITE ENABLE的配合)
                    [4*CMD_LUT_SEQ_IDX_SET_READ_PARAM]     = FLEXSPI_LUT_SEQ(CMD_SDR,   FLEXSPI_1PAD, 0x63, WRITE_SDR, FLEXSPI_1PAD, 0x01),
                    [4*CMD_LUT_SEQ_IDX_SET_READ_PARAM + 1] = FLEXSPI_LUT_SEQ(STOP,      FLEXSPI_1PAD, 0x00, 0, 0, 0),
                },
        },
    .pageSize           = 256u,
    .sectorSize         = 4u * 1024u,
    .blockSize          = 256u * 1024u,
    .isUniformBlockSize = false,
};

四、BootROM對FDCB裡Flash暫存器配置的處理

  說到BootROM對FDCB的處理,大家有必要先回顧下痞子衡寫過的一篇舊文 《深入i.MXRT1050系列ROM中序列NOR Flash啟動初始化流程》,文章雖然是針對i.MXRT1050寫的,但基本流程也差不多適用i.MXRT1170,在文中的 2.6 小節第二次初始化裡,我們在上面FDCB裡的關於Flash暫存器的額外配置操作才會被處理,程式碼大致如下:

status_t flexspi_init(uint32_t instance, flexspi_mem_config_t *config)
{
    status_t status = kStatus_InvalidArgument;

    // 決定是否用30MHz SDR的安全時鐘來做FlexSPI初始化
    bool need_safe_freq = (config->deviceModeCfgEnable || config->configCmdEnable) &&
                          ((config->controllerMiscOption & (1 << kFlexSpiMiscOffset_SafeConfigFreqEnable)));
    if (need_safe_freq)
    {
        flexspi_clock_config(instance, kFlexSpiSerialClk_SafeFreq, kFlexSpiClk_SDR);
    }
    else
    {
        flexspi_clock_config(instance, config->serialClkFreq, flexspi_is_ddr_mode_enable(config));
    }

    // 省略程式碼片段,包含FlexSPI引腳、模組本身等各種初始化
    
    // 實現Flash配置暫存器寫入操作
    if (config->configCmdEnable)
    {
        // PortA1/A2/B1/B2如使能則全寫一遍
        flexspi_device_cmd_config_all_chips(instance, config);
    }

    if (need_safe_freq)
    {
        // 重新配回指定的FlexSPI時鐘
        flexspi_clock_config(instance, config->serialClkFreq, flexspi_is_ddr_mode_enable(config));
    }

    return status;
}

void flexspi_device_cmd_config_all_chips(uint32_t instance, flexspi_mem_config_t *config)
{
    uint32_t baseAddr = 0;
    uint32_t *flashSizeStart = &config->sflashA1Size;

    for (uint32_t index = 0; index < 4; index++)
    {
        uint32_t currentFlashSize = *flashSizeStart++;
        if (currentFlashSize > 0)
        {
            flexspi_device_cmd_config(instance, config, baseAddr);
            baseAddr += currentFlashSize;
        }
    }
}

void flexspi_device_cmd_config(uint32_t instance, flexspi_mem_config_t *config, uint32_t baseAddr)
{
    FLEXSPI_Type *base = flexspi_get_module_base(instance);
    flexspi_xfer_t flashXfer;
    flashXfer.operation = kFlexSpiOperation_Config;
    flashXfer.baseAddress = baseAddr;
    flashXfer.isParallelModeEnable = false;
    flashXfer.txSize = 4;

    for (uint32_t index = 0; index < 3; index++)
    {
        if (config->configCmdSeqs[index].seqId > 0)
        {
            // If device is working under DPI/QPI/OPI mode, ignore SPI2XPI command
            uint32_t read_cmd_pads = (base->LUT[0] >> 8) & 0x03;
            if ((read_cmd_pads > FLEXSPI_1PAD) && (config->configModeType[index] == kDeviceConfigCmdType_Spi2Xpi))
            {
                continue;
            }

            flashXfer.seqId = config->configCmdSeqs[index].seqId;
            flashXfer.seqNum = config->configCmdSeqs[index].seqNum;
            flashXfer.txBuffer = &config->configCmdArgs[index];

            // 這裡需要呼叫WRITE ENABLE指令
            flexspi_device_write_enable(instance, config, false, baseAddr);

            flexspi_update_lut(instance, 1, &config->lookupTable[4 * flashXfer.seqId], flashXfer.seqNum);
            flashXfer.seqId = 1;
            flexspi_command_xfer(instance, &flashXfer);

            if ((!config->waitTimeCfgCommands) &&
                (config->configModeType[index] != (uint8_t)kDeviceConfigCmdType_Spi2Xpi) &&
                (config->configModeType[index] != (uint8_t)kDeviceConfigCmdType_Xpi2Spi))
            {
                // 這裡需要呼叫READ STATUS指令
                flexspi_device_wait_busy(instance, config, false, baseAddr);
            }
            else
            {
                flexspi_sw_delay_us(config->waitTimeCfgCommands * 100UL);
            }
        }
    }
}

  至此,Flash工作頻率與Dummy Cycle的聯絡痞子衡便介紹完畢了,掌聲在哪裡~~~

歡迎訂閱

文章會同時釋出到我的 部落格園主頁CSDN主頁知乎主頁微信公眾號 平臺上。

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

相關文章