1. 程式人生 > >痞子衡嵌入式:深入i.MXRT1050系列ROM中序列NOR Flash啟動初始化流程

痞子衡嵌入式:深入i.MXRT1050系列ROM中序列NOR Flash啟動初始化流程

----   大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是**深入i.MXRT1050系列ROM中序列NOR Flash啟動初始化流程**。   從外部序列NOR Flash啟動問題是i.MXRT系列開發最高頻的話題,無論是開發除錯XIP應用程式階段還是最終產品量產階段都繞不開NOR Flash選型以及為它設計一個匹配的FDCB配置塊。如果不瞭解FDCB是什麼,先去看痞子衡之前的文章 [《Bootable image格式與載入》](https://www.cnblogs.com/henjay724/p/9125869.html)。   實際開發過程中,影響序列NOR Flash正常下載/啟動的因素有很多,痞子衡已經寫過三篇:[《16MB以上使用不當因素》](https://www.cnblogs.com/henjay724/p/13374775.html)、[《SFDP因素》](https://www.cnblogs.com/henjay724/p/13602259.html)、[《QE bit因素》](https://www.cnblogs.com/henjay724/p/13614684.html),列舉了三個不同因素,當然這都是出了問題,具體除錯分析才定位出來的,顯然還有很多未知因素等待陸續被髮掘。   如果總是被動去解決問題,那問題是解不完的。不如我們主動出擊,摸清i.MXRT啟動序列NOR Flash裝置到底是怎樣的初始化流程,搞清這個流程,將來定位啟動問題才能遊刃有餘,話不多說,開始今天的主題。 > * 備註:本文主角是i.MXRT1050,但內容也基本適用i.MXRT1010、i.MXRT1020,僅細節微小差別。 ### 一、整體初始化流程   我們知道外部序列NOR Flash是接到i.MXRT的FlexSPI外設引腳上,有時序列NOR Flash啟動也叫FlexSPI NOR啟動。關於FlexSPI NOR啟動流程,i.MXRT1050參考手冊System Boot章節有如下流圖,藍框之外的流程屬於常規i.MXRT啟動XIP App流程,是個通用流程。藍框之內才是具體FlexSPI初始化步驟,這個步驟概括得比較精煉。 ![](http://henjay724.com/image/cnblogs/i.MXRT1050_ROM_InitFlexSPI_boot_flow.PNG)   為了讓大家對FlexSPI NOR裝置啟動初始化流程有個更具體的概念,痞子衡重新畫了一張更詳細的流程圖,圖中灰底框裡描述得是FlexSPI初始化流程,痞子衡將其分解成了六步,我們有必要深入這六步初始化流程。 ![](http://henjay724.com/image/cnblogs/i.MXRT1050_ROM_InitFlexSPI_overall_v2.PNG) ### 二、分解初始化流程 #### 2.1 復位Flash晶片(可選)   第一步是嘗試復位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。 ```text fuse 0x6e0[7] - FLEXSPI_RESET_PIN_EN ``` ![](http://henjay724.com/image/cnblogs/i.MXRT1050_ROM_InitFlexSPI_fusemap_reset_pin.PNG)   正常的Flash都提供了RESET#引腳來實現跟上電覆位一樣的功能,對於普通8-pin的QSPI Flash,這個RESET#引腳往往是跟訊號線IO3複用的(僅在QE bit沒使能情況下有效),而對於16-pin的QSPI Flash或者HyperFlash,其RESET#引腳都是獨立的。 ![](http://henjay724.com/image/cnblogs/i.MXRT1050_ROM_InitFlexSPI_chip_reset.PNG)   BootROM就是藉助了Flash的RESET#引腳來實現的復位操作,實現程式碼比較簡單,i.MXRT1050 BootROM直接指定了GPIO1[9]當做復位訊號線,板級設計裡需要你將GPIO1[9]連到Flash的RESET#引腳,然後BootROM就是簡單地拉低GPIO1[9]即可。RESET#訊號都是低電平有效,BootROM直接拉低這個訊號持續250us,這個低電平持續時間對於復位來說是夠夠的,很多Flash資料手冊裡其實僅要求幾us即可。 > * 備註:對於BootROM的Flash復位功能來說,主要適用有獨立RESET#引腳的Flash。 ```C #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<
DR_SET = (1U<DR_CLR = (1U<DR_SET = (1U<CFG3 & 0x100000) >> 20) { flashType = 7; } else { flashType = (OCOTP->CFG4 & 0x700) >> 8; } ``` ![](http://henjay724.com/image/cnblogs/i.MXRT1050_ROM_InitFlexSPI_flashType_assignment.PNG)   上圖中最重要的FDCB賦值是config.memConfig.lookupTable,它是FlexSPI外設需要的核心配置,有了這個配置,CPU便可以直接從AHB匯流排讀取Flash的內容,因為FlexSPI會自動解析AHB匯流排讀請求然後翻譯成具體FlexSPI讀時序,底層讀時序需要的命令、地址位元組數、DUMMY週期都在lookupTable裡。BootROM預存瞭如下6大類Flash的lookupTable: ```C // 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 }; ``` #### 2.3 第一次FlexSPI初始化   第三步就是利用上述配置完成的初始FDCB塊對FlexSPI外設進行第一次初始化,就是下面程式碼,這個流程跟官方SDK裡的flexspi_nor_flash_init()大同小異,這裡不予具體展開。如果在這裡初始化就返回失敗(這裡一般不會失敗,因為僅僅是FlexSPI外設自身初始化,並不涉及操作外部Flash晶片的動作),BootROM則直接退出FlexSPI NOR裝置啟動,轉入SDP下載。 ```C #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); ``` #### 2.4 若干善後工作   上述第一次FlexSPI初始化一般都會成功的,但這並不代表fuse裡的flashType等配置跟板子上Flash型號是匹配的,也就是說初始FDCB配置塊此時還沒有被充分驗證其是否適用板載Flash型號。   FlexSPI第一次初始化結束後,為了保證後續能正常AHB訪問,BootROM裡做了一些善後工作,主要是兩件事: >
1. 做一些訪問前的延時:根絕fuse 0x450[3:2] - HOLD TIME來呼叫microseconds_delay()做延時,以使FlexSPI外設完全準備好。 > 2. 做一次無效AHB訪問:類似這樣的程式碼 volatile uint32_t dummy = *(uint32_t *)0x60000000;,無效AHB讀可以使Flash退出continuous read模式 #### 2.5 獲取使用者FDCB配置塊   善後工作結束之後,此時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下載。 ```C #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表,即如下所示: ```C // 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 }; ``` #### 2.6 第二次FlexSPI初始化   到了這裡,基本代表第一次FlexSPI初始化是正確且可用的,並且能夠拿到有效的使用者FDCB配置塊。這時候就是利用使用者FDCB配置塊對FlexSPI外設做第二次初始化,初始化程式碼流程跟第一次初始化是一模一樣的。   這個第二次初始化是非常有必要的,因為它反映了使用者的真實需求,使用者FDCB配置塊裡會準確描述板載Flash的全面特性(訪問速度,真實儲存空間大小,特殊定製LUT等等),這些資訊必須由使用者來提供。   需要注意的是,第二次FlexSPI初始化返回成功並不代表使用者FDCB配置塊一定就是正確的,還是那句話,這僅僅是對FlexSPI外設自身的初始化。後續常規App解析流程裡才是對這個使用者FDCB配置塊的真正考驗。   至此,深入i.MXRT1050系列ROM中序列NOR Flash啟動初始化流程痞子衡便介紹完畢了,掌聲在哪裡~~~ ### 歡迎訂閱 文章會同時釋出到我的 [部落格園主頁](https://www.cnblogs.com/henjay724/)、[CSDN主頁](https://blog.csdn.net/henjay724)、[知乎主頁](https://www.zhihu.com/people/henjay724)、[微信公眾號](http://weixin.sogou.com/weixin?type=1&query=痞子衡嵌入式) 平臺上。 微信搜尋"__痞子衡嵌入式__"或者掃描下面二維碼,就可以在手機上第一時間看了哦。 ![](http://henjay724.com/image/github/pzhMcu_qrcode_258x