你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【stm32U3测评】使用FDCAN进行IAP升级-- FDCAN升级思路以及上位机发送

[复制链接]
短笛君 发布时间:2026-8-1 22:30

STM32U3 FDCAN IAP 升级思路与实现

本教程说明 STM32U3C5ZITxQ 上基于 FDCAN1 的 IAP 升级方案。Bootloader 固定位于 0x08000000,APP 从 0x08010000 运行,上位机通过标准 CAN 帧完成固件下载、CRC 校验和跳转。

对于FDCAN配置使用以及性能测试 先前教程已经做过测试 bootloader工程前前期教程也已经讲过 可以参考先前测评

当前 FDCAN1 配置为 Classic CAN,1 Mbps。整条链路包含三部分:

  • bootloader:启动决策、APP 校验、CAN IAP 状态机、Flash 擦写。
  • APP:链接到 Bootloader 之后的测试应用,并响应上位机的“进入 Bootloader”命令。
  • tool:PyQt5 上位机,负责解析固件、生成 CRC32、按协议逐帧发送。

1. 工程结构

U3_FDCAN_IAP/
├─ bootloader/
│  ├─ Core/Inc/boot_config.h
│  ├─ Core/Src/main.c
│  ├─ Core/Src/boot_app.c
│  ├─ Core/Src/iap_can.c
│  └─ Core/Src/iap_flash.c
├─ APP/
│  ├─ Core/Src/main.c
│  ├─ Core/Src/boot_app.c
│  └─ Core/Src/system_stm32u3xx.c
└─ tool/
   ├─ can_iap_gui.py
   ├─ requirements.txt
   └─ build_exe.bat

核心配置集中在 boot_config.h,Bootloader 和 APP 共用相同定义。

2. Flash 布局

区域 地址 说明
Bootloader 0x08000000 固定 64 KB
APP 0x08010000 APP 链接地址和运行地址
APP 元信息 0x081FF000 最后一个 4 KB Flash page

关键宏:

#define BOOT_FLASH_BASE_ADDR           FLASH_BASE
#define BOOT_FLASH_SIZE_BYTES          FLASH_SIZE
#define BOOT_FLASH_END_ADDR            (BOOT_FLASH_BASE_ADDR + BOOT_FLASH_SIZE_BYTES)

#define BOOTLOADER_SIZE_BYTES          (64U * 1024U)
#define APP_FLASH_ADDR                 (BOOT_FLASH_BASE_ADDR + BOOTLOADER_SIZE_BYTES)
#define APP_METADATA_ADDR              (BOOT_FLASH_END_ADDR - FLASH_PAGE_SIZE)
#define APP_MAX_SIZE_BYTES             (APP_METADATA_ADDR - APP_FLASH_ADDR)

Bootloader scatter 文件将 IROM 限制为 0x08000000 ~ 0x0800FFFF。APP scatter 文件将 IROM 设置为 0x08010000,长度为 0x001EF000。按 2 MB Flash、4 KB page 计算,APP 可用区域约为 1980 KB。

元信息与 APP 代码分开存放,避免 Bootloader 通过扫描代码段判断 APP 有效性。最后一个 Flash page 用于保存:

typedef struct
{
  uint32_t magic;
  uint32_t app_addr;
  uint32_t app_size;
  uint32_t app_crc;
  uint32_t app_version;
  uint32_t valid;
  uint32_t reserved0;
  uint32_t reserved1;
} IapFlash_AppInfoTypeDef;

3. 升级链路

上位机
  |
  | USB-CAN Serial / python-can
  v
FDCAN1 (PB8 RX, PB9 TX)
  |
  | 0x301 Host -> Boot
  | 0x302 Boot -> Host
  v
Bootloader IAP 状态机
  |
  | erase / write / verify
  v
STM32U3 Flash

FDCAN1 当前参数:

参数
Kernel clock 96 MHz
NominalPrescaler 4
TSEG1 19
TSEG2 4
SJW 4
Bitrate 1 Mbps

Bitrate = 96 MHz / 4 / (1 + 19 + 4) = 1 Mbps

FDCAN RX 过滤使用掩码模式接收所有标准数据帧,由上层根据 CAN ID 再过滤:

sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterType = FDCAN_FILTER_MASK;
sFilterConfig.FilterID1 = 0x000U;
sFilterConfig.FilterID2 = 0x000U;

发送时使用 Classic CAN 数据帧,FDFormat = FDCAN_CLASSIC_CAN,单帧最多 8 字节。

4. 启动决策

Bootloader 不使用“上电延时”策略,而是在复位后做确定性判断:

复位
  ├─ SRAM 魔术字存在?停留 Bootloader
  ├─ PC13 按下?停留 Bootloader
  ├─ APP 元信息和 CRC 有效?跳转 APP
  └─ 否则初始化 CAN IAP

对应主流程:

if (Bootloader_ShouldStay() == 0U)
{
  if (BOOT_APP_IsValid() != 0U)
  {
    BOOT_APP_Jump();
  }
}

Bootloader_Init();

while (1)
{
  Bootloader_Process();
}

Bootloader_ShouldStay()

static uint8_t Bootloader_ShouldStay(void)
{
  if (Bootloader_TakeAppRequest() != 0U)
  {
    return 1U;
  }

  if (Bootloader_IsButtonPressed() != 0U)
  {
    return 1U;
  }

  return 0U;
}

SRAM 魔术字方案:

static uint8_t Bootloader_TakeAppRequest(void)
{
  volatile uint32_t *magic = (volatile uint32_t *)IAP_BOOT_MAGIC_ADDR;

  if (*magic == IAP_APP_BOOT_MAGIC)
  {
    *magic = 0U;
    return 1U;
  }

  return 0U;
}

使用 SRAM 而非 Flash 标志位的好处是不增加 Flash 擦写磨损,且 Bootloader 读取后清零,不会让每次复位都重复进入升级模式。

5. APP 有效性校验与跳转

IAP_FLASH_IsAppValid() 的检查项:

  • 元信息 magic 必须为 APP_INFO_MAGIC
  • 元信息 valid 必须为 APP_INFO_VALID
  • app_addr 必须等于 APP_FLASH_ADDR
  • app_size 必须大于 0 且不超过 APP_MAX_SIZE_BYTES
  • APP 初始 MSP 必须位于 0x20000000 ~ 0x20040000
  • APP ResetHandler 必须位于 APP 区。
  • APP 区 app_size 字节的 CRC32 必须匹配元信息中的 app_crc

跳转实现:

void BOOT_APP_Jump(void)
{
  uint32_t appStack = *(uint32_t *)APP_FLASH_ADDR;
  uint32_t appReset = *(uint32_t *)(APP_FLASH_ADDR + 4U);
  AppEntryFunc appEntry = (AppEntryFunc)appReset;

  __disable_irq();
  HAL_DeInit();

  SysTick->CTRL = 0U;
  SysTick->LOAD = 0U;
  SysTick->VAL = 0U;

  for (uint32_t i = 0U; i < 8U; i++)
  {
    NVIC->ICER[i] = 0xFFFFFFFFU;
    NVIC->ICPR[i] = 0xFFFFFFFFU;
  }

  SCB->VTOR = APP_FLASH_ADDR;
  __set_MSP(appStack);
  __enable_irq();

  appEntry();
}

6. 进入升级模式

6.1 APP 远程唤醒

APP 正常运行时,上位机先发送 APP 唤醒命令:

方向 CAN ID 数据
上位机 -> APP 0x303 A5
APP -> 上位机 0x304 ACK

APP 收到命令后:

  1. 发送 ACK。
  2. 0x2003F000 写入魔术字 0x424C4455
  3. 延时 50 ms,确保 ACK 有足够时间发出。
  4. 调用 NVIC_SystemReset()
static void APP_RequestBootloader(void)
{
  volatile uint32_t *magic = (volatile uint32_t *)IAP_BOOT_MAGIC_ADDR;
  *magic = IAP_APP_BOOT_MAGIC;
  NVIC_SystemReset();
}

6.2 PC13 强制进入

按住 PC13 上电或复位时,BSP_PB_GetState(BUTTON_USER) 返回高电平,Bootloader 直接停留升级模式。该方式用于 APP 损坏或无法响应唤醒命令的场景。

7. IAP 协议

7.1 CAN ID

CAN ID 方向 用途
0x301 上位机 -> Bootloader IAP 命令帧和 DATA 帧
0x302 Bootloader -> 上位机 ACK/NACK 响应
0x303 上位机 -> APP APP 进入 Bootloader 命令
0x304 APP -> 上位机 APP ACK

7.2 命令定义

命令 说明
GET_INFO 0x01 读取当前 APP 元信息
ENTER_BOOT 0x02 初始化 IAP 会话
ERASE_APP 0x03 擦除 APP 区
DOWNLOAD_START 0x04 分三次下发 size、crc32、version
DATA 0x05 上传固件数据
DOWNLOAD_END 0x06 结束下载
VERIFY 0x07 计算 CRC 并保存有效元信息
RUN_APP 0x08 校验通过后跳转 APP
ABORT 0x09 取消当前会话

7.3 响应帧

所有响应帧统一 8 字节:

Byte0: cmd
Byte1: rsp,ACK=0x79,NACK=0x1F,BUSY=0x7F
Byte2: status
Byte3: reserved
Byte4~7: value,小端

状态码:

状态
OK 0x00
BAD_CMD 0x01
BAD_STATE 0x02
BAD_LENGTH 0x03
BAD_ADDRESS 0x04
SEQ_ERROR 0x05
FLASH_ERROR 0x06
CRC_ERROR 0x07
APP_INVALID 0x08
TIMEOUT 0x09

7.4 DATA 帧

Classic CAN 单帧最多 8 字节,因此 DATA 帧固定占用 3 字节协议头,固件载荷为 5 字节:

Byte0: CMD_DATA
Byte1~2: seq,小端
Byte3~7: 固件数据

最后一包可以小于 5 字节。上位机必须使用真实 DLC,不能补齐到 8 字节,否则 Bootloader 按真实长度计算后会返回 BAD_LENGTH

7.5 升级时序

上位机                                 Bootloader
  | GET_INFO / ENTER_BOOT                |
  |------------------------------------->|
  | ACK                                 |
  |<-------------------------------------|
  | DOWNLOAD_START: size/crc/version     |
  |------------------------------------->|
  | ACK                                 |
  |<-------------------------------------|
  | ERASE_APP                            |
  |------------------------------------->|
  | ACK                                 |
  |<-------------------------------------|
  | DATA seq=0..N                        |
  |------------------------------------->|
  | ACK value=next_seq                   |
  |<-------------------------------------|
  | DOWNLOAD_END                         |
  |------------------------------------->|
  | ACK                                 |
  |<-------------------------------------|
  | VERIFY                               |
  |------------------------------------->|
  | ACK                                 |
  |<-------------------------------------|
  | RUN_APP                              |
  |------------------------------------->|
  | 跳转 APP                             |

8. Bootloader 状态机

iap_can.c 使用状态机限制命令顺序:

typedef enum
{
  IAP_STATE_IDLE = 0,
  IAP_STATE_READY,
  IAP_STATE_ERASED,
  IAP_STATE_RECEIVING,
  IAP_STATE_DONE,
  IAP_STATE_VERIFIED
} IapStateTypeDef;

状态转换:

IDLE -> ENTER_BOOT -> READY
READY -> DOWNLOAD_START -> READY
READY -> ERASE_APP -> ERASED
ERASED -> DATA -> RECEIVING
RECEIVING -> DATA -> RECEIVING
RECEIVING -> DOWNLOAD_END -> DONE
DONE -> VERIFY -> VERIFIED
VERIFIED -> RUN_APP -> 跳转 APP

DATA 处理会同时校验状态、seq、DLC 和长度范围:

case IAP_CMD_DATA:
  if ((iap_state != IAP_STATE_ERASED) && (iap_state != IAP_STATE_RECEIVING))
  {
    IAP_SendResponse(cmd, IAP_RSP_NACK, IAP_STATUS_BAD_STATE, expected_seq, received_bytes);
    break;
  }

  if (seq != expected_seq)
  {
    IAP_SendResponse(cmd, IAP_RSP_NACK, IAP_STATUS_SEQ_ERROR, expected_seq, received_bytes);
    break;
  }

  len = frame->dlc - 3U;
  if ((frame->dlc < 4U) || (len > IAP_DATA_PAYLOAD_BYTES) ||
      ((received_bytes + len) > fw_size))
  {
    IAP_SendResponse(cmd, IAP_RSP_NACK, IAP_STATUS_BAD_LENGTH, expected_seq, received_bytes);
    break;
  }

  if (IAP_FLASH_Write(received_bytes, &frame->data[3], len) != HAL_OK)
  {
    IAP_SendResponse(cmd, IAP_RSP_NACK, IAP_STATUS_FLASH_ERROR, expected_seq, HAL_FLASH_GetError());
    break;
  }

  received_bytes += len;
  expected_seq++;
  iap_state = IAP_STATE_RECEIVING;
  IAP_SendResponse(cmd, IAP_RSP_ACK, IAP_STATUS_OK, (uint16_t)(expected_seq - 1U), expected_seq);
  break;

DATA ACK 的 value 返回 expected_seq,即下一包应使用的序号。上位机可以据此确认每一包已写入。

会话超过 IAP_SESSION_TIMEOUT_MS(10 秒)没有新帧时,IAP_CAN_Process() 会返回 TIMEOUT NACK,并把状态恢复为 IDLE

9. Flash 写入实现

STM32U3 Flash 编程单位为 8 字节 double-word,因此 CAN 收到的数据先进入 8 字节缓存:

#define IAP_FLASH_WRITE_UNIT  8U

if (write_cache_len == IAP_FLASH_WRITE_UNIT)
{
  HAL_FLASH_Unlock();
  FLASH_WriteDoubleWord(write_cache_addr, write_cache, IAP_FLASH_WRITE_UNIT);
  HAL_FLASH_Lock();

  write_cache_addr += IAP_FLASH_WRITE_UNIT;
  write_cache_len = 0U;

  for (uint8_t i = 0U; i < IAP_FLASH_WRITE_UNIT; i++)
  {
    write_cache[i] = 0xFFU;
  }
}

写入前先使 APP 元信息无效:

HAL_StatusTypeDef IAP_FLASH_Begin(uint32_t size, uint32_t crc, uint32_t version)
{
  if ((size == 0U) || (size > APP_MAX_SIZE_BYTES))
  {
    return HAL_ERROR;
  }

  download_size = size;
  download_crc = crc;
  download_version = version;

  return IAP_FLASH_SaveAppInfo(0U, 0U, 0U, APP_INFO_INVALID);
}

擦除范围是 APP_FLASH_ADDRAPP_METADATA_ADDR。擦除按页进行,并处理 Bank 边界:

static uint32_t FLASH_GetBank(uint32_t addr)
{
  if (addr < (FLASH_BASE + FLASH_BANK_SIZE))
  {
    return FLASH_BANK_1;
  }

  return FLASH_BANK_2;
}

static uint32_t FLASH_GetPage(uint32_t addr)
{
  if (addr < (FLASH_BASE + FLASH_BANK_SIZE))
  {
    return (addr - FLASH_BASE) / FLASH_PAGE_SIZE;
  }

  return (addr - (FLASH_BASE + FLASH_BANK_SIZE)) / FLASH_PAGE_SIZE;
}

下载结束时会补齐最后不足 8 字节的数据,重新计算 CRC32,只有 CRC 匹配才写入有效元信息:

crc = IAP_FLASH_CalcCrc32(APP_FLASH_ADDR, download_size);

if (crc != download_crc)
{
  return HAL_ERROR;
}

return IAP_FLASH_SaveAppInfo(download_size, download_crc, download_version, APP_INFO_VALID);

CRC32 使用标准 IEEE CRC32 算法,与上位机 binascii.crc32() 一致。这样 Bootloader 与上位机不需要额外约定 CRC 变体。

升级可靠性来自以下顺序:

元信息标记无效
  -> 擦除 APP 区
  -> 逐包写入
  -> 补齐最后 double-word
  -> 计算 CRC
  -> 校验通过后标记有效

升级中断时 APP 元信息不会变成有效状态,因此 Bootloader 不会跳转半包固件。

10. APP 工程要点

APP 必须单独链接到 0x08010000,并设置向量表偏移:

#define VECT_TAB_BASE_ADDRESS   FLASH_BASE
#define VECT_TAB_OFFSET         0x00010000U

system_stm32u3xx.c 中的初始化代码会写入:

SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET;

APP 启动后会打印 EnterAPP 和当前 VTOR,用于确认 Bootloader 跳转正确。测试 APP 同时初始化 FDCAN,用于接收 0x303/A5 唤醒命令。

11. 上位机实现

上位机核心流程:

  1. 加载 .bin 或解析 Intel HEX。
  2. 计算固件 size 和 CRC32。
  3. 可选发送 0x303/A5 唤醒 APP。
  4. 与 Bootloader 执行 IAP 协议。

Intel HEX 解析要求:

  • 支持记录类型 00010204
  • 忽略 0305
  • 校验每行 checksum。
  • 地址必须从 0x08010000 或之后开始。
  • 0x08010000 生成连续镜像,空洞用 0xFF 填充。

DATA 发送代码:

chunk = firmware[offset:offset + DATA_PAYLOAD_BYTES]
frame = bytes([CMD_DATA, seq & 0xFF, (seq >> 8) & 0xFF]) + chunk
rsp = self.command(CMD_DATA, frame, f"DATA seq={seq}", quiet=True)

next_seq = rsp["value"]
if next_seq != (seq + 1):
    raise IapError(f"DATA ACK next_seq 异常: got {next_seq}, expect {seq + 1}")

上位机支持两种后端:

  • USB-CAN Serial:串口 921600,可自动设置 CAN 波特率 1000 kbps。
  • python-can:通过 interfacechannelbitrate 接入其他 CAN 设备。

运行上位机:

cd tool
py -m pip install -r requirements.txt
py can_iap_gui.py

12. 编译与部署

  1. 打开 bootloader/MDK-ARM/FDCAN_U3.uvprojx,target 为 FDCAN_U3
  2. 编译并下载 Bootloader HEX。
  3. 打开 APP/MDK-ARM/FDCAN_U3.uvprojx,target 为 FDCAN_U3_APP
  4. 编译 APP HEX。
  5. 第一次可通过 ST-Link 烧录 APP,之后通过 CAN IAP 升级。

命令行编译示例:

# 在 U3_FDCAN_IAP 工程根目录执行
$repo = (Get-Location).Path
& "C:\Keil_v5\UV4\UV4.exe" -j0 -b "$repo\bootloader\MDK-ARM\FDCAN_U3.uvprojx" -t "FDCAN_U3"
& "C:\Keil_v5\UV4\UV4.exe" -j0 -b "$repo\APP\MDK-ARM\FDCAN_U3.uvprojx" -t "FDCAN_U3_APP"

13. 验证项

验证项 操作 预期结果
强制进入 Bootloader 按住 PC13 复位 串口显示Stay in bootloader
APP 正常启动 复位且 APP 有效 串口显示EnterAPP,VTOR 为 0x08010000
APP 远程唤醒 上位机发送0x303/A5 APP ACK,随后复位进入 Bootloader
正常升级 选择 APP HEX/BIN 后开始 所有命令 ACK,最终verify ok
乱序 DATA 修改 seq 后发送 返回SEQ_ERROR
错误长度 最后一包补齐到 8 字节 返回BAD_LENGTH
错误 CRC 修改固件 CRC 返回CRC_ERROR
中断恢复 下载过程中断电 元信息无效,不会跳转损坏 APP

预期日志:

[BOOT] CAN IAP Bootloader v1.0.0
[BOOT] Boot: 0x08000000 + 64 KB, App: 0x08010000, Meta: 0x081FF000
[BOOT] Stay in bootloader, waiting CAN IAP command
[IAP] enter boot
[IAP] erase app area...
[IAP] erase done
[IAP] download done bytes=...
[IAP] verify ok
[BOOT] Jump to app @0x08010000
EnterAPP
[APP] IAP test app, VTOR=0x08010000

14. 限制与扩展

当前实现的主要限制:

  • Classic CAN 每帧只有 8 字节,DATA 帧实际固件载荷为 5 字节,吞吐偏低。
  • 每个 DATA 帧都等待 ACK,可靠性高但会进一步降低吞吐。
  • 没有固件加密和签名校验,不适合直接作为量产安全升级方案。
  • 没有双 Bank 或回滚区,升级失败时需要重新下载或通过 PC13 恢复。
  • 0x2003F000 魔术字地址依赖 APP 不覆盖该区域,产品化时应使用专用 RAM 或备份寄存器。

可扩展方向:

  • 使用 FDCAN FD 模式,增大单帧数据载荷。
  • 使用滑动窗口或连续下载模式,减少 ACK 往返。
  • 增加 Bootloader 自升级、版本兼容检查和双 Bank 切换。
  • 增加签名、加密和 Secure Boot 支持。
收藏 评论0 发布时间:2026-8-1 22:30

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版