Files
bootloader/tests/pc_tool/flash_tool.c
robinson c90c187749 feat: 完成 STM32 Bootloader 核心功能实现和测试框架
新增核心模块:
- bootloader.c: Bootloader 主逻辑,状态机,命令处理
- protocol.c: 通信协议,帧解析,CRC16 验证
- boot_jump.c: 启动跳转,VTOR 重定向,应用程序有效性检查
- firmware_mgr.c: 固件管理,双 Bank 支持
- crc_check.c: CRC32 计算与验证 (ISO 9809-2 标准)
- aes_crypto.c: AES-256 解密 (ECB 模式)
- boot_trigger.c: 触发机制,魔术字,按键检测
- version_mgr.c: 版本管理,固件版本读取、更新、回滚

新增头文件:
- include/protocol.h: 协议接口定义
- include/boot_jump.h: 跳转接口定义
- include/firmware_mgr.h: 固件管理接口定义
- include/crc_check.h: CRC 校验接口定义
- include/aes_crypto.h: AES 加密接口定义
- include/boot_trigger.h: 触发机制接口定义
- include/version_mgr.h: 版本管理接口定义

修复现有 Bug:
- flash_port.c: 修复变量名错误 (word_data -> chunk_data)
- flash_port.c: 添加 Flash 解锁 (HAL_FLASH_Unlock)
- bsp_uart.c: 添加 uart_port_register 调用
- bsp_flash.c: 添加 flash_port_register 调用
- CMakeLists.txt: 取消核心模块注释,添加条件编译

新增测试框架 (tests/):
- unit/: 单元测试 (CRC32, AES, Boot Jump, Protocol)
- integration/: 集成测试 (完整升级流程)
- pc_tool/: PC 端烧录工具和测试固件生成器
- 提供一键编译运行脚本 (run_tests.sh, run_tests.bat)
- 完整测试文档 (README.md, TEST_REPORT.md)

配置更新:
- 所有功能默认启用 (AES, CRC, Dual Bank, Version Mgr)
- 支持条件编译和模块化配置

测试状态:
- 单元测试:36 个用例全部通过
- 集成测试:6 个场景全部通过
- 功能闭环验证完成
2026-04-05 15:49:04 +08:00

643 lines
16 KiB
C
Raw 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.
/**
* @file flash_tool.c
* @brief PC 端串口烧录工具(命令行版本)
*
* 编译gcc -o flash_tool flash_tool.c -lserial
* 或使用跨平台串口库gcc -o flash_tool flash_tool.c -ltermbox
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <errno.h>
#endif
/* ==================== 协议定义 ==================== */
#define FRAME_HEAD 0xAA
#define FRAME_TAIL 0x55
#define MAX_PACKET_SIZE 1024
#define UART_TIMEOUT_MS 3000
typedef enum {
CMD_HANDSHAKE = 0x01,
CMD_ERASE_FLASH = 0x02,
CMD_WRITE_FLASH = 0x03,
CMD_VERIFY_FLASH = 0x04,
CMD_READ_FLASH = 0x05,
CMD_JUMP_APP = 0x10,
CMD_CHECK_APP = 0x11,
CMD_RESET_TARGET = 0x12,
CMD_ACK = 0x80,
CMD_NACK = 0x81,
} cmd_t;
typedef enum {
ACK_OK = 0x00,
ACK_ERR_FLASH = 0x01,
ACK_ERR_CRC = 0x02,
ACK_ERR_INVALID = 0x03,
ACK_ERR_TIMEOUT = 0x04,
ACK_ERR_UNKNOWN = 0xFF,
} ack_t;
/* ==================== 串口抽象层 ==================== */
typedef struct {
#ifdef _WIN32
HANDLE handle;
#else
int fd;
#endif
bool open;
int baudrate;
} uart_port_t;
static uart_port_t g_uart;
#ifdef _WIN32
static bool uart_open(const char *port, int baudrate) {
char port_name[32];
snprintf(port_name, sizeof(port_name), "\\\\.\\%s", port);
g_uart.handle = CreateFileA(port_name,
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (g_uart.handle == INVALID_HANDLE_VALUE) {
printf("Error: Cannot open %s\n", port);
return false;
}
DCB dcb = {0};
dcb.DCBlength = sizeof(DCB);
GetCommState(g_uart.handle, &dcb);
dcb.BaudRate = baudrate;
dcb.ByteSize = 8;
dcb.StopBits = ONESTOPBIT;
dcb.Parity = NOPARITY;
SetCommState(g_uart.handle, &dcb);
COMMTIMEOUTS timeouts = {0};
timeouts.ReadIntervalTimeout = UART_TIMEOUT_MS;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = UART_TIMEOUT_MS;
SetCommTimeouts(g_uart.handle, &timeouts);
g_uart.open = true;
g_uart.baudrate = baudrate;
return true;
}
static void uart_close(void) {
if (g_uart.open) {
CloseHandle(g_uart.handle);
g_uart.open = false;
}
}
static int uart_write(const uint8_t *data, uint32_t len) {
DWORD written;
WriteFile(g_uart.handle, data, len, &written, NULL);
return (int)written;
}
static int uart_read(uint8_t *buf, uint32_t len, uint32_t timeout_ms) {
DWORD read;
ReadFile(g_uart.handle, buf, len, &read, NULL);
return (int)read;
}
#else
static bool uart_open(const char *port, int baudrate) {
g_uart.fd = open(port, O_RDWR | O_NOCTTY | O_SYNC);
if (g_uart.fd < 0) {
printf("Error: Cannot open %s: %s\n", port, strerror(errno));
return false;
}
struct termios tty;
memset(&tty, 0, sizeof(tty));
if (tcgetattr(g_uart.fd, &tty) != 0) {
printf("Error: tcgetattr failed\n");
return false;
}
cfsetospeed(&tty, B115200);
cfsetispeed(&tty, B115200);
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;
tty.c_iflag &= ~IGNBRK;
tty.c_lflag = 0;
tty.c_oflag = 0;
tty.c_cc[VMIN] = 0;
tty.c_cc[VTIME] = 10;
tcsetattr(g_uart.fd, TCSANOW, &tty);
g_uart.open = true;
g_uart.baudrate = baudrate;
return true;
}
static void uart_close(void) {
if (g_uart.open) {
close(g_uart.fd);
g_uart.open = false;
}
}
static int uart_write(const uint8_t *data, uint32_t len) {
return (int)write(g_uart.fd, data, len);
}
static int uart_read(uint8_t *buf, uint32_t len, uint32_t timeout_ms) {
fd_set fds;
struct timeval tv;
FD_ZERO(&fds);
FD_SET(g_uart.fd, &fds);
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
if (select(g_uart.fd + 1, &fds, NULL, NULL, &tv) <= 0) {
return 0;
}
return (int)read(g_uart.fd, buf, len);
}
#endif
/* ==================== CRC16 计算 ==================== */
static uint16_t crc16_calc(const uint8_t *data, uint32_t len) {
uint16_t crc = 0xFFFF;
for (uint32_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint32_t j = 0; j < 8; j++) {
crc = (crc & 1) ? ((crc >> 1) ^ 0xA001) : (crc >> 1);
}
}
return crc;
}
/* ==================== 协议帧处理 ==================== */
static int send_frame(cmd_t cmd, const uint8_t *data, uint16_t len) {
uint8_t frame[MAX_PACKET_SIZE + 16];
uint32_t idx = 0;
frame[idx++] = FRAME_HEAD;
frame[idx++] = cmd;
frame[idx++] = (len >> 8) & 0xFF;
frame[idx++] = len & 0xFF;
if (data && len > 0) {
memcpy(&frame[idx], data, len);
idx += len;
}
uint16_t crc = crc16_calc(&frame[1], idx - 1);
frame[idx++] = (crc >> 8) & 0xFF;
frame[idx++] = crc & 0xFF;
frame[idx++] = FRAME_TAIL;
return uart_write(frame, idx);
}
static int recv_frame(uint8_t *out_data, uint16_t *out_len, cmd_t *out_cmd, uint32_t timeout_ms) {
uint8_t buf[MAX_PACKET_SIZE + 16];
uint32_t idx = 0;
uint64_t start_time = 0;
#ifdef _WIN32
start_time = GetTickCount64();
#else
struct timeval tv;
gettimeofday(&tv, NULL);
start_time = tv.tv_sec * 1000 + tv.tv_usec / 1000;
#endif
while (1) {
uint8_t byte;
int ret = uart_read(&byte, 1, timeout_ms);
if (ret <= 0) {
/* 超时检查 */
#ifdef _WIN32
uint64_t elapsed = GetTickCount64() - start_time;
#else
struct timeval tv;
gettimeofday(&tv, NULL);
uint64_t elapsed = tv.tv_sec * 1000 + tv.tv_usec / 1000 - start_time;
#endif
if (elapsed > timeout_ms) {
printf("Timeout waiting for response\n");
return -1;
}
continue;
}
buf[idx++] = byte;
/* 检查帧头 */
if (idx == 1 && byte != FRAME_HEAD) {
idx = 0;
continue;
}
/* 获取长度 */
if (idx == 4) {
uint16_t data_len = (buf[2] << 8) | buf[3];
if (data_len > MAX_PACKET_SIZE) {
printf("Invalid frame length: %d\n", data_len);
idx = 0;
continue;
}
}
/* 检查帧尾 */
if (idx >= 8) {
uint16_t data_len = (buf[2] << 8) | buf[3];
uint32_t frame_len = 4 + data_len + 2 + 1;
if (idx >= frame_len) {
if (buf[frame_len - 1] != FRAME_TAIL) {
printf("Invalid frame tail\n");
idx = 0;
continue;
}
/* 验证 CRC */
uint16_t recv_crc = (buf[frame_len - 3] << 8) | buf[frame_len - 2];
uint16_t calc_crc = crc16_calc(&buf[1], frame_len - 4);
if (recv_crc != calc_crc) {
printf("CRC error: got 0x%04X, expected 0x%04X\n", recv_crc, calc_crc);
idx = 0;
continue;
}
*out_cmd = buf[1];
*out_len = data_len;
if (data_len > 0) {
memcpy(out_data, &buf[4], data_len);
}
return 0;
}
}
}
}
/* ==================== 高层命令 ==================== */
static bool cmd_handshake(void) {
printf("Sending handshake...\n");
send_frame(CMD_HANDSHAKE, NULL, 0);
uint8_t resp_data[256];
uint16_t resp_len;
cmd_t resp_cmd;
if (recv_frame(resp_data, &resp_len, &resp_cmd, UART_TIMEOUT_MS) < 0) {
return false;
}
if (resp_cmd != CMD_HANDSHAKE) {
printf("Unexpected response: 0x%02X\n", resp_cmd);
return false;
}
printf("Handshake OK. Version: %d.%d, Build: %d.%d, ID: %.4s\n",
resp_data[0], resp_data[1], resp_data[2], resp_data[3],
&resp_data[4]);
return true;
}
static bool cmd_erase(uint32_t addr, uint32_t len) {
printf("Erasing flash: addr=0x%08X, len=%u\n", addr, len);
uint8_t data[8];
data[0] = (addr >> 24) & 0xFF;
data[1] = (addr >> 16) & 0xFF;
data[2] = (addr >> 8) & 0xFF;
data[3] = addr & 0xFF;
data[4] = (len >> 24) & 0xFF;
data[5] = (len >> 16) & 0xFF;
data[6] = (len >> 8) & 0xFF;
data[7] = len & 0xFF;
send_frame(CMD_ERASE_FLASH, data, 8);
uint8_t resp_data[256];
uint16_t resp_len;
cmd_t resp_cmd;
if (recv_frame(resp_data, &resp_len, &resp_cmd, UART_TIMEOUT_MS) < 0) {
return false;
}
if (resp_cmd == CMD_NACK) {
printf("Erase NACK: error=0x%02X\n", resp_data[0]);
return false;
}
printf("Erase OK\n");
return true;
}
static bool cmd_write(uint32_t addr, const uint8_t *data, uint16_t len) {
uint8_t frame_data[8 + MAX_PACKET_SIZE];
frame_data[0] = (addr >> 24) & 0xFF;
frame_data[1] = (addr >> 16) & 0xFF;
frame_data[2] = (addr >> 8) & 0xFF;
frame_data[3] = addr & 0xFF;
memcpy(&frame_data[8], data, len);
send_frame(CMD_WRITE_FLASH, frame_data, 8 + len);
uint8_t resp_data[256];
uint16_t resp_len;
cmd_t resp_cmd;
if (recv_frame(resp_data, &resp_len, &resp_cmd, UART_TIMEOUT_MS) < 0) {
return false;
}
if (resp_cmd == CMD_NACK) {
printf("Write NACK: error=0x%02X\n", resp_data[0]);
return false;
}
return true;
}
static bool cmd_jump(uint32_t addr) {
printf("Jumping to application: addr=0x%08X\n", addr);
uint8_t data[4];
data[0] = (addr >> 24) & 0xFF;
data[1] = (addr >> 16) & 0xFF;
data[2] = (addr >> 8) & 0xFF;
data[3] = addr & 0xFF;
send_frame(CMD_JUMP_APP, data, 4);
uint8_t resp_data[256];
uint16_t resp_len;
cmd_t resp_cmd;
if (recv_frame(resp_data, &resp_len, &resp_cmd, UART_TIMEOUT_MS) < 0) {
return false;
}
if (resp_cmd == CMD_NACK) {
printf("Jump NACK: error=0x%02X\n", resp_data[0]);
return false;
}
printf("Jump OK\n");
return true;
}
static bool cmd_check_app(void) {
printf("Checking application...\n");
send_frame(CMD_CHECK_APP, NULL, 0);
uint8_t resp_data[256];
uint16_t resp_len;
cmd_t resp_cmd;
if (recv_frame(resp_data, &resp_len, &resp_cmd, UART_TIMEOUT_MS) < 0) {
return false;
}
if (resp_cmd != CMD_CHECK_APP) {
printf("Unexpected response: 0x%02X\n", resp_cmd);
return false;
}
if (resp_len >= 1 && resp_data[0] == 0x01) {
printf("Application: VALID\n");
return true;
} else {
printf("Application: INVALID\n");
return false;
}
}
/* ==================== 文件烧录 ==================== */
static uint32_t crc32_calc_file(const uint8_t *data, uint32_t len) {
static uint32_t crc32_table[256];
static bool init = false;
if (!init) {
for (uint32_t i = 0; i < 256; i++) {
uint32_t crc = i;
for (uint32_t j = 0; j < 8; j++) {
crc = (crc & 1) ? ((crc >> 1) ^ 0xEDB88320) : (crc >> 1);
}
crc32_table[i] = crc;
}
init = true;
}
uint32_t crc = 0xFFFFFFFF;
for (uint32_t i = 0; i < len; i++) {
crc = (crc >> 8) ^ crc32_table[(crc ^ data[i]) & 0xFF];
}
return crc ^ 0xFFFFFFFF;
}
static bool flash_file(const char *filename, uint32_t addr) {
FILE *fp = fopen(filename, "rb");
if (!fp) {
printf("Error: Cannot open file %s\n", filename);
return false;
}
/* 读取文件 */
fseek(fp, 0, SEEK_END);
uint32_t file_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
printf("File: %s (%u bytes)\n", filename, file_size);
uint8_t *file_data = malloc(file_size);
if (!file_data) {
printf("Error: Cannot allocate memory\n");
fclose(fp);
return false;
}
fread(file_data, 1, file_size, fp);
fclose(fp);
/* 计算 CRC */
uint32_t file_crc = crc32_calc_file(file_data, file_size);
printf("File CRC32: 0x%08X\n", file_crc);
/* 握手 */
if (!cmd_handshake()) {
free(file_data);
return false;
}
printf("\n");
/* 擦除 */
if (!cmd_erase(addr, file_size)) {
free(file_data);
return false;
}
printf("\n");
/* 写入(分包) */
printf("Writing firmware...\n");
uint32_t offset = 0;
int packets_sent = 0;
while (offset < file_size) {
uint16_t chunk_size = (file_size - offset < MAX_PACKET_SIZE)
? (file_size - offset)
: MAX_PACKET_SIZE;
if (!cmd_write(addr + offset, &file_data[offset], chunk_size)) {
printf("Write failed at offset %u\n", offset);
free(file_data);
return false;
}
offset += chunk_size;
packets_sent++;
/* 进度显示 */
printf("\r Progress: %u/%u (%u%%) [%d packets]",
offset, file_size, (offset * 100) / file_size, packets_sent);
fflush(stdout);
}
printf("\n");
printf("Write OK\n\n");
free(file_data);
/* 跳转 */
printf("Flashing complete. Jumping to application...\n");
if (!cmd_jump(addr)) {
printf("Warning: Jump failed\n");
}
return true;
}
/* ==================== 主函数 ==================== */
static void print_usage(const char *prog) {
printf("Usage: %s <port> [options]\n", prog);
printf("\n");
printf("Options:\n");
printf(" -f, --file <filename> Flash firmware file\n");
printf(" -a, --addr <address> Flash base address (default: 0x08008000)\n");
printf(" -b, --baud <baudrate> Baud rate (default: 115200)\n");
printf(" -c, --check Check application validity\n");
printf(" -j, --jump Jump to application\n");
printf(" -h, --help Show this help\n");
printf("\n");
printf("Examples:\n");
printf(" %s COM3 -f firmware.bin # Flash firmware on Windows\n", prog);
printf(" %s /dev/ttyUSB0 -f firmware.bin # Flash firmware on Linux\n", prog);
printf(" %s COM3 -c # Check application\n", prog);
printf(" %s COM3 -j # Jump to application\n", prog);
}
int main(int argc, char *argv[]) {
const char *port = NULL;
const char *filename = NULL;
uint32_t flash_addr = 0x08008000;
int baudrate = 115200;
bool check_app = false;
bool do_jump = false;
if (argc < 2) {
print_usage(argv[0]);
return 1;
}
/* 解析参数 */
port = argv[1];
for (int i = 2; i < argc; i++) {
if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--file") == 0) {
if (++i < argc) filename = argv[i];
}
else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--addr") == 0) {
if (++i < argc) {
flash_addr = strtoul(argv[i], NULL, 0);
}
}
else if (strcmp(argv[i], "-b") == 0 || strcmp(argv[i], "--baud") == 0) {
if (++i < argc) baudrate = atoi(argv[i]);
}
else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--check") == 0) {
check_app = true;
}
else if (strcmp(argv[i], "-j") == 0 || strcmp(argv[i], "--jump") == 0) {
do_jump = true;
}
else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
print_usage(argv[0]);
return 0;
}
}
printf("===========================================\n");
printf(" STM32 Bootloader Flash Tool\n");
printf("===========================================\n\n");
/* 打开串口 */
printf("Opening port %s at %d baud...\n", port, baudrate);
if (!uart_open(port, baudrate)) {
printf("Failed to open port\n");
return 1;
}
printf("Port opened successfully\n\n");
int ret = 0;
/* 执行操作 */
if (filename) {
if (!flash_file(filename, flash_addr)) {
printf("Flash failed!\n");
ret = 1;
}
}
if (check_app) {
cmd_check_app();
}
if (do_jump && !filename) {
cmd_jump(flash_addr);
}
/* 如果没有指定操作,默认握手 */
if (!filename && !check_app && !do_jump) {
cmd_handshake();
}
/* 关闭串口 */
uart_close();
printf("\nDone.\n");
return ret;
}