企业手机网站建设特色,杭州知名设计公司,软件系统,找回网站备案密码这篇文章将带你写第三个BootLoader程序#xff0c;对应的是以下那篇博文的第三种启动方式#xff1a;应用程序(APP)存储在文件系统里面或者存储在一些非XIP的存储芯片上#xff0c;BootLoader将APP复制到RAM指定地址#xff0c;最后通过跳转指令转入RAM中执行APP 带你搞懂B…这篇文章将带你写第三个BootLoader程序对应的是以下那篇博文的第三种启动方式应用程序(APP)存储在文件系统里面或者存储在一些非XIP的存储芯片上BootLoader将APP复制到RAM指定地址最后通过跳转指令转入RAM中执行APP带你搞懂BootLoader一原理大致框图就如下图当APP程序较大且存储在外部Flash时通常通过SPI串行接口与主控MCU连接由于外部Flash不具备XIPeXecute In Place功能特性CPU无法像操作内部Flash那样直接跳转到外部Flash地址空间执行程序代码。此时需要依赖Bootloader完成关键的数据搬运工作——将外部Flash中的APP程序二进制映像复制到RAM的特定链接地址区域才能实现APP程序的正常加载和执行。Bootloader在执行APP程序复制操作时必须明确三个核心参数源地址Source Address、目标地址Destination Address和数据长度Data Length。这三个参数构成了程序搬运的基本要素1.源地址的确定相对简单开发者可以预先规划外部Flash的存储布局例如将APP程序固定存放在0x90000000开始的扇区实际工程中常配合Flash分区表使用通过分区表记录各应用程序的存储位置某些情况下会在Flash起始位置保留配置区存储各APP的元信息2.目标地址即链接地址的处理更为复杂虽然可以硬编码指定固定RAM地址如0x20001000但会带来显著局限性不利于动态内存管理在多应用系统中容易造成地址冲突无法适应不同容量RAM的硬件平台更专业的做法是在APP程序映像中嵌入头部信息(Header)通过头部携带关键加载参数典型的APP程序头部信息结构包含以下字段以32位系统为例偏移量字段名称长度说明0x00Magic Number4字节固定标识符如0xAA55AA55用于验证文件有效性0x04Load Address4字节APP程序应加载到的RAM物理地址如0x200010000x08Entry Point4字节程序入口地址通常是Load Address向量表偏移0x0CData Length4字节需要复制的有效数据长度不包括头部自身0x10CRC324字节数据校验和用于验证传输完整性0x14Version2字节映像版本号0x16Reserved2字节对齐填充实际工作流程示例Bootloader从外部Flash固定位置如0x08040000读取头部信息校验Magic Number确认是有效APP映像解析Load Address确定目标RAM位置根据Data Length复制后续程序数据到指定RAM区域计算CRC校验确保数据传输无误最后跳转到Entry Point开始执行APP程序uboot的头部信息结构体下面就以Linux的uboot来举例这是uboot的头部信息结构体每个字段含义详解如下字段类型含义作用ih_magicbe32魔数Magic Number标识这是 U-Boot 可识别的镜像通常为0x4d4d4945IEMM或0x4d4d4946IFMMih_hcrcbe32头部 CRC 校验和校验头部是否损坏ih_timebe32创建时间戳便于调试、版本管理ih_sizebe32数据区大小字节要复制的数据长度ih_loadbe32加载地址Load Address数据应复制到的内存地址通常是 RAM 地址ih_epbe32入口点地址Entry Point执行开始地址即main()或Reset_Handlerih_dcrcbe32数据区 CRC 校验和校验数据完整性ih_os,ih_arch, ...uint8_t操作系统、架构、类型等用于判断是否兼容注意所有be32表示大端字节序因为网络协议/跨平台传输常用大端。在嵌入式中即使 MCU 是小端如 STM32也需用be32_to_cpu()转换。mkimage工具介绍它是 U-Boot 官方提供的一个非常强大的工具用于生成带有 U-Boot 标准头部Image Header的镜像文件。作用将普通的二进制文件 (xxx.bin) 打包成一个带U-Boot Image Header的可加载镜像支持校验、压缩、签名等高级功能。一、什么是mkimagemkimage是U-Boot 项目的一部分它会为你的原始二进制数据添加一个标准的image_header_t结构体我们之前讲过从而让 Bootloader 能“智能”地识别和加载这个镜像。类比普通.bin文件就像一本没有封面的书使用mkimage打包后就相当于加上了封面、目录、作者、ISBN 等信息方便阅读者快速了解内容。二、基本用法格式mkimage -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file image_name参数详解参数含义示例-A archCPU 架构-A armARM、-A mips-O os操作系统-O linux、-O u-boot-T type镜像类型-T kernel内核、-T ramdisk根文件系统、-T firmware固件-C comp压缩方式-C none无、-C gzip、-C bzip2-a addr加载地址Load Address-a 0x20000000RAM 地址-e ep入口点地址Entry Point-e 0x20000100程序入口-n name镜像名称-n MyApp-d data_file数据源文件-d app.binimage_name输出文件名app.img示例1创建一个未压缩的 App 镜像假设你有一个 App 编译出的app.bin你想让它在 RAM 中运行mkimage -A arm -O linux -T kernel -C none -a 0x20000000 -e 0x20000100 -n MyApp -d app.bin app.img输出app.img结构------------------- ← 0x00000000 (Flash) | image_header_t | ← 0x00000000 (32 字节) ------------------- | app.bin | ← 0x00000020 (原始数据) -------------------⚠️ 注意-T kernel表示“这是一个可执行镜像”即使不是内核也可以用。示例2查看镜像头部信息mkimage -l app.img输出类似Image Name: MyApp Created: Wed May 5 10:00:00 2024 Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 16384 Bytes 16.0 KiB Load Address: 0x20000000 Entry Point: 0x20000100Bootloader程序先说一下整体架构[Flash] 0x08000000 ──┬── Bootloader (本程序) │ 0x08040000 ──┼── image_header_t ├── App Code Data (紧跟头部) │ [RAM] 0x20000000 ──┴── App 被复制到这里运行Bootloader 在 Flash 中运行 → 读取 App 头部 → 搬运 App 到 RAM → 跳转执行。主程序main.c#include uart.h typedef unsigned int __be32; typedef unsigned char uint8_t; #define IH_MAGIC 0x27051956 /* Image Magic Number */ #define IH_NMLEN 32 /* Image Name Length */ typedef struct image_header { __be32 ih_magic; /* Image Header Magic Number */ __be32 ih_hcrc; /* Image Header CRC Checksum */ __be32 ih_time; /* Image Creation Timestamp */ __be32 ih_size; /* Image Data Size */ __be32 ih_load; /* Data Load Address */ __be32 ih_ep; /* Entry Point Address */ __be32 ih_dcrc; /* Image Data CRC Checksum */ uint8_t ih_os; /* Operating System */ uint8_t ih_arch; /* CPU architecture */ uint8_t ih_type; /* Image Type */ uint8_t ih_comp; /* Compression Type */ uint8_t ih_name[IH_NMLEN]; /* Image Name */ } image_header_t; unsigned int be32_to_cpu(unsigned int x) { unsigned char *p (unsigned char *)x; unsigned int le; le (p[0] 24) (p[1] 16) (p[2] 8) (p[3]); return le; } extern void start_app(unsigned int new_vector); void delay(int d) { while(d--); } void copy_app(int *from, int *to, int len) { // 从哪里到哪里, 多长 ? int i; for (i 0; i len/41; i) { to[i] from[i]; } } void relocate_and_start_app(unsigned int pos) { image_header_t *head; unsigned int load; unsigned int size; unsigned int new_pos possizeof(image_header_t); /* 读出头部 */ head (image_header_t *)pos; /* 解析头部 */ load be32_to_cpu(head-ih_load); size be32_to_cpu(head-ih_size); putstr(load ); puthex(load); putstr(\r\n); putstr(size ); puthex(size); putstr(\r\n); /* 把程序复制到RAM */ copy_app((int *)new_pos, (int *)load, size); /* 跳转执行 */ start_app(new_pos); } int mymain() { unsigned int app_pos 0x08040000; uart_init(); putstr(bootloader\r\n); /* start app */ relocate_and_start_app(app_pos); //start_app(new_vector); return 0; }typedef unsigned int __be32; typedef unsigned char uint8_t;__be32表示“大端 32 位整数”Big-Endian 32-bituint8_t标准无符号 8 位整型为什么需要因为 U-Boot 镜像头部使用大端字节序存储而 STM32 等 Cortex-M 是小端Little-Endian必须转换#define IH_MAGIC 0x27051956 /* Image Magic Number */ #define IH_NMLEN 32 /* Image Name Length */IH_MAGIC 0x27051956U-Boot 镜像的固定魔数注意这是小端 CPU 上看到的值实际在镜像中存储为大端0x56190527IH_NMLEN 32镜像名称最大长度。typedef struct image_header { __be32 ih_magic; /* 魔数 */ __be32 ih_hcrc; /* 头部 CRC */ __be32 ih_time; /* 时间戳 */ __be32 ih_size; /* 数据区大小 */ __be32 ih_load; /* 加载地址RAM*/ __be32 ih_ep; /* 入口点可选*/ __be32 ih_dcrc; /* 数据 CRC */ uint8_t ih_os; /* 操作系统 */ uint8_t ih_arch; /* 架构 */ uint8_t ih_type; /* 类型 */ uint8_t ih_comp; /* 压缩方式 */ uint8_t ih_name[IH_NMLEN]; /* 名称 */ } image_header_t;这是U-Boot 标准镜像头部共 64 字节。所有__be32字段在镜像中以大端格式存储必须用be32_to_cpu()转换才能在小端 MCU 上正确读取。unsigned int be32_to_cpu(unsigned int x) { unsigned char *p (unsigned char *)x; unsigned int le; le (p[0] 24) (p[1] 16) (p[2] 8) (p[3]); return le; }作用将大端 32 位整数转换为当前 CPU 的本地字节序小端。原理假设从 Flash 读出的ih_load字节序为[A][B][C][D]大端AMSB在小端内存中这 4 字节被解释为0xDCBA错误be32_to_cpu重新组合为(A24)(B16)(C8)D得到正确值。void relocate_and_start_app(unsigned int pos) { image_header_t *head; unsigned int load; unsigned int size; unsigned int new_pos pos sizeof(image_header_t);posApp 镜像在 Flash 中的起始地址如0x08040000new_posApp 数据区起始地址紧跟头部之后。head (image_header_t *)pos;将pos强制转换为image_header_t*即可访问头部字段。load be32_to_cpu(head-ih_load); size be32_to_cpu(head-ih_size);关键步骤解析出加载地址和数据大小并进行字节序转换putstr(load ); puthex(load); putstr(\r\n); putstr(size ); puthex(size); putstr(\r\n);调试输出确认解析正确。copy_app((int *)new_pos, (int *)load, size);将 App 数据从 Flash (new_pos) 复制到 RAM (load)。start_app(load);跳转到RAM的APP地址运行程序启动文件start.sPRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD 0 DCD Reset_Handler ; Reset Handler AREA |.text|, CODE, READONLY ; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT mymain LDR SP, (0x200000000x10000) BL mymain ENDP start_app PROC EXPORT start_app ; set vector base address as 0x08040000 ldr r3, 0xE000ED08 str r0, [r3] ldr sp, [r0] ; read val from new vector ldr r1, [r0, #4] ; read val from new vector 4“ BX r1 ENDP ENDstart_app PROC EXPORT start_app导出函数供 C 调用。; set vector base address as 0x08040000 ldr r3, 0xE000ED08 str r0, [r3]设置 VTORVector Table Offset Register0xE000ED08是 SCB-VTOR 地址r0是传入的向量表地址0x08040000作用让 CPU 从中断时去 RAM 查找 ISRldr sp, [r0] ; 从向量表第 0 项读取 MSP ldr r1, [r0, #4] ; 从向量表第 1 项读取 Reset_Handler 地址 BX r1 ; 跳转执行 ENDP切换栈指针到 App 的栈MSP读取 App 的入口地址BX跳转移交控制权。总结Bootloader 工作流程芯片上电→ 从0x08000000读取 Bootloader 向量表执行Reset_Handler→ 设栈 → 调用mymainmymain初始化 UART调用relocate_and_start_app(0x08040000)解析 U-Boot 头部→ 获取loadRAM 地址、size将 App 数据从 Flash 复制到 RAM调用start_app(load)设置 VTOR loadRAM 向量表切换 MSP跳转到 App 的Reset_HandlerApp 在 RAM 中运行可安全使用中断