Files
bootloader/docs/PORTING.md
rovina d2b8bd7940 Initial commit: STM32 Bootloader extension module
Add a production-ready, educational bootloader extension for STM32 MCUs.

Core features:
- Custom binary protocol with CRC16/CRC32 verification
- AES-256 encryption for firmware security
- Dual-bank firmware management with rollback support
- Version management and firmware validation
- Modular architecture with BSP abstraction layer

Project structure:
- include/: Header files (bootloader.h, bsp_flash.h, bsp_uart.h)
- src/: Core implementation (bootloader, protocol, crypto, firmware manager)
- port/: MCU-specific adaptation layer (STM32F4xx)
- docs/: Documentation (integration guide, porting guide)

Supported platforms:
- STM32F4xx (primary)
- STM32F1xx (via porting)

Quick start:
1. Copy extension module to project
2. Configure bootloader_config.h
3. Modify linker script for APP_BASE_ADDR
4. Build and flash bootloader to 0x08000000

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 15:03:51 +08:00

18 KiB
Raw Permalink Blame History

Bootloader移植指南

本文档详细介绍如何将Bootloader移植到不同的STM32系列或其他MCU平台。


目录


移植概述

需要移植的内容

Bootloader采用分层设计只有以下部分需要移植

模块 文件 说明
Flash驱动 port/<mcu>/flash_port.c Flash擦写读操作
UART驱动 port/<mcu>/uart_port.c 串口收发操作

其他模块(无需移植):

  • 协议处理模块
  • AES加密模块
  • CRC校验模块
  • 固件管理模块
  • 跳转逻辑模块

BSP抽象层接口

移植前理解BSP抽象层定义的接口

Flash接口 (bsp_flash.h)

typedef struct {
    int (*init)(void);                                    // 初始化Flash
    int (*erase)(uint32_t addr, uint32_t size);          // 擦除指定区域
    int (*write)(uint32_t addr, const uint8_t *data, uint32_t len); // 写入数据
    int (*read)(uint32_t addr, uint8_t *data, uint32_t len);        // 读取数据
    bool (*verify)(uint32_t addr, const uint8_t *data, uint32_t len); // 校验数据
} bsp_flash_ops_t;

UART接口 (bsp_uart.h)

typedef struct {
    int (*init)(uint32_t baudrate);                       // 初始化串口
    int (*send)(const uint8_t *data, uint32_t len);       // 发送数据
    int (*recv)(uint8_t *data, uint32_t len, uint32_t timeout); // 接收数据
    void (*deinit)(void);                                 // 关闭串口
} bsp_uart_ops_t;

移植步骤

步骤1: 创建移植目录

cd extensions/bootloader/port

# 复制移植模板
cp -r port_template <your_mcu>
# 例如: cp -r port_template stm32f1xx

步骤2: 实现Flash驱动

编辑 port/<your_mcu>/flash_port.c,实现以下函数:

int flash_port_init(void) {
    // 解锁Flash
    // 配置Flash参数
    // 返回成功/失败
}

int flash_port_erase(uint32_t addr, uint32_t size) {
    // 计算需要擦除的扇区
    // 逐个擦除扇区
    // 返回成功/失败
}

int flash_port_write(uint32_t addr, const uint8_t *data, uint32_t len) {
    // 写入数据到Flash
    // 注意地址对齐和数据长度
    // 返回成功/失败
}

int flash_port_read(uint32_t addr, uint8_t *data, uint32_t len) {
    // 从Flash读取数据
    // 可以直接内存拷贝
    // 返回成功/失败
}

bool flash_port_verify(uint32_t addr, const uint8_t *data, uint32_t len) {
    // 读取Flash数据
    // 与原始数据比较
    // 返回是否一致
}

步骤3: 实现UART驱动

编辑 port/<your_mcu>/uart_port.c,实现以下函数:

int uart_port_init(uint32_t baudrate) {
    // 配置UART参数: 波特率、数据位、停止位、校验位
    // 初始化UART硬件
    // 开启接收中断或DMA
    // 返回成功/失败
}

int uart_port_send(const uint8_t *data, uint32_t len) {
    // 发送数据
    // 可以使用HAL_UART_Transmit或自定义实现
    // 等待发送完成
    // 返回成功/失败
}

int uart_port_recv(uint8_t *data, uint32_t len, uint32_t timeout) {
    // 接收数据
    // 可以使用HAL_UART_Receive或中断缓冲
    // 支持超时机制
    // 返回接收的字节数或错误
}

void uart_port_deinit(void) {
    // 关闭UART
    // 清除缓冲区
}

步骤4: 注册BSP操作

port/<your_mcu>/port.c 中注册:

#include "bsp_flash.h"
#include "bsp_uart.h"

// Flash操作实例
static const bsp_flash_ops_t flash_ops = {
    .init   = flash_port_init,
    .erase  = flash_port_erase,
    .write  = flash_port_write,
    .read   = flash_port_read,
    .verify = flash_port_verify,
};

// UART操作实例
static const bsp_uart_ops_t uart_ops = {
    .init   = uart_port_init,
    .send   = uart_port_send,
    .recv   = uart_port_recv,
    .deinit = uart_port_deinit,
};

// 初始化移植层
int port_init(void) {
    bsp_flash_register(&flash_ops);
    bsp_uart_register(&uart_ops);
    return 0;
}

步骤5: 修改CMakeLists.txt

编辑 extensions/bootloader/CMakeLists.txt:

# 设置MCU系列
set(MCU_FAMILY "<your_mcu>" CACHE STRING "MCU family")

# 添加移植层源文件
target_sources(bootloader PRIVATE
    port/${MCU_FAMILY}/flash_port.c
    port/${MCU_FAMILY}/uart_port.c
)

# 添加头文件路径
target_include_directories(bootloader PUBLIC
    port/${MCU_FAMILY}
)

Flash驱动移植

STM32 Flash特性

不同STM32系列的Flash特性差异

系列 Flash大小 扇区大小 页大小
STM32F1 最大512KB 1KB/2KB/4KB -
STM32F4 最大2MB 16KB/64KB/128KB -
STM32F7 最大2MB 32KB/128KB/256KB -
STM32L4 最大512KB 2KB 256B

STM32F4 Flash详解

STM32F407ZGTx的Flash布局

扇区  地址范围         大小     用途
─────────────────────────────────────
0     0x08000000      16KB     Bootloader
1     0x08004000      16KB     Bootloader
2     0x08008000      16KB     App开始
3     0x0800C000      16KB     App
4     0x08010000      64KB     App
5     0x08020000      128KB    App
6     0x08040000      128KB    App
7     0x08060000      128KB    App
8-11  0x08080000+     128KB    Reserved

Flash擦除实现

int flash_port_erase(uint32_t addr, uint32_t size) {
    HAL_FLASH_Unlock();
    
    // 计算起始和结束扇区
    uint32_t start_sector = flash_get_sector(addr);
    uint32_t end_sector = flash_get_sector(addr + size - 1);
    
    // 清除错误标志
    FLASH->SR = FLASH_FLAG_EOP | FLASH_FLAG_OPERR | 
                FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR |
                FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR;
    
    // 逐个擦除扇区
    for (uint32_t sector = start_sector; sector <= end_sector; sector++) {
        FLASH_Erase_Sector(sector, FLASH_VOLTAGE_RANGE_3);
        
        // 等待擦除完成
        while (FLASH->SR & FLASH_FLAG_BSY);
        
        // 检查错误
        if (FLASH->SR & FLASH_FLAG_EOP) {
            FLASH->SR = FLASH_FLAG_EOP;
        }
    }
    
    HAL_FLASH_Lock();
    return 0;
}

// 获取扇区号 (STM32F4xx)
static uint32_t flash_get_sector(uint32_t addr) {
    addr -= FLASH_BASE;  // 0x08000000
    
    if (addr < 0x4000) return FLASH_SECTOR_0;       // 16KB
    else if (addr < 0x8000) return FLASH_SECTOR_1;  // 16KB
    else if (addr < 0xC000) return FLASH_SECTOR_2;  // 16KB
    else if (addr < 0x10000) return FLASH_SECTOR_3; // 16KB
    else if (addr < 0x20000) return FLASH_SECTOR_4; // 64KB
    else if (addr < 0x40000) return FLASH_SECTOR_5; // 128KB
    else if (addr < 0x60000) return FLASH_SECTOR_6; // 128KB
    else if (addr < 0x80000) return FLASH_SECTOR_7; // 128KB
    else return FLASH_SECTOR_8;                     // 128KB+
}

Flash写入实现

STM32F4只能按32位写入

int flash_port_write(uint32_t addr, const uint8_t *data, uint32_t len) {
    HAL_FLASH_Unlock();
    
    uint32_t written = 0;
    
    // 按字(4字节)写入
    while (written < len) {
        uint32_t word_data;
        
        // 构造32位数据
        if (len - written >= 4) {
            word_data = *((uint32_t*)(data + written));
        } else {
            // 处理不足4字节的情况
            word_data = 0;
            for (uint32_t i = 0; i < len - written; i++) {
                word_data |= (data[written + i] << (i * 8));
            }
        }
        
        // 写入Flash
        if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + written, word_data) != HAL_OK) {
            HAL_FLASH_Lock();
            return -1;
        }
        
        written += 4;
    }
    
    HAL_FLASH_Lock();
    return 0;
}

Flash读取实现

Flash读取可以直接内存访问

int flash_port_read(uint32_t addr, uint8_t *data, uint32_t len) {
    // Flash可直接内存读取
    memcpy(data, (uint8_t*)addr, len);
    return 0;
}

UART驱动移植

UART配置参数

根据MCU配置UART

int uart_port_init(uint32_t baudrate) {
    UART_HandleTypeDef huart;
    
    huart.Instance = USART1;  // 或其他UART实例
    huart.Init.BaudRate = baudrate;
    huart.Init.WordLength = UART_WORDLENGTH_8B;
    huart.Init.StopBits = UART_STOPBITS_1;
    huart.Init.Parity = UART_PARITY_NONE;
    huart.Init.Mode = UART_MODE_TX_RX;
    huart.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart.Init.OverSampling = UART_OVERSAMPLING_16;
    
    if (HAL_UART_Init(&huart) != HAL_OK) {
        return -1;
    }
    
    return 0;
}

UART发送实现

int uart_port_send(const uint8_t *data, uint32_t len) {
    extern UART_HandleTypeDef huart1;  // 声明UART句柄
    
    if (HAL_UART_Transmit(&huart1, data, len, HAL_MAX_DELAY) != HAL_OK) {
        return -1;
    }
    
    return 0;
}

UART接收实现

方法1: 阻塞接收

int uart_port_recv(uint8_t *data, uint32_t len, uint32_t timeout) {
    extern UART_HandleTypeDef huart1;
    
    if (HAL_UART_Receive(&huart1, data, len, timeout) != HAL_OK) {
        return -1;  // 超时或错误
    }
    
    return len;  // 返回接收的字节数
}

方法2: 中断接收(推荐)

// 接收缓冲区
static uint8_t rx_buffer[2048];
static volatile uint32_t rx_index = 0;
static volatile uint32_t rx_len = 0;
static volatile bool rx_complete = false;

int uart_port_recv(uint8_t *data, uint32_t len, uint32_t timeout) {
    extern UART_HandleTypeDef huart1;
    
    rx_index = 0;
    rx_len = len;
    rx_complete = false;
    
    // 启动中断接收
    HAL_UART_Receive_IT(&huart1, rx_buffer, len);
    
    // 等待接收完成或超时
    uint32_t start_tick = HAL_GetTick();
    while (!rx_complete && (HAL_GetTick() - start_tick < timeout)) {
        // 等待
    }
    
    if (rx_complete) {
        memcpy(data, rx_buffer, rx_len);
        return rx_len;
    } else {
        // 超时,停止接收
        HAL_UART_AbortReceive_IT(&huart1);
        return rx_index;  // 返回已接收的字节数
    }
}

// UART接收完成回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == USART1) {
        rx_complete = true;
    }
}

配置适配

MCU特定配置

bootloader_config.h 中添加MCU特定配置

// MCU系列选择
#define MCU_FAMILY_STM32F4XX   1  // STM32F4系列
#define MCU_FAMILY_STM32F1XX   0  // STM32F1系列
#define MCU_FAMILY_STM32F7XX   0  // STM32F7系列
#define MCU_FAMILY_STM32L4XX   0  // STM32L4系列

// MCU时钟配置
#if MCU_FAMILY_STM32F4XX
    #define MCU_FLASH_LATENCY   FLASH_LATENCY_5
    #define MCU_VOLTAGE_RANGE   FLASH_VOLTAGE_RANGE_3
#endif

#if MCU_FAMILY_STM32F1XX
    #define MCU_FLASH_LATENCY   FLASH_LATENCY_2
#endif

// Flash布局 (根据MCU调整)
#if MCU_FAMILY_STM32F4XX
    // STM32F407ZGTx (1MB Flash)
    #define BOOTLOADER_SIZE_KB  32
    #define APP_BASE_ADDR       0x08008000
    #define BANK1_ADDR          0x08008000
    #define BANK2_ADDR          0x08038000
    #define APP_SIZE_MAX        (192 * 1024)
#endif

#if MCU_FAMILY_STM32F1XX
    // STM32F103ZETx (512KB Flash)
    #define BOOTLOADER_SIZE_KB  16
    #define APP_BASE_ADDR       0x08004000
    #define BANK1_ADDR          0x08004000
    #define BANK2_ADDR          0x08024000
    #define APP_SIZE_MAX        (192 * 1024)
#endif

STM32F1移植示例

目录结构

extensions/bootloader/port/stm32f1xx/
├── flash_port.c
├── uart_port.c
└── port.h

flash_port.c (STM32F1xx)

STM32F1的Flash扇区较小且不规则

#include "stm32f1xx_hal.h"
#include "bsp_flash.h"

int flash_port_init(void) {
    HAL_FLASH_Unlock();
    
    // 清除所有错误标志
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPERR);
    
    HAL_FLASH_Lock();
    return 0;
}

int flash_port_erase(uint32_t addr, uint32_t size) {
    HAL_FLASH_Unlock();
    
    uint32_t page_error = 0;
    FLASH_EraseInitTypeDef erase_init;
    
    // STM32F1按页擦除每页1KB或2KB
    erase_init.TypeErase = FLASH_TYPEERASE_PAGES;
    erase_init.PageAddress = addr;
    erase_init.NbPages = (size + FLASH_PAGE_SIZE - 1) / FLASH_PAGE_SIZE;
    
    if (HAL_FLASHEx_Erase(&erase_init, &page_error) != HAL_OK) {
        HAL_FLASH_Lock();
        return -1;
    }
    
    HAL_FLASH_Lock();
    return 0;
}

int flash_port_write(uint32_t addr, const uint8_t *data, uint32_t len) {
    HAL_FLASH_Unlock();
    
    uint32_t written = 0;
    
    // STM32F1按半字(2字节)写入
    while (written < len) {
        uint16_t halfword;
        
        if (len - written >= 2) {
            halfword = *((uint16_t*)(data + written));
        } else {
            halfword = data[written];
        }
        
        if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr + written, halfword) != HAL_OK) {
            HAL_FLASH_Lock();
            return -1;
        }
        
        written += 2;
    }
    
    HAL_FLASH_Lock();
    return 0;
}

int flash_port_read(uint32_t addr, uint8_t *data, uint32_t len) {
    memcpy(data, (uint8_t*)addr, len);
    return 0;
}

bool flash_port_verify(uint32_t addr, const uint8_t *data, uint32_t len) {
    uint8_t flash_data[len];
    flash_port_read(addr, flash_data, len);
    return memcmp(data, flash_data, len) == 0;
}

uart_port.c (STM32F1xx)

#include "stm32f1xx_hal.h"
#include "bsp_uart.h"

extern UART_HandleTypeDef huart1;

int uart_port_init(uint32_t baudrate) {
    // 通常在MX_USART1_UART_Init中已初始化
    // 这里可以重新配置波特率
    huart1.Init.BaudRate = baudrate;
    
    if (HAL_UART_Init(&huart1) != HAL_OK) {
        return -1;
    }
    
    return 0;
}

int uart_port_send(const uint8_t *data, uint32_t len) {
    return (HAL_UART_Transmit(&huart1, data, len, HAL_MAX_DELAY) == HAL_OK) ? 0 : -1;
}

int uart_port_recv(uint8_t *data, uint32_t len, uint32_t timeout) {
    return (HAL_UART_Receive(&huart1, data, len, timeout) == HAL_OK) ? len : -1;
}

void uart_port_deinit(void) {
    HAL_UART_DeInit(&huart1);
}

其他MCU移植

移植到非STM32平台

ESP32移植示例

// flash_port.c (ESP32)
#include "esp_flash.h"

int flash_port_init(void) {
    return esp_flash_init();  // ESP-IDF API
}

int flash_port_erase(uint32_t addr, uint32_t size) {
    return esp_flash_erase_region(addr, size);
}

int flash_port_write(uint32_t addr, const uint8_t *data, uint32_t len) {
    return esp_flash_write(addr, data, len);
}

int flash_port_read(uint32_t addr, uint8_t *data, uint32_t len) {
    return esp_flash_read(addr, data, len);
}

NRF52移植示例

// flash_port.c (NRF52)
#include "nrf_nvmc.h"

int flash_port_init(void) {
    return 0;  // NRF52无需初始化
}

int flash_port_erase(uint32_t addr, uint32_t size) {
    uint32_t page_start = addr / NRF_NVMC_PAGE_SIZE;
    uint32_t page_count = size / NRF_NVMC_PAGE_SIZE;
    
    for (uint32_t i = 0; i < page_count; i++) {
        nrf_nvmc_page_erase(page_start + i);
    }
    return 0;
}

int flash_port_write(uint32_t addr, const uint8_t *data, uint32_t len) {
    nrf_nvmc_write_bytes(addr, data, len);
    return 0;
}

int flash_port_read(uint32_t addr, uint8_t *data, uint32_t len) {
    memcpy(data, (uint8_t*)addr, len);
    return 0;
}

移植验证

验证步骤

1. Flash驱动验证

void test_flash_driver(void) {
    uint8_t test_data[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
    uint8_t read_data[8];
    
    // 初始化
    flash_port_init();
    
    // 擦除
    if (flash_port_erase(0x08008000, 8) == 0) {
        printf("Erase OK\r\n");
    }
    
    // 写入
    if (flash_port_write(0x08008000, test_data, 8) == 0) {
        printf("Write OK\r\n");
    }
    
    // 读取
    if (flash_port_read(0x08008000, read_data, 8) == 0) {
        printf("Read OK\r\n");
        printf("Data: %02X %02X %02X %02X\r\n", 
               read_data[0], read_data[1], read_data[2], read_data[3]);
    }
    
    // 校验
    if (flash_port_verify(0x08008000, test_data, 8)) {
        printf("Verify OK\r\n");
    }
}

2. UART驱动验证

void test_uart_driver(void) {
    uint8_t test_data[] = "Hello Bootloader\r\n";
    uint8_t recv_data[20];
    
    // 初始化
    uart_port_init(115200);
    
    // 发送
    if (uart_port_send(test_data, strlen(test_data)) == 0) {
        printf("Send OK\r\n");
    }
    
    // 接收(回显测试)
    int recv_len = uart_port_recv(recv_data, 20, 1000);
    if (recv_len > 0) {
        printf("Recv: %s\r\n", recv_data);
    }
}

3. 完整功能验证

使用上位机工具发送测试命令:

# 连接设备
./flash_tool --port COM3 --connect

# 发送握手命令
./flash_tool --port COM3 --handshake

# 查询版本
./flash_tool --port COM3 --version

# 测试写入
./flash_tool --port COM3 --test-write

常见问题

Q1: Flash擦除失败

原因:

  • Flash未解锁
  • 地址不在有效范围
  • 擦除范围超出限制

解决:

// 检查Flash状态
void check_flash_status(void) {
    FLASH_TypeDef *flash = FLASH;
    printf("Flash SR: 0x%08X\r\n", flash->SR);
    
    // 清除错误标志
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
}

Q2: Flash写入失败

原因:

  • 写入地址未擦除
  • 地址未对齐
  • 数据长度不匹配

解决:

// 确保写入前已擦除
flash_port_erase(addr, len);

// 确保地址对齐
addr = (addr / 4) * 4;  // STM32F4按字对齐

// 确保数据长度对齐
len = ((len + 3) / 4) * 4;

Q3: UART接收不完整

原因:

  • 波特率不匹配
  • 缓冲区溢出
  • 中断未正确配置

解决:

// 使用中断接收
HAL_UART_Receive_IT(&huart1, rx_buffer, len);

// 或使用DMA接收
HAL_UART_Receive_DMA(&huart1, rx_buffer, len);

Q4: 移植后Bootloader不启动

排查步骤:

  1. 检查链接脚本起始地址
  2. 检查中断向量表位置
  3. 检查时钟配置
  4. 使用调试器单步执行

下一步

完成移植后,请:

  1. 验证所有功能正常
  2. 提交移植代码到项目
  3. 撰写移植文档

祝您移植顺利!