STM32 Bootloader内存不够用?给F103C8T6的IAP程序做一次“瘦身”与空间规划实战
在嵌入式开发中,STM32F103C8T6这类资源受限型MCU的Flash空间往往捉襟见肘。当开发者需要在64KB的Bootloader区域塞入更多功能时,如何优化内存占用就成了一门必修课。本文将带你深入源码层面,从模块裁剪、缓冲区优化到分区策略,一步步实现Bootloader的"瘦身"计划。
1. 现有Bootloader的空间瓶颈分析
以正点原子IAP例程为基础,我们首先解剖其内存占用情况。通过Keil的map文件分析,主要模块占用比例如下:
| 模块 | 占用空间(Byte) | 占比 |
|---|---|---|
| USART驱动 | 3,872 | 23.6% |
| Flash操作 | 2,456 | 15.0% |
| 延时函数 | 1,024 | 6.2% |
| 主逻辑代码 | 9,048 | 55.2% |
关键问题出在USART1_RX_BUF这个15KB的接收缓冲区上。它被定义在Flash中,直接吞噬了23%的可用空间。更合理的做法是:
// 原定义(占用Flash) u8 USART1_RX_BUF[USART1_REC_LEN] __attribute__((at(0x20001000))); // 优化方案(改为RAM存储) #define APP_BUFFER_ADDR 0x20001000 u8* const pAppBuffer = (u8*)APP_BUFFER_ADDR;2. 关键模块的深度优化策略
2.1 USART驱动的精简方案
原始USART驱动包含了许多冗余功能,我们可以进行以下裁剪:
- 移除
printf1等复杂格式化输出 - 简化中断处理逻辑
- 使用DMA替代中断接收
优化后的初始化代码示例:
void USART1_Minimal_Init(uint32_t baudrate) { GPIO_InitTypeDef GPIO_InitStruct = {0}; USART_InitTypeDef USART_InitStruct = {0}; // 仅配置必要引脚 GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); USART_InitStruct.BaudRate = baudrate; USART_InitStruct.WordLength = USART_WORDLENGTH_8B; USART_InitStruct.StopBits = USART_STOPBITS_1; USART_InitStruct.Parity = USART_PARITY_NONE; HAL_USART_Init(&USART_InitStruct); }2.2 Flash操作优化技巧
原始Flash驱动每次写入都进行全页备份,这在Bootloader中并不必要。我们可以:
- 移除
FLASH_BUF缓存区 - 实现最小页擦除策略
- 使用半字编程加速写入
关键优化代码:
void FLASH_Write_Direct(uint32_t Addr, uint16_t *pData, uint16_t len) { HAL_FLASH_Unlock(); // 仅擦除需要修改的页 if(Addr % SECTOR_SIZE == 0) { FLASH_Erase_Sector(FLASH_SECTOR_1, VOLTAGE_RANGE_3); } // 直接写入不检查 for(uint16_t i=0; i<len; i++) { HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, Addr, pData[i]); Addr += 2; } HAL_FLASH_Lock(); }3. 内存布局的精细规划
针对STM32F103C8T6的128KB Flash,推荐采用以下分区方案:
| 区域 | 地址范围 | 大小 | 用途说明 |
|---|---|---|---|
| Bootloader | 0x08000000-0x0800BFFF | 48KB | 包含基础IAP功能 |
| App1 | 0x0800C000-0x0801FFFF | 80KB | 主应用程序区 |
| Config | 0x0801F000-0x0801FFFF | 4KB | 参数存储区 |
实现这种布局需要在链接脚本中精确配置:
MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 48K APP (rx) : ORIGIN = 0x0800C000, LENGTH = 80K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K }4. 实战:构建极简Bootloader
结合上述优化,我们重构主逻辑流程:
初始化阶段:
- 仅开启必要外设时钟
- 配置精简版USART
- 初始化关键GPIO
数据传输阶段:
- 使用RAM缓冲区接收数据
- 实现流式写入,避免大缓存
- 添加CRC校验确保数据完整
跳转逻辑优化:
- 移除冗余延时
- 简化状态判断
- 增强异常处理
关键跳转代码实现:
void JumpToApp(uint32_t appAddr) { typedef void (*pFunction)(void); pFunction AppStart; // 检查栈顶地址有效性 if(((*(__IO uint32_t*)appAddr) & 0x2FFE0000) == 0x20000000) { // 设置主堆栈指针 __set_MSP(*(__IO uint32_t*)appAddr); // 获取复位地址 AppStart = (pFunction)*(__IO uint32_t*)(appAddr + 4); // 跳转前关闭所有中断 __disable_irq(); // 执行跳转 AppStart(); } }经过这些优化,最终Bootloader大小可控制在30KB以内,相比原始方案节省超过50%的空间。实际项目中,开发者还可以根据具体需求进一步裁剪,比如移除非必要的调试信息、合并相似功能模块等。