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

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

一、系统概述

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

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

二、硬件设计

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 系统架构

graph TD
    A[上位机(PC/服务器)] -->|TCP发送固件| B[W5500以太网模块]
    B -->|SPI通信| C[STM32 Bootloader]
    C -->|擦除+写入| D[STM32 Flash App区]
    C -->|跳转| E[STM32 App程序]
    E -->|触发升级| C

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

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通信)

#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写入

#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程序

// 跳转到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示例

// 在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校验和。

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

 

专注于matlab/simulink,电子电路,编程