基于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校验保证固件完整性,可扩展支持断点续传、加密传输等高级功能。

相关推荐
不做无法实现的梦~15 小时前
86步进电机和DM860H驱动器的使用方法和记录
单片机·嵌入式硬件
Aaron158815 小时前
RFSOC+VU13P/VU9P+GPU多通道同步一体化解决方案
人工智能·嵌入式硬件·算法·matlab·fpga开发·硬件架构·基带工程
所见即所得1111116 小时前
stm32烧录过程中串口问题(串口被占用无法使用)
stm32·单片机·嵌入式硬件
Freak嵌入式16 小时前
WIZnet-EVB-Pico2开始,用MicroPython玩转以太网开发
arm开发·人工智能·python·嵌入式硬件·机器人·嵌入式·micropython
Ligocious16 小时前
stm32---1.两种开发方式点亮LED
stm32·单片机
黑白园17 小时前
STM32F103C8TC使用ST-Link下载
stm32·单片机·嵌入式硬件
时空自由民.17 小时前
嵌入式MCU的中断系统工作流程及其原理
单片机·嵌入式硬件
LCG元17 小时前
STM32实战:基于STM32F103的智慧教室环境监控系统(CO₂+光照+人数统计)
前端·stm32·嵌入式硬件
振南的单片机世界17 小时前
推挽输出:上管推、下管拉,驱动强但不“合群”
arm开发·stm32·单片机·嵌入式硬件
森利威尔电子-18 小时前
森利威尔SL7140|2.5–24V 宽压 / 10mA–2A / PWM 调光 线性 LED 恒流驱动
单片机·嵌入式硬件·集成电路·芯片·电源芯片