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

822 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Bootloader移植指南
本文档详细介绍如何将Bootloader移植到不同的STM32系列或其他MCU平台。
---
## 目录
- [移植概述](#移植概述)
- [移植步骤](#移植步骤)
- [Flash驱动移植](#flash驱动移植)
- [UART驱动移植](#uart驱动移植)
- [配置适配](#配置适配)
- [STM32F1移植示例](#stm32f1移植示例)
- [其他MCU移植](#其他mcu移植)
- [移植验证](#移植验证)
- [常见问题](#常见问题)
---
## 移植概述
### 需要移植的内容
Bootloader采用分层设计只有以下部分需要移植
| 模块 | 文件 | 说明 |
|------|------|------|
| Flash驱动 | `port/<mcu>/flash_port.c` | Flash擦写读操作 |
| UART驱动 | `port/<mcu>/uart_port.c` | 串口收发操作 |
**其他模块(无需移植):**
- 协议处理模块
- AES加密模块
- CRC校验模块
- 固件管理模块
- 跳转逻辑模块
### BSP抽象层接口
移植前理解BSP抽象层定义的接口
#### Flash接口 (bsp_flash.h)
```c
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)
```c
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: 创建移植目录
```bash
cd extensions/bootloader/port
# 复制移植模板
cp -r port_template <your_mcu>
# 例如: cp -r port_template stm32f1xx
```
### 步骤2: 实现Flash驱动
编辑 `port/<your_mcu>/flash_port.c`,实现以下函数:
```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`,实现以下函数:
```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` 中注册:
```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`:
```cmake
# 设置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擦除实现
```c
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位写入
```c
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读取可以直接内存访问
```c
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
```c
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发送实现
```c
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: 阻塞接收
```c
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: 中断接收(推荐)
```c
// 接收缓冲区
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特定配置
```c
// 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扇区较小且不规则
```c
#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)
```c
#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移植示例
```c
// 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移植示例
```c
// 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驱动验证
```c
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驱动验证
```c
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. 完整功能验证
使用上位机工具发送测试命令:
```bash
# 连接设备
./flash_tool --port COM3 --connect
# 发送握手命令
./flash_tool --port COM3 --handshake
# 查询版本
./flash_tool --port COM3 --version
# 测试写入
./flash_tool --port COM3 --test-write
```
---
## 常见问题
### Q1: Flash擦除失败
**原因:**
- Flash未解锁
- 地址不在有效范围
- 擦除范围超出限制
**解决:**
```c
// 检查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写入失败
**原因:**
- 写入地址未擦除
- 地址未对齐
- 数据长度不匹配
**解决:**
```c
// 确保写入前已擦除
flash_port_erase(addr, len);
// 确保地址对齐
addr = (addr / 4) * 4; // STM32F4按字对齐
// 确保数据长度对齐
len = ((len + 3) / 4) * 4;
```
### Q3: UART接收不完整
**原因:**
- 波特率不匹配
- 缓冲区溢出
- 中断未正确配置
**解决:**
```c
// 使用中断接收
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. 撰写移植文档
---
**祝您移植顺利!**