痞子衡嵌入式:利用i.MXRT1xxx系列ROM提供的FlexSPI driver API可輕鬆IAP

痞子衡發表於2020-06-28

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是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演算法還在陸續完善中。

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

  至此,i.MXRT系列ROM中的FlexSPI驅動API實現IAP痞子衡便介紹完畢了,掌聲在哪裡~~~

歡迎訂閱

文章會同時釋出到我的 部落格園主頁CSDN主頁微信公眾號 平臺上。

微信搜尋"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。

相關文章