基于STM32+W5500的IAP远程升级程序设计

一、系统概述

IAP(In-Application Programming) 是一种允许设备在应用程序运行时通过外部接口(如以太网、串口)更新固件的技术。本方案基于STM32F103C8T6(Cortex-M3,64KB Flash)和W5500以太网模块,实现通过TCP/IP协议远程升级固件,核心功能包括:

  • Bootloader程序:运行于STM32 Flash起始区域,负责接收新固件、校验、擦除App区域并写入;

  • App程序:运行于Flash后续区域,实现正常业务逻辑(如传感器采集),支持触发升级(如收到远程指令后跳转至Bootloader);

  • 网络通信:W5500通过SPI接口与STM32通信,基于TCP协议(如Server模式)接收上位机发送的固件数据(二进制文件)。

应用场景:工业设备远程维护、物联网终端固件更新、智能家居设备升级等。

二、硬件设计

2.1 核心组件选型

模块 型号/参数 功能说明
主控 STM32F103C8T6(64KB Flash,20KB RAM) Bootloader与App运行载体,SPI控制W5500
以太网模块 W5500(硬件TCP/IP协议栈,SPI接口) 实现以太网通信(MAC+PHY集成,支持10/100M)
存储 STM32内部Flash(64KB) 划分Bootloader区(16KB)、App区(48KB)
电源 3.3V/500mA DC(AMS1117-3.3V) 为STM32和W5500供电(W5500需独立3.3V)

2.2 硬件连接

模块 STM32引脚(F103C8T6) W5500引脚 说明
SPI接口 PA5(SCK) SCK SPI时钟线
PA7(MOSI) MOSI SPI数据输入(W5500输出)
PA6(MISO) MISO SPI数据输出(W5500输入)
PA4(NSS) SCS 片选(低有效)
中断 PB0(EXTI0) INT W5500中断输出(低有效)
电源 3.3V VCC 3.3V供电
GND GND 共地

三、软件设计(STM32 HAL库)

3.1 系统架构

TCP发送固件
SPI通信
擦除+写入
跳转
触发升级
上位机(PC/服务器)
W5500以太网模块
STM32 Bootloader
STM32 Flash App区
STM32 App程序

Flash分区规划(以64KB Flash为例):

  • Bootloader区:0x08000000 ~ 0x08003FFF(16KB,存放IAP引导程序);

  • App区:0x08004000 ~ 0x0800FFFF(48KB,存放用户应用程序);

  • 参数区:0x0800F000 ~ 0x0800FFFF(4KB,可选,存放升级标志、网络参数)。

3.2 Bootloader程序设计(核心)

Bootloader负责网络通信、固件接收、Flash写入,流程如下:

  1. 初始化:系统时钟、SPI、W5500、UART(调试用);

  2. 网络配置:设置W5500为TCP Server模式(IP:192.168.1.100,端口:8080);

  3. 等待连接:监听TCP连接,接收上位机发送的固件数据(按帧传输,含校验和);

  4. 固件校验:验证CRC32校验和,确保数据完整;

  5. Flash擦除:擦除App区(0x08004000 ~ 0x0800FFFF);

  6. 写入固件:将接收的固件数据写入App区(按半字/字操作);

  7. 跳转App:校验通过后,跳转到App区执行(修改SP和PC指针)。

3.2.1 W5500驱动(SPI通信)
c 复制代码
#include "w5500.h"
#include "spi.h"
#include "gpio.h"

// W5500寄存器定义
#define W5500_MR     0x0000  // 模式寄存器
#define W5500_GAR    0x0001  // 网关地址寄存器
#define W5500_SUBR   0x0005  // 子网掩码寄存器
#define W5500_SHAR   0x0009  // 源MAC地址寄存器
#define W5500_SIPR   0x000F  // 源IP地址寄存器
#define W5500_SPORT  0x0014  // 源端口寄存器
#define W5500_RCR    0x0016  // 重试计数寄存器
#define W5500_RTR    0x0017  // 重试超时寄存器

// 写W5500寄存器(8位)
void W5500_WriteReg8(uint8_t reg, uint8_t val) {
  HAL_GPIO_WritePin(W5500_CS_GPIO_Port, W5500_CS_Pin, GPIO_PIN_RESET);
  uint8_t tx[2] = {reg, val};
  HAL_SPI_Transmit(&hspi1, tx, 2, 100);
  HAL_GPIO_WritePin(W5500_CS_GPIO_Port, W5500_CS_Pin, GPIO_PIN_SET);
}

// 读W5500寄存器(8位)
uint8_t W5500_ReadReg8(uint8_t reg) {
  uint8_t tx[2] = {reg | 0x80, 0x00};  // 读命令(最高位1)
  uint8_t rx[2] = {0};
  HAL_GPIO_WritePin(W5500_CS_GPIO_Port, W5500_CS_Pin, GPIO_PIN_RESET);
  HAL_SPI_TransmitReceive(&hspi1, tx, rx, 2, 100);
  HAL_GPIO_WritePin(W5500_CS_GPIO_Port, W5500_CS_Pin, GPIO_PIN_SET);
  return rx[1];
}

// 初始化W5500(TCP Server模式)
void W5500_Init(void) {
  // 复位W5500
  HAL_GPIO_WritePin(W5500_RST_GPIO_Port, W5500_RST_Pin, GPIO_PIN_RESET);
  HAL_Delay(10);
  HAL_GPIO_WritePin(W5500_RST_GPIO_Port, W5500_RST_Pin, GPIO_PIN_SET);
  HAL_Delay(100);

  // 配置网络参数(静态IP)
  W5500_WriteReg8(W5500_MR, 0x08);  // 软复位
  uint8_t mac[6] = {0x00, 0x08, 0xDC, 0x12, 0x34, 0x56};  // MAC地址
  for (int i=0; i<6; i++) W5500_WriteReg8(W5500_SHAR+i, mac[i]);
  uint8_t ip[4] = {192, 168, 1, 100};  // IP地址
  for (int i=0; i<4; i++) W5500_WriteReg8(W5500_SIPR+i, ip[i]);
  uint8_t sub[4] = {255, 255, 255, 0};  // 子网掩码
  for (int i=0; i<4; i++) W5500_WriteReg8(W5500_SUBR+i, sub[i]);
  uint8_t gw[4] = {192, 168, 1, 1};  // 网关
  for (int i=0; i<4; i++) W5500_WriteReg8(W5500_GAR+i, gw[i]);

  // 配置Socket 0为TCP Server(端口8080)
  W5500_WriteReg8(W5500_SPORT, 8080 >> 8);  // 端口高8位
  W5500_WriteReg8(W5500_SPORT+1, 8080 & 0xFF);  // 端口低8位
  W5500_WriteReg8(W5500_RCR, 8);  // 重试计数8次
  W5500_WriteReg8(W5500_RTR, 200);  // 重试超时200ms
}
3.2.2 固件接收与Flash写入
c 复制代码
#include "flash.h"
#include "crc32.h"

#define APP_START_ADDR 0x08004000  // App区起始地址
#define APP_SIZE       48 * 1024      // App区大小(48KB)
#define BUFFER_SIZE    1024         // 接收缓冲区大小

uint8_t rx_buffer[BUFFER_SIZE];
uint32_t app_addr = APP_START_ADDR;  // 当前写入地址

// 擦除App区Flash
void Erase_App_Flash(void) {
  FLASH_EraseInitTypeDef erase;
  uint32_t sector_error;
  erase.TypeErase = FLASH_TYPEERASE_SECTORS;
  erase.Sector = FLASH_SECTOR_1;  // 扇区1(0x08004000起,64KB芯片扇区1为16KB,需擦除3个扇区:1-3)
  erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;
  erase.NbSectors = 3;  // 16KB*3=48KB
  HAL_FLASH_Unlock();
  HAL_FLASHEx_Erase(&erase, &sector_error);
  HAL_FLASH_Lock();
}

// 写入数据到App区Flash
void Write_App_Flash(uint32_t addr, uint8_t *data, uint16_t len) {
  HAL_FLASH_Unlock();
  for (uint16_t i=0; i<len; i+=2) {  // 按半字写入
    uint16_t val = (data[i+1] << 8) | data[i];
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr+i, val);
  }
  HAL_FLASH_Lock();
}

// 接收固件数据并写入Flash(TCP Server模式)
void Receive_Firmware(void) {
  uint32_t total_len = 0;  // 固件总长度
  uint32_t crc_received = 0;  // 接收到的CRC32
  uint32_t crc_calculated = 0;  // 计算的CRC32

  // 1. 接收固件头(4字节长度+4字节CRC32)
  W5500_TCP_Receive(rx_buffer, 8);  // 自定义TCP接收函数
  total_len = *(uint32_t*)rx_buffer;
  crc_received = *(uint32_t*)(rx_buffer+4);

  // 2. 接收固件数据并写入Flash
  app_addr = APP_START_ADDR;
  while (total_len > 0) {
    uint16_t len = (total_len > BUFFER_SIZE) ? BUFFER_SIZE : total_len;
    W5500_TCP_Receive(rx_buffer, len);  // 接收数据
    Write_App_Flash(app_addr, rx_buffer, len);  // 写入Flash
    crc_calculated = CRC32_Update(crc_calculated, rx_buffer, len);  // 更新CRC
    app_addr += len;
    total_len -= len;
  }

  // 3. 校验CRC32
  if (crc_calculated != crc_received) {
    // 校验失败,擦除App区并返回错误
    Erase_App_Flash();
    return 1;  // 失败
  }
  return 0;  // 成功
}
3.2.3 跳转到App程序
c 复制代码
// 跳转到App区执行
void JumpToApp(void) {
  typedef void (*AppFunc_t)(void);
  AppFunc_t app_func;
  uint32_t app_addr = APP_START_ADDR;

  // 检查App区是否有合法栈顶地址(0x20000000以上)
  if (((*(__IO uint32_t*)app_addr) & 0x2FFE0000) == 0x20000000) {
    // 设置栈顶指针
    __set_MSP(*(__IO uint32_t*)app_addr);
    // 获取App入口地址(复位中断向量)
    app_func = (AppFunc_t)(*(__IO uint32_t*)(app_addr + 4));
    // 跳转
    app_func();
  }
}

3.3 App程序设计

App程序需实现正常业务逻辑,并支持触发升级(如通过UART指令或网络指令跳转至Bootloader)。关键是编译时设置正确的链接脚本,确保App的起始地址为0x08004000,中断向量表偏移量为0x4000(16KB)。

App跳转至Bootloader示例

c 复制代码
// 在App中触发升级(如收到网络指令后)
void Trigger_Upgrade(void) {
  // 设置升级标志(存储在参数区Flash)
  uint32_t flag_addr = 0x0800F000;
  HAL_FLASH_Unlock();
  HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, flag_addr, 0xAA55AA55);  // 写入升级标志
  HAL_FLASH_Lock();
  
  // 跳转至Bootloader(软复位)
  NVIC_SystemReset();
}

四、上位机软件(Python示例)

上位机通过TCP发送固件文件(二进制),包含固件长度和CRC32校验和。

python 复制代码
import socket
import struct
import binascii

def send_firmware(ip, port, file_path):
    # 读取固件文件
    with open(file_path, 'rb') as f:
        firmware = f.read()
    # 计算CRC32校验和
    crc = binascii.crc32(firmware) & 0xFFFFFFFF
    # 构造固件头(4字节长度+4字节CRC)
    header = struct.pack('>II', len(firmware), crc)
    # 建立TCP连接
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((ip, port))
    # 发送固件头
    sock.send(header)
    # 发送固件数据
    sock.send(firmware)
    sock.close()

# 使用示例:发送固件到192.168.1.100:8080
send_firmware('192.168.1.100', 8080, 'app.bin')

参考代码 stm32 IAP远程升级程序 STM32+W5500+IAP www.youwenfan.com/contentcst/182193.html

五、测试与验证

  1. 硬件连接:按2.2节连接STM32、W5500、电源,确保网线接入路由器(与PC同网段)。

  2. Bootloader测试 :上电后W5500指示灯闪烁,用网络调试助手(如NetAssist)连接192.168.1.100:8080,发送固件文件,观察Bootloader是否接收并写入Flash。

  3. App测试:升级后,App应正常运行(如LED闪烁),通过指令触发再次升级,验证循环升级功能。

六、总结

基于STM32+W5500实现了IAP远程升级,核心是Bootloader网络接收、Flash安全写入与App跳转。通过TCP协议确保数据传输可靠,CRC32校验保证固件完整性,可扩展支持断点续传、加密传输等高级功能。

相关推荐
[J] 一坚6 小时前
嵌入式高手C
c语言·开发语言·stm32·单片机·mcu·51单片机·iot
FreakStudio6 小时前
和做工厂系统的印尼老哥,复刻了一套属于 MicroPython 的包管理系统
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机
HIZYUAN7 小时前
AG32 MCU Reference Manual(202401008修订版)使用手册
单片机·嵌入式硬件
guygg888 小时前
STM32 汉字显示程序(标准外设库版本)
stm32·单片机·嵌入式硬件
Strange_Head13 小时前
补充知识点`makefile`、`config`、`GLP协议` 1/3 ——《驱动篇》
linux·嵌入式硬件
国科安芯14 小时前
商业航天与航空安全场景下抗辐射 MCU 选型、应用实践及发展趋势
单片机·嵌入式硬件·无人机·cocos2d·risc-v
Jun62616 小时前
【STM32】HMC5883驱动(带航向角计算)
stm32·单片机·嵌入式硬件
wearegogog12317 小时前
基于STM32的酒精检测仪设计
stm32·单片机·嵌入式硬件
Deitymoon17 小时前
STM32——led灯点亮
stm32·单片机·嵌入式硬件
Jack_022017 小时前
基于51单片机的双路倒车雷达测距报警系统设计_LCD1602显示+超声波
单片机·51单片机·雷达·超声波·倒车·lcd1602显示