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 升级流程
- 正常运行:MCU从Bootloader启动,检查升级标志,跳转到Application
- 接收固件:Application通过网络接收新固件,存储到OTA Buffer
- 验证固件:CRC32校验,确保固件完整性
- 设置标志:写入升级标志,重启系统
- 执行升级:Bootloader检测到升级标志,将OTA Buffer内容拷贝到Application区域
- 跳转运行:清除标志,跳转到新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, §or_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,
ðernetif_init, ðernet_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 编译步骤
- 编译Bootloader:
make bootloader
- 编译Application:
make application
- 生成完整固件:
make firmware
7.2 烧录步骤
首次烧录:
- 使用ST-Link或J-Link烧录
firmware.bin
到0x08000000地址
- 使用ST-Link或J-Link烧录
OTA升级:
- 启动OTA服务器:
python ota_server.py
- 将新的application.bin放到firmware目录
- 通过HTTP触发升级:
curl http://设备IP/update
- 启动OTA服务器:
7.3 测试流程
- 版本查询:
curl http://192.168.1.100/version
- 触发升级:
curl http://192.168.1.100/update
- 监控串口输出,观察升级过程
八、安全性增强
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加密的固件传输
- 添加防回滚机制
九、优化建议
- 双备份机制:实现A/B分区,保证升级失败可回滚
- 断点续传:支持升级中断后继续下载
- 差分升级:只传输变化的部分,减少流量
- 压缩传输:使用LZMA等算法压缩固件
- 版本管理:实现版本号比对和强制/可选升级策略
十、常见问题
Q1: 升级后无法启动?
- 检查链接脚本地址是否正确
- 验证中断向量表重定位
- 确认Flash写入是否成功
Q2: 下载过程中断?
- 增加超时重试机制
- 实现断点续传功能
- 检查网络稳定性
Q3: Flash写入失败?
- 确认Flash解锁
- 检查擦除是否完成
- 验证写入地址对齐
总结
本文详细介绍了STM32网络在线升级的完整实现方案,包括Bootloader设计、Application实现、服务器端开发等关键技术。通过合理的Flash分区设计和可靠的升级流程,可以实现稳定的OTA功能。在实际应用中,还需要根据具体需求添加安全验证、错误处理等机制,确保升级过程的安全可靠。