你們好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給你們介紹的是i.MXRT系列ROM中的FlexSPI驅動API實現IAP。html
痞子衡的技術交流羣裏常常有羣友提問: i.MXRT中的FlexSPI驅動API到底怎麼用啊?這個問題已經出現過好幾回了,原本痞子衡不打算專門爲這個寫文章的,由於這部份內容在芯片手冊System Boot章節裏的最後一節ROM APIs裏其實介紹得很是詳細了,可是既然仍是有很多朋友在問這個,看起來手冊裏的內容藏得有點深,這麼好的東西被埋沒太惋惜了,那麼今天痞子衡就跟你們再認真聊一聊。git
i.MXRT系列都是Flashless(沒有內置NVM)的芯片,因此BootROM必不可少。BootROM是個很特殊的東西,本質上它是一個完整的C代碼寫成的系統級App,這個系統級App專門用於從外部存儲器中加載用戶級App執行。簡單地說,BootROM就是PC機裏的BIOS。github
BootROM代碼是存放在專門的ROM區域的(前面講i.MXRT系列沒有內置NVM,其實不夠準確,實際上是有內部ROM空間的,只不過這個ROM區域用戶沒法下載程序使用,所以等效於沒有NVM),ROM顧名思義Readonly,因此BootROM代碼只能隨着芯片一塊兒Tapeout,代碼沒法更改(其實也有ROM patch機制,之後再介紹)。算法
ROM空間其實挺大的,從64KB到512KB不等,因芯片啓動功能複雜程度而異。下圖是i.MXRT1050系列的BootROM所佔空間,ROM起始地址是0x200000(起始地址在i.MXRT上都同樣),ROM大小爲96KB(這是標準啓動功能所要的代碼長度。在i.MXRT1010上是64KB - 精簡啓動功能,在i.MXRT1170上是256KB - 複雜啓動功能)。api
BootROM代碼其實並無佔滿所有ROM空間,總有些剩餘空間(由於工藝緣由,ROM空間都是8/16KB倍數),這部分空間浪費了着實惋惜。若是咱們能把SDK裏的一些經常使用模塊驅動(好比WDOG)順便放進去供用戶調用,既充分利用ROM空間,也爲用戶節省Flash空間,豈不是一箭雙鵰。此外,BootROM功能代碼中也有一些現成模塊驅動(好比各類啓動設備存儲器驅動接口)能夠一併導出,這即是API由來。微信
有了API想法,如今就是設計實現了。其實i.MXRT ROM API設計並非重頭開始的,在這個MCU系列被主推以前,Kinetis系列也曾當紅過,Kinetis中也內置了ROM,而且提供了ROM API,痞子衡以前爲此寫過一篇文章 《飛思卡爾Kinetis系列MCU啓動那些事(11)- KBOOT特性(ROM API)》。 i.MXRT ROM API設計思路徹底複用了Kinetis ROM API的設計。app
API說到底就是一個個功能函數的結合,咱們知道工程代碼都是由連接器自動分配的,所以每一個函數實際連接地址是沒法預期的(在連接文件裏給每一個函數分配固定地址連接這種方法不在考慮範疇,當函數數量衆多時,這種方法太麻煩),業界上一個比較通用的作法是定義成員是函數指針的結構體,i.MXRT ROM API就是採用的業界通用方式,下面bootloader_api_entry_t即是i.MXRT1060中API原型,g_bootloaderTree就是實例:less
typedef struct { const uint32_t version; const char *copyright; void (*runBootloader)(void *arg); const hab_rvt_t *habDriver; //!< FlexSPI NOR Flash API const flexspi_nor_driver_interface_t *flexSpiNorDriver; const nand_ecc_driver_interface_t *nandEccDriver; const clock_driver_interface_t *clockDriver; const rtwdog_driver_interface_t *rtwdogDriver; const wdog_driver_interface_t *wdogDriver; const stdlib_driver_interface_t *stdlibDriver; } bootloader_api_entry_t; // Bootloader API Tree const bootloader_api_entry_t g_bootloaderTree = { .copyright = "Copyright 2018 NXP", .version = MAKE_VERSION(1, 0, 0), .runBootloader = run_bootloader, .habDriver = &hab_rvt, .flexSpiNorDriver = &g_flexspiNorDriverInterface, .nandEccDriver = &g_nandEccDriverInterface, .clockDriver = &g_clockDriverInterface, .rtwdogDriver = &g_rtwdogDriverInterface, .wdogDriver = &g_wdogDriverInterface, .stdlibDriver = &g_stdlibDriverInterface, };
從上面代碼咱們能夠看出,bootloader_api_entry_t成員好像並非函數指針,是的,爲了分組方便,bootloader_api_entry_t成員仍是一個個結構體,它的這些結構體成員(好比flexspi_nor_driver_interface_t)纔是真正包含一個個函數指針的結構體。API從功能來分一共提供了7類:HAB、FlexSPI NOR、NAND ECC、Clock、RT-WDOG、WDOG、stdlib。函數
設計到這裏,咱們經過g_bootloaderTree結構體常量就能夠調用全部的API函數了,最後剩下的問題就是如何在ROM裏找一個肯定的地方保存隨機連接的g_bootloaderTree地址(只要4字節便可)。是的,仍是Kinetis ROM API用的那個巧妙的方法,下面是BootROM工程的startup文件(Keil版),BootROM將g_bootloaderTree的地址放到了中斷向量表第8個向量的位置處(該向量爲ARM Cortex-M未定義的系統向量),所以0x20001c處開始的4bytes便固定是g_bootloaderTree地址。工具
PRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors EXPORT __Vectors_End EXPORT __Vectors_Size IMPORT |Image$$ARM_LIB_STACK$$ZI$$Limit| IMPORT g_bootloaderTree __Vectors DCD |Image$$ARM_LIB_STACK$$ZI$$Limit| DCD Reset_Handler DCD DefaultISR DCD HardFault_Handler DCD DefaultISR DCD DefaultISR DCD DefaultISR DCD g_bootloaderTree DCD 0 DCD 0 DCD 0 DCD SVC_Handler DCD DefaultISR DCD 0 DCD DefaultISR DCD DefaultISR ;; ...
瞭解了前面介紹的ROM API產生背景與設計實現,它的調用方法就很是簡單了,以WDOG API調用爲例,只須要以下簡單3句代碼:
// 找到API根結構體 #define g_bootloaderTree (*(bootloader_api_entry_t **)0x0020001c) // 定義WDOG模塊配置變量 wdog_config_t config; // 調用API中WDOG_Init() g_bootloaderTree->wdogDriver->WDOG_Init(WDOG1, config);
截止目前,i.MXRT1xxx系列一共出了7款型號,但並非每一個型號都開放了ROM API,最先誕生的三款型號(105x、102一、1015)就並無開放API(不是沒有API,而是沒有嚴格測試),其他型號都支持API。
RT芯片型號 | 是否支持ROM API |
---|---|
i.MXRT117x | 支持 |
i.MXRT1064 | 支持 |
i.MXRT106x | 支持 |
i.MXRT105x | 未開放 |
i.MXRT1021 | 未開放 |
i.MXRT1015 | 未開放 |
i.MXRT1011 | 支持 |
前面鋪墊了太多ROM API設計細節,到這裏纔算進入正題,本文其實主要是要跟你們聊如何利用API裏的FlexSPI NOR驅動實現IAP。痞子衡在前面鋪墊那麼多的緣由其實主要是想告訴你們,API裏的每一個驅動都是通過完善測試的,尤爲是這個FlexSPI NOR驅動,更是通過了千錘百煉,不管是易用性、運行穩定性仍是Flash型號的支持度上都是數一數二的。
對於JESD216標準下的串行SPI接口Flash驅動,你們知道更多的多是RT-Thread技術總監朱天龍大神的開源 SFUD 項目,但痞子衡告訴你,i.MXRT ROM API裏的這個串行Flash驅動也絕不遜色(持續維護與優化了近6年,歷經多款MCU的ROM,是真正的產品級),只是不如開源項目那麼知名,不過它的源代碼也是開源在SDK裏的(\SDK\middleware\mcu-boot\src\drivers\flexspi_nor),BSD-3-Clause許可證。
flexspi_nor_driver_interface_t即是FlexSPI NOR驅動的原型,尋常的讀寫擦功能天然不在話下,除此之外,API裏面還有一個很是厲害的xfer()函數,這個函數能夠用來實現其餘定製化的Flash操做函數,有興趣的朋友能夠進一步去研究。
typedef struct { uint32_t version; status_t (*init)(uint32_t instance, flexspi_nor_config_t *config); status_t (*program)(uint32_t instance, flexspi_nor_config_t *config, uint32_t dst_addr, const uint32_t *src); status_t (*erase_all)(uint32_t instance, flexspi_nor_config_t *config); status_t (*erase)(uint32_t instance, flexspi_nor_config_t *config, uint32_t start, uint32_t lengthInBytes); status_t (*read)(uint32_t instance, flexspi_nor_config_t *config, uint32_t *dst, uint32_t addr, uint32_t lengthInBytes); void (*clear_cache)(uint32_t instance); status_t (*xfer)(uint32_t instance, flexspi_xfer_t *xfer); status_t (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase, uint32_t seqNumber); status_t (*get_config)(uint32_t instance, flexspi_nor_config_t *config, serial_nor_config_option_t *option); } flexspi_nor_driver_interface_t;
FlexSPI驅動使用基本三步走,先調用get_config()獲取完整FlexSPI模塊配置,而後調用init()函數去初始化FlexSPI以及訪問Flash獲取SFDP表信息,最後就是調用Flash操做函數(好比erase())。
// 找到API根結構體 #define g_bootloaderTree (*(bootloader_api_entry_t **)0x0020001c) // 定義FlexSPI, Flash配置變量 flexspi_nor_config_t config; serial_nor_config_option_t option; option.option0.U = 0xC0000008; // QuadSPI NOR, Frequency: 133MHz uint32_t instance = 0; // 調用API中get_config()函數 g_bootloaderTree->flexSpiNorDriver->get_config(instance, &config, &option); // 調用API中init()函數 g_bootloaderTree->flexSpiNorDriver->init(instance, &config); // 調用API中erase()函數 g_bootloaderTree->flexSpiNorDriver->erase(instance, &config, 0x40000, 0x1000);
由於FlexSPI NOR驅動API來自於BootROM,所以其在使用上有一些小小的限制,也算是其特色吧。FlexSPI驅動API裏並無提供Flash鏈接的Pinmux配置,其Pinmux配置已經寫死在init()函數中,就是ROM支持啓動的FlexSPI PORTA上的那些pin(片選是SS0)。
在上面的使用示例代碼中,你會看到option.option0.U = 0xC0000008代碼,這算是FlexSPI驅動最大的特色了,這是一個簡化的option配置word(其原型可在芯片手冊裏找到),經過這個簡化的option,用戶能夠輕鬆配置來訪問不一樣廠商的Flash,下面是經常使用的Flash模式配置值。
• QuadSPI NOR - Quad SDR Read: option0 = 0xc0000008 (133MHz) • QuadSPI NOR - Quad DDR Read: option0 = 0xc0100003 (60MHz) • HyperFLASH 1V8: option0 = 0xc0233009 (166MHz) • HyperFLASH 3V0: option0 = 0xc0333006 (100MHz) • MXIC OPI DDR (OPI DDR enabled by default): option=0xc0433008(133MHz) • Micron Octal DDR: option0=0xc0600006 (100MHz) • Micron OPI DDR: option0=0xc0603008 (133MHz), SPI->OPI DDR • Micron OPI DDR (DDR read enabled by default): option0 = 0xc0633008 (133MHz) • Adesto OPI DDR: option0=0xc0803008(133MHz)
IAP其實就是在App中實現Flash擦寫,單純從技術上來講並非一個很難的東西。但i.MXRT上不少時候App代碼自己也在同一片Flash裏執行(也叫XIP),而市面上不少Flash都是不支持RWW(Read-While-Write)的,這就致使一個問題,當你調用Flash操做函數去擦寫Flash時,CPU又須要繼續去Flash獲取指令,違反了RWW,所以你只能把Flash相關操做函數所有放在RAM中去執行(這涉及分散加載了,對於初級嵌入式用戶來講稍微有點難)。
如今咱們有了ROM API,FlexSPI驅動代碼體所有都在ROM空間裏,並不佔用Flash空間,所以不存在RWW問題,真是自然爲IAP而生,不再用再管什麼分散加載這麼麻煩的事了。
最後再介紹一下i.MXRT FlexSPI API在業界的應用,這個API其實並不小衆,目前已被主流IDE和調試工具用做i.MXRT Flash下載算法。
若是你的IAR版本夠新,可以支持i.MXRT1060等型號,隨便打開一個i.MXRT1060 SDK工程,在工程Option裏找到Debugger,而後進入Flashloader配置,你會看到頁面裏有Extra parameters一欄,在下面的解釋裏有這個參數的示例,它就是前面2.3節裏介紹的option0。有了這種方式設計的Flash下載算法,你不再用手動更新下載算法文件去支持不一樣的Flash了,改參數就好了。
目前最新的Jlink驅動裏的下載算法也是基於ROM API的,痞子衡有一個開源項目,收集了i.MXRT全部型號的下載算法源代碼工程,其中jlink算法是最全的,其餘IDE算法還在陸續完善中。
至此,i.MXRT系列ROM中的FlexSPI驅動API實現IAP痞子衡便介紹完畢了,掌聲在哪裏~~~
文章會同時發佈到個人 博客園主頁、CSDN主頁、微信公衆號 平臺上。
微信搜索"痞子衡嵌入式"或者掃描下面二維碼,就能夠在手機上第一時間看了哦。