大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是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 = {
.version = MAKE_VERSION(1, 0, 0),
.copyright = "Copyright 2018 NXP",
.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-M7未定義的系統向量),因此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 | 是否全功能API |
---|---|---|
i.MXRT117x | 支持 | 是 |
i.MXRT1064 | 支持 | 是 |
i.MXRT106x | 支持 | 是 |
i.MXRT105x | 未開放 | N/A |
i.MXRT1021 | 未開放 | N/A |
i.MXRT1015 | 未開放 | N/A |
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;
// FlexSPI NOR Driver API
const flexspi_nor_driver_interface_t g_flexspiNorDriverInterface = {
.version = MAKE_VERSION(1, 5, 0),
.init = flexspi_nor_flash_init,
.program = flexspi_nor_flash_page_program,
.erase_all = flexspi_nor_flash_erase_all,
.erase = flexspi_nor_flash_erase,
.read = flexspi_nor_flash_read,
.clear_cache = flexspi_clear_cache,
.xfer = flexspi_command_xfer,
.update_lut = flexspi_update_lut,
.get_config = flexspi_nor_get_config,
};
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而生,再也不用再管什么分散加載這么麻煩的事了。
2.5、關於非全功能API
前面1.4節講了,i.MXRT1010中並沒有全功能API,主要是因為其BootROM空間較小,無法存放多余API代碼,下面是i.MXRT1010中API實例,可以看到提供的FlexSPI驅動函數相當少,關於其用法后面再單獨介紹。
// Bootloader API Tree
const bootloader_api_entry_t g_bootloaderTree = {
.version = MAKE_VERSION(1, 0, 0),
.copyright = "Copyright 2019 NXP",
.runBootloader = run_bootloader,
.habDriver = &hab_rvt,
.flexSpiNorDriver = &g_flexspiNorDriverInterface,
};
// FlexSPI NOR Driver API
const flexspi_nor_driver_interface_t g_flexspiNorDriverInterface = {
.version = MAKE_VERSION(1, 5, 0),
.init = flexspi_nor_flash_init,
.clear_cache = flexspi_clear_cache,
.xfer = flexspi_command_xfer,
.update_lut = flexspi_update_lut,
};
三、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痞子衡便介紹完畢了,掌聲在哪里~~~