基于STM32 的bootloader程序实现简单的AB分区 1.首先明确app程序都是使用axf转bin文件bin文件最小只有2进制代码无其他本次需要3个程序即app1app2 bootloader。2.明白bootloader流程首先禁用全局中断清除清除中断挂起状态然后判定flash和ram地址是否合法初始化堆栈进入复位中断然后跳转到app的main函数之后app在一开始就设置VTOR及使能全局中断(因为没有硬件初始化让全局中断开启)3.明白flash和ram怎么设置4.了解堆栈地址,复位中断地址 分别为flash地址第一个和第二个字32bit5.flash和ram的容量查看(ram多大需要查资料)6.对AB分区程序及bootloader程序进行规划flash和ram首先一开始flash地址默认运行地址0x08000000必须是bootloader程序保证bootloader先运行且他占用的flash空间是比较小的我给了8K,AB分区的app程序2个都为24K,其次设置的地址不能重合至于为什么相信都懂然后需要保证地址是0x200的倍数因为要中断向量表确保可以被存下同时保证地址对齐所以取2的幂次的下一个即256的下一个512。ram首先AB占用全部,bootloader占用4K其他为接收更新的bin文件代码数据防止编译器把变量分配到里面干扰flash的缓冲区。至于内存分配原因因为不是一起运行可以复用。7.编写app应用程序和bootloader程序的思路本次采用正点原子的上位机软件传输bin文件因此采用串口的方式配置方面采用DMA环形空闲中断但是容易中间误触发需要额外做空闲判断因此使用中断一个一个接收也可以这个无关紧要怎么传都行只要bin文件内容正确就行8.编写bootloader里面需要用到一点汇编因为MSP这是cpu核心的特殊寄存器顺便介绍一下MSP的三兄弟这是MSP堆栈的初始化函数//设置栈顶地址(存储在flash偏移一个字的地址) //addr:栈顶地址 //将addr参数存入r0移动到MSP读取r14返回到存储的指令地址执行下一个c程序 __asm void MSR_MSP(uint32_t addr) { MSR MSP, r0 //set Main Stack value BX r14 }然后这是总的跳转代码(使用宏将结构体映射到flash地址然后读取使用实现跳转至于怎么做参考st的库怎么做结构体的寄存器映射的就知道了)void Jump_App(vector_table *app){ /* 防止升级中途断电 Flash 被擦除后默认是 0xFFFFFFFF 直接 MSR_MSP(0xFFFFFFFF)CPU 会把栈顶设到非法地址就会死机 */ if(((uint32_t)app-reset_handler 0xFF000000) 0x08000000){ if (((app-MSP_Addr) 0x2FFE0000) 0x20000000){ //关闭中断防止被打断升级 __disable_irq(); //清除所有挂起中断 防止切换到app程序时意外启动 for (int i 0; i 8; i) { NVIC-ICPR[i] 0xFFFFFFFF; } //关闭并清理 SysTick内核定时器 SysTick-CTRL 0; // 关闭定时器 禁止中断 SysTick-LOAD 0; // 清空重载值 SysTick-VAL 0; // 清空当前计数值 //初始化MSP 堆栈 MSR_MSP(app-MSP_Addr); //跳转到复位程序 app-reset_handler(); } }while(1);//如果失败则死循环 //然后在app程序设置SCB的VTOR寄存器和全局中断开启即可bootloader程序不用 }这是flash代码用于写bin文件到flash(flash最低16位写串口8位因此我在外部使用了联合体别的就不多说了)/** * 功能向内部 Flash 写入数据写入前自动擦除写入范围涉及的页 * 参数1WriteAddress - 数据写入的目标地址Flash 绝对地址 * 参数2data - 待写入数据的首地址int16_t 数组每次写入一个半字 * 参数3length - 待写入的半字个数 * 返回值无 * 注意1. 地址必须在 [FLASH_APP_BASE, FLASH_APP_END] 范围内且 2 字节对齐 * 2. F1 擦除以页为单位写入范围内的非空页会被整页擦除 * 3. 写入过程为半字编程WriteAddress 必须 2 字节对齐 * 4. 若擦除或写入失败函数直接返回不保证数据完整性。 */ void WriteFlashData(uint32_t WriteAddress, uint16_t *data, uint32_t length) { FLASH_EraseInitTypeDef FlashEraseInit; // F1 擦除参数结构体 uint32_t PageError 0; // 擦除出错页号 uint32_t page_addr 0; // 页遍历地址 uint32_t endaddr 0; // 写入结束地址不含 /* 参数合法性检查 */ if ((length 0) || (WriteAddress FLASH_APP_BASE) || (WriteAddress FLASH_APP_END) || ((WriteAddress 0x01U) ! 0)) { return; // 非法参数直接返回 } endaddr WriteAddress ((uint32_t)length 1); // 半字个数换算为字节数 if (endaddr FLASH_APP_END) { return; // 写入范围越界 } HAL_FLASH_Unlock(); // 解锁 Flash /* 第一步按页擦除凡是写入范围内有非 0xFF 数据的页先整页擦除 */ for (page_addr WriteAddress ~(FLASH_PAGE_SIZE - 1); page_addr endaddr; page_addr FLASH_PAGE_SIZE) { if (STMFLASH_CheckPageErased(page_addr) 0) // 该页有数据需要擦除 { FlashEraseInit.TypeErase FLASH_TYPEERASE_PAGES; // 页擦除方式 FlashEraseInit.PageAddress page_addr; // 页起始地址 FlashEraseInit.NbPages 1; // 一次擦除一页 if (HAL_FLASHEx_Erase(FlashEraseInit, PageError) ! HAL_OK) { break; // 擦除失败中止 } } } /* 第二步半字编程写入 */ while (WriteAddress endaddr) { if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, WriteAddress, (uint64_t)(uint16_t)(*data)) ! HAL_OK) { break; // 写入异常中止 } WriteAddress 2; // 地址前进一个半字 data; // 数据指针后移 } HAL_FLASH_Lock(); // 上锁 Flash }9.然后写app程序别忘了开头设置VTOR及中断10.至于bin文件怎么转看我另一篇博客11.先对bootloader程序进行验证即3个程序通过keil烧录看bootloader能不能跳转后关闭bootloader的调试到另一个程序通过另一个程序的keil调试进行查看(借鉴于ZYNQ 的调试思路,可能有点抽象却行之有效)12.通过STM32CubeProgrammer清空flash防止前一步骤的影响将bin文件通过串口接收flash写入对应app设置的地址之后尝试调试(还是可以采用9的方法一招鲜吃遍天)13.测试通过之后就是通过改变flash烧录地址决定对哪个分区烧录或运行哪个设计个按键类似的交互即可这我就不多说了