大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家介紹的是 i.MXRT 系列 ROM 中的 FlexSPI 驅(qū)動(dòng) API 實(shí)現(xiàn) IAP。
痞子衡的技術(shù)交流群里經(jīng)常有群友提問: i.MXRT 中的 FlexSPI 驅(qū)動(dòng) API 到底怎么用啊?這個(gè)問題已經(jīng)出現(xiàn)過好幾次了,本來痞子衡不打算專門為這個(gè)寫文章的,因?yàn)檫@部分內(nèi)容在芯片手冊 System Boot 章節(jié)里的最后一節(jié) ROM APIs 里其實(shí)介紹得非常詳細(xì)了,但是既然還是有不少朋友在問這個(gè),看起來手冊里的內(nèi)容藏得有點(diǎn)深,這么好的東西被埋沒太可惜了,那么今天痞子衡就跟大家再認(rèn)真聊一聊。
一、ROM API 簡介
1.1、API 產(chǎn)生背景
i.MXRT 系列都是 Flashless(沒有內(nèi)置 NVM)的芯片,所以 BootROM 必不可少。BootROM 是個(gè)很特殊的東西,本質(zhì)上它是一個(gè)完整的 C 代碼寫成的系統(tǒng)級(jí) App,這個(gè)系統(tǒng)級(jí) App 專門用于從外部存儲(chǔ)器中加載用戶級(jí) App 執(zhí)行。簡單地說,BootROM 就是 PC 機(jī)里的 BIOS。
BootROM 代碼是存放在專門的 ROM 區(qū)域的(前面講 i.MXRT 系列沒有內(nèi)置 NVM,其實(shí)不夠準(zhǔn)確,其實(shí)是有內(nèi)部 ROM 空間的,只不過這個(gè) ROM 區(qū)域用戶無法下載程序使用,因此等效于沒有 NVM),ROM 顧名思義 Readonly,所以 BootROM 代碼只能隨著芯片一起 Tapeout,代碼無法更改(其實(shí)也有 ROM patch 機(jī)制,以后再介紹)。
ROM 空間其實(shí)挺大的,從 64KB 到 512KB 不等,因芯片啟動(dòng)功能復(fù)雜程度而異。下圖是 i.MXRT1050 系列的 BootROM 所占空間,ROM 起始地址是 0x200000(起始地址在 i.MXRT 上都一樣),ROM 大小為 96KB(這是標(biāo)準(zhǔn)啟動(dòng)功能所要的代碼長度。在 i.MXRT1010 上是 64KB - 精簡啟動(dòng)功能,在 i.MXRT1170 上是 256KB - 復(fù)雜啟動(dòng)功能)。
?
?
BootROM 代碼其實(shí)并沒有占滿全部 ROM 空間,總有些剩余空間(因?yàn)楣に囋?,ROM 空間都是 8/16KB 倍數(shù)),這部分空間浪費(fèi)了著實(shí)可惜。如果我們能把 SDK 里的一些常用模塊驅(qū)動(dòng)(比如 WDOG)順便放進(jìn)去供用戶調(diào)用,既充分利用 ROM 空間,也為用戶節(jié)省 Flash 空間,豈不是一舉兩得。此外,BootROM 功能代碼中也有一些現(xiàn)成模塊驅(qū)動(dòng)(比如各種啟動(dòng)設(shè)備存儲(chǔ)器驅(qū)動(dòng)接口)可以一并導(dǎo)出,這便是 API 由來。
1.2、API 設(shè)計(jì)實(shí)現(xiàn)
有了 API 想法,現(xiàn)在就是設(shè)計(jì)實(shí)現(xiàn)了。其實(shí) i.MXRT ROM API 設(shè)計(jì)并不是重頭開始的,在這個(gè) MCU 系列被主推之前,Kinetis 系列也曾當(dāng)紅過,Kinetis 中也內(nèi)置了 ROM,并且提供了 ROM API,痞子衡之前為此寫過一篇文章 《飛思卡爾 Kinetis 系列 MCU 啟動(dòng)那些事(11)- KBOOT 特性(ROM API)》。i.MXRT ROM API 設(shè)計(jì)思路完全復(fù)用了 Kinetis ROM API 的設(shè)計(jì)。
API 說到底就是一個(gè)個(gè)功能函數(shù)的結(jié)合,我們知道工程代碼都是由鏈接器自動(dòng)分配的,因此每個(gè)函數(shù)實(shí)際鏈接地址是無法預(yù)期的(在鏈接文件里給每個(gè)函數(shù)分配固定地址鏈接這種方法不在考慮范疇,當(dāng)函數(shù)數(shù)量眾多時(shí),這種方法太麻煩),業(yè)界上一個(gè)比較通用的做法是定義成員是函數(shù)指針的結(jié)構(gòu)體,i.MXRT ROM API 就是采用的業(yè)界通用方式,下面 bootloader_api_entry_t 便是 i.MXRT1060 中 API 原型,g_bootloaderTree 就是實(shí)例:
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 成員好像并不是函數(shù)指針,是的,為了分組方便,bootloader_api_entry_t 成員還是一個(gè)個(gè)結(jié)構(gòu)體,它的這些結(jié)構(gòu)體成員(比如 flexspi_nor_driver_interface_t)才是真正包含一個(gè)個(gè)函數(shù)指針的結(jié)構(gòu)體。API 從功能來分一共提供了 7 類:HAB、FlexSPI NOR、NAND ECC、Clock、RT-WDOG、WDOG、stdlib。
設(shè)計(jì)到這里,我們通過 g_bootloaderTree 結(jié)構(gòu)體常量就可以調(diào)用所有的 API 函數(shù)了,最后剩下的問題就是如何在 ROM 里找一個(gè)確定的地方保存隨機(jī)鏈接的 g_bootloaderTree 地址(只要 4 字節(jié)即可)。是的,還是 Kinetis ROM API 用的那個(gè)巧妙的方法,下面是 BootROM 工程的 startup 文件(Keil 版),BootROM 將 g_bootloaderTree 的地址放到了中斷向量表第 8 個(gè)向量的位置處(該向量為 ARM Cortex-M 未定義的系統(tǒng)向量),因此 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 調(diào)用方法
了解了前面介紹的 ROM API 產(chǎn)生背景與設(shè)計(jì)實(shí)現(xiàn),它的調(diào)用方法就非常簡單了,以 WDOG API 調(diào)用為例,只需要如下簡單 3 句代碼:
//?找到 API 根結(jié)構(gòu)體
#define?g_bootloaderTree?(*(bootloader_api_entry_t?**)0x0020001c)
//?定義 WDOG 模塊配置變量
wdog_config_t?config;
//?調(diào)用 API 中 WDOG_Init()
g_bootloaderTree->wdogDriver->WDOG_Init(WDOG1,?config);
?
?
1.4、支持 API 的 i.MXRT 型號(hào)
截止目前,i.MXRT1xxx 系列一共出了 7 款型號(hào),但并不是每個(gè)型號(hào)都開放了 ROM API,最早誕生的三款型號(hào)(105x、1021、1015)就并沒有開放 API(不是沒有 API,而是沒有嚴(yán)格測試),其余型號(hào)都支持 API。
RT 芯片型號(hào) | 是否支持 ROM API |
---|---|
i.MXRT117x | 支持 |
i.MXRT1064 | 支持 |
i.MXRT106x | 支持 |
i.MXRT105x | 未開放 |
i.MXRT1021 | 未開放 |
i.MXRT1015 | 未開放 |
i.MXRT1011 | 支持 |
二、API 之 FlexSPI 驅(qū)動(dòng)
前面鋪墊了太多 ROM API 設(shè)計(jì)細(xì)節(jié),到這里才算進(jìn)入正題,本文其實(shí)主要是要跟大家聊如何利用 API 里的 FlexSPI NOR 驅(qū)動(dòng)實(shí)現(xiàn) IAP。痞子衡在前面鋪墊那么多的原因其實(shí)主要是想告訴大家,API 里的每個(gè)驅(qū)動(dòng)都是經(jīng)過完善測試的,尤其是這個(gè) FlexSPI NOR 驅(qū)動(dòng),更是經(jīng)過了千錘百煉,無論是易用性、運(yùn)行穩(wěn)定性還是 Flash 型號(hào)的支持度上都是首屈一指的。
對(duì)于 JESD216 標(biāo)準(zhǔn)下的串行 SPI 接口 Flash 驅(qū)動(dòng),大家知道更多的可能是 RT-Thread 技術(shù)總監(jiān)朱天龍大神的開源 SFUD 項(xiàng)目,但痞子衡告訴你,i.MXRT ROM API 里的這個(gè)串行 Flash 驅(qū)動(dòng)也毫不遜色(持續(xù)維護(hù)與優(yōu)化了近 6 年,歷經(jīng)多款 MCU 的 ROM,是真正的產(chǎn)品級(jí)),只是不如開源項(xiàng)目那么知名,不過它的源代碼也是開源在 SDK 里的(SDKmiddlewaremcu-bootsrcdriversflexspi_nor),BSD-3-Clause 許可證。
2.1 FlexSPI 驅(qū)動(dòng)原型
flexspi_nor_driver_interface_t 便是 FlexSPI NOR 驅(qū)動(dòng)的原型,尋常的讀寫擦功能自然不在話下,除此以外,API 里面還有一個(gè)非常厲害的 xfer()函數(shù),這個(gè)函數(shù)可以用來實(shí)現(xiàn)其他定制化的 Flash 操作函數(shù),有興趣的朋友可以進(jìn)一步去研究。
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 驅(qū)動(dòng)使用示例
FlexSPI 驅(qū)動(dòng)使用基本三步走,先調(diào)用 get_config()獲取完整 FlexSPI 模塊配置,然后調(diào)用 init()函數(shù)去初始化 FlexSPI 以及訪問 Flash 獲取 SFDP 表信息,最后就是調(diào)用 Flash 操作函數(shù)(比如 erase())。
//?找到 API 根結(jié)構(gòu)體
#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;
//?調(diào)用 API 中 get_config()函數(shù)
g_bootloaderTree->flexSpiNorDriver->get_config(instance,?&config,?&option);
//?調(diào)用 API 中 init()函數(shù)
g_bootloaderTree->flexSpiNorDriver->init(instance,?&config);
//?調(diào)用 API 中 erase()函數(shù)
g_bootloaderTree->flexSpiNorDriver->erase(instance,?&config,?0x40000,?0x1000);
2.3 FlexSPI 驅(qū)動(dòng)特點(diǎn)
因?yàn)?FlexSPI NOR 驅(qū)動(dòng) API 來自于 BootROM,因此其在使用上有一些小小的限制,也算是其特點(diǎn)吧。FlexSPI 驅(qū)動(dòng) API 里并沒有提供 Flash 連接的 Pinmux 配置,其 Pinmux 配置已經(jīng)寫死在 init()函數(shù)中,就是 ROM 支持啟動(dòng)的 FlexSPI PORTA 上的那些 pin(片選是 SS0)。
在上面的使用示例代碼中,你會(huì)看到 option.option0.U = 0xC0000008 代碼,這算是 FlexSPI 驅(qū)動(dòng)最大的特點(diǎn)了,這是一個(gè)簡化的 option 配置 word(其原型可在芯片手冊里找到),通過這個(gè)簡化的 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 驅(qū)動(dòng)用作 IAP
IAP 其實(shí)就是在 App 中實(shí)現(xiàn) Flash 擦寫,單純從技術(shù)上來說并不是一個(gè)很難的東西。但 i.MXRT 上很多時(shí)候 App 代碼本身也在同一片 Flash 里執(zhí)行(也叫 XIP),而市面上很多 Flash 都是不支持 RWW(Read-While-Write)的,這就導(dǎo)致一個(gè)問題,當(dāng)你調(diào)用 Flash 操作函數(shù)去擦寫 Flash 時(shí),CPU 又需要繼續(xù)去 Flash 獲取指令,違反了 RWW,因此你只能把 Flash 相關(guān)操作函數(shù)全部放在 RAM 中去執(zhí)行(這涉及分散加載了,對(duì)于初級(jí)嵌入式用戶來說稍微有點(diǎn)難)。
?
現(xiàn)在我們有了 ROM API,F(xiàn)lexSPI 驅(qū)動(dòng)代碼體全部都在 ROM 空間里,并不占用 Flash 空間,因此不存在 RWW 問題,真是天然為 IAP 而生,再也不用再管什么分散加載這么麻煩的事了。
三、FlexSPI API 業(yè)界應(yīng)用
最后再介紹一下 i.MXRT FlexSPI API 在業(yè)界的應(yīng)用,這個(gè) API 其實(shí)并不小眾,目前已被主流 IDE 和調(diào)試工具用作 i.MXRT Flash 下載算法。
3.1 用于 IAR 下載算法
如果你的 IAR 版本夠新,能夠支持 i.MXRT1060 等型號(hào),隨便打開一個(gè) i.MXRT1060 SDK 工程,在工程 Option 里找到 Debugger,然后進(jìn)入 Flashloader 配置,你會(huì)看到頁面里有 Extra parameters 一欄,在下面的解釋里有這個(gè)參數(shù)的示例,它就是前面 2.3 節(jié)里介紹的 option0。有了這種方式設(shè)計(jì)的 Flash 下載算法,你再也不用手動(dòng)更新下載算法文件去支持不同的 Flash 了,改參數(shù)就行了。
?
3.2 用于 J-Link 下載算法
目前最新的 Jlink 驅(qū)動(dòng)里的下載算法也是基于 ROM API 的,痞子衡有一個(gè)開源項(xiàng)目,收集了 i.MXRT 所有型號(hào)的下載算法源代碼工程,其中 jlink 算法是最全的,其他 IDE 算法還在陸續(xù)完善中。
https://github.com/JayHeng/imxrt-tool-flash-algo
至此,i.MXRT 系列 ROM 中的 FlexSPI 驅(qū)動(dòng) API 實(shí)現(xiàn) IAP 痞子衡便介紹完畢了,掌聲在哪里~~~