一、系统概述
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写入,流程如下:
-
初始化:系统时钟、SPI、W5500、UART(调试用);
-
网络配置:设置W5500为TCP Server模式(IP:192.168.1.100,端口:8080);
-
等待连接:监听TCP连接,接收上位机发送的固件数据(按帧传输,含校验和);
-
固件校验:验证CRC32校验和,确保数据完整;
-
Flash擦除:擦除App区(0x08004000 ~ 0x0800FFFF);
-
写入固件:将接收的固件数据写入App区(按半字/字操作);
-
跳转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, §or_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
五、测试与验证
-
硬件连接:按2.2节连接STM32、W5500、电源,确保网线接入路由器(与PC同网段)。
-
Bootloader测试 :上电后W5500指示灯闪烁,用网络调试助手(如NetAssist)连接
192.168.1.100:8080,发送固件文件,观察Bootloader是否接收并写入Flash。 -
App测试:升级后,App应正常运行(如LED闪烁),通过指令触发再次升级,验证循环升级功能。
六、总结
基于STM32+W5500实现了IAP远程升级,核心是Bootloader网络接收、Flash安全写入与App跳转。通过TCP协议确保数据传输可靠,CRC32校验保证固件完整性,可扩展支持断点续传、加密传输等高级功能。