i.MX RT1050学习笔记9-镜像文件分解
1 前言 我们在《小猫爪:i.MX RT1050学习笔记1-启动》中介绍了一下RT系列芯片的启动过程与一般MCU的启动机制不一样,提到RT的bin文件是经过加工之后的镜像文件,接下就让我们看看RT的镜像文件进行了怎么的加工,才能让RT正常启动。
2 RT启动过程 之前在介绍RT的启动,就简单介绍了RT的正常启动流程,接下来再简单介绍一下: 首先BootROM根据BootMode引脚选择从哪里启动,再根据具体的CFG引脚或者eFUSE的相关配置决定从什么存储设备开始启动,随后BootROM才会从该指定的存储设备读取镜像文件,然后再开始分析镜像并执行镜像中的APP。 我们这一章的重点在于BootROM读取镜像的过程。
3 头信息组成 在介绍读取镜像的过程前,我们首先得了解一下镜像的组成。为了让RT正常启动,我们需要在传统意义上的bin文件(也就是APP主体)增加一个头,那这个头具体内容是什么。
这个头信息包含了以下部分: ①FCB(存储器接口配置数据) ②IVT(Image VectorTable) ③Boot Data ④DCD(Deviceconfiguration data)
下面我们根据具体的实例来对每一个部分进行分解。我打开了NXP的官方的demo Hello World生成的镜像来进行详细的介绍。(环境:IAR;链接文件为:MIMXRT1052xxxxx_flexspi_nor_sdram.icf;使能SDRAM)
3.1 FCB 该部分是可选的,也是整个镜像最开始部分,其本身没有统一的结构,具体结构根据启动FLASH的接口类型而定,其一般是用来存储RT存储器接口配置数据以及当前连接的FLASH的具体特性参数,BootROM首先会使用通用且可靠的FLASH接口控制器配置(即BootROM中默认参数配置、CFG引脚以及eFUSE的配置,一般是比较低速通用的配置)去访问外接FLASH并获取这部分数据,然后根其参数去重新配置FLASH接口控制器再去进一步访问FLASH。占的空间大小随着外部存储器类型而改变,下表为不同类型存储器对应的所占空间大小:
这一部分数据对于不同的存储器类型是不一样的,我们知道RT有FlexSPI,SEMC,uSDHC 三种外部存储器接口,所以这一部分数据是根据不同的存储器接口来进行划分的。 ①对于FlexSPI,存储器接口配置数据则包括两个部分,分别是FlexSPI初始化配置数据和存储器配置数据。这部分数据的具体定义大家可参考官方参考指南《i.MX RT1050 Processor Reference Manual》的SerialNAND Flash Boot over FlexSPI。 ②对于SEMC,存储器接口配置数据则包括两个部分,分别是SEMC初始化配置数据和存储器配置数据。这部分数据的具体定义大家可参考官方参考指南《i.MXRT1050 Processor Reference Manual》的Parallel NOR andNAND configuration based on SEMC interface。 ③对于uSDHC ,这一部分则是不需要了。因为对于SD协议来说,有相关的信息来区别不同的存储器。 (对于NAND而言,这一部分还包括关于坏块处理的FCB和DBBT数据,后续将具体介绍。)
以通过FlexSPI NOR为启动存储设备为例,在NXP官方demo程序中,是通过一个结构体来实现这部分数据,代码如下: - /*
- * Serial NOR configuration block
- */
- typedef struct _flexspi_nor_config
- {
- flexspi_mem_config_t memConfig; //!< Common memory configuration info via FlexSPI
- uint32_t pageSize; //!< Page size of Serial NOR
- uint32_t sectorSize; //!< Sector size of Serial NOR
- uint8_t ipcmdSerialClkFreq; //!< Clock frequency for IP command
- uint8_t isUniformBlockSize; //!< Sector/Block size is the same
- uint8_t reserved0[2]; //!< Reserved for future use
- uint8_t serialNorType; //!< Serial NOR Flash type: 0/1/2/3
- uint8_t needExitNoCmdMode; //!< Need to exit NoCmd mode before other IP command
- uint8_t halfClkForNonReadCmd; //!< Half the Serial Clock for non-read command: true/false
- uint8_t needRestoreNoCmdMode; //!< Need to Restore NoCmd mode after IP commmand execution
- uint32_t blockSize; //!< Block size
- uint32_t reserve2[11]; //!< Reserved for future use
- } flexspi_nor_config_t;
复制代码
3.2 IVT IVT里面包含了一系列的地址信息,这些地址信息按照固定的序列存放着,对于RT启动至关重要, 大小为32个字节。
IVT的具体组成如下:
打开Hello World.bin,因为链接文件MIMXRT1052xxxxx_flexspi_nor_sdram.icf决定了程序从FlexSPINOR启动,我们可以知道IVT的偏移地址为0x10000。我们找到Bin文件的0x10000处:
再对应IVT结构我们可以得到:
(注:链接地址就是当前数据被存储到RT的映射地址位置, 可以理解成其在RT的memorymap中所在的绝对地址,通过查询可得到FlexSPI NOR的映射地址为0x60000000。)
在NXP官方demo程序中,是通过一个结构体来实现这部分数据,代码如下: - /*************************************
- * IVT Data
- *************************************/
- typedef struct _ivt_ {
- /** @ref hdr with tag #HAB_TAG_IVT, length and HAB version fields
- * (see @ref data)
- */
- uint32_t hdr;
- /** Absolute address of the first instruction to execute from the
- * image
- */
- uint32_t entry;
- /** Reserved in this version of HAB: should be NULL. */
- uint32_t reserved1;
- /** Absolute address of the image DCD: may be NULL. */
- uint32_t dcd;
- /** Absolute address of the Boot Data: may be NULL, but not interpreted
- * any further by HAB
- */
- uint32_t boot_data;
- /** Absolute address of the IVT.*/
- uint32_t self;
- /** Absolute address of the image CSF.*/
- uint32_t csf;
- /** Reserved in this version of HAB: should be zero. */
- uint32_t reserved2;
- } ivt;
复制代码
3.3 Boot Data Boot Data即启动数据,包含了镜像要拷贝到哪个地址,拷贝的大小是多少,其必须紧跟在IVT的后面,位置不能随意变动。所占空间大小为12个字节,其结构如下图。
我们从IVT中可以得到BootData的链接地址为0X60001020,所以我们在bin文件中找到0x1020处:
再对应Boot Data结构我们可以得到:
在NXP官方demo程序中,是通过一个结构体来实现这部分数据,代码如下: - typedef struct _boot_data_ {
- uint32_t start; /* boot start location */
- uint32_t size; /* size */
- uint32_t plugin; /* plugin flag - 1 if downloaded application is plugin */
- uint32_t placeholder; /* placehoder to make even 0x10 size */
- }BOOT_DATA_T;
复制代码
最后一项placeholder不是必须的,只是用来凑数,让数据对齐用的,可省略。
3.4 DCD DCD即设备配置信息,其实就是RT启动前初始化,目的就是为了初始化SEMC,所以能通过DCD初始化的寄存器都是和SEMC有关的寄存器。原理就是里面存储着寄存器地址和数据对,读取地址和数据后,再讲相应的数据写到对应的地址。(注:我们在《小猫爪:i.MX RT1050学习笔记8-SEMC》就曾经介绍过可以使用DCD来提前初始化SEMC。)
DCD的结构如下图所示:
其中Header的格式如下:
Tag为0xD2,长度为整个DCD的长度,Version为0x41。 我们从IVT中可以得到BootData的链接地址为0X60001030,所以我们在bin文件中找到0x1030处:
从图中,0x1030处的数据0x411004D2,可以得到DCD大小为0x0410 = 1040Byte,我们找到DCD结束的位置如下:
所以DCD的大小为0x1440-0x1030=0x0410,和上面对应正确,所以这个推断没有错。 CMD即为指令,一般有三种:写指令,检查指令和空指令。每一种指令都有自己的格式,在这里我就不展开说了。
在NXP官方demo程序中,是通过一个数组来实现DCD数据,我们可以直接通过修改这个数组来更改DCD数据,根据所使用的SDRAM、FLASH特性来初始化SEMC,具体代码如下: - const uint8_t dcd_data[] = {
- /* HEADER */
- /* Tag */
- 0xD2,
- /* Image Length */
- 0x04, 0x10,
- /* Version */
- 0x41,
- /* COMMANDS */
- /* group: 'Imported Commands' */
- /* #1.1-113, command header bytes for merged 'Write - value' command */
- 0xCC, 0x03, 0x8C, 0x04,
- /* #1.1, command: write_value, address: CCM_CCGR0, value: 0xFFFFFFFF, size: 4 */
- 0x40, 0x0F, 0xC0, 0x68, 0xFF, 0xFF, 0xFF, 0xFF,
- /* #1.2, command: write_value, address: CCM_CCGR1, value: 0xFFFFFFFF, size: 4 */
- 0x40, 0x0F, 0xC0, 0x6C, 0xFF, 0xFF, 0xFF, 0xFF,
-
- .........
-
- /* #9, command: write_value, address: SEMC_SDRAMCR3, value: 0x50210A09, size: 4 */
- 0xCC, 0x00, 0x0C, 0x04, 0x40, 0x2F, 0x00, 0x4C, 0x50, 0x21, 0x0A, 0x09
- };
复制代码
到这里,我们给镜像添加的头文件分解完毕。我们只要把这个头信息和APP合二为一就是一个最简单的镜像文件了,这样可以确保RT的正常运行。 除了上面介绍的头信息部分和APP部分,一个镜像文件还可以有CSF(Command Sequence File)和 KeyBlob两个部分,这两个部分主要用于安全启动的认证相关特性。后面我们接触到再对其进行深入学习。
4 镜像文件的生成 对于镜像文件的生成可以通过NXP的elftosb工具来生成,实例命令如下: - elftosb.exe -f imx -V -c APP.bd -o APP.bin APP.out
复制代码
APP.bin就是我们最终的镜像文件;APP.out就是你的APP工程编译链接生成的ELF文件;APP.bd是用户配置文件,该文件主要是指示elftosb工具如何在Application binary基础上添加头信息等其他信息数据从而构成镜像文件,bd文件有专门语法格式,在\Flashloader_i.MXRT1050_GA\Flashloader_RT1050_1.1\Tools\bd_file\imx10xx目录下给了很多bd文件示例。
不过这样太过麻烦。现在NXP官方在例程中都添加了XIP文件,在文件中,我们可以非常方便的修改这些信息,在修改相关的链接文件,就可以按照我们的想法做出相关镜像文件。最后生成的bin文件即为最终的镜像文件,每一个部分在代码中怎么去实现,前面已经介绍的非常清楚。
———————————————— 版权声明:本文为「小猫爪」的原创文章
|