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

相关推荐
LCG元2 小时前
STM32实战:基于STM32CubeMX的串口通信(UART)与DMA传输优化
stm32·单片机·嵌入式硬件
qq_150841992 小时前
用Simplicity Studio开发EFM8单片机(续)
单片机·嵌入式硬件
yong99903 小时前
基于STM32与TFTLCD的示波器设计
stm32·单片机·嵌入式硬件
我叫洋洋3 小时前
[ESP32-S3 点亮灯]
单片机·嵌入式硬件·esp32
搁浅小泽4 小时前
可靠性试验测试时间制定方法简介
单片机·嵌入式硬件·可靠性工程师
yoyobravery4 小时前
蓝桥杯第13届单片机(满分)
单片机·蓝桥杯
清风6666664 小时前
基于单片机的正弦波与方波峰峰值与频率测量系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
gihigo19984 小时前
基于51单片机的TB6600步进电机驱动程序
stm32·单片机·51单片机
搁浅小泽4 小时前
变频空调检修完整流程(通用版)
单片机·嵌入式硬件·可靠性工程师