什么是IAP?

IAP(In-Application Programming)是一种在应用内编程的技术,允许设备在运行主程序时,通过通信接口(如UART、USB、CAN)接收新固件并写入Flash,实现现场升级。与ISP相比,IAP不需要外部编程器,只需在启动时运行一段Bootloader引导程序。

STM32的IAP流程通常是:设备上电后先运行Bootloader(位于Flash起始地址),它检测是否有升级请求,如果有则接收固件数据并写入指定Flash区域;如果没有则直接跳转到用户应用程序(APP)。这样,系统的升级灵活性和可维护性大大增强。

内存分区设计

以STM32F103系列为例,其Flash起始地址为0x08000000。我们规划Bootloader占用前16KB(地址0x08000000~0x08003FFF),APP放在0x08004000起始。也可根据实际需求调整,但需注意扇区大小对齐。

中断向量表重映射

APP必须将中断向量表偏移到新地址,否则中断无法响应。对于Cortex-M3/M4,设置SCB->VTOR寄存器(例如STM32F1/F4),或者使用NVIC_SetVectorTable函数(STM32标准库)。

在APP工程中的system_stm32f10x.cmain.c早期调用:

SCB->VTOR = 0x08004000;

同时,修改APP工程的链接脚本(.ld或散列文件),将FLASH起始地址改为0x08004000,并保留适当大小。

Bootloader代码实现

Bootloader的主要任务:

  1. 初始化系统时钟、串口等外设。
  2. 检测是否进入升级模式(按键、串口命令等)。
  3. 接收固件包,写入Flash。
  4. 校验无误后跳转到APP。

接收协议

这里使用简单的帧格式:[0xAA][0x55][长度][数据][CRC8]。每包数据最大256字节。Bootloader根据帧头识别数据包,接收完成后再进行擦写。

Flash写入操作

以STM32F1为例,写入前必须擦除扇区(注意:标准库中F1没有扇区概念,页面大小为1KB,擦除按页)。使用FLASH_ProgramHalfWord写入半字。

跳转函数实现

跳转的核心代码:

typedef void (*pFunction)(void);
void JumpToApp(uint32_t app_addr)
{
    uint32_t app_jump_addr = *(volatile uint32_t*)(app_addr + 4);
    pFunction jumpF = (pFunction)app_jump_addr;
    __disable_irq();
    SCB->VTOR = app_addr;  // 重设向量表
    __set_MSP(*(volatile uint32_t*)app_addr);  // 设置栈指针
    jumpF();  // 跳转
}

完整代码示例

以下是一个简化的Bootloader核心代码,演示串口接收和升级过程。

#include "stm32f10x.h"
// 假设串口1接收中断

void Flash_WriteBuf(uint32_t addr, uint8_t *buf, uint16_t len)
{
    FLASH_Unlock();
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
    // 擦除页
    FLASH_ErasePage(addr);
    for (int i = 0; i < len; i += 2) {
        uint16_t halfword = buf[i] | (buf[i+1] << 8);
        FLASH_ProgramHalfWord(addr + i, halfword);
    }
    FLASH_Lock();
}

int main(void)
{
    SystemInit();
    UART_Init();
    if (CheckUpgradeCommand()) {  // 判断是否进入IAP
        uint8_t buf[256];
        uint32_t write_addr = APP_ADDR;
        while (1) {
            // 等待接收包,收到一帧后处理
            uint16_t len = UART_ReceivePacket(buf);
            if (len > 0) {
                Flash_WriteBuf(write_addr, buf, len);
                write_addr += len;
                // 发送ACK
            }
            if (LastPacket) break;
        }
        // 校验完整固件(可选)
    }
    JumpToApp(APP_ADDR);
}

说明:实际项目中需要加入超时、重传、CRC校验等机制,此处仅为演示。

配置步骤

    1. Bootloader工程:设置Flash起始为0x08000000,大小16K。编译生成hex/bin。
    1. APP工程:设置起始地址为0x08004000,并在代码开头设置SCB->VTOR。编译生成bin文件(如fromelf --bin --output=app.bin)。
    1. 烧录Bootloader:用ST-Link/J-Link烧录到Flash。
    1. 执行升级:通过串口发送APP的bin文件数据,Bootloader接收并写入对应内存后跳转。

注意事项

  • 向量表偏移:必须在APP代码最早期设置,且地址需对齐到0x100(某些系列)。
  • 硬件设计:确保Bootloader和APP共用同一个时钟树,注意外设冲突。
  • 串口升级协议:一定要有校验和应答,防止传输错误导致变砖。
  • 看门狗:如果开启看门狗,在Bootloader中记得及时喂狗。
  • Flash擦写时序:写入时不能有其他中断打扰,必要时关闭全局中断。

通过IAP,STM32设备可以实现远程升级,大幅降低维护成本。希望本文能帮助你快速搭建属于自己的Bootloader。