STM32远程升级系统(Bootloader + 上位机)

一、系统概述

本系统实现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. 下位机部署

  1. 编译Bootloader程序,烧录到0x08000000

  2. 编译用户应用程序,烧录到0x08008000

  3. 配置系统参数(如升级标志位)

2. 上位机使用

  1. 选择固件文件(.bin格式)

  2. 选择串口号和波特率

  3. 连接设备

  4. 点击"开始升级"按钮

  5. 等待升级完成(进度条显示)

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远程升级解决方案:

  1. Bootloader设计:支持双备份、回滚保护、安全校验

  2. YMODEM协议:可靠的文件传输机制

  3. 上位机工具:友好的图形界面,支持进度显示

  4. 安全机制:固件校验、断点续传、回滚保护

  5. 扩展能力:支持串口/网络/HTTP多种升级方式

相关推荐
Heartache boy2 小时前
野火STM32_HAL库版课程笔记-ADC多通道采集热敏、光敏、反射传感器(轮询)
笔记·stm32·单片机
AI+程序员在路上2 小时前
嵌入式软件技术大全
linux·开发语言·arm开发·单片机
秀秀更健康4 小时前
STM32的程序下载不进去----VDDA悬空
stm32·单片机·嵌入式硬件
长安第一美人5 小时前
AI辅助下的嵌入式UI系统设计与实践(二)[代码阅读理解]
c++·嵌入式硬件·ui·显示屏·工业应用
我在人间贩卖青春6 小时前
DMA的应用
单片机·dma·gpdma
学嵌入式的小杨同学7 小时前
STM32 进阶封神之路(二十五):ESP8266 深度解析 —— 从 WiFi 通信原理到 AT 指令开发(底层逻辑 + 实战基础)
c++·vscode·stm32·单片机·嵌入式硬件·mcu·智能硬件
树爷只认钱7 小时前
ESP01S模块+串口底座 AT指令连接中移Onenet物联网全过程(第1篇)
单片机·嵌入式硬件·物联网·esp8266
学嵌入式的小杨同学7 小时前
STM32 进阶封神之路(二十六):ESP8266 实战全攻略 ——TCP 通信 + 数据上传 + 远程控制 + 透传模式(库函数 + 代码落地)
stm32·单片机·嵌入式硬件·mcu·硬件架构·硬件工程·智能硬件
Nice__J7 小时前
Mcu架构以及原理——7.寄存器编程与抽象
stm32·单片机·架构