大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是 i.MXRT 系列 ROM 中的 FlexSPI 驅動 API 實現 IAP。
痞子衡的技術交流群里經常有群友提問: i.MXRT 中的 FlexSPI 驅動 API 到底怎么用???這個問題已經出現過好幾次了,本來痞子衡不打算專門為這個寫文章的,因為這部分內容在芯片手冊 System Boot 章節(jié)里的最后一節(jié) ROM APIs 里其實介紹得非常詳細了,但是既然還是有不少朋友在問這個,看起來手冊里的內容藏得有點深,這么好的東西被埋沒太可惜了,那么今天痞子衡就跟大家再認真聊一聊。
一、ROM API 簡介
1.1、API 產生背景
i.MXRT 系列都是 Flashless(沒有內置 NVM)的芯片,所以 BootROM 必不可少。BootROM 是個很特殊的東西,本質上它是一個完整的 C 代碼寫成的系統級 App,這個系統級 App 專門用于從外部存儲器中加載用戶級 App 執(zhí)行。簡單地說,BootROM 就是 PC 機里的 BIOS。
BootROM 代碼是存放在專門的 ROM 區(qū)域的(前面講 i.MXRT 系列沒有內置 NVM,其實不夠準確,其實是有內部 ROM 空間的,只不過這個 ROM 區(qū)域用戶無法下載程序使用,因此等效于沒有 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 - 復雜啟動功能)。
?
?
BootROM 代碼其實并沒有占滿全部 ROM 空間,總有些剩余空間(因為工藝原因,ROM 空間都是 8/16KB 倍數),這部分空間浪費了著實可惜。如果我們能把 SDK 里的一些常用模塊驅動(比如 WDOG)順便放進去供用戶調用,既充分利用 ROM 空間,也為用戶節(jié)省 Flash 空間,豈不是一舉兩得。此外,BootROM 功能代碼中也有一些現成模塊驅動(比如各種啟動設備存儲器驅動接口)可以一并導出,這便是 API 由來。
1.2、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 的設計。
API 說到底就是一個個功能函數的結合,我們知道工程代碼都是由鏈接器自動分配的,因此每個函數實際鏈接地址是無法預期的(在鏈接文件里給每個函數分配固定地址鏈接這種方法不在考慮范疇,當函數數量眾多時,這種方法太麻煩),業(yè)界上一個比較通用的做法是定義成員是函數指針的結構體,i.MXRT ROM API 就是采用的業(yè)界通用方式,下面 bootloader_api_entry_t 便是 i.MXRT1060 中 API 原型,g_bootloaderTree 就是實例:
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 字節(jié)即可)。是的,還是 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
??????????;;?...
1.3、API 調用方法
了解了前面介紹的 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);
?
?
1.4、支持 API 的 i.MXRT 型號
截止目前,i.MXRT1xxx 系列一共出了 7 款型號,但并不是每個型號都開放了 ROM API,最早誕生的三款型號(105x、1021、1015)就并沒有開放 API(不是沒有 API,而是沒有嚴格測試),其余型號都支持 API。
RT 芯片型號 | 是否支持 ROM API |
---|---|
i.MXRT117x | 支持 |
i.MXRT1064 | 支持 |
i.MXRT106x | 支持 |
i.MXRT105x | 未開放 |
i.MXRT1021 | 未開放 |
i.MXRT1015 | 未開放 |
i.MXRT1011 | 支持 |
二、API 之 FlexSPI 驅動
前面鋪墊了太多 ROM API 設計細節(jié),到這里才算進入正題,本文其實主要是要跟大家聊如何利用 API 里的 FlexSPI NOR 驅動實現 IAP。痞子衡在前面鋪墊那么多的原因其實主要是想告訴大家,API 里的每個驅動都是經過完善測試的,尤其是這個 FlexSPI NOR 驅動,更是經過了千錘百煉,無論是易用性、運行穩(wěn)定性還是 Flash 型號的支持度上都是首屈一指的。
對于 JESD216 標準下的串行 SPI 接口 Flash 驅動,大家知道更多的可能是 RT-Thread 技術總監(jiān)朱天龍大神的開源 SFUD 項目,但痞子衡告訴你,i.MXRT ROM API 里的這個串行 Flash 驅動也毫不遜色(持續(xù)維護與優(yōu)化了近 6 年,歷經多款 MCU 的 ROM,是真正的產品級),只是不如開源項目那么知名,不過它的源代碼也是開源在 SDK 里的(SDKmiddlewaremcu-bootsrcdriversflexspi_nor),BSD-3-Clause 許可證。
2.1 FlexSPI 驅動原型
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;
2.2 FlexSPI 驅動使用示例
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);
2.3 FlexSPI 驅動特點
因為 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)
2.4 FlexSPI 驅動用作 IAP
IAP 其實就是在 App 中實現 Flash 擦寫,單純從技術上來說并不是一個很難的東西。但 i.MXRT 上很多時候 App 代碼本身也在同一片 Flash 里執(zhí)行(也叫 XIP),而市面上很多 Flash 都是不支持 RWW(Read-While-Write)的,這就導致一個問題,當你調用 Flash 操作函數去擦寫 Flash 時,CPU 又需要繼續(xù)去 Flash 獲取指令,違反了 RWW,因此你只能把 Flash 相關操作函數全部放在 RAM 中去執(zhí)行(這涉及分散加載了,對于初級嵌入式用戶來說稍微有點難)。
?
現在我們有了 ROM API,FlexSPI 驅動代碼體全部都在 ROM 空間里,并不占用 Flash 空間,因此不存在 RWW 問題,真是天然為 IAP 而生,再也不用再管什么分散加載這么麻煩的事了。
三、FlexSPI API 業(yè)界應用
最后再介紹一下 i.MXRT FlexSPI API 在業(yè)界的應用,這個 API 其實并不小眾,目前已被主流 IDE 和調試工具用作 i.MXRT Flash 下載算法。
3.1 用于 IAR 下載算法
如果你的 IAR 版本夠新,能夠支持 i.MXRT1060 等型號,隨便打開一個 i.MXRT1060 SDK 工程,在工程 Option 里找到 Debugger,然后進入 Flashloader 配置,你會看到頁面里有 Extra parameters 一欄,在下面的解釋里有這個參數的示例,它就是前面 2.3 節(jié)里介紹的 option0。有了這種方式設計的 Flash 下載算法,你再也不用手動更新下載算法文件去支持不同的 Flash 了,改參數就行了。
?
3.2 用于 J-Link 下載算法
目前最新的 Jlink 驅動里的下載算法也是基于 ROM API 的,痞子衡有一個開源項目,收集了 i.MXRT 所有型號的下載算法源代碼工程,其中 jlink 算法是最全的,其他 IDE 算法還在陸續(xù)完善中。
https://github.com/JayHeng/imxrt-tool-flash-algo
至此,i.MXRT 系列 ROM 中的 FlexSPI 驅動 API 實現 IAP 痞子衡便介紹完畢了,掌聲在哪里~~~