查看: 5566|回复: 1

[分享] 想在i.MXRT上搞IAP,岂能放过这个ROM API?

[复制链接]
  • TA的每日心情
    开心
    2025-7-11 08:53
  • 签到天数: 301 天

    连续签到: 2 天

    [LV.8]以坛为家I

    3917

    主题

    7535

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    39884
    最后登录
    2025-8-18
    发表于 2020-6-29 11:11:09 | 显示全部楼层 |阅读模式
    想在i.MXRT上搞IAP,岂能放过这个ROM API?

    今天痞子衡给大家介绍的是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 - 复杂启动功能)。
    1.png
    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就是实例:


    1. typedef struct
    2. {
    3.     const uint32_t version;
    4.     const char *copyright;
    5.     void (*runBootloader)(void *arg);
    6.     const hab_rvt_t *habDriver;

    7.     //!< FlexSPI NOR Flash API
    8.     const flexspi_nor_driver_interface_t *flexSpiNorDriver;

    9.     const nand_ecc_driver_interface_t *nandEccDriver;
    10.     const clock_driver_interface_t *clockDriver;
    11.     const rtwdog_driver_interface_t *rtwdogDriver;
    12.     const wdog_driver_interface_t *wdogDriver;
    13.     const stdlib_driver_interface_t *stdlibDriver;
    14. } bootloader_api_entry_t;

    15. // Bootloader API Tree
    16. const bootloader_api_entry_t g_bootloaderTree = {
    17.     .copyright = "Copyright 2018 NXP",
    18.     .version = MAKE_VERSION(1, 0, 0),
    19.     .runBootloader = run_bootloader,
    20.     .habDriver = &hab_rvt,

    21.     .flexSpiNorDriver = &g_flexspiNorDriverInterface,

    22.     .nandEccDriver = &g_nandEccDriverInterface,
    23.     .clockDriver = &g_clockDriverInterface,
    24.     .rtwdogDriver = &g_rtwdogDriverInterface,
    25.     .wdogDriver = &g_wdogDriverInterface,
    26.     .stdlibDriver = &g_stdlibDriverInterface,
    27. };
    复制代码
    从上面代码我们可以看出,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地址。
    1.   PRESERVE8
    2.                 THUMB

    3. ; Vector Table Mapped to Address 0 at Reset

    4.                 AREA    RESET, DATA, READONLY
    5.                 EXPORT  __Vectors
    6.                 EXPORT  __Vectors_End
    7.                 EXPORT  __Vectors_Size
    8.                 IMPORT  |Image$ARM_LIB_STACK$ZI$Limit|
    9.                 IMPORT  g_bootloaderTree

    10. __Vectors       DCD     |Image$ARM_LIB_STACK$ZI$Limit|
    11.                 DCD     Reset_Handler
    12.                 DCD     DefaultISR
    13.                 DCD     HardFault_Handler
    14.                 DCD     DefaultISR
    15.                 DCD     DefaultISR
    16.                 DCD     DefaultISR
    17.                 DCD     g_bootloaderTree
    18.                 DCD     0
    19.                 DCD     0
    20.                 DCD     0
    21.                 DCD     SVC_Handler
    22.                 DCD     DefaultISR
    23.                 DCD     0
    24.                 DCD     DefaultISR
    25.                 DCD     DefaultISR
    26.           ;; ...
    复制代码
    1.3、API调用方法
    了解了前面介绍的ROM API产生背景与设计实现,它的调用方法就非常简单了,以WDOG API调用为例,只需要如下简单3句代码:
    1. // 找到API根结构体
    2. #define g_bootloaderTree (*(bootloader_api_entry_t **)0x0020001c)
    3. // 定义WDOG模块配置变量
    4. wdog_config_t config;
    5. // 调用API中WDOG_Init()
    6. g_bootloaderTree->wdogDriver->WDOG_Init(WDOG1, config);
    复制代码
    2.png
    1.4、支持API的i.MXRT型号
    截止目前,i.MXRT1xxx系列一共出了7款型号,但并不是每个型号都开放了ROM API,最早诞生的三款型号(105x、1021、1015)就并没有开放API(不是没有API,而是没有严格测试),其余型号都支持API。


    二、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操作函数,有兴趣的朋友可以进一步去研究。
    1. typedef struct
    2. {
    3.     uint32_t version;
    4.     status_t (*init)(uint32_t instance, flexspi_nor_config_t *config);
    5.     status_t (*program)(uint32_t instance, flexspi_nor_config_t *config, uint32_t dst_addr, const uint32_t *src);
    6.     status_t (*erase_all)(uint32_t instance, flexspi_nor_config_t *config);
    7.     status_t (*erase)(uint32_t instance, flexspi_nor_config_t *config, uint32_t start, uint32_t lengthInBytes);
    8.     status_t (*read)(uint32_t instance, flexspi_nor_config_t *config, uint32_t *dst, uint32_t addr, uint32_t lengthInBytes);
    9.     void (*clear_cache)(uint32_t instance);
    10.     status_t (*xfer)(uint32_t instance, flexspi_xfer_t *xfer);
    11.     status_t (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase, uint32_t seqNumber);
    12.     status_t (*get_config)(uint32_t instance, flexspi_nor_config_t *config, serial_nor_config_option_t *option);
    13. } flexspi_nor_driver_interface_t;
    复制代码
    2.2 FlexSPI驱动使用示例
    FlexSPI驱动使用基本三步走,先调用get_config()获取完整FlexSPI模块配置,然后调用init()函数去初始化FlexSPI以及访问Flash获取SFDP表信息,最后就是调用Flash操作函数(比如erase())。
    1. // 找到API根结构体
    2. #define g_bootloaderTree (*(bootloader_api_entry_t **)0x0020001c)

    3. // 定义FlexSPI, Flash配置变量
    4. flexspi_nor_config_t config;
    5. serial_nor_config_option_t option;
    6. option.option0.U = 0xC0000008; // QuadSPI NOR, Frequency: 133MHz
    7. uint32_t instance = 0;

    8. // 调用API中get_config()函数
    9. g_bootloaderTree->flexSpiNorDriver->get_config(instance, &config, &option);
    10. // 调用API中init()函数
    11. g_bootloaderTree->flexSpiNorDriver->init(instance, &config);
    12. // 调用API中erase()函数
    13. 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模式配置值。
    1. • QuadSPI NOR - Quad SDR Read: option0 = 0xc0000008 (133MHz)
    2. • QuadSPI NOR - Quad DDR Read: option0 = 0xc0100003 (60MHz)
    3. • HyperFLASH 1V8: option0 = 0xc0233009 (166MHz)
    4. • HyperFLASH 3V0: option0 = 0xc0333006 (100MHz)
    5. • MXIC OPI DDR (OPI DDR enabled by default): option=0xc0433008(133MHz)
    6. • Micron Octal DDR: option0=0xc0600006 (100MHz)
    7. • Micron OPI DDR: option0=0xc0603008 (133MHz), SPI->OPI DDR
    8. • Micron OPI DDR (DDR read enabled by default): option0 = 0xc0633008 (133MHz)
    9. • 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中去执行(这涉及分散加载了,对于初级嵌入式用户来说稍微有点难)。
    3.png
    现在我们有了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了,改参数就行了。
    4.png
    3.2 用于J-Link下载算法

    目前最新的Jlink驱动里的下载算法也是基于ROM API的,痞子衡有一个开源项目,收集了i.MXRT所有型号的下载算法源代码工程,其中jlink算法是最全的,其他IDE算法还在陆续完善中。


    1. http://github.com/JayHeng/imxrt-tool-flash-algo
    复制代码
    至此,i.MXRT系列ROM中的FlexSPI驱动API实现IAP痞子衡便介绍完毕了,掌声在哪里~~~


    文章出处:痞子衡嵌入式

    qiandao qiandao
    回复

    使用道具 举报

    该用户从未签到

    1

    主题

    6

    帖子

    0

    注册会员

    Rank: 2

    积分
    77
    最后登录
    2025-8-12
    发表于 2025-8-4 16:23:57 | 显示全部楼层
    您好,我按照您的方法测试,程序在调用API的时候卡住了
    void fun_IAP(void)
    {
            // 定义flexspi模块配置变量
            flexspi_config_t config;
            serial_nor_config_option_t option;
            option.option0.U = 0xC0000008; // QuadSPI NOR, Frequency: 133MHz
            uint32_t instance = 0;
            PRINTF("g_bootloaderTree addr = %X\r\n",g_bootloaderTree);
            // 调用API中get_config()函数
            g_bootloaderTree->flexSpiNorDriver->get_config(instance, (flexspi_nor_config_t *)&config, &option);
            // 调用API中init()函数
            PRINTF("write_test start\r\n");
            g_bootloaderTree->flexSpiNorDriver->init(instance, (flexspi_nor_config_t *)&config);
            // 调用API中erase()函数
            g_bootloaderTree->flexSpiNorDriver->erase(instance, (flexspi_nor_config_t *)&config, 0x60200000, 0x1000);
            //调用API中Program()函数
            g_bootloaderTree->flexSpiNorDriver->program(instance,(flexspi_nor_config_t *)&config,0x60200000,(uint32_t *)&m_nor_temp_buffer[0]);
            //调用API中Program()函数
            g_bootloaderTree->flexSpiNorDriver->program(instance,(flexspi_nor_config_t *)&config,(0x60200000 + 256),(uint32_t *)&m_nor_temp_buffer[256]);
            PRINTF("write_test down\r\n ");
    }

    void write_test(void)
    {
            uint16_t i=0;
            memset(m_nor_read_buffer,0,256);
              for (i = 0; i < 0x1000; i++)
        {
            m_nor_temp_buffer[i] = i;
        }
                    PRINTF("write_test\r\n ");
                   
                    fun_IAP();
                   
                    memcpy(m_nor_read_buffer,(uint32_t *)ReadDataFromFlash_XIP(0x60200000),256);
                   
                    for (i = 0; i < 256; i++)
        {
            PRINTF("%2X ",m_nor_read_buffer[i]);
        }
                    PRINTF("\r\nend write_test\r\n");
    }
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /3 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2025-8-18 18:37 , Processed in 0.076240 second(s), 20 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

    快速回复 返回顶部 返回列表