# 基于 RT-Thread 的 OTA 升级中 Bootloader 与 App 共享 RAM 的边界设计 ## 1. 为什么需要共享 RAM 边界设计? 在 OTA 升级流程中,Bootloader 负责接收固件、写入 Flash,而 App 是实际运行的应用。两者通常独立编译,但共用同一块物理 RAM。若边界设计不当,可能出现以下问题: - **栈溢出冲突**:Bootloader 的栈指针(SP)在跳转前未复位,App 启动时可能覆盖 Bootloader 的临时数据。 - **堆区重叠**:Bootloader 使用 malloc 分配的内存区域与 App 的静态变量或堆区重叠,导致数据损坏。 - **中断向量表错位**:App 的中断服务函数(ISR)访问的 RAM 地址与 Bootloader 的保留区域冲突。 因此,必须通过链接脚本和运行时约定,明确划分 RAM 的归属区域,确保升级过程中数据安全。 ## 2. 原理讲解:RAM 分区模型 以 STM32F407 为例,其 RAM 总大小为 128KB(0x20000000 - 0x2001FFFF)。我们将其划分为三个区域: - **Bootloader 专用区**:起始地址 0x20000000,大小 16KB,存放 Bootloader 的栈、堆和全局变量。 - **共享缓冲区**:起始地址 0x20004000,大小 32KB,用于升级过程中暂存固件数据(例如从网络接收的数据包)。 - **App 专用区**:起始地址 0x2000C000,大小 64KB,存放 App 的栈、堆和全局变量。 边界设计的关键在于: - **静态划分**:通过链接脚本(.ld 文件)为 Bootloader 和 App 分别指定 RAM 起始地址和长度。 - **动态约定**:在跳转前,Bootloader 必须复位 SP 和 PC,并确保共享缓冲区的内容在 App 启动前被清空或标记为无效。 RT-Thread 作为实时操作系统,其内核对象(如线程栈、信号量)会占用大量 RAM,因此边界设计需考虑 RT-Thread 的内存池配置。 ## 3. 配置步骤:基于 RT-Thread 的实现 ### 3.1 修改链接脚本 Bootloader 的链接脚本(如 `link.lds`)中,定义 RAM 区域: ```c MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 16K } ``` App 的链接脚本中,定义 RAM 区域: ```c MEMORY { FLASH (rx) : ORIGIN = 0x08008000, LENGTH = 960K RAM (xrw) : ORIGIN = 0x2000C000, LENGTH = 64K } ``` 注意:App 的 Flash 起始地址需与 Bootloader 的跳转地址匹配,RAM 起始地址必须避开共享缓冲区。 ### 3.2 配置 RT-Thread 内存堆 RT-Thread 使用 `rt_system_heap_init` 初始化堆,需在启动代码中指定堆的起始和结束地址。在 App 的 `board.c` 中: ```c #define HEAP_BEGIN (0x2000C000 + 0x1000) // 避开栈顶区域 #define HEAP_END (0x2001FFFF) void rt_hw_board_init(void) { rt_system_heap_init((void*)HEAP_BEGIN, (void*)HEAP_END); // 其他初始化 } ``` ### 3.3 Bootloader 跳转前的 RAM 清理 在 Bootloader 中,跳转到 App 前,需要复位 SP 并清理共享缓冲区: ```c #define APP_FLASH_ADDR 0x08008000 #define SHARED_RAM_ADDR 0x20004000 #define SHARED_RAM_SIZE 0x8000 typedef void (*pFunction)(void); void jump_to_app(void) { uint32_t app_sp = *(volatile uint32_t*)APP_FLASH_ADDR; pFunction app_pc = (pFunction)(*(volatile uint32_t*)(APP_FLASH_ADDR + 4)); // 清空共享缓冲区,防止残留数据 memset((void*)SHARED_RAM_ADDR, 0, SHARED_RAM_SIZE); // 设置主栈指针 __set_MSP(app_sp); // 跳转 app_pc(); } ``` ### 3.4 共享缓冲区的使用约定 在升级过程中,Bootloader 将接收到的固件数据暂存到共享缓冲区,App 启动后可通过约定地址读取元数据(如固件版本、校验值): ```c // 共享结构体定义(Bootloader 和 App 均需包含) typedef struct { uint32_t magic; // 魔数,用于验证有效性 uint32_t version; // 固件版本 uint32_t length; // 固件长度 uint32_t crc32; // 校验值 } ota_shared_info_t; #define SHARED_INFO_ADDR (0x20004000) ``` App 启动时检查魔数,若有效则进行固件搬移或校验。 ## 4. 完整代码示例 以下是一个简化的 Bootloader 跳转函数,结合 RT-Thread 的线程暂停机制: ```c #include #include #define APP_FLASH_ADDR 0x08008000 #define SHARED_RAM_ADDR 0x20004000 #define SHARED_RAM_SIZE 0x8000 typedef void (*pFunction)(void); void bootloader_jump_to_app(void) { uint32_t app_sp; pFunction app_pc; // 暂停所有 RT-Thread 线程,避免干扰 rt_enter_critical(); // 从 App 向量表读取初始 SP 和 PC app_sp = *(volatile uint32_t*)APP_FLASH_ADDR; app_pc = (pFunction)(*(volatile uint32_t*)(APP_FLASH_ADDR + 4)); // 清空共享缓冲区 memset((void*)SHARED_RAM_ADDR, 0, SHARED_RAM_SIZE); // 复位 SysTick 和中断,防止中断冲突 SysTick->CTRL = 0; SysTick->LOAD = 0; SysTick->VAL = 0; // 设置主栈指针并跳转 __set_MSP(app_sp); app_pc(); // 不应到达此处 rt_exit_critical(); } ``` App 端在 `main` 函数中检查共享信息: ```c #include typedef struct { uint32_t magic; uint32_t version; uint32_t length; uint32_t crc32; } ota_shared_info_t; #define SHARED_INFO_ADDR (0x20004000) #define OTA_MAGIC 0x4F544131 // "OTA1" int app_check_ota_info(void) { ota_shared_info_t *info = (ota_shared_info_t*)SHARED_INFO_ADDR; if (info->magic == OTA_MAGIC) { rt_kprintf("OTA info: version %d, length %d\n", info->version, info->length); // 进行固件搬移或校验 // ... info->magic = 0; // 清除魔数,防止重复处理 return 0; } return -1; } INIT_APP_EXPORT(app_check_ota_info); ``` ## 5. 注意事项 - **对齐问题**:共享缓冲区地址建议按 8 字节对齐,以满足 RT-Thread 内存管理的要求。 - **栈大小估算**:Bootloader 的栈大小需根据实际调用深度设置,避免溢出到共享缓冲区。 - **中断向量表重定向**:App 中需设置 `SCB->VTOR` 指向 App 的 Flash 起始地址,否则中断服务函数无法正确执行。 - **编译优化**:在跳转函数中,禁止使用局部变量(或使用 `volatile`),防止编译器优化导致 SP 设置失败。 - **测试覆盖**:建议在升级流程中加入 CRC 校验和看门狗超时机制,防止升级中断导致系统卡死。 ## 6. 总结 通过合理的 RAM 分区和严格的跳转约定,基于 RT-Thread 的 OTA 升级可以避免内存冲突,提升可靠性。本文的边界设计方法适用于大多数 Cortex-M 系列 MCU,开发者可根据实际 RAM 大小调整分区参数。记住:边界设计不仅是技术问题,更是工程规范,需在项目初期就明确约定。