加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

想在i.MXRT上搞IAP,豈能放過這個ROM API?

2020/06/29
198
閱讀需 19 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家介紹的是 i.MXRT 系列 ROM 中的 FlexSPI 驅(qū)動 API 實現(xiàn) IAP。

痞子衡的技術(shù)交流群里經(jīng)常有群友提問: i.MXRT 中的 FlexSPI 驅(qū)動 API 到底怎么用?。窟@個問題已經(jīng)出現(xiàn)過好幾次了,本來痞子衡不打算專門為這個寫文章的,因為這部分內(nèi)容在芯片手冊 System Boot 章節(jié)里的最后一節(jié) ROM APIs 里其實介紹得非常詳細(xì)了,但是既然還是有不少朋友在問這個,看起來手冊里的內(nèi)容藏得有點深,這么好的東西被埋沒太可惜了,那么今天痞子衡就跟大家再認(rèn)真聊一聊。

一、ROM API 簡介

1.1、API 產(chǎn)生背景

i.MXRT 系列都是 Flashless(沒有內(nèi)置 NVM)的芯片,所以 BootROM 必不可少。BootROM 是個很特殊的東西,本質(zhì)上它是一個完整的 C 代碼寫成的系統(tǒng)級 App,這個系統(tǒng)級 App 專門用于從外部存儲器中加載用戶級 App 執(zhí)行。簡單地說,BootROM 就是 PC 機(jī)里的 BIOS。

BootROM 代碼是存放在專門的 ROM 區(qū)域的(前面講 i.MXRT 系列沒有內(nèi)置 NVM,其實不夠準(zhǔn)確,其實是有內(nèi)部 ROM 空間的,只不過這個 ROM 區(qū)域用戶無法下載程序使用,因此等效于沒有 NVM),ROM 顧名思義 Readonly,所以 BootROM 代碼只能隨著芯片一起 Tapeout,代碼無法更改(其實也有 ROM patch 機(jī)制,以后再介紹)。

ROM 空間其實挺大的,從 64KB 到 512KB 不等,因芯片啟動功能復(fù)雜程度而異。下圖是 i.MXRT1050 系列的 BootROM 所占空間,ROM 起始地址是 0x200000(起始地址在 i.MXRT 上都一樣),ROM 大小為 96KB(這是標(biāo)準(zhǔn)啟動功能所要的代碼長度。在 i.MXRT1010 上是 64KB - 精簡啟動功能,在 i.MXRT1170 上是 256KB - 復(fù)雜啟動功能)。

?

?

BootROM 代碼其實并沒有占滿全部 ROM 空間,總有些剩余空間(因為工藝原因,ROM 空間都是 8/16KB 倍數(shù)),這部分空間浪費了著實可惜。如果我們能把 SDK 里的一些常用模塊驅(qū)動(比如 WDOG)順便放進(jìn)去供用戶調(diào)用,既充分利用 ROM 空間,也為用戶節(jié)省 Flash 空間,豈不是一舉兩得。此外,BootROM 功能代碼中也有一些現(xiàn)成模塊驅(qū)動(比如各種啟動設(shè)備存儲器驅(qū)動接口)可以一并導(dǎo)出,這便是 API 由來。

1.2、API 設(shè)計實現(xiàn)

有了 API 想法,現(xiàn)在就是設(shè)計實現(xiàn)了。其實 i.MXRT ROM API 設(shè)計并不是重頭開始的,在這個 MCU 系列被主推之前,Kinetis 系列也曾當(dāng)紅過,Kinetis 中也內(nèi)置了 ROM,并且提供了 ROM API,痞子衡之前為此寫過一篇文章 《飛思卡爾 Kinetis 系列 MCU 啟動那些事(11)- KBOOT 特性(ROM API)》。i.MXRT ROM API 設(shè)計思路完全復(fù)用了 Kinetis ROM API 的設(shè)計。

API 說到底就是一個個功能函數(shù)的結(jié)合,我們知道工程代碼都是由鏈接器自動分配的,因此每個函數(shù)實際鏈接地址是無法預(yù)期的(在鏈接文件里給每個函數(shù)分配固定地址鏈接這種方法不在考慮范疇,當(dāng)函數(shù)數(shù)量眾多時,這種方法太麻煩),業(yè)界上一個比較通用的做法是定義成員是函數(shù)指針的結(jié)構(gòu)體,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 成員好像并不是函數(shù)指針,是的,為了分組方便,bootloader_api_entry_t 成員還是一個個結(jié)構(gòu)體,它的這些結(jié)構(gòu)體成員(比如 flexspi_nor_driver_interface_t)才是真正包含一個個函數(shù)指針的結(jié)構(gòu)體。API 從功能來分一共提供了 7 類:HAB、FlexSPI NOR、NAND ECC、Clock、RT-WDOG、WDOG、stdlib。

設(shè)計到這里,我們通過 g_bootloaderTree 結(jié)構(gòu)體常量就可以調(diào)用所有的 API 函數(shù)了,最后剩下的問題就是如何在 ROM 里找一個確定的地方保存隨機(jī)鏈接的 g_bootloaderTree 地址(只要 4 字節(jié)即可)。是的,還是 Kinetis ROM API 用的那個巧妙的方法,下面是 BootROM 工程的 startup 文件(Keil 版),BootROM 將 g_bootloaderTree 的地址放到了中斷向量表第 8 個向量的位置處(該向量為 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è)計實現(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 型號

截止目前,i.MXRT1xxx 系列一共出了 7 款型號,但并不是每個型號都開放了 ROM API,最早誕生的三款型號(105x、1021、1015)就并沒有開放 API(不是沒有 API,而是沒有嚴(yán)格測試),其余型號都支持 API。

RT 芯片型號 是否支持 ROM API
i.MXRT117x 支持
i.MXRT1064 支持
i.MXRT106x 支持
i.MXRT105x 未開放
i.MXRT1021 未開放
i.MXRT1015 未開放
i.MXRT1011 支持

二、API 之 FlexSPI 驅(qū)動

前面鋪墊了太多 ROM API 設(shè)計細(xì)節(jié),到這里才算進(jìn)入正題,本文其實主要是要跟大家聊如何利用 API 里的 FlexSPI NOR 驅(qū)動實現(xiàn) IAP。痞子衡在前面鋪墊那么多的原因其實主要是想告訴大家,API 里的每個驅(qū)動都是經(jīng)過完善測試的,尤其是這個 FlexSPI NOR 驅(qū)動,更是經(jīng)過了千錘百煉,無論是易用性、運行穩(wěn)定性還是 Flash 型號的支持度上都是首屈一指的。

對于 JESD216 標(biāo)準(zhǔn)下的串行 SPI 接口 Flash 驅(qū)動,大家知道更多的可能是 RT-Thread 技術(shù)總監(jiān)朱天龍大神的開源 SFUD 項目,但痞子衡告訴你,i.MXRT ROM API 里的這個串行 Flash 驅(qū)動也毫不遜色(持續(xù)維護(hù)與優(yōu)化了近 6 年,歷經(jīng)多款 MCU 的 ROM,是真正的產(chǎn)品級),只是不如開源項目那么知名,不過它的源代碼也是開源在 SDK 里的(SDKmiddlewaremcu-bootsrcdriversflexspi_nor),BSD-3-Clause 許可證。

2.1 FlexSPI 驅(qū)動原型

flexspi_nor_driver_interface_t 便是 FlexSPI NOR 驅(qū)動的原型,尋常的讀寫擦功能自然不在話下,除此以外,API 里面還有一個非常厲害的 xfer()函數(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ū)動使用示例

FlexSPI 驅(qū)動使用基本三步走,先調(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ū)動特點

因為 FlexSPI NOR 驅(qū)動 API 來自于 BootROM,因此其在使用上有一些小小的限制,也算是其特點吧。FlexSPI 驅(qū)動 API 里并沒有提供 Flash 連接的 Pinmux 配置,其 Pinmux 配置已經(jīng)寫死在 init()函數(shù)中,就是 ROM 支持啟動的 FlexSPI PORTA 上的那些 pin(片選是 SS0)。

在上面的使用示例代碼中,你會看到 option.option0.U = 0xC0000008 代碼,這算是 FlexSPI 驅(qū)動最大的特點了,這是一個簡化的 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 驅(qū)動用作 IAP

IAP 其實就是在 App 中實現(xiàn) Flash 擦寫,單純從技術(shù)上來說并不是一個很難的東西。但 i.MXRT 上很多時候 App 代碼本身也在同一片 Flash 里執(zhí)行(也叫 XIP),而市面上很多 Flash 都是不支持 RWW(Read-While-Write)的,這就導(dǎo)致一個問題,當(dāng)你調(diào)用 Flash 操作函數(shù)去擦寫 Flash 時,CPU 又需要繼續(xù)去 Flash 獲取指令,違反了 RWW,因此你只能把 Flash 相關(guān)操作函數(shù)全部放在 RAM 中去執(zhí)行(這涉及分散加載了,對于初級嵌入式用戶來說稍微有點難)。

?

現(xiàn)在我們有了 ROM API,F(xiàn)lexSPI 驅(qū)動代碼體全部都在 ROM 空間里,并不占用 Flash 空間,因此不存在 RWW 問題,真是天然為 IAP 而生,再也不用再管什么分散加載這么麻煩的事了。

三、FlexSPI API 業(yè)界應(yīng)用

最后再介紹一下 i.MXRT FlexSPI API 在業(yè)界的應(yīng)用,這個 API 其實并不小眾,目前已被主流 IDE 和調(diào)試工具用作 i.MXRT Flash 下載算法。

3.1 用于 IAR 下載算法

如果你的 IAR 版本夠新,能夠支持 i.MXRT1060 等型號,隨便打開一個 i.MXRT1060 SDK 工程,在工程 Option 里找到 Debugger,然后進(jìn)入 Flashloader 配置,你會看到頁面里有 Extra parameters 一欄,在下面的解釋里有這個參數(shù)的示例,它就是前面 2.3 節(jié)里介紹的 option0。有了這種方式設(shè)計的 Flash 下載算法,你再也不用手動更新下載算法文件去支持不同的 Flash 了,改參數(shù)就行了。

?

3.2 用于 J-Link 下載算法

目前最新的 Jlink 驅(qū)動里的下載算法也是基于 ROM API 的,痞子衡有一個開源項目,收集了 i.MXRT 所有型號的下載算法源代碼工程,其中 jlink 算法是最全的,其他 IDE 算法還在陸續(xù)完善中。

https://github.com/JayHeng/imxrt-tool-flash-algo

至此,i.MXRT 系列 ROM 中的 FlexSPI 驅(qū)動 API 實現(xiàn) IAP 痞子衡便介紹完畢了,掌聲在哪里~~~

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

碩士畢業(yè)于蘇州大學(xué)電子信息學(xué)院,目前就職于恩智浦(NXP)半導(dǎo)體MCU系統(tǒng)部門,擔(dān)任嵌入式系統(tǒng)應(yīng)用工程師。痞子衡會定期分享嵌入式相關(guān)文章