STM32 USB ISP 升级深度解析
一、STM32 存储架构与启动机制
1. STM32 存储区域划分
- 主闪存存储器 (Flash Memory):用户代码存储区,通常为 128KB-2MB 不等
- 系统存储器 (System Memory):固化的 Bootloader 区域,由 ST 官方预编程
- 内置 SRAM:程序运行时的临时数据存储区
- 选项字节 (Option Bytes):存储芯片配置信息,如读写保护、启动模式等
2. 启动模式选择
STM32 通过 BOOT0 和 BOOT1 引脚组合决定启动位置:
- BOOT0=0, BOOT1=X → 从主闪存启动 (正常运行模式)
- BOOT0=1, BOOT1=0 → 从系统存储器启动 (ISP 模式)
- BOOT0=1, BOOT1=1 → 从内置 SRAM 启动 (通常用于调试)
3. 系统存储器 Bootloader 功能
ST 官方 Bootloader 支持的通信接口:
- USART1/2/3
- CAN
- I2C
- USB DFU(Device Firmware Upgrade)
- SWIM (针对 STM8)
二、ISP 技术原理详解
1. 底层通信原理
- 协议层:基于异步串行通信,通常采用 8N1 格式 (8 数据位、无校验、1 停止位)
- 物理层:通过 USB 转串口芯片 (如 CH340、CP2102) 或 STM32 内置 USB 功能实现
- 波特率选择:典型值为 115200bps,部分 STM32 支持高达 2Mbps 的速率
2. 命令帧格式
ISP 通信基于特定命令集,每个命令帧格式为:
plaintext
[同步字节][命令][数据长度][数据][校验和]
- 同步字节:0x7F (首次通信) 或 0xFF (后续通信)
- 命令:如 0x01 (获取版本)、0x11 (擦除扇区) 等
- 数据长度:命令参数的字节数
- 校验和:前面所有字节的异或结果
3. 通信流程
plaintext
主机 -> 设备: 同步字节(0x7F)
设备 -> 主机: 应答(0x79)
主机 -> 设备: 命令帧
设备 -> 主机: 应答帧
4. 安全机制
- 校验和验证:确保数据完整性
- 超时机制:防止通信阻塞
- 状态反馈:通过应答码确认操作结果
三、USB 虚拟串口 (VCP) 实现原理
1. USB 协议栈结构
STM32 USB 协议栈分层:
- 物理层:USB 收发器硬件
- 底层驱动:USB 控制器寄存器操作
- 协议层:处理 USB 标准请求
- 类驱动:VCP 功能实现
- 应用层:用户代码接口
2. CDC 类协议
VCP 基于 CDC (通信设备类) 协议,包含两个接口:
- 通信接口:控制通道,用于 AT 命令
- 数据接口:数据通道,用于实际数据传输
3. 数据传输流程
- 主机枚举设备并加载 CDC 驱动
- 建立通信接口 (控制端点) 和数据接口 (IN/OUT 端点)
- 应用程序通过 COM 端口读写数据
- 数据在端点缓冲区和用户缓冲区间传输
4. 关键寄存器配置
- USB_CNTR:控制寄存器
- USB_ISTR:中断状态寄存器
- USB_EPnR:端点寄存器 (n=0~7)
四、代码实现细节解析
1. 初始化阶段
// 时钟配置
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE);
// USB 时钟设置
USB_ClockConfig();
// 初始化 USB 核心
USB_Init();
// 初始化传输协议
TransportProtocol_Init(&TransportProtocol, uu_rxfifo.buffer, Checksum_Sum);
// 初始化接收缓冲区
memset(uu_rxfifo.buffer, 0, sizeof(uu_rxfifo.buffer));
uu_rxfifo.writeptr = 0;
uu_rxfifo.readptr = 0;
2. 接收数据处理
void USB_ReceiveData(void)
{
uint8_t data;
uint16_t count = CDC_Receive_DATA(&data, 1);
if(count)
{
// 存储接收到的数据
uu_rxfifo.buffer[uu_rxfifo.writeptr] = data;
uu_rxfifo.writeptr = (uu_rxfifo.writeptr + 1) % FIFO_SIZE;
}
}
3. 协议解包实现
uint8_t TransportProtocol_Unpacked(void)
{
uint8_t i, checksum = 0;
// 检查帧头
if(TransportProtocol.Buffer[0] != 0xAA || TransportProtocol.Buffer[1] != 0x55)
return UNPACKED_ERROR_HEADER;
// 计算数据长度
TransportProtocol.Data_Length = TransportProtocol.Buffer[2];
// 检查数据长度合法性
if(TransportProtocol.Data_Length > MAX_DATA_LENGTH)
return UNPACKED_ERROR_LENGTH;
// 获取功能码
TransportProtocol.Function_Type = TransportProtocol.Buffer[3];
// 复制数据部分
for(i = 0; i < TransportProtocol.Data_Length; i++)
{
TransportProtocol.Data[i] = TransportProtocol.Buffer[4 + i];
}
// 计算校验和
for(i = 0; i < 4 + TransportProtocol.Data_Length; i++)
{
checksum += TransportProtocol.Buffer[i];
}
// 校验和验证
if(checksum != TransportProtocol.Buffer[4 + TransportProtocol.Data_Length])
return UNPACKED_ERROR_CHECKSUM;
return UNPACKED_SUCCESS;
}
4. Flash 操作函数
// Flash 解锁
void FLASH_Unlock(void)
{
if((FLASH->CR & FLASH_CR_LOCK) != 0)
{
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
}
}
// 擦除扇区
uint8_t FLASH_EraseSector(uint32_t SectorAddr)
{
uint32_t timeout = FLASH_TIMEOUT_VALUE;
// 检查是否忙
if((FLASH->SR & FLASH_SR_BSY) != 0)
return FLASH_BUSY;
// 设置擦除命令
FLASH->CR |= FLASH_CR_PER;
FLASH->AR = SectorAddr;
FLASH->CR |= FLASH_CR_STRT;
// 等待操作完成
while(((FLASH->SR & FLASH_SR_BSY) != 0) && (timeout-- > 0));
// 清除标志位
FLASH->CR &= ~FLASH_CR_PER;
if(timeout == 0)
return FLASH_TIMEOUT;
return FLASH_COMPLETE;
}
// 写入数据
uint8_t FLASH_WriteBuffer(uint32_t Address, uint8_t* Buffer, uint16_t Length)
{
uint32_t i = 0;
uint32_t timeout = FLASH_TIMEOUT_VALUE;
// 解锁Flash
FLASH_Unlock();
// 按字(2字节)写入
for(i = 0; i < Length; i += 2)
{
// 等待上一次操作完成
while(((FLASH->SR & FLASH_SR_BSY) != 0) && (timeout-- > 0));
if(timeout == 0)
return FLASH_TIMEOUT;
// 设置编程命令
FLASH->CR |= FLASH_CR_PG;
// 写入半字
if(i + 1 < Length)
{
*(__IO uint16_t*)Address = ((uint16_t)Buffer[i + 1] << 8) | Buffer[i];
}
else
{
*(__IO uint16_t*)Address = Buffer[i];
}
Address += 2;
// 等待操作完成
while(((FLASH->SR & FLASH_SR_BSY) != 0) && (timeout-- > 0));
// 清除编程标志
FLASH->CR &= ~FLASH_CR_PG;
if(timeout == 0)
return FLASH_TIMEOUT;
}
// 锁定Flash
FLASH_Lock();
return FLASH_COMPLETE;
}
5. 主程序逻辑
int main(void)
{
uint8_t res;
uint16_t oldcount = 0;
uint32_t Flash_App_Pos = FIRMWARE_START_ADDR;
uint8_t IsTransportOK = 0;
uint16_t revcnt = 0;
uint8_t iap_buf[IAP_BUFFER_SIZE];
// 系统初始化
SystemInit();
delay_init();
ledInit();
keyInit();
// 检查是否需要进入升级模式
if(isUpgradeFirmware())
{
// 进入升级模式
LED_Upgrade_Indicator_ON();
// 初始化USB VCP
usb_vcp_init();
// 初始化传输协议
TransportProtocol_Init(&TransportProtocol, uu_rxfifo.buffer, Checksum_Sum);
while(1)
{
// 处理USB接收数据
USB_ReceiveData();
// 检测是否有新数据
if(uu_rxfifo.writeptr)
{
// 数据接收完成判断
if(oldcount == uu_rxfifo.writeptr)
{
// 设置接收字节数
TransportProtocol_Manager.ReceiveByteCount = uu_rxfifo.writeptr;
// 解包数据
res = TransportProtocol_Manager.Unpacked();
if(res != UPACKED_SUCCESS)
{
// 解包失败,发送错误码请求重发
OneByteToStr(res, ReceiveBuf);
CDC_Send_DATA(ReceiveBuf, 2);
}
else
{
// 解包成功,处理数据
if(TransportProtocol.Function_Type == 0x01)
{
// 文件数据帧
if(TransportProtocol.Data_Length == 0)
{
// 数据传输完毕
IsTransportOK = 1;
// 写入Flash
iap_write_appbin(Flash_App_Pos, iap_buf, revcnt);
revcnt = 0;
// 恢复Flash起始地址
Flash_App_Pos = FIRMWARE_START_ADDR;
// 发送成功标志
CDC_Send_DATA("OK", 2);
// 重启系统
NVIC_SystemReset();
}
else
{
if(IsTransportOK == 0)
{
// 拷贝数据到缓冲区
memcpy(iap_buf + revcnt, TransportProtocol.Data, TransportProtocol.Data_Length);
revcnt += TransportProtocol.Data_Length;
// 缓冲区满,写入Flash
if(revcnt >= IAP_BUFFER_SIZE)
{
iap_write_appbin(Flash_App_Pos, iap_buf, revcnt);
Flash_App_Pos += revcnt;
revcnt = 0;
}
// 发送接收确认
CDC_Send_DATA("ACK", 3);
}
}
}
else if(TransportProtocol.Function_Type == 0x02)
{
// 擦除命令
uint32_t sector = *(uint32_t*)TransportProtocol.Data;
FLASH_EraseSector(sector);
CDC_Send_DATA("ERASE_OK", 8);
}
}
// 清空接收缓冲区
uu_rxfifo.writeptr = 0;
oldcount = 0;
}
else
{
oldcount = uu_rxfifo.writeptr;
}
}
// 超时检测
if(++timeout_counter > TIMEOUT_THRESHOLD)
{
// 通信超时,重启系统
NVIC_SystemReset();
}
}
}
else
{
// 正常模式,跳转到应用程序
iap_load_app(FLASH_APP1_ADDR);
}
}
五、上位机开发指南
1. 上位机架构设计
- 界面层:用户交互界面
- 通信层:USB 串口通信
- 协议层:命令帧封装与解析
- 文件处理层:固件文件读取与校验
- 状态监控层:进度显示与错误处理
六、常见问题与解决方案
1. 通信失败
- 原因:波特率不匹配、USB 连接问题、BOOT 引脚设置错误
- 解决方案:检查设备管理器、调整波特率、确认 BOOT 引脚状态
2. 写入错误
- 原因:Flash 保护、校验和错误、写入超时
- 解决方案:解除 Flash 保护、检查数据完整性、增加超时时间
3. 升级后设备无法启动
- 原因:程序损坏、地址设置错误、Flash 擦除不完整
- 解决方案:重新进入 Bootloader 模式、检查固件文件、确认起始地址
4. 性能优化
- 优化策略:
- 使用 DMA 加速数据传输
- 增加缓冲区大小减少 Flash 操作次数
- 实现断点续传功能
- 优化校验算法提高传输效率
七、高级应用场景
1. 远程升级
- 结合网络模块 (GSM/WiFi/Ethernet) 实现远程固件更新
- 应用于物联网设备、工业控制设备等
2. 差分升级
- 只传输新旧版本差异部分,减少数据传输量
- 适用于大固件文件和低带宽环境
3. 安全升级
- 实现固件签名验证,防止恶意固件
- 添加版本控制,避免降级攻击
4. 多区域存储
- 实现双备份存储,确保升级失败时可回滚
- 支持 A/B 分区升级策略
八、总结
STM32 USB ISP 升级技术是嵌入式系统开发中不可或缺的重要组成部分。通过深入理解 STM32 的存储架构、启动机制和通信协议,我们可以实现高效、可靠的固件更新方案。本文从原理到代码实现,全面解析了 USB ISP 升级的各个环节,为开发者提供了完整的技术参考。
在实际应用中,需要根据具体项目需求选择合适的升级策略,并注意处理好安全验证、错误处理和性能优化等关键问题。通过不断实践和改进,我们可以打造出更加健壮、易用的固件升级系统。