大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家介紹的是i.MXRT中FlexSPI外設(shè)lookupTable里配置Normal read的一個小誤區(qū)。
關(guān)于串行四線NOR Flash,當(dāng)其作為啟動(XiP)設(shè)備時,我們最常配置的讀模式應(yīng)該是 Fast Read Quad I/O SDR (0xEB),這種模式在數(shù)據(jù)傳輸時會用上全部四根I/O線(IO0-3),并且SCK可達(dá)最高頻率(通常133MHz),這種讀模式下Flash性能相當(dāng)高。但有時候某些設(shè)計里為了保證通用性(比如我們想要一個兼容所有類型Flash型號的啟動頭),我們也會嘗試配置最基礎(chǔ)的讀模式 Normal Read (0x03),基礎(chǔ)的讀模式在數(shù)據(jù)傳輸時僅使用一根I/O線(IO1),并且SCK頻率通常最高50MHz,這種模式其實更多是為了兼容SPI接口的EEPROM器件。
Normal Read是任何串行NOR Flash都支持的讀模式,也是最簡單的一種模式,但在i.MXRT的FlexSPI外設(shè)里配置這種模式會存在關(guān)于Dummy Cycle設(shè)置的一個小誤區(qū),且聽痞子衡道來:
一、在FDCB里使能Normal Read
關(guān)于FDCB及l(fā)ookupTable相關(guān)知識詳見痞子衡舊文 《從頭開始認(rèn)識i.MXRT啟動頭FDCB里的lookupTable》。現(xiàn)在我們嘗試準(zhǔn)備一個使能Normal read的FDCB頭,F(xiàn)lash器件就以華邦W25Q64JWS-IQ為例,查看其數(shù)據(jù)手冊,找到如下Normal read時序圖:
從Normal read時序圖里可以看出,其僅包含命令序列、地址序列、讀數(shù)據(jù)序列、停止序列共四個子序列,與Fast Read Quad I/O SDR時序相比少了模式序列和Dummy序列,因此示例FDCB如下。經(jīng)i.MXRT1050-EVKB板子實測,這個示例FDCB是可以正常用于啟動的。
const flexspi_nor_config_t qspiflash_config = {
.memConfig =
{
.tag = FLEXSPI_CFG_BLK_TAG,
.version = FLEXSPI_CFG_BLK_VERSION,
// 低速情況下可以使用LoopbackInternally
.readSampleClkSrc = kFlexSPIReadSampleClk_LoopbackInternally,
.csHoldTime = 3u,
.csSetupTime = 3u,
.controllerMiscOption = 0x10,
.deviceType = kFlexSpiDeviceType_SerialNOR,
// 實際上這里不管設(shè)置1Pad/2Pads/4Pads,在iomuxc里都會配置IO0-3
.sflashPadType = kSerialFlash_1Pad,
// 配置SCK頻率不要超過50MHz
.serialClkFreq = kFlexSpiSerialClk_50MHz,
.sflashA1Size = 8u * 1024u * 1024u,
.lookupTable =
{
// Normal Read LUTs
// 包含四個子序列,且全是通過 FLEXSPI_1PAD 傳輸
[4*CMD_LUT_SEQ_IDX_READ + 0] = FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x03, RADDR_SDR, FLEXSPI_1PAD, 0x18),
[4*CMD_LUT_SEQ_IDX_READ + 1] = FLEXSPI_LUT_SEQ(READ_SDR, FLEXSPI_1PAD, 0x04, STOP, FLEXSPI_1PAD, 0x00),
},
},
.pageSize = 256u,
.sectorSize = 4u * 1024u,
.blockSize = 64u * 1024u,
.isUniformBlockSize = false,
};
按照文章 《實抓Flash信號波形來看i.MXRT的FlexSPI外設(shè)下AHB讀訪問情形(無緩存)》 第二小節(jié)里的軟硬件測試環(huán)境,我們測試無緩存下的 memcpy((void *)0x20200000, (void *)0x60002400, 8); 語句執(zhí)行在Flash端時序如下(為便于捕捉Flash信號,實際測試時SCK頻率降到了30MHz):
二、關(guān)于Dummy Cycle的小誤區(qū)是什么?
在配置Fast Read Quad I/O SDR時序的lookupTable里,我們常常會根據(jù)不同的Flash器件特性在Dummy子序列里填入不同的Cycle數(shù)值。現(xiàn)在我們配置的是Normal read時序,有人可能會保留Dummy子序列,但是將其參數(shù)值設(shè)0(如下代碼所示),根據(jù)字面理解,這樣似乎也沒問題,但在FlexSPI外設(shè)里,這樣是不行的,這就是關(guān)于Dummy Cycle的誤區(qū)。
#include "fsl_flexspi.h"
#define NOR_CMD_LUT_SEQ_IDX_READ_NORMAL 0
uint32_t s_customLUT_wrong[4] = {
/* Normal read mode -SDR */
[4*NOR_CMD_LUT_SEQ_IDX_READ_NORMAL + 0] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x03, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),
// 保留kFLEXSPI_Command_DUMMY_SDR序列,但參數(shù)值填0
[4*NOR_CMD_LUT_SEQ_IDX_READ_NORMAL + 1] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_1PAD, 0x00, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),
[4*NOR_CMD_LUT_SEQ_IDX_READ_NORMAL + 2] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0x00, 0, 0, 0),
};
三、Dummy Cycle設(shè)0的誤區(qū)帶來什么后果?
使用第二小節(jié)里的 s_customLUT_wrong[] 去配置FlexSPI LUT到底會產(chǎn)生什么后果,我們繼續(xù)在文章 《實抓Flash信號波形來看i.MXRT的FlexSPI外設(shè)下AHB讀訪問情形(無緩存)》 第二小節(jié)里的軟硬件測試環(huán)境下抓一下時序圖。可先借助SDK里flexspi驅(qū)動如下兩個函數(shù)來更新LUT,因為是在XiP環(huán)境下執(zhí)行的ramfunc代碼,所以無需再從頭初始化FlexSPI模塊。
#include "fsl_flexspi.h"
/* Update LUT table. */
FLEXSPI_UpdateLUT(FLEXSPI, 0, s_customLUT_wrong, 4);
/* Do software reset. */
FLEXSPI_SoftwareReset(FLEXSPI);
現(xiàn)在我們在IAR下在線調(diào)試看看結(jié)果,當(dāng)使用 s_customLUT_wrong 更新掉FlexSPI的LUT后,F(xiàn)lash的訪問立刻出現(xiàn)了異常,memory窗口觀察到的數(shù)值全部變成了0xFF, 并且拷貝目的地內(nèi)部RAM相應(yīng)地址處也全是0xFF,說明從功能角度 memcpy 語句并沒有產(chǎn)生預(yù)想效果(但此時 ramfunc 代碼是能正常往下執(zhí)行的)。
抓出Flash端波形,我們發(fā)現(xiàn)僅8字節(jié)數(shù)據(jù)的 memcpy 竟然產(chǎn)生了長達(dá) 33ms 的讀時序(換算一下,即實際讀取了約128KB的數(shù)據(jù)),放大最前面的時序,可以看到命令(0x03)、地址(0x002400)、初始讀出數(shù)據(jù)(0x01、0x02、0x03...)都是正常的,并且確實沒有Dummy Cycle子序列,對比應(yīng)用程序binary文件后發(fā)現(xiàn)還真的按序讀出了128KB有效Flash數(shù)據(jù),所以Flash端本身的時序響應(yīng)沒有問題,但AHB總線與FlexSPI外設(shè)之間的配合出了問題,導(dǎo)致CPU無法拿到有效Flash數(shù)據(jù),這也是開發(fā)環(huán)境memory窗口無法再看到真實Flash數(shù)據(jù)的原因。至于為何讀到128KB數(shù)據(jù)才停下來,痞子衡也不得而知。如果代碼里循環(huán)執(zhí)行這句 memcpy 語句,F(xiàn)lash端就也相應(yīng)循環(huán)出現(xiàn) 33ms 的讀時序。
至此,i.MXRT中FlexSPI外設(shè)lookupTable里配置Normal read的一個小誤區(qū)痞子衡便介紹完畢了,掌聲在哪里~~~
附錄、MIMXRT1050下完整FlexSPI初始化代碼
如果上述異常Normal read時序測試不是在XiP環(huán)境下進(jìn)行的,那么就需要完整地初始化FlexSPI外設(shè),可以使用如下 flexspi_nor_flash_init() 函數(shù):
#include "clock_config.h"
#include "fsl_flexspi.h"
flexspi_device_config_t s_deviceconfig = {
.flexspiRootClk = 30000000,
.flashSize = 0x2000, /* 64Mb/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_NORMAL,
.ARDSeqNumber = 1,
.AHBWriteWaitUnit = kFLEXSPI_AhbWriteWaitUnit2AhbCycle,
.AHBWriteWaitInterval = 0,
};
static inline void flexspi_clock_init(void)
{
const clock_usb_pll_config_t g_ccmConfigUsbPll = {.loopDivider = 0U};
CLOCK_InitUsb1Pll(&g_ccmConfigUsbPll);
CLOCK_InitUsb1Pfd(kCLOCK_Pfd0, 35); /* Set PLL3 PFD0 clock 247MHZ. */
CLOCK_SetMux(kCLOCK_FlexspiMux, 0x3); /* Choose PLL3 PFD0 clock as flexspi source clock. */
CLOCK_SetDiv(kCLOCK_FlexspiDiv, 7); /* flexspi clock 30M. */
}
void flexspi_nor_flash_init(void)
{
flexspi_clock_init();
/* Get FLEXSPI default settings and configure the flexspi. */
flexspi_config_t config;
FLEXSPI_GetDefaultConfig(&config);
/* Set AHB buffer size for reading data through AHB bus. */
config.ahbConfig.enableAHBPrefetch = false;
config.ahbConfig.enableAHBBufferable = true;
config.ahbConfig.enableReadAddressOpt = true;
config.ahbConfig.enableAHBCachable = false;
config.rxSampleClock = kFLEXSPI_ReadSampleClkLoopbackInternally;
FLEXSPI_Init(FLEXSPI, &config);
/* Configure flash settings according to serial flash feature. */
FLEXSPI_SetFlashConfig(FLEXSPI, &s_deviceconfig, kFLEXSPI_PortA1);
/* Update LUT table. */
FLEXSPI_UpdateLUT(FLEXSPI, 0, s_customLUT_wrong, 4);
/* Do software reset. */
FLEXSPI_SoftwareReset(FLEXSPI);
}