你們好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給你們分享的是深刻i.MXRT1050系列ROM中串行NOR Flash啓動初始化流程。html
從外部串行NOR Flash啓動問題是i.MXRT系列開發最高頻的話題,不管是開發調試XIP應用程序階段仍是最終產品量產階段都繞不開NOR Flash選型以及爲它設計一個匹配的FDCB配置塊。若是不瞭解FDCB是什麼,先去看痞子衡以前的文章 《Bootable image格式與加載》。微信
實際開發過程當中,影響串行NOR Flash正常下載/啓動的因素有不少,痞子衡已經寫過三篇:《16MB以上使用不當因素》、《SFDP因素》、《QE bit因素》,列舉了三個不一樣因素,固然這都是出了問題,具體調試分析才定位出來的,顯然還有不少未知因素等待陸續被髮掘。性能
若是老是被動去解決問題,那問題是解不完的。不如咱們主動出擊,摸清i.MXRT啓動串行NOR Flash設備究竟是怎樣的初始化流程,搞清這個流程,未來定位啓動問題才能遊刃有餘,話很少說,開始今天的主題。flex
- 備註:本文主角是i.MXRT1050,但內容也基本適用i.MXRT10十、i.MXRT1020,僅細節微小差異。
咱們知道外部串行NOR Flash是接到i.MXRT的FlexSPI外設引腳上,有時串行NOR Flash啓動也叫FlexSPI NOR啓動。關於FlexSPI NOR啓動流程,i.MXRT1050參考手冊System Boot章節有以下流圖,藍框以外的流程屬於常規i.MXRT啓動XIP App流程,是個通用流程。藍框以內纔是具體FlexSPI初始化步驟,這個步驟歸納得比較精煉。ui
爲了讓你們對FlexSPI NOR設備啓動初始化流程有個更具體的概念,痞子衡從新畫了一張更詳細的流程圖,圖中灰底框裏描述得是FlexSPI初始化流程,痞子衡將其分解成了六步,咱們有必要深刻這六步初始化流程。.net
第一步是嘗試復位Flash芯片,這步是可選的,在fuse_0x6e0[7]裏配置,默認是不使能的。復位Flash目的是爲了讓Flash處於一個肯定的初始狀態,方便i.MXRT BootROM去配置訪問。爲何要強調Flash的初始狀態,由於不少時候i.MXRT未必是冷啓動(上電啓動),也有多是軟復位啓動(好比調用NVIC_SystemReset),這時候外部Flash已經被軟復位前執行過的BootROM甚至用戶App配置過,所以Flash的狀態可能不是上電初始狀態(通常來講板級設計裏Flash的RESET#引腳要麼懸空,要麼鏈接i.MXRT的POR#引腳),這可能會影響軟復位後BootROM去再次配置啓動這塊不定態的Flash。翻譯
fuse 0x6e0[7] - FLEXSPI_RESET_PIN_EN
正常的Flash都提供了RESET#引腳來實現跟上電覆位同樣的功能,對於普通8-pin的QSPI Flash,這個RESET#引腳每每是跟信號線IO3複用的(僅在QE bit沒使能狀況下有效),而對於16-pin的QSPI Flash或者HyperFlash,其RESET#引腳都是獨立的。設計
BootROM就是藉助了Flash的RESET#引腳來實現的復位操做,實現代碼比較簡單,i.MXRT1050 BootROM直接指定了GPIO1[9]當作復位信號線,板級設計裏須要你將GPIO1[9]連到Flash的RESET#引腳,而後BootROM就是簡單地拉低GPIO1[9]便可。RESET#信號都是低電平有效,BootROM直接拉低這個信號持續250us,這個低電平持續時間對於復位來講是夠夠的,不少Flash數據手冊裏其實僅要求幾us便可。3d
- 備註:對於BootROM的Flash復位功能來講,主要適用有獨立RESET#引腳的Flash。
#define RESET_PAD_IDX kIOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_09 #define RESET_PIN_MUX IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(5) #define RESET_PIN_GPIO GPIO1 #define RESET_PIN_INDEX 9 if ((OCOTP->MISC_CONF1 & 0x80) >> 7) { // Set pinmux as GPIO IOMUXC->SW_MUX_CTL_PAD[RESET_PAD_IDX] = RESET_PIN_MUX; // Set GPIO to output mode RESET_PIN_GPIO->GDIR |= (1U<<RESET_PIN_INDEX); // High RESET_PIN_GPIO->DR_SET = (1U<<RESET_PIN_INDEX); sw_delay_us(250); // Low RESET_PIN_GPIO->DR_CLR = (1U<<RESET_PIN_INDEX); sw_delay_us(250); // High RESET_PIN_GPIO->DR_SET = (1U<<RESET_PIN_INDEX); sw_delay_us(500); }
第二步是準備一個初始的FDCB配置塊(即flexspi_nor_config_t,大小爲512字節),這個初始FDCB配置塊將被用來作FlexSPI外設的第一次初始化,目的是爲了可以保證FlexSPI初始化以後CPU可以使用AHB方式正常讀取Flash(訪問性能不要求最高,但求穩定訪問)。這個初始FDCB並非一個徹底定死的配置塊,部分值也是根據fuse來配置的,一共有三處fuse位置,其中最重要的是FLASH_TYPE:調試
fuse 0x440[20] - QSPI_2ND_BOOTPIN_ENABLE,決定是否啓動第二組FlexSPI pinmux fuse 0x450[10:8] - FLASH_TYPE,決定當前鏈接的Flash類型 fuse 0x470[30:24] - DELAY_CELL_NUM,設置Flash讀訪問時序數據線有效時間
初始FDCB配置塊中僅給memConfig設了值,這個memConfig纔是用於配置FlexSPI外設自己。以下部分賦值是固定的FDCB設置,不受fuse影響,從這個固定配置你能夠看到,BootROM假定了全部外接Flash都是128MB,且訪問時鐘(SCK)速度能支持30MHz,不要對這個假定感到焦慮,它只是用於FlexSPI第一次初始化,目的只求能正常訪問Flash前4KB便可:
flexspi_nor_config_t config; memset(config, 0, sizeof(config)); // 公共的FDCB配置 config.memConfig.tag = FLEXSPI_CFG_BLK_TAG; config.memConfig.version = FLEXSPI_CFG_BLK_VERSION; config.memConfig.deviceType = kFlexSpiDeviceType_SerialNOR; config.memConfig.sflashA1Size = 128UL*1024*1024; config.memConfig.serialClkFreq = kFlexSpiSerialClk_30MHz; config.memConfig.dataHoldTime = 3; config.memConfig.dataSetupTime = 3; config.memConfig.timeoutInMs = 1000;
而後即是從fuse裏獲取flashType,根據具體flashType來對初始FDCB配置塊作進一步動態賦值,這進一步賦值才用於區分不一樣Flash種類(Pad數量、DQS信號屬性、最重要的lookupTable等)。
// 從fuse裏獲取flash類型 uint32_t flashType; if ((OCOTP->CFG3 & 0x100000) >> 20) { flashType = 7; } else { flashType = (OCOTP->CFG4 & 0x700) >> 8; }
上圖中最重要的FDCB賦值是config.memConfig.lookupTable,它是FlexSPI外設須要的核心配置,有了這個配置,CPU即可以直接從AHB總線讀取Flash的內容,由於FlexSPI會自動解析AHB總線讀請求而後翻譯成具體FlexSPI讀時序,底層讀時序須要的命令、地址字節數、DUMMY週期都在lookupTable裏。BootROM預存了以下6大類Flash的lookupTable:
// Dedicated 3Byte Address Read(0x03), 24bit address static const uint32_t s_dedicated3bRead[4] = { FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x03, RADDR_SDR, FLEXSPI_1PAD, 0x18), FLEXSPI_LUT_SEQ(READ_SDR, FLEXSPI_1PAD, 0x04, STOP, FLEXSPI_1PAD, 0), 0, 0 }; // Dedicated 4Byte Address Read(0x13), 32 bit address static const uint32_t s_dedicated4bRead[4] = { FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x13, RADDR_SDR, FLEXSPI_1PAD, 0x20), FLEXSPI_LUT_SEQ(READ_SDR, FLEXSPI_1PAD, 0x04, STOP, FLEXSPI_1PAD, 0), 0, 0 }; // HyperFlash Read static const uint32_t s_hyperflashRead[4] = { FLEXSPI_LUT_SEQ(CMD_DDR, FLEXSPI_8PAD, 0xA0, RADDR_DDR, FLEXSPI_8PAD, 0x18), FLEXSPI_LUT_SEQ(CADDR_DDR, FLEXSPI_8PAD, 0x10, DUMMY_RWDS_DDR, FLEXSPI_8PAD, 0x0c), FLEXSPI_LUT_SEQ(READ_DDR, FLEXSPI_8PAD, 0x04, STOP, FLEXSPI_8PAD, 0), 0 }; // MXIC Octal DDR read static const uint32_t s_mxicOctDdrRead[4] = { FLEXSPI_LUT_SEQ(CMD_DDR, FLEXSPI_8PAD, 0xEE, CMD_DDR, FLEXSPI_8PAD, 0x11), FLEXSPI_LUT_SEQ(RADDR_DDR, FLEXSPI_8PAD, 0x20, DUMMY_DDR, FLEXSPI_8PAD, 0xc), FLEXSPI_LUT_SEQ(READ_DDR, FLEXSPI_8PAD, 0x04, STOP, FLEXSPI_8PAD, 0), 0 }; // Micron Octal DDR read static const uint32_t s_micronOctDdrRead[4] = { FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_8PAD, 0xFD, RADDR_DDR, FLEXSPI_8PAD, 0x20), FLEXSPI_LUT_SEQ(DUMMY_DDR, FLEXSPI_8PAD, 0x8, READ_DDR, FLEXSPI_8PAD, 0x04), 0, 0 }; // Adesto Octal DDR read static const uint32_t s_adestoOctDdrRead[4] = { FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_8PAD, 0x0B, RADDR_DDR, FLEXSPI_8PAD, 0x20), FLEXSPI_LUT_SEQ(DUMMY_DDR, FLEXSPI_8PAD, 0x8, READ_DDR, FLEXSPI_8PAD, 0x04), 0, 0 };
第三步就是利用上述配置完成的初始FDCB塊對FlexSPI外設進行第一次初始化,就是下面代碼,這個流程跟官方SDK裏的flexspi_nor_flash_init()大同小異,這裏不予具體展開。若是在這裏初始化就返回失敗(這裏通常不會失敗,由於僅僅是FlexSPI外設自身初始化,並不涉及操做外部Flash芯片的動做),BootROM則直接退出FlexSPI NOR設備啓動,轉入SDP下載。
#define FLEXSPI_INSTANCE 0 uint32_t instance = FLEXSPI_INSTANCE; status_t status = flexspi_init(instance, (flexspi_mem_config_t *)(&config)); if (status != kStatus_Success) { return status; } flexspi_update_lut(instance, 0, &config.memConfig.lookupTable, 1);
上述第一次FlexSPI初始化通常都會成功的,但這並不表明fuse裏的flashType等配置跟板子上Flash型號是匹配的,也就是說初始FDCB配置塊此時尚未被充分驗證其是否適用板載Flash型號。
FlexSPI第一次初始化結束後,爲了保證後續能正常AHB訪問,BootROM裏作了一些善後工做,主要是兩件事:
- 作一些訪問前的延時:根絕fuse 0x450[3:2] - HOLD TIME來調用microseconds_delay()作延時,以使FlexSPI外設徹底準備好。
- 作一次無效AHB訪問:相似這樣的代碼 volatile uint32_t dummy = *(uint32_t *)0x60000000;,無效AHB讀可使Flash退出continuous read模式
善後工做結束以後,此時CPU應該能夠經過AHB正常訪問Flash了,這個階段咱們只須要從Flash的偏移0地址處讀取用戶FDCB,驗證用戶FDCB是否存在,這裏纔是對前面初始FDCB配置塊以及第一次FlexSPI外設初始化的真正考驗。
驗證用戶FDCB是否存在就是簡單讀取FDCB的前四個字節(tag),驗證這個tag是否合法。若是第一次驗證tag不成功(有多是FlexSPI配置不正確,也有多是用戶FDCB不存在),會嘗試作一次三字節地址切換到四字節地址的LUT更新(僅適用QSPI Flash),而後作第二次tag讀取驗證,若是此時仍是驗證失敗(大機率是不存在用戶FDCB了),BootROM則直接退出FlexSPI NOR設備啓動,轉入SDP下載。
#define FlexSPI_AMBA_BASE (0x60000000U) #define FLASH_BASE FlexSPI_AMBA_BASE // 使用三字節地址的LUT對Flash進行初次AHB訪問 flexspi_clear_cache(FLEXSPI_INSTANCE); flexspi_nor_config_t *pConfig = (flexspi_nor_config_t *)FLASH_BASE; if (pConfig->memConfig.tag != FLEXSPI_CFG_BLK_TAG) { // 由於拿不到用戶FDCB的tag,嘗試切換使用四字節地址的LUT if (flashType == 0) { flexspi_update_lut(FLEXSPI_INSTANCE, 0, s_basic4bRead, 1); } flexspi_clear_cache(FLEXSPI_INSTANCE); pConfig = (flexspi_nor_config_t *)FLASH_BASE; } // 對Flash進行第二次AHB訪問,再次確認可否拿到用戶FDCB的tag if (pConfig->memConfig.tag != FLEXSPI_CFG_BLK_TAG) { return kStatus_Fail; }
上面代碼裏有flexspi_clear_cache()操做,這個其實就是利用FLEXSPI0->MCR0[SWRESET]作一個外設級別的軟復位,另外代碼裏還涉及到一個四字節地址QSPI Flash的LUT表,即以下所示:
// Basic read with 32bit address static const uint32_t s_basic4bRead[4] = { FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x03, RADDR_SDR, FLEXSPI_1PAD, 0x20), FLEXSPI_LUT_SEQ(READ_SDR, FLEXSPI_1PAD, 0x04, STOP, FLEXSPI_1PAD, 0), 0, 0 };
到了這裏,基本表明第一次FlexSPI初始化是正確且可用的,而且可以拿到有效的用戶FDCB配置塊。這時候就是利用用戶FDCB配置塊對FlexSPI外設作第二次初始化,初始化代碼流程跟第一次初始化是如出一轍的。
這個第二次初始化是很是有必要的,由於它反映了用戶的真實需求,用戶FDCB配置塊裏會準確描述板載Flash的全面特性(訪問速度,真實存儲空間大小,特殊定製LUT等等),這些信息必須由用戶來提供。
須要注意的是,第二次FlexSPI初始化返回成功並不表明用戶FDCB配置塊必定就是正確的,仍是那句話,這僅僅是對FlexSPI外設自身的初始化。後續常規App解析流程裏纔是對這個用戶FDCB配置塊的真正考驗。
至此,深刻i.MXRT1050系列ROM中串行NOR Flash啓動初始化流程痞子衡便介紹完畢了,掌聲在哪裏~~~
文章會同時發佈到個人 博客園主頁、CSDN主頁、知乎主頁、微信公衆號 平臺上。
微信搜索"痞子衡嵌入式"或者掃描下面二維碼,就能夠在手機上第一時間看了哦。