大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家介紹的是Flash工作頻率與Dummy Cycle的聯(lián)系。
上一篇文章 《從頭開始認(rèn)識(shí)i.MXRT啟動(dòng)頭FDCB里的lookupTable》,痞子衡帶大家從頭梳理了下i.MXRT下啟動(dòng)頭FDCB里lookupTable的設(shè)計(jì)與實(shí)現(xiàn)細(xì)節(jié),在這個(gè)過程中也簡(jiǎn)單跟大家介紹了串行NOR Flash的工作模式(主要是Fast Read Quad I/O),今天痞子衡在FDCB設(shè)置范疇下跟大家再進(jìn)一步地探討Flash工作頻率與Dummy Cycle設(shè)置問題。
一、引入Flash工作頻率與Dummy Cycle對(duì)應(yīng)關(guān)系問題
今天我們以i.MXRT1170-EVK上的板載Flash為例,其具體型號(hào)為IS25WP128-JBLE,在Flash數(shù)據(jù)手冊(cè)特性介紹里可以看到這顆Flash最高可以運(yùn)行在133MHz頻率下(SDR模式),并且其Dummy Cycle也是可選的,那么Dummy Cycle是不是可以任意配呢?答案既是也不是,痞子衡先賣個(gè)關(guān)子。
讓我們?cè)賮砘仡櫹逻@顆Flash的Fast Read Quad I/O時(shí)序圖,從時(shí)序圖里可以看到Dummy Cycle默認(rèn)是6(包括Mode Bits時(shí)序一共6個(gè)SCK時(shí)鐘周期),那么默認(rèn)的6個(gè)Dummy Cycle是不是適用全部的Flash工作頻率呢?咱們繼續(xù)看Flash數(shù)據(jù)手冊(cè)。
在數(shù)據(jù)手冊(cè)里找到了下面這張表,Read Dummy Cycle與最大工作頻率的聯(lián)系,從表里可以看到當(dāng)Flash工作在Fast Read Quad I/O模式時(shí),默認(rèn)的6個(gè)Dummy Cycle適用的最大工作頻率是104MHz,即104MHz工作頻率及以下均可以使用默認(rèn)的6個(gè)Dummy Cycle。如果工作頻率高于104MHz,Dummy Cycle應(yīng)相應(yīng)調(diào)大,比如133MHz工作頻率需對(duì)應(yīng)至少9個(gè)Dummy Cycle。
二、如何在FDCB里設(shè)置FlexSPI的Dummy Cycle?
根據(jù)上面的分析,如果我們希望i.MXRT1170-EVK上這顆IS25WP128-JBLE啟動(dòng)后工作在133MHz,那么我們需要在FDCB里至少配置9個(gè)Dummy Cycle。因此下述FDCB啟動(dòng)頭里 FLASH_DUMMY_CYCLES 宏應(yīng)設(shè)為9, qspiflash_config.memConfig.serialClkFreq 應(yīng)改為 kFlexSpiSerialClk_133MHz,這樣的設(shè)置對(duì)于i.MXRT端是足夠的,因?yàn)楦暮驠lexSPI外設(shè)確實(shí)可以輸出133MHz的SCK,并且按9個(gè)Dummy Cycle的時(shí)序去讀Flash。
但是把這樣的FDCB啟動(dòng)頭直接下載進(jìn)Flash后發(fā)現(xiàn)i.MXRT無法從Flash正常啟動(dòng),因?yàn)镕lash端的Dummy Cycle還是默認(rèn)的6個(gè)SCK周期,上面的FDCB僅僅是調(diào)整了FlexSPI輸出,并不會(huì)同步調(diào)整Flash端,此時(shí)主從兩端Dummy Cycle數(shù)不匹配,時(shí)序錯(cuò)亂了,傳輸數(shù)據(jù)發(fā)生了錯(cuò)位,應(yīng)用程序當(dāng)然無法啟動(dòng)。
所以Flash這邊需要其他的方式設(shè)置好Dummy Cycle后,上述方式更改的FDCB啟動(dòng)頭才能正常使用。
有一個(gè)現(xiàn)象需要特別說明一下,如果我們直接修改 qspiflash_config.memConfig.serialClkFreq 到 kFlexSpiSerialClk_133MHz, 但是 FLASH_DUMMY_CYCLES 依舊保持默認(rèn)的6,這樣的FDCB頭下載進(jìn)Flash也可以正常工作的,雖然有點(diǎn)違反Flash數(shù)據(jù)手冊(cè)里的Dummy Cycle規(guī)范,但實(shí)測(cè)下來似乎是沒問題的,那是不是意味著我們根本不需要?jiǎng)幽J(rèn)的Dummy Cycle?其實(shí)不是的,當(dāng)我們使能Flash的 continuous read mode 的時(shí)候,Dummy Cycle不規(guī)范就會(huì)出問題,關(guān)于這點(diǎn)痞子衡會(huì)單獨(dú)寫一篇文章細(xì)聊。
三、如何更改Flash里的Dummy Cycle?
那么Flash里的Dummy Cycle到底怎么改呢?這需要我們繼續(xù)看Flash數(shù)據(jù)手冊(cè),IS25WP128-JBLE內(nèi)部有個(gè)8bit的Read Register,其bit6-bit3是Dummy Cycles設(shè)置,可設(shè)范圍是1-15,并且在寄存器類型里可以看到其有易失性和非易失性兩種屬性,也就是說我們既可以臨時(shí)地改Dummy Cycle(掉電即恢復(fù)默認(rèn)6),也可以永久地改Dummy Cycle(掉電仍保持上一次設(shè)置)。
在IS25WP128-JBLE的指令集表里,可以看到有專門寫Read Register的指令,SRPNV指令是非易失性方式地寫Read Register,SRPV指令是易失性方式去寫Read Register。
分析到這里,問題就變成到底是使用一個(gè)額外的小工程(比如借助SDK里的flexspi example稍微更改下代碼)以SRPNV指令去永久性更改下Flash里的Dummy Cycle再用作i.MXRT啟動(dòng),還是i.MXRT每次啟動(dòng)時(shí)直接借助FDCB啟動(dòng)頭里的設(shè)置用SRPV指令臨時(shí)地更改Flash的Dummy Cycle?從靈活性角度,痞子衡推薦第二種方式,那么在FDCB里應(yīng)該怎么做?痞子衡直接給答案:
// 設(shè)置Dummy Cycle數(shù)
#define FLASH_DUMMY_CYCLES 9
// 寫Read Register時(shí)序在LUT中的index(可自定義位置,但不要占BootROM預(yù)設(shè)的幾個(gè)時(shí)序位置)
#define CMD_LUT_SEQ_IDX_SET_READ_PARAM 7
// BootROM中預(yù)設(shè)的LUT命令時(shí)序的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寄存器配置時(shí)序在LUT中index
.configCmdSeqs[0] =
{
.seqNum = 1,
.seqId = CMD_LUT_SEQ_IDX_SET_READ_PARAM,
.reserved = 0,
},
// 設(shè)定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寄存器配置時(shí)序(這個(gè)時(shí)序需要上面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對(duì)FDCB里Flash寄存器配置的處理
說到BootROM對(duì)FDCB的處理,大家有必要先回顧下痞子衡寫過的一篇舊文 《深入i.MXRT1050系列ROM中串行NOR Flash啟動(dòng)初始化流程》,文章雖然是針對(duì)i.MXRT1050寫的,但基本流程也差不多適用i.MXRT1170,在文中的 2.6 小節(jié)第二次初始化里,我們?cè)谏厦鍲DCB里的關(guān)于Flash寄存器的額外配置操作才會(huì)被處理,代碼大致如下:
status_t flexspi_init(uint32_t instance, flexspi_mem_config_t *config)
{
status_t status = kStatus_InvalidArgument;
// 決定是否用30MHz SDR的安全時(shí)鐘來做FlexSPI初始化
bool needSafeFreq = (config->deviceModeCfgEnable || config->configCmdEnable) &&
((config->controllerMiscOption & (1 << kFlexSpiMiscOffset_SafeConfigFreqEnable)));
flexspi_clock_gate_disable(instance);
flexspi_iomux_config(instance, config);
if (needSafeFreq)
{
flexspi_clock_config(instance, kFlexSpiSerialClk_SafeFreq, kFlexSpiClk_SDR);
}
flexspi_clock_gate_enable(instance);
// 省略代碼片段,F(xiàn)lexSPI模塊本身的完整初始化
// ...
flexspi_swreset(base);
// 實(shí)現(xiàn)Flash配置寄存器寫入操作
if (config->configCmdEnable)
{
// Port A1/A2/B1/B2如使能則全寫一遍
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;
}
}
}
if (needSafeFreq)
{
// 重新配回指定的FlexSPI時(shí)鐘
flexspi_clock_config(instance, config->serialClkFreq, flexspi_is_ddr_mode_enable(config));
}
return status;
}
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];
// 這里需要調(diào)用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))
{
// 這里需要調(diào)用READ STATUS指令
flexspi_device_wait_busy(instance, config, false, baseAddr);
}
else
{
flexspi_sw_delay_us(config->waitTimeCfgCommands * 100UL);
}
}
}
}
至此,F(xiàn)lash工作頻率與Dummy Cycle的聯(lián)系痞子衡便介紹完畢了,掌聲在哪里~~~