大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是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主頁、知乎主頁、微信公眾號 平臺上。
微信搜尋"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。