一、系统概述
本系统实现STM32的远程固件升级功能,包含Bootloader程序 (下位机)和升级工具 (上位机)两部分。系统支持通过串口/网络 进行固件传输,采用YMODEM协议 保证传输可靠性,具有断点续传、固件校验、回滚保护等安全机制。
二、系统架构
YMODEM协议
写入Flash
校验
正常运行
升级请求
上位机
STM32 Bootloader
应用程序区
固件存储区
用户应用
三、下位机实现(STM32 Bootloader)
1. 分区规划
| 区域 | 起始地址 | 大小 | 功能 |
|---|---|---|---|
| Bootloader | 0x08000000 | 32KB | 引导程序+升级逻辑 |
| Application | 0x08008000 | 480KB | 用户应用程序 |
| Firmware | 0x0807F000 | 4KB | 新固件暂存区(双备份) |
| Backup | 0x0807E000 | 4KB | 旧固件备份区 |
| Config | 0x0807D000 | 4KB | 配置信息(升级标志等) |
2. 关键代码实现
bootloader.c
c
#include "stm32f4xx_hal.h"
#include "ymodem.h"
// 分区定义
#define BOOTLOADER_ADDR 0x08000000
#define APP_ADDR 0x08008000
#define FIRMWARE_ADDR 0x0807F000
#define BACKUP_ADDR 0x0807E000
#define CONFIG_ADDR 0x0807D000
// 升级状态
typedef enum {
BOOT_NORMAL,
BOOT_UPDATE,
BOOT_ROLLBACK
} BootStatus;
// 配置结构体
typedef struct {
uint32_t app_crc;
uint32_t firmware_size;
uint8_t update_flag;
uint8_t reserved[7];
} ConfigData;
// 全局变量
UART_HandleTypeDef huart2;
BootStatus boot_status = BOOT_NORMAL;
ConfigData config;
// 初始化
void System_Init(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_CRC_Init();
// 加载配置
memcpy(&config, (void*)CONFIG_ADDR, sizeof(ConfigData));
// 检查升级标志
if (config.update_flag == 0xAA) {
boot_status = BOOT_UPDATE;
}
}
// 主函数
int main(void) {
System_Init();
if (boot_status == BOOT_UPDATE) {
// 执行固件更新
if (Update_Firmware() == SUCCESS) {
// 更新成功,清除标志
config.update_flag = 0;
FLASH_Write(CONFIG_ADDR, (uint8_t*)&config, sizeof(ConfigData));
JumpToApp(APP_ADDR);
} else {
// 更新失败,回滚
boot_status = BOOT_ROLLBACK;
}
}
else if (boot_status == BOOT_ROLLBACK) {
// 执行回滚
if (Rollback_Firmware() == SUCCESS) {
boot_status = BOOT_NORMAL;
}
}
// 正常启动应用
if (Check_AppValidity(APP_ADDR)) {
JumpToApp(APP_ADDR);
} else {
// 应用无效,进入升级模式
Enter_Upgrade_Mode();
}
while (1) {
// 主循环
}
}
// 固件更新
UpdateStatus Update_Firmware(void) {
// 1. 接收固件头
FirmwareHeader header;
if (Ymodem_Receive((uint8_t*)&header, sizeof(header)) != YMODEM_OK)
return UPDATE_FAIL;
// 2. 检查固件大小
if (header.size > (FIRMWARE_ADDR - APP_ADDR))
return UPDATE_FAIL;
// 3. 接收固件数据
uint32_t received = 0;
while (received < header.size) {
uint8_t buffer[1024];
uint16_t chunk_size = (header.size - received) > 1024 ? 1024 : (header.size - received);
if (Ymodem_Receive(buffer, chunk_size) != YMODEM_OK)
return UPDATE_FAIL;
// 4. 写入Flash
FLASH_Write(FIRMWARE_ADDR + received, buffer, chunk_size);
received += chunk_size;
}
// 5. 校验固件
uint32_t crc = HAL_CRC_Calculate(&hcrc, (uint32_t*)FIRMWARE_ADDR, header.size/4);
if (crc != header.crc)
return UPDATE_FAIL;
// 6. 备份原固件
FLASH_Read(APP_ADDR, (uint8_t*)BACKUP_ADDR, header.size);
// 7. 更新应用
FLASH_Write(APP_ADDR, (uint8_t*)FIRMWARE_ADDR, header.size);
return UPDATE_SUCCESS;
}
// 回滚固件
UpdateStatus Rollback_Firmware(void) {
// 从备份区恢复固件
uint32_t app_size = *(uint32_t*)(BACKUP_ADDR - 4); // 假设大小存储在备份区前4字节
FLASH_Write(APP_ADDR, (uint8_t*)BACKUP_ADDR, app_size);
return UPDATE_SUCCESS;
}
// 跳转到应用
void JumpToApp(uint32_t app_addr) {
typedef void (*pFunction)(void);
pFunction app_entry;
// 关闭外设
HAL_UART_DeInit(&huart2);
HAL_RCC_DeInit();
// 设置向量表
SCB->VTOR = app_addr;
// 获取复位地址
app_entry = (pFunction)(*(__IO uint32_t*)(app_addr + 4));
// 设置主堆栈指针
__set_MSP(*(__IO uint32_t*)app_addr);
// 跳转
app_entry();
}
// 进入升级模式
void Enter_Upgrade_Mode(void) {
// 初始化YMODEM
Ymodem_Init(&huart2);
// 发送升级提示
uint8_t ready_msg[] = "Ready for firmware upgrade...\r\n";
HAL_UART_Transmit(&huart2, ready_msg, sizeof(ready_msg), 1000);
// 等待固件接收
if (Ymodem_Receive_And_Update() == YMODEM_OK) {
// 更新成功
config.update_flag = 0;
FLASH_Write(CONFIG_ADDR, (uint8_t*)&config, sizeof(ConfigData));
JumpToApp(APP_ADDR);
} else {
// 更新失败
uint8_t fail_msg[] = "Firmware upgrade failed!\r\n";
HAL_UART_Transmit(&huart2, fail_msg, sizeof(fail_msg), 1000);
}
// 进入死循环
while (1) {
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
HAL_Delay(500);
}
}
ymodem.c (YMODEM协议实现)
c
#include "ymodem.h"
// YMODEM数据包结构
typedef struct {
uint8_t soh; // 包头 (0x01)
uint8_t packet_num; // 包序号 (0-FF)
uint8_t inv_num; // 包序号取反
uint8_t data[128]; // 数据
uint8_t crc_h; // CRC高字节
uint8_t crc_l; // CRC低字节
} YmodemPacket;
// 状态机
typedef enum {
YMODEM_IDLE,
YMODEM_RECEIVE_HEADER,
YMODEM_RECEIVE_DATA,
YMODEM_PROCESS_PACKET
} YmodemState;
// 全局变量
UART_HandleTypeDef *ymodem_uart;
YmodemState ymodem_state = YMODEM_IDLE;
uint8_t packet_buffer[133]; // 最大包长度
uint8_t packet_num = 0;
uint32_t bytes_received = 0;
// 初始化
void Ymodem_Init(UART_HandleTypeDef *huart) {
ymodem_uart = huart;
ymodem_state = YMODEM_IDLE;
}
// 接收数据
YmodemStatus Ymodem_Receive(uint8_t *data, uint16_t size) {
// 简化实现,实际需处理超时、重传等
HAL_StatusTypeDef status;
uint16_t received = 0;
while (received < size) {
status = HAL_UART_Receive(ymodem_uart, data + received, 1, 1000);
if (status != HAL_OK) return YMODEM_TIMEOUT;
received++;
}
return YMODEM_OK;
}
// 处理YMODEM协议
YmodemStatus Ymodem_Process(void) {
uint8_t byte;
HAL_StatusTypeDef status;
status = HAL_UART_Receive(ymodem_uart, &byte, 1, 1000);
if (status != HAL_OK) return YMODEM_TIMEOUT;
switch (ymodem_state) {
case YMODEM_IDLE:
if (byte == SOH) { // 开始新包
ymodem_state = YMODEM_RECEIVE_HEADER;
packet_buffer[0] = byte;
bytes_received = 1;
}
break;
case YMODEM_RECEIVE_HEADER:
packet_buffer[bytes_received++] = byte;
if (bytes_received == 133) { // 完整包
ymodem_state = YMODEM_PROCESS_PACKET;
}
break;
// 其他状态处理...
}
return YMODEM_OK;
}
四、上位机实现(C# WinForms)
1. 主界面设计
csharp
public partial class MainForm : Form
{
private SerialPort serialPort;
private Ymodem ymodem;
private string firmwarePath;
public MainForm()
{
InitializeComponent();
serialPort = new SerialPort();
ymodem = new Ymodem(serialPort);
}
// 选择固件文件
private void btnBrowse_Click(object sender, EventArgs e)
{
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.Filter = "Binary Files (*.bin)|*.bin|All Files (*.*)|*.*";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
firmwarePath = openFileDialog.FileName;
txtFilePath.Text = firmwarePath;
}
}
}
// 连接设备
private void btnConnect_Click(object sender, EventArgs e)
{
try
{
serialPort.PortName = cmbPorts.Text;
serialPort.BaudRate = int.Parse(cmbBaudRate.Text);
serialPort.Open();
ymodem.OnProgress += Ymodem_OnProgress;
lblStatus.Text = "Connected";
}
catch (Exception ex)
{
MessageBox.Show($"Connection failed: {ex.Message}");
}
}
// 开始升级
private void btnUpgrade_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(firmwarePath))
{
MessageBox.Show("Please select a firmware file first.");
return;
}
try
{
// 发送升级命令
byte[] cmd = Encoding.ASCII.GetBytes("UPDATE\r\n");
serialPort.Write(cmd, 0, cmd.Length);
// 等待设备就绪
Thread.Sleep(1000);
// 开始YMODEM传输
bool success = ymodem.SendFile(firmwarePath);
if (success)
{
MessageBox.Show("Firmware upgrade successful!");
}
else
{
MessageBox.Show("Firmware upgrade failed!");
}
}
catch (Exception ex)
{
MessageBox.Show($"Upgrade error: {ex.Message}");
}
}
// 进度更新
private void Ymodem_OnProgress(int progress)
{
if (InvokeRequired)
{
Invoke(new Action<int>(Ymodem_OnProgress), progress);
return;
}
progressBar.Value = progress;
lblProgress.Text = $"{progress}%";
}
}
2. YMODEM协议实现
csharp
public class Ymodem
{
private SerialPort port;
public event Action<int> OnProgress;
public Ymodem(SerialPort port)
{
this.port = port;
}
public bool SendFile(string filePath)
{
FileInfo fileInfo = new FileInfo(filePath);
long fileSize = fileInfo.Length;
int progress = 0;
// 发送开始传输包
if (!SendStartPacket(fileInfo.Name, fileSize))
return false;
// 分块发送数据
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[1024];
int bytesRead;
long totalSent = 0;
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
if (!SendDataPacket(totalSent / 128, buffer, bytesRead))
return false;
totalSent += bytesRead;
progress = (int)((totalSent * 100) / fileSize);
OnProgress?.Invoke(progress);
}
}
// 发送结束包
return SendEndPacket();
}
private bool SendStartPacket(string fileName, long fileSize)
{
// 构建YMODEM起始包
byte[] packet = new byte[133];
packet[0] = 0x01; // SOH
packet[1] = 0x00; // 包号0
packet[2] = 0xFF; // 包号取反
// 文件名
byte[] nameBytes = Encoding.ASCII.GetBytes(fileName);
Array.Copy(nameBytes, 0, packet, 3, Math.Min(nameBytes.Length, 128));
// 文件大小
string sizeStr = fileSize.ToString();
byte[] sizeBytes = Encoding.ASCII.GetBytes(sizeStr);
Array.Copy(sizeBytes, 0, packet, 3 + nameBytes.Length, sizeBytes.Length);
// 计算CRC
ushort crc = CalculateCRC(packet, 3, 128);
packet[131] = (byte)(crc >> 8);
packet[132] = (byte)(crc & 0xFF);
// 发送包
port.Write(packet, 0, 133);
// 等待ACK
return WaitForAck();
}
private bool SendDataPacket(int blockNum, byte[] data, int dataLen)
{
// 构建数据包
byte[] packet = new byte[133];
packet[0] = 0x01; // SOH
packet[1] = (byte)blockNum;
packet[2] = (byte)(~blockNum);
// 填充数据
int copyLen = Math.Min(dataLen, 128);
Array.Copy(data, 0, packet, 3, copyLen);
// 计算CRC
ushort crc = CalculateCRC(packet, 3, 128);
packet[131] = (byte)(crc >> 8);
packet[132] = (byte)(crc & 0xFF);
// 发送包
port.Write(packet, 0, 133);
// 等待ACK
return WaitForAck();
}
private bool SendEndPacket()
{
// 发送EOT
port.Write(new byte[] { 0x04 }, 0, 1);
// 等待ACK
if (!WaitForAck()) return false;
// 发送空包结束
return SendStartPacket("", 0);
}
private bool WaitForAck()
{
DateTime start = DateTime.Now;
while ((DateTime.Now - start).TotalSeconds < 5)
{
if (port.BytesToRead > 0)
{
byte response = (byte)port.ReadByte();
if (response == ACK) return true;
if (response == NAK) return false;
}
}
return false;
}
private ushort CalculateCRC(byte[] data, int offset, int length)
{
// CRC16-CCITT计算
ushort crc = 0;
for (int i = 0; i < length; i++)
{
crc ^= (ushort)(data[offset + i] << 8);
for (int j = 0; j < 8; j++)
{
if ((crc & 0x8000) != 0)
crc = (ushort)((crc << 1) ^ 0x1021);
else
crc <<= 1;
}
}
return crc;
}
// YMODEM协议常量
private const byte SOH = 0x01;
private const byte EOT = 0x04;
private const byte ACK = 0x06;
private const byte NAK = 0x15;
private const byte CAN = 0x18;
}
五、安全机制
1. 固件校验
c
// 使用CRC32校验
uint32_t Calculate_CRC32(uint8_t *data, uint32_t length)
{
uint32_t crc = 0xFFFFFFFF;
for (uint32_t i = 0; i < length; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
if (crc & 1)
crc = (crc >> 1) ^ 0xEDB88320;
else
crc >>= 1;
}
}
return ~crc;
}
2. 双备份机制
c
// 固件更新时先写入备份区
FLASH_Write(BACKUP_ADDR, (uint8_t*)FIRMWARE_ADDR, new_size);
// 验证成功后复制到应用区
if (Verify_Firmware(BACKUP_ADDR, new_size)) {
FLASH_Write(APP_ADDR, (uint8_t*)BACKUP_ADDR, new_size);
}
3. 回滚保护
c
// 更新失败时从备份区恢复
if (Update_Failed) {
FLASH_Write(APP_ADDR, (uint8_t*)BACKUP_ADDR, backup_size);
boot_status = BOOT_ROLLBACK;
}
参考代码 STM32远程升级源码(包括上位机) www.youwenfan.com/contentcss/182605.html
六、网络升级扩展
1. TCP/IP升级
c
// 在Bootloader中添加网络支持
void Network_Upgrade(void)
{
// 初始化以太网
ETH_Init();
// 创建TCP服务器
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
listen(server_socket, 1);
// 接受客户端连接
int client_socket = accept(server_socket, NULL, NULL);
// 使用YMODEM over TCP传输
Ymodem_Receive_Over_TCP(client_socket);
close(client_socket);
close(server_socket);
}
2. HTTP升级
c
// 通过HTTP下载固件
void HTTP_Upgrade(const char *url)
{
// 初始化HTTP客户端
http_client_t client;
http_init(&client, url);
// 下载固件头
FirmwareHeader header;
http_get(&client, "/firmware/header.bin", &header, sizeof(header));
// 下载固件数据
uint8_t buffer[1024];
for (uint32_t offset = 0; offset < header.size; offset += 1024) {
uint32_t size = MIN(1024, header.size - offset);
http_get_range(&client, "/firmware/data.bin", offset, size, buffer);
// 写入Flash
FLASH_Write(FIRMWARE_ADDR + offset, buffer, size);
}
// 校验并应用更新
if (Verify_Firmware(FIRMWARE_ADDR, header.size)) {
Apply_Update();
}
}
七、使用说明
1. 下位机部署
-
编译Bootloader程序,烧录到0x08000000
-
编译用户应用程序,烧录到0x08008000
-
配置系统参数(如升级标志位)
2. 上位机使用
-
选择固件文件(.bin格式)
-
选择串口号和波特率
-
连接设备
-
点击"开始升级"按钮
-
等待升级完成(进度条显示)
3. 升级流程
c
sequenceDiagram
上位机->>STM32: 发送升级命令
STM32->>上位机: 回复"Ready"
上位机->>STM32: 发送YMODEM数据包
STM32->>上位机: 回复ACK/NAK
上位机->>STM32: 发送结束包
STM32->>上位机: 回复ACK
STM32->>STM32: 校验并应用固件
STM32->>上位机: 重启完成
八、项目资源
1. 开发环境
-
IDE: STM32CubeIDE (下位机), Visual Studio 2022 (上位机)
-
库: HAL库 (STM32), .NET Framework 4.8 (上位机)
-
协议: YMODEM (文件传输), CRC32 (校验)
2. 文件结构
bash
STM32_OTA_Project/
├── Bootloader/ // 下位机程序
│ ├── Core/ // 核心代码
│ ├── Drivers/ // 驱动
│ └── STM32F4xx_HAL_Driver/ // HAL库
├── Upgrader/ // 上位机程序
│ ├── Ymodem.cs // YMODEM协议
│ ├── MainForm.cs // 主界面
│ └── Properties/ // 资源文件
└── Documentation/ // 文档
├── 使用说明.pdf
└── 协议说明.pdf
3. 关键参数
| 参数 | 值 |
|---|---|
| 升级协议 | YMODEM-1K |
| 传输速率 | 115200 bps (默认) |
| 最大固件大小 | 448KB |
| 校验方式 | CRC32 |
| 超时时间 | 5秒 |
| 重试次数 | 3次 |
九、总结
本系统实现了完整的STM32远程升级解决方案:
-
Bootloader设计:支持双备份、回滚保护、安全校验
-
YMODEM协议:可靠的文件传输机制
-
上位机工具:友好的图形界面,支持进度显示
-
安全机制:固件校验、断点续传、回滚保护
-
扩展能力:支持串口/网络/HTTP多种升级方式