目录

STM32网络在线升级OTA完整实现指南

STM32网络在线升级(OTA)完整实现指南

STM32网络在线升级(OTA)完整实现指南

一、概述

STM32在线升级(OTA - Over The Air)是一种通过网络远程更新设备固件的技术。本文将详细介绍基于STM32F4系列的网络升级方案,包括Bootloader设计、Flash分区、升级流程和完整代码实现。

二、系统架构

2.1 Flash分区设计

STM32 Flash Memory Layout:
┌─────────────────────┐ 0x08000000
│    Bootloader       │ (32KB)
├─────────────────────┤ 0x08008000
│    Application      │ (224KB)
├─────────────────────┤ 0x08040000
│    OTA Buffer       │ (224KB)
├─────────────────────┤ 0x08078000
│    Config Data      │ (32KB)
└─────────────────────┘ 0x08080000

2.2 升级流程

  1. 正常运行:MCU从Bootloader启动,检查升级标志,跳转到Application
  2. 接收固件:Application通过网络接收新固件,存储到OTA Buffer
  3. 验证固件:CRC32校验,确保固件完整性
  4. 设置标志:写入升级标志,重启系统
  5. 执行升级:Bootloader检测到升级标志,将OTA Buffer内容拷贝到Application区域
  6. 跳转运行:清除标志,跳转到新Application

三、Bootloader实现

3.1 Bootloader主程序

// bootloader.c
#include "stm32f4xx_hal.h"
#include <string.h>

// Flash分区定义
#define BOOTLOADER_BASE     0x08000000
#define APPLICATION_BASE    0x08008000
#define OTA_BUFFER_BASE     0x08040000
#define CONFIG_BASE         0x08078000

#define APPLICATION_SIZE    (224 * 1024)
#define OTA_BUFFER_SIZE     (224 * 1024)
#define FLASH_PAGE_SIZE     2048

// 升级标志结构
typedef struct {
    uint32_t magic;         // 魔术字 0xDEADBEEF
    uint32_t update_flag;   // 升级标志 1:需要升级
    uint32_t firmware_size; // 固件大小
    uint32_t crc32;        // CRC32校验值
} ota_config_t;

// 函数声明
static void jump_to_application(uint32_t address);
static int check_application_valid(uint32_t address);
static HAL_StatusTypeDef flash_erase(uint32_t start_addr, uint32_t size);
static HAL_StatusTypeDef flash_write(uint32_t dest_addr, uint8_t *src, uint32_t size);
static uint32_t calculate_crc32(uint8_t *data, uint32_t size);
static int perform_firmware_update(void);

// CRC32计算
static uint32_t calculate_crc32(uint8_t *data, uint32_t size) {
    CRC_HandleTypeDef hcrc;
    uint32_t crc = 0;
    
    __HAL_RCC_CRC_CLK_ENABLE();
    hcrc.Instance = CRC;
    HAL_CRC_Init(&hcrc);
    
    crc = HAL_CRC_Calculate(&hcrc, (uint32_t*)data, size/4);
    return crc;
}

// 检查应用程序有效性
static int check_application_valid(uint32_t address) {
    uint32_t sp = *(__IO uint32_t*)address;
    
    // 检查栈指针是否在RAM范围内
    if ((sp & 0x2FFE0000) == 0x20000000) {
        return 1;
    }
    return 0;
}

// 跳转到应用程序
static void jump_to_application(uint32_t address) {
    typedef void (*pFunction)(void);
    pFunction jump_to_app;
    uint32_t jump_address;
    
    // 获取复位向量
    jump_address = *(__IO uint32_t*)(address + 4);
    jump_to_app = (pFunction)jump_address;
    
    // 设置主栈指针
    __set_MSP(*(__IO uint32_t*)address);
    
    // 关闭所有中断
    __disable_irq();
    
    // 复位所有外设
    HAL_DeInit();
    
    // 设置向量表
    SCB->VTOR = address;
    
    // 跳转到应用程序
    jump_to_app();
}

// Flash擦除
static HAL_StatusTypeDef flash_erase(uint32_t start_addr, uint32_t size) {
    FLASH_EraseInitTypeDef erase_init;
    uint32_t sector_error;
    uint32_t sector_start, sector_end;
    
    // 解锁Flash
    HAL_FLASH_Unlock();
    
    // 计算起始和结束扇区
    sector_start = (start_addr - FLASH_BASE) / FLASH_PAGE_SIZE;
    sector_end = (start_addr + size - FLASH_BASE) / FLASH_PAGE_SIZE;
    
    erase_init.TypeErase = FLASH_TYPEERASE_SECTORS;
    erase_init.Sector = sector_start;
    erase_init.NbSectors = sector_end - sector_start + 1;
    erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3;
    
    // 执行擦除
    if (HAL_FLASHEx_Erase(&erase_init, &sector_error) != HAL_OK) {
        HAL_FLASH_Lock();
        return HAL_ERROR;
    }
    
    HAL_FLASH_Lock();
    return HAL_OK;
}

// Flash写入
static HAL_StatusTypeDef flash_write(uint32_t dest_addr, uint8_t *src, uint32_t size) {
    uint32_t i;
    
    HAL_FLASH_Unlock();
    
    // 按字写入
    for (i = 0; i < size; i += 4) {
        if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, 
                             dest_addr + i, 
                             *(uint32_t*)(src + i)) != HAL_OK) {
            HAL_FLASH_Lock();
            return HAL_ERROR;
        }
    }
    
    HAL_FLASH_Lock();
    return HAL_OK;
}

// 执行固件更新
static int perform_firmware_update(void) {
    ota_config_t *config = (ota_config_t*)CONFIG_BASE;
    uint8_t *ota_data = (uint8_t*)OTA_BUFFER_BASE;
    uint32_t calculated_crc;
    
    // 检查魔术字
    if (config->magic != 0xDEADBEEF) {
        return -1;
    }
    
    // 检查更新标志
    if (config->update_flag != 1) {
        return -1;
    }
    
    // 验证CRC
    calculated_crc = calculate_crc32(ota_data, config->firmware_size);
    if (calculated_crc != config->crc32) {
        return -2;  // CRC错误
    }
    
    // 擦除应用程序区域
    if (flash_erase(APPLICATION_BASE, config->firmware_size) != HAL_OK) {
        return -3;
    }
    
    // 写入新固件
    if (flash_write(APPLICATION_BASE, ota_data, config->firmware_size) != HAL_OK) {
        return -4;
    }
    
    // 清除更新标志
    ota_config_t clear_config = {0};
    flash_erase(CONFIG_BASE, sizeof(ota_config_t));
    flash_write(CONFIG_BASE, (uint8_t*)&clear_config, sizeof(ota_config_t));
    
    return 0;
}

// Bootloader主函数
int main(void) {
    // HAL初始化
    HAL_Init();
    
    // 配置系统时钟
    SystemClock_Config();
    
    // 初始化LED用于状态指示
    __HAL_RCC_GPIOA_CLK_ENABLE();
    GPIO_InitTypeDef gpio_init = {0};
    gpio_init.Pin = GPIO_PIN_5;
    gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
    gpio_init.Pull = GPIO_NOPULL;
    gpio_init.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &gpio_init);
    
    // 检查是否需要升级
    ota_config_t *config = (ota_config_t*)CONFIG_BASE;
    if (config->magic == 0xDEADBEEF && config->update_flag == 1) {
        // LED快闪表示正在升级
        for (int i = 0; i < 10; i++) {
            HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
            HAL_Delay(100);
        }
        
        // 执行升级
        if (perform_firmware_update() == 0) {
            // 升级成功,LED常亮1秒
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
            HAL_Delay(1000);
        } else {
            // 升级失败,LED慢闪
            for (int i = 0; i < 5; i++) {
                HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
                HAL_Delay(500);
            }
        }
    }
    
    // 检查应用程序是否有效
    if (check_application_valid(APPLICATION_BASE)) {
        // 跳转到应用程序
        jump_to_application(APPLICATION_BASE);
    }
    
    // 如果无法跳转,LED持续慢闪
    while (1) {
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
        HAL_Delay(1000);
    }
}

四、Application实现

4.1 OTA管理模块

// ota_manager.c
#include "ota_manager.h"
#include "lwip/tcp.h"
#include "lwip/apps/http_client.h"
#include <string.h>

typedef struct {
    uint8_t *buffer;
    uint32_t current_size;
    uint32_t total_size;
    uint32_t flash_addr;
    ota_callback_t callback;
    uint8_t state;
} ota_context_t;

static ota_context_t ota_ctx = {0};

// HTTP下载回调
static void http_recv_callback(void *arg, struct tcp_pcb *tpcb, 
                              struct pbuf *p, err_t err) {
    if (p == NULL) {
        // 下载完成
        if (ota_ctx.callback) {
            ota_ctx.callback(OTA_EVENT_COMPLETE, ota_ctx.current_size);
        }
        return;
    }
    
    // 拷贝数据到缓冲区
    struct pbuf *q;
    uint32_t offset = 0;
    for (q = p; q != NULL; q = q->next) {
        memcpy(ota_ctx.buffer + ota_ctx.current_size + offset, 
               q->payload, q->len);
        offset += q->len;
    }
    
    ota_ctx.current_size += p->tot_len;
    
    // 每收到4KB数据写入Flash
    if (ota_ctx.current_size % 4096 == 0) {
        uint32_t write_size = 4096;
        flash_write(ota_ctx.flash_addr, 
                   ota_ctx.buffer + ota_ctx.current_size - write_size, 
                   write_size);
        ota_ctx.flash_addr += write_size;
        
        if (ota_ctx.callback) {
            ota_ctx.callback(OTA_EVENT_PROGRESS, ota_ctx.current_size);
        }
    }
    
    tcp_recved(tpcb, p->tot_len);
    pbuf_free(p);
}

// 初始化OTA
int ota_init(void) {
    ota_ctx.buffer = (uint8_t*)malloc(OTA_BUFFER_SIZE);
    if (ota_ctx.buffer == NULL) {
        return -1;
    }
    
    ota_ctx.state = OTA_STATE_IDLE;
    return 0;
}

// 开始OTA下载
int ota_start_download(const char *url, ota_callback_t callback) {
    if (ota_ctx.state != OTA_STATE_IDLE) {
        return -1;
    }
    
    ota_ctx.callback = callback;
    ota_ctx.current_size = 0;
    ota_ctx.flash_addr = OTA_BUFFER_BASE;
    ota_ctx.state = OTA_STATE_DOWNLOADING;
    
    // 擦除OTA缓冲区
    flash_erase(OTA_BUFFER_BASE, OTA_BUFFER_SIZE);
    
    // 开始HTTP下载
    httpc_connection_t conn_settings;
    memset(&conn_settings, 0, sizeof(conn_settings));
    
    err_t err = httpc_get_file(
        url,
        &conn_settings,
        http_recv_callback,
        NULL,
        &ota_ctx
    );
    
    if (err != ERR_OK) {
        ota_ctx.state = OTA_STATE_IDLE;
        return -2;
    }
    
    return 0;
}

// 验证固件
int ota_verify_firmware(void) {
    uint32_t crc = calculate_crc32((uint8_t*)OTA_BUFFER_BASE, 
                                   ota_ctx.current_size);
    
    // 这里应该与服务器提供的CRC比对
    // 简化起见,这里只检查固件头部
    uint32_t *fw_header = (uint32_t*)OTA_BUFFER_BASE;
    if ((fw_header[0] & 0x2FFE0000) != 0x20000000) {
        return -1;  // 无效的栈指针
    }
    
    return 0;
}

// 触发重启升级
int ota_trigger_update(void) {
    ota_config_t config;
    
    config.magic = 0xDEADBEEF;
    config.update_flag = 1;
    config.firmware_size = ota_ctx.current_size;
    config.crc32 = calculate_crc32((uint8_t*)OTA_BUFFER_BASE, 
                                   ota_ctx.current_size);
    
    // 写入配置
    flash_erase(CONFIG_BASE, sizeof(ota_config_t));
    flash_write(CONFIG_BASE, (uint8_t*)&config, sizeof(ota_config_t));
    
    // 系统复位
    HAL_NVIC_SystemReset();
    
    return 0;
}

4.2 Application主程序

// main_app.c
#include "stm32f4xx_hal.h"
#include "lwip.h"
#include "ota_manager.h"
#include <stdio.h>

// 版本信息
#define FIRMWARE_VERSION "1.0.0"
#define UPDATE_SERVER    "http://192.168.1.100:8080"
#define UPDATE_PATH      "/firmware/app.bin"

// 网络配置
static struct netif gnetif;
static ip4_addr_t ipaddr, netmask, gateway;

// OTA回调函数
static void ota_event_handler(ota_event_t event, uint32_t data) {
    switch (event) {
        case OTA_EVENT_START:
            printf("OTA: Download started\n");
            break;
            
        case OTA_EVENT_PROGRESS:
            printf("OTA: Progress %lu bytes\n", data);
            break;
            
        case OTA_EVENT_COMPLETE:
            printf("OTA: Download complete, total %lu bytes\n", data);
            
            // 验证固件
            if (ota_verify_firmware() == 0) {
                printf("OTA: Firmware verified, restarting...\n");
                HAL_Delay(1000);
                ota_trigger_update();
            } else {
                printf("OTA: Firmware verification failed!\n");
            }
            break;
            
        case OTA_EVENT_ERROR:
            printf("OTA: Error occurred\n");
            break;
    }
}

// 初始化以太网
static void ethernet_init(void) {
    // 初始化LwIP
    MX_LWIP_Init();
    
    // 设置静态IP
    IP4_ADDR(&ipaddr, 192, 168, 1, 100);
    IP4_ADDR(&netmask, 255, 255, 255, 0);
    IP4_ADDR(&gateway, 192, 168, 1, 1);
    
    netif_add(&gnetif, &ipaddr, &netmask, &gateway, NULL, 
              &ethernetif_init, &ethernet_input);
    netif_set_default(&gnetif);
    netif_set_up(&gnetif);
}

// HTTP服务器处理函数
static void http_server_handler(struct netconn *conn) {
    struct netbuf *inbuf;
    char *buf;
    u16_t len;
    
    if (netconn_recv(conn, &inbuf) == ERR_OK) {
        netbuf_data(inbuf, (void**)&buf, &len);
        
        // 解析HTTP请求
        if (strstr(buf, "GET /update") != NULL) {
            // 触发OTA更新
            char url[256];
            snprintf(url, sizeof(url), "%s%s", UPDATE_SERVER, UPDATE_PATH);
            
            const char *response = "HTTP/1.1 200 OK\r\n"
                                 "Content-Type: text/plain\r\n\r\n"
                                 "OTA update started\r\n";
            netconn_write(conn, response, strlen(response), NETCONN_COPY);
            
            // 启动OTA下载
            ota_start_download(url, ota_event_handler);
        } else if (strstr(buf, "GET /version") != NULL) {
            // 返回版本信息
            char response[256];
            snprintf(response, sizeof(response),
                    "HTTP/1.1 200 OK\r\n"
                    "Content-Type: text/plain\r\n\r\n"
                    "Firmware Version: %s\r\n", FIRMWARE_VERSION);
            netconn_write(conn, response, strlen(response), NETCONN_COPY);
        }
        
        netbuf_delete(inbuf);
    }
    
    netconn_close(conn);
    netconn_delete(conn);
}

// HTTP服务器任务
static void http_server_task(void *arg) {
    struct netconn *conn, *newconn;
    
    conn = netconn_new(NETCONN_TCP);
    netconn_bind(conn, NULL, 80);
    netconn_listen(conn);
    
    while (1) {
        if (netconn_accept(conn, &newconn) == ERR_OK) {
            http_server_handler(newconn);
        }
    }
}

// 主函数
int main(void) {
    // 重定位中断向量表
    SCB->VTOR = APPLICATION_BASE;
    
    // HAL初始化
    HAL_Init();
    SystemClock_Config();
    
    // 初始化调试串口
    uart_init();
    printf("\n\nApplication Started!\n");
    printf("Firmware Version: %s\n", FIRMWARE_VERSION);
    printf("Application Base: 0x%08X\n", APPLICATION_BASE);
    
    // 初始化网络
    ethernet_init();
    printf("Network initialized\n");
    
    // 初始化OTA管理器
    ota_init();
    printf("OTA manager initialized\n");
    
    // 创建HTTP服务器任务
    xTaskCreate(http_server_task, "HTTP_Server", 
                512, NULL, 1, NULL);
    
    // 启动FreeRTOS调度器
    vTaskStartScheduler();
    
    while (1) {
        // 不应该到达这里
    }
}

五、编译配置

5.1 链接脚本修改

Bootloader链接脚本 (bootloader.ld):

MEMORY
{
  FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 32K
  RAM (xrw)   : ORIGIN = 0x20000000, LENGTH = 128K
}

SECTIONS
{
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector))
    . = ALIGN(4);
  } >FLASH
  
  .text :
  {
    . = ALIGN(4);
    *(.text)
    *(.text*)
    *(.rodata)
    *(.rodata*)
    . = ALIGN(4);
  } >FLASH
}

Application链接脚本 (application.ld):

MEMORY
{
  FLASH (rx)  : ORIGIN = 0x08008000, LENGTH = 224K
  RAM (xrw)   : ORIGIN = 0x20000000, LENGTH = 128K
}

SECTIONS
{
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector))
    . = ALIGN(4);
  } >FLASH
  
  .text :
  {
    . = ALIGN(4);
    *(.text)
    *(.text*)
    *(.rodata)
    *(.rodata*)
    . = ALIGN(4);
  } >FLASH
}

5.2 Makefile配置

# Makefile for STM32 OTA Project

# 工具链
CC = arm-none-eabi-gcc
AS = arm-none-eabi-as
LD = arm-none-eabi-ld
OBJCOPY = arm-none-eabi-objcopy
SIZE = arm-none-eabi-size

# 编译选项
CFLAGS = -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16
CFLAGS += -DSTM32F407xx -DUSE_HAL_DRIVER
CFLAGS += -Os -g -Wall
CFLAGS += -I./Inc -I./Drivers/STM32F4xx_HAL_Driver/Inc

# 链接选项
LDFLAGS = -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16
LDFLAGS += -specs=nosys.specs -specs=nano.specs

# Bootloader构建
bootloader: 
	$(CC) $(CFLAGS) -c bootloader.c -o bootloader.o
	$(CC) $(LDFLAGS) -T bootloader.ld bootloader.o -o bootloader.elf
	$(OBJCOPY) -O binary bootloader.elf bootloader.bin
	$(SIZE) bootloader.elf

# Application构建
application:
	$(CC) $(CFLAGS) -c main_app.c -o main_app.o
	$(CC) $(CFLAGS) -c ota_manager.c -o ota_manager.o
	$(CC) $(LDFLAGS) -T application.ld main_app.o ota_manager.o -o application.elf
	$(OBJCOPY) -O binary application.elf application.bin
	$(SIZE) application.elf

# 合并固件
firmware: bootloader application
	cat bootloader.bin application.bin > firmware.bin

# 清理
clean:
	rm -f *.o *.elf *.bin

.PHONY: bootloader application firmware clean

六、服务器端实现

6.1 Python OTA服务器

# ota_server.py
from flask import Flask, send_file, jsonify
import hashlib
import os

app = Flask(__name__)

FIRMWARE_DIR = './firmware'
CURRENT_VERSION = '1.0.0'

@app.route('/firmware/<filename>')
def download_firmware(filename):
    """下载固件文件"""
    file_path = os.path.join(FIRMWARE_DIR, filename)
    if os.path.exists(file_path):
        return send_file(file_path, as_attachment=True)
    return "File not found", 404

@app.route('/version')
def get_version():
    """获取最新版本信息"""
    return jsonify({
        'version': CURRENT_VERSION,
        'url': f'http://192.168.1.100:8080/firmware/app.bin',
        'size': os.path.getsize(os.path.join(FIRMWARE_DIR, 'app.bin')),
        'md5': calculate_md5(os.path.join(FIRMWARE_DIR, 'app.bin'))
    })

@app.route('/check_update/<device_version>')
def check_update(device_version):
    """检查是否需要更新"""
    if device_version < CURRENT_VERSION:
        return jsonify({
            'update_available': True,
            'new_version': CURRENT_VERSION,
            'url': f'http://192.168.1.100:8080/firmware/app.bin'
        })
    return jsonify({'update_available': False})

def calculate_md5(file_path):
    """计算文件MD5"""
    hash_md5 = hashlib.md5()
    with open(file_path, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

if __name__ == '__main__':
    os.makedirs(FIRMWARE_DIR, exist_ok=True)
    app.run(host='0.0.0.0', port=8080, debug=True)

七、使用说明

7.1 编译步骤

  1. 编译Bootloader:
make bootloader
  1. 编译Application:
make application
  1. 生成完整固件:
make firmware

7.2 烧录步骤

  1. 首次烧录:

    • 使用ST-Link或J-Link烧录firmware.bin到0x08000000地址
  2. OTA升级:

    • 启动OTA服务器:python ota_server.py
    • 将新的application.bin放到firmware目录
    • 通过HTTP触发升级:curl http://设备IP/update

7.3 测试流程

  1. 版本查询:
curl http://192.168.1.100/version
  1. 触发升级:
curl http://192.168.1.100/update
  1. 监控串口输出,观察升级过程

八、安全性增强

8.1 固件签名验证

// 使用RSA或ECDSA签名验证
typedef struct {
    uint8_t signature[256];  // RSA-2048签名
    uint32_t firmware_size;
    uint32_t version;
    uint32_t timestamp;
} firmware_header_t;

int verify_firmware_signature(uint8_t *firmware, uint32_t size) {
    firmware_header_t *header = (firmware_header_t*)firmware;
    
    // 使用公钥验证签名
    // 这里需要集成加密库如mbedTLS
    
    return 0;
}

8.2 加密传输

  • 使用HTTPS代替HTTP
  • 实现AES加密的固件传输
  • 添加防回滚机制

九、优化建议

  1. 双备份机制:实现A/B分区,保证升级失败可回滚
  2. 断点续传:支持升级中断后继续下载
  3. 差分升级:只传输变化的部分,减少流量
  4. 压缩传输:使用LZMA等算法压缩固件
  5. 版本管理:实现版本号比对和强制/可选升级策略

十、常见问题

Q1: 升级后无法启动?

  • 检查链接脚本地址是否正确
  • 验证中断向量表重定位
  • 确认Flash写入是否成功

Q2: 下载过程中断?

  • 增加超时重试机制
  • 实现断点续传功能
  • 检查网络稳定性

Q3: Flash写入失败?

  • 确认Flash解锁
  • 检查擦除是否完成
  • 验证写入地址对齐

总结

本文详细介绍了STM32网络在线升级的完整实现方案,包括Bootloader设计、Application实现、服务器端开发等关键技术。通过合理的Flash分区设计和可靠的升级流程,可以实现稳定的OTA功能。在实际应用中,还需要根据具体需求添加安全验证、错误处理等机制,确保升级过程的安全可靠。