痞子衡的技術交流群里經常有群友提問: i.MXRT中的FlexSPI驅動API到底怎麼用啊?這個問題已經出現過好幾次了,本來痞子衡不打算專門為這個寫文章的,因為這部分內容在晶元手冊System Boot章節里的最後一節ROM APIs里其實介紹得非常詳細了,但是既然還是有不少朋友在問這個,看起來手冊... ...
大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是i.MXRT系列ROM中的FlexSPI驅動API實現IAP。
痞子衡的技術交流群里經常有群友提問: i.MXRT中的FlexSPI驅動API到底怎麼用啊?這個問題已經出現過好幾次了,本來痞子衡不打算專門為這個寫文章的,因為這部分內容在晶元手冊System Boot章節里的最後一節ROM APIs里其實介紹得非常詳細了,但是既然還是有不少朋友在問這個,看起來手冊里的內容藏得有點深,這麼好的東西被埋沒太可惜了,那麼今天痞子衡就跟大家再認真聊一聊。
一、ROM API簡介
1.1、API產生背景
i.MXRT系列都是Flashless(沒有內置NVM)的晶元,所以BootROM必不可少。BootROM是個很特殊的東西,本質上它是一個完整的C代碼寫成的系統級App,這個系統級App專門用於從外部存儲器中載入用戶級App執行。簡單地說,BootROM就是PC機里的BIOS。
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 - 複雜啟動功能)。
BootROM代碼其實並沒有占滿全部ROM空間,總有些剩餘空間(因為工藝原因,ROM空間都是8/16KB倍數),這部分空間浪費了著實可惜。如果我們能把SDK里的一些常用模塊驅動(比如WDOG)順便放進去供用戶調用,既充分利用ROM空間,也為用戶節省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說到底就是一個個功能函數的結合,我們知道工程代碼都是由鏈接器自動分配的,因此每個函數實際鏈接地址是無法預期的(在鏈接文件里給每個函數分配固定地址鏈接這種方法不在考慮範疇,當函數數量眾多時,這種方法太麻煩),業界上一個比較通用的做法是定義成員是函數指針的結構體,i.MXRT ROM API就是採用的業界通用方式,下麵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位元組即可)。是的,還是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設計細節,到這裡才算進入正題,本文其實主要是要跟大家聊如何利用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許可證。
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里執行(也叫XIP),而市面上很多Flash都是不支持RWW(Read-While-Write)的,這就導致一個問題,當你調用Flash操作函數去擦寫Flash時,CPU又需要繼續去Flash獲取指令,違反了RWW,因此你只能把Flash相關操作函數全部放在RAM中去執行(這涉及分散載入了,對於初級嵌入式用戶來說稍微有點難)。
現在我們有了ROM API,FlexSPI驅動代碼體全部都在ROM空間里,並不占用Flash空間,因此不存在RWW問題,真是天然為IAP而生,再也不用再管什麼分散載入這麼麻煩的事了。
三、FlexSPI API業界應用
最後再介紹一下i.MXRT FlexSPI API在業界的應用,這個API其實並不小眾,目前已被主流IDE和調試工具用作i.MXRT Flash下載演算法。
3.1 用於IAR下載演算法
如果你的IAR版本夠新,能夠支持i.MXRT1060等型號,隨便打開一個i.MXRT1060 SDK工程,在工程Option里找到Debugger,然後進入Flashloader配置,你會看到頁面里有Extra parameters一欄,在下麵的解釋里有這個參數的示例,它就是前面2.3節里介紹的option0。有了這種方式設計的Flash下載演算法,你再也不用手動更新下載演算法文件去支持不同的Flash了,改參數就行了。
3.2 用於J-Link下載演算法
目前最新的Jlink驅動里的下載演算法也是基於ROM API的,痞子衡有一個開源項目,收集了i.MXRT所有型號的下載演算法源代碼工程,其中jlink演算法是最全的,其他IDE演算法還在陸續完善中。
至此,i.MXRT系列ROM中的FlexSPI驅動API實現IAP痞子衡便介紹完畢了,掌聲在哪裡~~~
歡迎訂閱
文章會同時發佈到我的 博客園主頁、CSDN主頁、微信公眾號 平臺上。
微信搜索"痞子衡嵌入式"或者掃描下麵二維碼,就可以在手機上第一時間看了哦。