Bootloader与IAP

次浏览

概述

Bootloader是嵌入式系统启动时运行的第一段程序,负责初始化硬件并加载主应用程序。IAP(In-Application Programming)是一种在线升级技术,允许设备在运行状态下更新固件,无需外部编程器。


一、Bootloader基础

1.1 什么是Bootloader

Bootloader是一段固化在微控制器Flash起始地址的程序,主要功能:

功能 描述
硬件初始化 时钟、GPIO、外设等
应用程序加载 跳转到主程序
固件升级 接收新固件并写入Flash
安全验证 校验固件完整性
恢复模式 异常情况下的恢复机制

1.2 启动流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
上电/复位
    
    
┌─────────────────┐
   Bootloader    
  (0x08000000)   
└────────┬────────┘
         
         ├──── 检测升级模式?
                    
                    
              ┌──────────┐
               接收新固件 
               写入Flash  
              └──────────┘
         
         
┌─────────────────┐
   应用程序      
  (0x08008000)  
└─────────────────┘

1.3 内存布局

典型STM32内存分配:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Flash: 0x08000000 - 0x0807FFFF (512KB)
┌──────────────────────────────┐ 0x08000000
         Bootloader              16KB
        (Sector 0)            
├──────────────────────────────┤ 0x08004000
         App标志区                4KB
├──────────────────────────────┤ 0x08005000
         应用程序                492KB
                              
├──────────────────────────────┤
         参数存储区                4KB
└──────────────────────────────┘ 0x0807FFFF

二、Bootloader设计

2.1 核心功能模块

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
┌────────────────────────────────────────────┐
               Bootloader                   
├────────────────────────────────────────────┤
  ┌──────────┐  ┌──────────┐  ┌──────────┐  
   硬件初始化    通信接口     Flash操作   
               UART/USB    //    
  └──────────┘  └──────────┘  └──────────┘  
                                            
  ┌──────────┐  ┌──────────┐  ┌──────────┐  
   协议解析     固件校验     跳转管理    
   YMODEM等    CRC/MD5     MSP/VTOR   
  └──────────┘  └──────────┘  └──────────┘  
└────────────────────────────────────────────┘

2.2 启动检测逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
typedef struct {
    uint32_t magic;        // 魔数,标识有效App
    uint32_t app_addr;     // App起始地址
    uint32_t app_size;     // App大小
    uint32_t app_crc;      // App校验值
    uint32_t boot_flag;    // 启动标志(用于触发升级)
} app_header_t;

#define APP_MAGIC      0x41505031  // "APP1"
#define BOOT_FLAG_UPG  0x55504752  // "UPGR"
#define APP_ADDR       0x08005000
#define HEADER_ADDR    0x08004000

bool check_app_valid(void) {
    app_header_t *header = (app_header_t *)HEADER_ADDR;
    
    // 检查魔数
    if (header->magic != APP_MAGIC) {
        return false;
    }
    
    // 检查栈指针是否在RAM范围内
    uint32_t sp = *(uint32_t *)header->app_addr;
    if ((sp < 0x20000000) || (sp > 0x20020000)) {
        return false;
    }
    
    // 检查复位向量是否在Flash范围内
    uint32_t pc = *(uint32_t *)(header->app_addr + 4);
    if ((pc < APP_ADDR) || (pc > 0x0807FFFF)) {
        return false;
    }
    
    return true;
}

2.3 跳转到应用程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
typedef void (*app_func_t)(void);

void jump_to_app(uint32_t app_addr) {
    uint32_t app_stack;
    app_func_t app_entry;
    
    // 获取栈指针和入口地址
    app_stack = *(uint32_t *)app_addr;
    app_entry = (app_func_t)(*(uint32_t *)(app_addr + 4));
    
    // 关闭所有中断
    __disable_irq();
    
    // 清除所有中断挂起标志
    for (int i = 0; i < 8; i++) {
        NVIC->ICER[i] = 0xFFFFFFFF;
        NVIC->ICPR[i] = 0xFFFFFFFF;
    }
    
    // 复位所有外设
    RCC->AHB1RSTR = 0xFFFFFFFF;
    RCC->AHB2RSTR = 0xFFFFFFFF;
    RCC->APB1RSTR = 0xFFFFFFFF;
    RCC->APB2RSTR = 0xFFFFFFFF;
    
    // 设置VTOR(Cortex-M3/M4/M7)
    SCB->VTOR = app_addr;
    
    // 设置栈指针
    __set_MSP(app_stack);
    
    // 跳转到App
    app_entry();
    
    // 不应该执行到这里
    while(1);
}

2.4 完整Bootloader流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int main(void) {
    // 硬件初始化
    HAL_Init();
    SystemClock_Config();
    
    // 读取App头信息
    app_header_t *header = (app_header_t *)HEADER_ADDR;
    
    // 检查是否需要升级
    if (header->boot_flag == BOOT_FLAG_UPG) {
        // 进入升级模式
        bootloader_upgrade_mode();
    }
    
    // 检查按键是否按下(强制升级)
    if (check_upgrade_key()) {
        bootloader_upgrade_mode();
    }
    
    // 检查App是否有效
    if (!check_app_valid()) {
        // App无效,进入升级模式
        bootloader_upgrade_mode();
    }
    
    // 跳转到App
    jump_to_app(header->app_addr);
    
    while(1);
}

三、IAP升级实现

3.1 IAP升级方式

方式 描述 优点 缺点
UART 串口传输 简单,成本低 速度慢
USB USB传输 速度快 复杂
SD卡 从SD卡读取 离线升级 需要SD卡槽
网络 TCP/HTTP下载 远程升级 需要网络模块
CAN CAN总线传输 工业场景 需要CAN接口

3.2 升级协议设计

自定义协议帧格式:

1
2
3
4
┌────────┬────────┬────────┬────────┬────────┬────────┐
│ 帧头   │ 命令   │ 长度   │ 序号   │ 数据   │ 校验   │
│ 2字节  │ 1字节  │ 2字节  │ 2字节  │ N字节  │ 2字节  │
└────────┴────────┴────────┴────────┴────────┴────────┘

命令定义:

命令码 名称 描述
0x01 CMD_HANDSHAKE 握手请求
0x02 CMD_ERASE 擦除Flash
0x03 CMD_WRITE 写入数据
0x04 CMD_VERIFY 校验固件
0x05 CMD_JUMP 跳转运行
0x06 CMD_ABORT 中止升级

3.3 协议实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#define FRAME_HEAD   0xAA55
#define CRC16_INIT   0xFFFF

typedef struct {
    uint16_t head;
    uint8_t  cmd;
    uint16_t length;
    uint16_t seq;
    uint8_t  data[256];
    uint16_t crc;
} __packed frame_t;

typedef enum {
    STATE_IDLE,
    STATE_RECEIVING,
    STATE_VERIFY,
    STATE_DONE,
    STATE_ERROR
} upgrade_state_t;

static upgrade_state_t state = STATE_IDLE;
static uint32_t write_addr = APP_ADDR;

// 处理接收数据
void process_frame(frame_t *frame) {
    uint16_t crc = calculate_crc16((uint8_t *)frame, 
                                    sizeof(frame_t) - 2);
    
    if (frame->crc != crc) {
        send_response(frame->cmd, 0xFF);  // CRC错误
        return;
    }
    
    switch (frame->cmd) {
        case CMD_HANDSHAKE:
            handle_handshake(frame);
            break;
        case CMD_ERASE:
            handle_erase(frame);
            break;
        case CMD_WRITE:
            handle_write(frame);
            break;
        case CMD_VERIFY:
            handle_verify(frame);
            break;
        case CMD_JUMP:
            handle_jump(frame);
            break;
        default:
            send_response(frame->cmd, 0xFE);  // 未知命令
            break;
    }
}

// 擦除处理
void handle_erase(frame_t *frame) {
    uint32_t size = *(uint32_t *)frame->data;
    uint32_t pages = (size + FLASH_PAGE_SIZE - 1) / FLASH_PAGE_SIZE;
    
    HAL_FLASH_Unlock();
    
    for (uint32_t i = 0; i < pages; i++) {
        if (flash_erase_page(APP_ADDR + i * FLASH_PAGE_SIZE) != HAL_OK) {
            send_response(CMD_ERASE, 0x01);
            HAL_FLASH_Lock();
            return;
        }
    }
    
    HAL_FLASH_Lock();
    write_addr = APP_ADDR;
    send_response(CMD_ERASE, 0x00);
}

// 写入处理
void handle_write(frame_t *frame) {
    uint16_t len = frame->length;
    
    HAL_FLASH_Unlock();
    
    for (uint16_t i = 0; i < len; i += 4) {
        uint32_t data = *(uint32_t *)&frame->data[i];
        if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, 
                              write_addr + i, data) != HAL_OK) {
            send_response(CMD_WRITE, 0x01);
            HAL_FLASH_Lock();
            return;
        }
    }
    
    HAL_FLASH_Lock();
    write_addr += len;
    send_response(CMD_WRITE, 0x00);
}

3.4 YMODEM协议(串口升级)

YMODEM是常用的串口文件传输协议:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// YMODEM帧格式
#define SOH  0x01  // 128字节数据
#define STX  0x02  // 1024字节数据
#define EOT  0x04  // 传输结束
#define ACK  0x06  // 确认
#define NAK  0x15  // 否认
#define CAN  0x18  // 取消

// 接收YMODEM数据包
int ymodem_receive(uint8_t *buf, int *size) {
    uint8_t packet[1024 + 5];
    int packet_num = 0;
    int total_size = 0;
    
    // 发送'C'请求CRC校验
    send_byte('C');
    
    while (1) {
        int len = receive_packet(packet);
        if (len < 0) continue;
        
        switch (packet[0]) {
            case SOH:  // 128字节
            case STX:  // 1024字节
                if (check_crc(packet)) {
                    // 第0包包含文件名和大小
                    if (packet[1] == 0x00 && packet_num == 0) {
                        parse_filename_size(packet, buf, size);
                        send_byte(ACK);
                        send_byte('C');
                        packet_num++;
                    } else {
                        // 数据包
                        memcpy(buf + total_size, packet + 3, len);
                        total_size += len - 5;
                        send_byte(ACK);
                    }
                } else {
                    send_byte(NAK);
                }
                break;
                
            case EOT:  // 结束
                send_byte(ACK);
                return total_size;
                
            case CAN:  // 取消
                return -1;
        }
    }
}

四、Flash操作

4.1 STM32 Flash编程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Flash解锁
void flash_unlock(void) {
    FLASH->KEYR = 0x45670123;
    FLASH->KEYR = 0xCDEF89AB;
}

// Flash上锁
void flash_lock(void) {
    FLASH->CR |= FLASH_CR_LOCK;
}

// 擦除页
HAL_StatusTypeDef flash_erase_page(uint32_t addr) {
    // 等待操作完成
    while (FLASH->SR & FLASH_SR_BSY);
    
    // 解锁
    flash_unlock();
    
    // 设置页擦除
    FLASH->CR |= FLASH_CR_PER;
    FLASH->AR = addr;
    FLASH->CR |= FLASH_CR_STRT;
    
    // 等待完成
    while (FLASH->SR & FLASH_SR_BSY);
    
    // 清除标志
    FLASH->SR |= FLASH_SR_EOP;
    FLASH->CR &= ~FLASH_CR_PER;
    
    flash_lock();
    
    return HAL_OK;
}

// 写入数据(按字)
HAL_StatusTypeDef flash_write_word(uint32_t addr, uint32_t data) {
    while (FLASH->SR & FLASH_SR_BSY);
    
    flash_unlock();
    
    FLASH->CR |= FLASH_CR_PG;
    *(__IO uint32_t *)addr = data;
    
    while (FLASH->SR & FLASH_SR_BSY);
    
    FLASH->SR |= FLASH_SR_EOP;
    FLASH->CR &= ~FLASH_CR_PG;
    
    flash_lock();
    
    return HAL_OK;
}

4.2 Flash写入优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 按页写入(更高效)
HAL_StatusTypeDef flash_write_page(uint32_t addr, 
                                     uint8_t *data, 
                                     uint16_t len) {
    flash_unlock();
    
    // 按字(4字节)写入
    for (uint16_t i = 0; i < len; i += 4) {
        uint32_t word = *(uint32_t *)&data[i];
        if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, 
                              addr + i, word) != HAL_OK) {
            flash_lock();
            return HAL_ERROR;
        }
    }
    
    flash_lock();
    return HAL_OK;
}

五、固件校验

5.1 CRC校验

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// CRC32计算
uint32_t crc32_table[256];

void crc32_init(void) {
    for (uint32_t i = 0; i < 256; i++) {
        uint32_t crc = i;
        for (int j = 0; j < 8; j++) {
            if (crc & 1)
                crc = (crc >> 1) ^ 0xEDB88320;
            else
                crc >>= 1;
        }
        crc32_table[i] = crc;
    }
}

uint32_t crc32_calc(uint8_t *data, uint32_t len) {
    uint32_t crc = 0xFFFFFFFF;
    for (uint32_t i = 0; i < len; i++) {
        crc = crc32_table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
    }
    return crc ^ 0xFFFFFFFF;
}

// 校验固件
bool verify_firmware(uint32_t addr, uint32_t size, uint32_t expected_crc) {
    uint32_t calc_crc = crc32_calc((uint8_t *)addr, size);
    return (calc_crc == expected_crc);
}

5.2 数字签名验证(安全升级)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include "mbedtls/rsa.h"
#include "mbedtls/sha256.h"

bool verify_signature(uint8_t *firmware, uint32_t size,
                      uint8_t *signature, uint32_t sig_len) {
    uint8_t hash[32];
    mbedtls_sha256_context sha_ctx;
    mbedtls_rsa_context rsa_ctx;
    
    // 计算固件哈希
    mbedtls_sha256_init(&sha_ctx);
    mbedtls_sha256_starts(&sha_ctx, 0);
    mbedtls_sha256_update(&sha_ctx, firmware, size);
    mbedtls_sha256_finish(&sha_ctx, hash);
    
    // 加载公钥
    mbedtls_rsa_init(&rsa_ctx);
    // ... 加载公钥 ...
    
    // 验证签名
    int ret = mbedtls_rsa_pkcs1_verify(&rsa_ctx, 
                                         MBEDTLS_MD_SHA256, 
                                         32, hash, signature);
    
    mbedtls_rsa_free(&rsa_ctx);
    
    return (ret == 0);
}

六、应用程序适配

6.1 链接脚本修改

STM32 GCC链接脚本(ld文件):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/* 原始配置 */
MEMORY {
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
    RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}

/* IAP配置 - App起始地址改变 */
MEMORY {
    FLASH (rx) : ORIGIN = 0x08005000, LENGTH = 492K
    RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}

6.2 启动代码修改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 在SystemInit中设置VTOR
void SystemInit(void) {
    // 设置中断向量表偏移
    SCB->VTOR = 0x08005000;  // App起始地址
    
    // ... 其他初始化 ...
}

// 或在main函数开头设置
int main(void) {
    SCB->VTOR = 0x08005000;
    
    HAL_Init();
    SystemClock_Config();
    
    // ...
}

6.3 Keil配置

在Keil MDK中配置IAP:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Options for Target -> Target:
  - ROM (IROM1): Start: 0x08005000, Size: 0x0007B000

Options for Target -> Debug:
  - Initialization File: 
    FUNC void Setup(void) {
      SP = _RDWORD(0x08005000);
      PC = _RDWORD(0x08005004);
    }
    Setup();

七、实际项目示例

7.1 完整Bootloader框架

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// main.c - Bootloader
int main(void) {
    // HAL初始化
    HAL_Init();
    SystemClock_Config();
    
    // 外设初始化
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    MX_FLASH_Init();
    
    // 启动指示
    LED_ON();
    HAL_Delay(100);
    LED_OFF();
    
    // 读取App头信息
    app_header_t *header = (app_header_t *)HEADER_ADDR;
    
    // 检查升级请求标志
    if (header->boot_flag == BOOT_FLAG_UPG) {
        printf("Enter upgrade mode (flag)\n");
        goto upgrade_mode;
    }
    
    // 检查按键(强制升级)
    if (HAL_GPIO_ReadPin(KEY_GPIO, KEY_PIN) == GPIO_PIN_RESET) {
        printf("Enter upgrade mode (key)\n");
        goto upgrade_mode;
    }
    
    // 检查App有效性
    if (!check_app_valid()) {
        printf("App invalid, enter upgrade mode\n");
        goto upgrade_mode;
    }
    
    // 校验App
    if (!verify_firmware(header->app_addr, header->app_size, 
                          header->app_crc)) {
        printf("CRC check failed\n");
        goto upgrade_mode;
    }
    
    // 关闭用到的外设
    HAL_UART_DeInit(&huart1);
    HAL_RCC_DeInit();
    
    // 跳转到App
    printf("Jump to app @ 0x%08X\n", header->app_addr);
    jump_to_app(header->app_addr);
    
upgrade_mode:
    // 升级模式主循环
    bootloader_main();
    
    while (1);
}

7.2 App中触发升级

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// app_upgrade.c - 在App中触发升级
#include "app_upgrade.h"

#define BOOT_FLAG_ADDR  0x08004000
#define BOOT_FLAG_UPG   0x55504752

void trigger_upgrade(void) {
    // 清除标志区
    HAL_FLASH_Unlock();
    flash_erase_page(BOOT_FLAG_ADDR);
    
    // 写入升级标志
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, 
                      BOOT_FLAG_ADDR + 16, BOOT_FLAG_UPG);
    
    HAL_FLASH_Lock();
    
    // 复位进入Bootloader
    NVIC_SystemReset();
}

// 接收到升级命令
void on_upgrade_command(void) {
    printf("Preparing for upgrade...\n");
    trigger_upgrade();
}

八、注意事项

8.1 中断向量表

问题: Bootloader和App都使用中断,需要正确设置VTOR。

解决:

  • Bootloader中使用默认VTOR(0x08000000)
  • App启动时设置SCB->VTOR = APP_ADDR

8.2 栈指针

问题: 跳转前需要正确设置栈指针。

解决:

1
__set_MSP(*(uint32_t *)APP_ADDR);

8.3 时钟配置

问题: Bootloader配置的时钟可能影响App。

解决:

  • 跳转前调用HAL_RCC_DeInit()
  • App重新配置时钟

8.4 Flash保护

问题: 意外写入导致Bootloader损坏。

解决:

  • 启用Flash写保护
  • Bootloader区域设置为只读
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 启用Flash保护(HAL库)
void enable_flash_protection(void) {
    FLASH_OBProgramInitTypeDef ob_config;
    
    HAL_FLASHEx_OBGetConfig(&ob_config);
    
    ob_config.WRPPage = 0x0001;  // 保护前16KB
    ob_config.WRPState = OB_WRPSTATE_ENABLE;
    
    HAL_FLASHEx_OBProgram(&ob_config);
    HAL_FLASH_OB_Launch();
}

九、调试技巧

9.1 串口调试

1
2
3
4
5
6
7
#define DEBUG_PRINT(fmt, ...) \
    printf("[%s:%d] " fmt "\n", __func__, __LINE__, ##__VA_ARGS__)

// 跟踪升级过程
DEBUG_PRINT("Erase flash: addr=0x%08X, pages=%d", addr, pages);
DEBUG_PRINT("Write flash: addr=0x%08X, len=%d", write_addr, len);
DEBUG_PRINT("Verify CRC: calc=0x%08X, expected=0x%08X", calc, expected);

9.2 LED状态指示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void set_upgrade_status(status_t status) {
    switch (status) {
        case STATUS_IDLE:
            LED_OFF();
            break;
        case STATUS_RECEIVING:
            LED_TOGGLE();  // 快闪
            break;
        case STATUS_WRITING:
            LED_ON();
            break;
        case STATUS_ERROR:
            // 错误闪烁模式
            for (int i = 0; i < 3; i++) {
                LED_ON(); HAL_Delay(100);
                LED_OFF(); HAL_Delay(100);
            }
            break;
    }
}

总结

组件 功能 注意事项
Bootloader 启动引导、升级 放置在Flash起始位置
IAP协议 固件传输 选择合适的协议
Flash操作 擦写存储 注意保护和校验
跳转机制 进入App 设置VTOR和栈指针
固件校验 完整性验证 CRC或数字签名

设计原则:

  • Bootloader尽可能简单稳定
  • 支持异常恢复机制
  • 完善的错误处理
  • 详细的日志记录

参考资料

  • STM32 Programming Manual (PM0075)
  • AN3155: USART protocol used in the STM32 bootloader
  • YMODEM Protocol Specification
  • 《嵌入式系统Bootloader开发》

🎯 Bootloader和IAP是嵌入式产品远程升级的基础,掌握它们对产品维护至关重要!

使用 Hugo 构建
主题 StackJimmy 设计