☰
STM32H743 串口升级实战:Ymodem 协议与双 Bank Flash 实现
2026/10/28 7:56:43 网站建设 项目流程

简介:这份源码包面向嵌入式开发工程师与STM32学习者,聚焦基于STM32H743单片机的Ymodem文件传输与APP程序在线更新方案。STM32H743采用ARM Cortex-M7内核,主频可达400MHz,接口资源丰富,适合高速处理与实时控制场景;Ymodem协议在XMODEM基础上优化,支持更大数据包与批处理传输,可提升固件升级的可靠性与效率。资源包共833个文件,约8.28MB,以297个h头文件、242个c源文件为核心,配合146个icf、62个s、21个sct、20个ld等链接与启动脚本,以及16个a、3个lib库文件,覆盖底层驱动、协议实现与工程配置,另含uvprojx、uvoptx工程文件与hex固件,便于直接编译验证。目前已有69人学习下载。读者可据此掌握Ymodem在资源受限单片机上的收发处理、数据完整性校验与APP跳转更新思路,理解远程升级、固件更新等场景的落地方法,并借鉴其模块化代码结构与工程组织方式。

1. STM32H743 上跑 Ymodem 更新 APP:为什么串口升级值得认真做

手里有一块 STM32H743 的板子,Bootloader 已经烧进去,APP 程序却还在用调试器一遍遍下载——这个场景做嵌入式的人都不陌生。产品一旦装进外壳、放到现场,JTAG/SWD 接口就够不着了,这时候能靠一根串口线把新的 APP 固件传进去并自动完成烧写,就是刚需。Ymodem 协议配合 STM32H743 的双 Bank Flash,正好能把这套流程做扎实:上位机发文件,Bootloader 收数据、写 Flash、校验、跳转,全程不需要拆机。这套方案适合做工业控制器、数据采集终端、需要远程或现场升级的嵌入式产品开发者,也适合正在学 Bootloader 和 IAP 的嵌入式新手拿来吃透一个完整链路。下面从协议、Flash 分区、代码实现到踩坑,一步步拆开讲。

2. Ymodem 协议与 STM32H743 Flash 分区:先把地基打对

2.1 Ymodem 的帧结构和握手流程

Ymodem 是 Xmodem 的增强版,核心区别在于它支持多文件传输和 1024 字节数据块。每一帧的结构是固定的:

字段长度说明
SOH/STX1 字节SOH 表示 128 字节数据,STX 表示 1024 字节数据
包序号1 字节从 0x01 开始递增
包序号反码1 字节包序号的按位取反,用于校验
数据区128 或 1024 字节不足部分用 0x1A 填充
CRC162 字节对数据区做 CRC16-CCITT 校验

传输开始时,接收方先发一个字符 'C'(0x43),告诉发送方"我准备好了,用 CRC 校验模式"。发送方收到后发出第一帧——这一帧比较特殊,数据区放的是文件名和文件大小,格式是文件名\0文件大小\0。接收方回应 ACK 后再发一个 'C',发送方才开始发真正的数据帧。最后一帧数据发完后,发送方发一个全零帧表示结束,接收方回 ACK 再回一个 'C' 等待下一个文件,如果发送方没有更多文件就发 EOT,接收方回 ACK 后整个传输结束。

理解这个流程很关键,因为后面写 Bootloader 的时候,状态机的每一步都对应这里的握手动作。很多人第一次写 Ymodem 接收端,卡在"发了 'C' 但对方没反应"或者"收到数据但 CRC 一直不过",根子都在握手时序和 CRC 算法上。

2.2 STM32H743 的 Flash 结构和双 Bank 特性

STM32H743 的 Flash 容量通常是 2MB,分成两个 Bank,每个 Bank 1MB。关键参数如下:

参数Bank1Bank2
起始地址0x080000000x08100000
结束地址0x080FFFFF0x081FFFFF
Sector 大小128KB128KB
Sector 数量88

写 Flash 之前必须先擦除,H743 的最小擦除单位是 Sector(128KB),不像 F1 系列可以按页擦。这意味着你的 APP 如果只有 50KB,占用的那一个 Sector 也要整个擦掉再写。擦除一个 Sector 的时间大概在几百毫秒到一秒之间,具体看芯片的擦写寿命和供电情况。

双 Bank 的好处在于:你可以在 Bank1 跑当前 APP,把新固件写到 Bank2,写完校验通过后再切换启动地址。这样即使升级过程中断电,原来的 APP 还在,不会变砖。如果不用双 Bank,那就得把 Bootloader 放在最前面,APP 从某个偏移地址开始,升级时先擦 APP 区再写入,风险是擦除后断电就什么都没有了。

2.3 分区方案怎么定

我一般会这样分:

/* Flash 分区定义 - STM32H743 2MB 双 Bank */ #define BOOTLOADER_ADDR 0x08000000 /* Bootloader 起始地址 */ #define BOOTLOADER_SIZE (128 * 1024) /* 128KB 给 Bootloader */ #define APP_ADDR 0x08020000 /* APP 起始地址,Sector 1 开始 */ #define APP_SIZE (896 * 1024) /* APP 可用空间 896KB */ #define UPDATE_FLAG_ADDR 0x0801F000 /* 升级标志存储地址,Sector 0 末尾 */ #define UPDATE_FLAG_MAGIC 0x5A5A5A5A /* 标志魔数,表示需要升级 */

Bootloader 占 Sector 0(128KB),APP 从 Sector 1 开始。升级标志放在 Sector 0 的末尾,因为 Bootloader 本身不会频繁改动,这个位置比较安全。标志的作用是:Bootloader 启动时先读这个地址,如果是魔数就进入升级模式,否则直接跳转到 APP。

注意:H743 的 Flash 写操作必须按 256 位(32 字节)对齐,写之前要确保目标地址已经擦除。如果往没擦除的区域写,会触发写入错误标志。

3. Bootloader 接收端实现:从串口收包到写 Flash

3.1 串口接收的缓冲策略

Ymodem 每帧最多 1029 字节(STX + 序号 + 反码 + 1024 数据 + 2 字节 CRC),串口接收不能一个字节一个字节处理,那样太慢也容易丢包。我一般用 DMA + 空闲中断的方式:DMA 负责把串口数据搬到缓冲区,空闲中断触发后通知主循环"这一帧收完了"。

/* 串口 DMA 接收配置 - 以 UART1 为例 */ #define UART_RX_BUF_SIZE 2048 uint8_t uart_rx_buf[UART_RX_BUF_SIZE]; volatile uint16_t uart_rx_len = 0; volatile uint8_t uart_rx_flag = 0; void UART1_Init(void) { /* 波特率 115200,8N1,使能 DMA 接收 */ huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; HAL_UART_Init(&huart1); /* 启动 DMA 接收,空闲中断在 HAL_UARTEx_ReceiveToIdle_DMA 里处理 */ HAL_UARTEx_ReceiveToIdle_DMA(&huart1, uart_rx_buf, UART_RX_BUF_SIZE); } /* 空闲中断回调,一帧数据收完后触发 */ void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) { if (huart->Instance == USART1) { uart_rx_len = Size; uart_rx_flag = 1; /* 通知主循环处理 */ } }

这里用HAL_UARTEx_ReceiveToIdle_DMA而不是普通的HAL_UART_Receive_DMA,因为前者能在串口空闲时自动触发回调,不用等缓冲区满。参数上,波特率 115200 是 Ymodem 的常用值,再高比如 921600 也可以,但要看你的串口线质量和 Flash 写入速度能不能跟上。如果 Flash 擦除慢,波特率太高反而会因为来不及处理导致丢包。

3.2 Ymodem 状态机的实现

接收端的状态机是整个 Bootloader 的核心。我一般把它写成这样几个状态:

typedef enum { YMODEM_STATE_IDLE, /* 空闲,等待开始 */ YMODEM_STATE_WAIT_HEADER, /* 等待文件信息帧 */ YMODEM_STATE_RECEIVE_DATA, /* 接收数据帧 */ YMODEM_STATE_FINISH, /* 传输完成 */ YMODEM_STATE_ERROR /* 出错 */ } ymodem_state_t; /* 主处理函数,在收到一帧数据后调用 */ int ymodem_receive_file(uint32_t flash_addr, uint32_t max_size) { uint8_t packet_num = 0; uint32_t write_addr = flash_addr; uint32_t total_written = 0; uint8_t *data; uint16_t data_len; /* 发送 'C' 启动传输 */ uart_send_byte('C'); while (1) { if (!wait_for_packet(1000)) { /* 1 秒超时 */ uart_send_byte('C'); /* 超时重发 'C' */ continue; } uint8_t header = uart_rx_buf[0]; if (header == SOH || header == STX) { data_len = (header == STX) ? 1024 : 128; data = &uart_rx_buf[3]; /* 跳过 header + 序号 + 反码 */ /* 校验包序号 */ if (uart_rx_buf[1] != packet_num + 1) { uart_send_byte(NAK); continue; } /* 校验 CRC16 */ uint16_t crc_recv = (uart_rx_buf[3 + data_len] << 8) | uart_rx_buf[3 + data_len + 1]; uint16_t crc_calc = crc16_ccitt(data, data_len); if (crc_recv != crc_calc) { uart_send_byte(NAK); continue; } packet_num++; /* 第一帧是文件信息,解析文件名和大小 */ if (packet_num == 1) { /* 数据区格式:文件名\0文件大小\0 */ char *filename = (char *)data; char *filesize_str = filename + strlen(filename) + 1; uint32_t filesize = atoi(filesize_str); if (filesize > max_size) { uart_send_byte(CA); /* 取消传输 */ return -1; } uart_send_byte(ACK); uart_send_byte('C'); continue; } /* 写 Flash */ if (flash_write(write_addr, data, data_len) != 0) { uart_send_byte(CA); return -1; } write_addr += data_len; total_written += data_len; uart_send_byte(ACK); } else if (header == EOT) { uart_send_byte(ACK); uart_send_byte('C'); /* 等待最后一个空帧 */ if (wait_for_packet(1000) && uart_rx_buf[0] == SOH) { uart_send_byte(ACK); } break; } } return total_written; }

这段代码的逻辑是:每收到一帧,先检查包序号对不对,再算 CRC16,都通过了才写 Flash。第一帧特殊处理,从数据区里解析出文件名和文件大小。EOT 表示数据传输结束,收到后回 ACK 再回 'C',等发送方发最后一个空帧确认。

参数说明:flash_addr是 APP 区的起始地址,max_size是 APP 区最大容量,防止写入越界。wait_for_packet的超时设 1 秒,太短容易误判超时,太长会让升级过程变慢。CRC16 用的是 CCITT 多项式 0x1021,初始值 0x0000,这个和标准 Ymodem 一致。

3.3 Flash 写入的关键细节

H743 的 Flash 写入和 F1/F4 系列不太一样,需要先解锁、配置 Flash 时钟、按 32 字节对齐写入。下面是我常用的写入函数:

/* STM32H743 Flash 写入 - 按 32 字节对齐 */ int flash_write(uint32_t addr, uint8_t *data, uint32_t len) { HAL_FLASH_Unlock(); /* 配置 Flash 编程并行位数和时钟 */ __HAL_FLASH_SET_PROGRAM_WIDTH(FLASH_PROGRAM_WIDTH_256BITS); for (uint32_t i = 0; i < len; i += 32) { /* 准备 32 字节数据,不足部分补 0xFF */ uint8_t buf[32]; uint32_t remain = len - i; uint32_t copy_len = (remain >= 32) ? 32 : remain; memcpy(buf, data + i, copy_len); if (copy_len < 32) { memset(buf + copy_len, 0xFF, 32 - copy_len); } /* 写入 32 字节 */ if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, addr + i, (uint32_t)buf) != HAL_OK) { HAL_FLASH_Lock(); return -1; } } HAL_FLASH_Lock(); return 0; }

这里有几个点要注意:FLASH_PROGRAM_WIDTH_256BITS对应 32 字节写入,这是 H743 的最小写入单位。如果数据长度不是 32 的倍数,最后一包要用 0xFF 补齐,因为 Flash 擦除后的状态就是 0xFF,补 0xFF 不会改变已擦除区域的内容。写入前必须确保目标 Sector 已经擦除,否则会返回错误。

提示:H743 的 Flash 擦除是按 Sector 操作的,擦除一个 128KB 的 Sector 大概需要 0.5 到 1 秒。如果 APP 比较大,升级过程中要保证供电稳定,否则擦除中途断电可能导致该 Sector 数据损坏。

4. APP 跳转与中断向量表重映射:最容易翻车的地方

4.1 跳转前的准备工作

Bootloader 收到完整固件并校验通过后,下一步就是跳转到 APP。跳转本身不复杂,但跳之前要做几件事:关闭所有中断、关闭 SysTick、设置主堆栈指针(MSP)、设置中断向量表偏移。任何一步漏了,APP 跑起来都可能出现玄学问题。

/* 跳转到 APP */ void jump_to_app(uint32_t app_addr) { typedef void (*app_func_t)(void); uint32_t app_stack = *(volatile uint32_t *)app_addr; app_func_t app_entry = (app_func_t)(*(volatile uint32_t *)(app_addr + 4)); /* 检查栈顶地址是否合法(在 RAM 范围内) */ if ((app_stack & 0xFFF00000) != 0x20000000) { return; /* 栈顶不合法,不跳转 */ } /* 关闭所有中断 */ __disable_irq(); /* 关闭 SysTick */ SysTick->CTRL = 0; SysTick->LOAD = 0; SysTick->VAL = 0; /* 清除所有挂起的中断标志 */ for (int i = 0; i < 8; i++) { NVIC->ICER[i] = 0xFFFFFFFF; NVIC->ICPR[i] = 0xFFFFFFFF; } /* 设置主堆栈指针 */ __set_MSP(app_stack); /* 设置中断向量表偏移 */ SCB->VTOR = app_addr; /* 跳转到 APP 入口 */ app_entry(); /* 正常情况下不会执行到这里 */ while (1); }

这段代码里,app_stack是 APP 固件开头 4 个字节存的栈顶地址,app_entry是接下来 4 个字节存的复位向量地址。检查栈顶地址是否在 RAM 范围内是个好习惯,能防止 APP 固件损坏时跳转到非法地址导致 HardFault。

4.2 中断向量表偏移的坑

APP 程序里必须设置SCB->VTOR = APP_ADDR,否则中断会跳到 Bootloader 的向量表去,结果就是中断响应了但执行的是 Bootloader 的中断服务函数,轻则功能异常,重则死机。这个设置一般放在SystemInit函数里,或者 APP 的main函数开头。

/* APP 的 main 函数开头,设置向量表偏移 */ int main(void) { /* 设置中断向量表偏移到 APP 起始地址 */ SCB->VTOR = 0x08020000; /* 其他初始化 */ HAL_Init(); SystemClock_Config(); /* ... */ }

如果你用的是 STM32CubeMX 生成的代码,SystemInit里默认会把 VTOR 设成 0x08000000,需要在main里覆盖掉。另外,Keil 工程里要设置 IROM1 的起始地址为 0x08020000,大小根据 APP 区实际容量填。IAR 类似,在链接器配置里改。

4.3 升级标志的管理

升级标志的作用是告诉 Bootloader "这次启动要进入升级模式"。我一般把它存在 Flash 的固定地址,上电时先读这个地址:

/* 检查是否需要升级 */ int check_update_flag(void) { uint32_t flag = *(volatile uint32_t *)UPDATE_FLAG_ADDR; return (flag == UPDATE_FLAG_MAGIC) ? 1 : 0; } /* 清除升级标志 */ void clear_update_flag(void) { flash_erase_sector(UPDATE_FLAG_ADDR); } /* 设置升级标志 */ void set_update_flag(void) { uint32_t magic = UPDATE_FLAG_MAGIC; flash_write(UPDATE_FLAG_ADDR, (uint8_t *)&magic, 4); }

Bootloader 启动流程是:读标志 → 如果标志有效就进入 Ymodem 接收模式 → 接收完成并校验通过后清除标志 → 跳转到 APP。如果标志无效就直接跳转 APP。APP 里可以通过串口命令或者其他触发方式调用set_update_flag然后复位,下次启动就会进入升级模式。

注意:写升级标志之前要先擦除对应的 Sector。H743 的 Sector 0 是 128KB,如果你把标志放在 Sector 0 末尾,擦除时会连 Bootloader 一起擦掉。所以标志最好单独占一个 Sector,或者放在 Bootloader 不会用到的区域。我一般把 Bootloader 控制在 64KB 以内,标志放在 Sector 0 的 0x0801F000 位置,擦除时只擦 Sector 0 但 Bootloader 本身不跨到这个地址。

5. 避坑与排查:Ymodem 升级常见的 5 个翻车现场

5.1 发了 'C' 但上位机没反应

现象:Bootloader 启动后通过串口发了 'C',但上位机(比如 SecureCRT、Xshell 或者自己写的发送工具)一直停在"等待接收方就绪"。

原因:最常见的是串口参数不匹配。Ymodem 要求 8 数据位、无校验、1 停止位,但有些人习惯用 7 数据位或者带校验。另一个原因是流控没关,硬件流控(RTS/CTS)如果开着但没接对应引脚,数据发不出去。

解决:检查上位机串口配置是否为 115200-8-N-1,流控设为"无"。如果用的是 USB 转串口模块,确认驱动正常、TX/RX 没接反。还可以用示波器或者逻辑分析仪抓一下 TX 引脚,看 'C'(0x43)有没有真的发出来。

5.2 CRC 校验一直不过

现象:能收到数据帧,但每次 CRC 校验都失败,Bootloader 一直回 NAK,上位机重传几次后报错退出。

原因:CRC16 算法实现不对是最常见的。Ymodem 用的是 CRC16-CCITT,多项式 0x1021,初始值 0x0000,但有些人用了 Modbus 的 CRC16(多项式 0xA001,初始值 0xFFFF),结果永远对不上。另一个原因是数据长度算错了,比如 STX 帧应该是 1024 字节数据,但代码里按 128 字节算。

解决:用标准的 CRC16-CCITT 实现,初始值 0x0000,不反转输入输出。下面是一个验证过的实现:

/* CRC16-CCITT 查表法,Ymodem 标准 */ static const uint16_t crc16_table[256] = { 0x0000, 0x1021, 0x2042, 0x3063, /* ... 省略中间项 ... */ }; uint16_t crc16_ccitt(uint8_t *data, uint32_t len) { uint16_t crc = 0; for (uint32_t i = 0; i < len; i++) { crc = (crc << 8) ^ crc16_table[((crc >> 8) ^ data[i]) & 0xFF]; } return crc; }

如果不想用查表法,也可以用逐位计算,但速度会慢一些。关键是初始值必须是 0x0000,不是 0xFFFF。

5.3 Flash 写入失败,返回 HAL_ERROR

现象:CRC 校验通过了,但调用HAL_FLASH_Program返回错误,写入失败。

原因:三种可能——目标地址没擦除、写入地址没按 32 字节对齐、Flash 时钟配置不对。H743 的 Flash 写入对时序比较敏感,如果FLASH_PROGRAM_WIDTH没设对,或者 Flash 等待周期和主频不匹配,都会导致写入失败。

解决:写之前先擦除对应的 Sector,确认addr是 32 字节对齐的。检查SystemClock_Config里 Flash 等待周期(FLASH_LATENCY)是否和主频匹配,H743 跑 480MHz 时一般设 4 个等待周期。如果还是不行,读一下 Flash 的状态寄存器,看具体是哪个错误标志置位了。

5.4 跳转到 APP 后跑不起来

现象:Bootloader 显示升级成功,跳转后 APP 没反应,或者跑了几秒就 HardFault。

原因:中断向量表偏移没设对是最常见的。APP 里如果没设SCB->VTOR,中断会跳到 Bootloader 的向量表,导致中断服务函数执行错误。另一个原因是 APP 的链接地址和实际烧写地址不一致,比如 Keil 里 IROM1 还是 0x08000000,但实际烧到了 0x08020000。

解决:确认 APP 的main函数开头有SCB->VTOR = APP_ADDR;,确认 Keil/IAR 的链接器配置里 IROM1 起始地址和大小正确。还可以在跳转前打印一下 APP 地址处的栈顶值和复位向量值,看是否合理。

5.5 升级过程中断电,设备变砖

现象:升级到一半断电,重新上电后 Bootloader 还在,但 APP 跑不起来,串口也没反应。

原因:如果用的是单 Bank 方案,擦除 APP 区后断电,APP 数据就丢了,Bootloader 跳转过去也是空的。如果 Bootloader 里没有超时机制,它会一直等 Ymodem 连接,但串口没数据,看起来就像死机。

解决:Bootloader 里加超时机制,比如等待 10 秒没收到 'C' 的回应就自动跳转到 APP(即使 APP 是空的,至少不会卡在 Bootloader)。更好的做法是用双 Bank 方案,新固件写到另一个 Bank,写完校验通过再切换,这样断电也不会丢原来的 APP。

6. 进阶技巧:用 Python 写上位机发送脚本和固件校验

6.1 为什么自己写上位机

SecureCRT 和 Xshell 虽然支持 Ymodem 发送,但不够灵活——比如你想在发送前自动计算固件的 CRC32 并附加到文件末尾,或者想批量升级多台设备,这些工具就做不到了。用 Python 写一个发送脚本,几十行代码就能搞定,还能集成到自动化测试流程里。

import serial import os import time import struct SOH = 0x01 STX = 0x02 EOT = 0x04 ACK = 0x06 NAK = 0x15 CA = 0x18 CRC16 = 0x43 def crc16_ccitt(data): crc = 0 for byte in data: crc ^= byte << 8 for _ in range(8): if crc & 0x8000: crc = (crc << 1) ^ 0x1021 else: crc <<= 1 crc &= 0xFFFF return crc def send_ymodem(ser, filepath): filename = os.path.basename(filepath) filesize = os.path.getsize(filepath) # 等待接收方发 'C' while ser.read(1) != bytes([CRC16]): pass # 发送文件信息帧 header = filename.encode() + b'\0' + str(filesize).encode() + b'\0' header = header.ljust(128, b'\0') packet = bytes([SOH, 0x01, 0xFE]) + header packet += struct.pack('>H', crc16_ccitt(header)) ser.write(packet) if ser.read(1) != bytes([ACK]): return False if ser.read(1) != bytes([CRC16]): return False # 发送数据帧 with open(filepath, 'rb') as f: packet_num = 1 while True: data = f.read(1024) if not data: break if len(data) < 1024: data = data.ljust(1024, b'\x1A') packet_num = (packet_num + 1) & 0xFF packet = bytes([STX, packet_num, 0xFF - packet_num]) + data packet += struct.pack('>H', crc16_ccitt(data)) ser.write(packet) if ser.read(1) != bytes([ACK]): return False # 发送 EOT ser.write(bytes([EOT])) if ser.read(1) != bytes([ACK]): return False ser.write(bytes([EOT])) if ser.read(1) != bytes([ACK]): return False return True if __name__ == '__main__': ser = serial.Serial('COM3', 115200, timeout=5) if send_ymodem(ser, 'app.bin'): print('升级成功') else: print('升级失败') ser.close()

这段脚本的逻辑和 Bootloader 接收端是对应的:先等 'C',发文件信息帧,等 ACK 和 'C',然后循环发数据帧,最后发 EOT。参数上,timeout=5是串口读写超时,可以根据实际情况调整。packet_num从 1 开始递增,到 255 后回绕到 0。

6.2 固件校验的几种方式

发送完固件后,怎么确认写入的数据是对的?我一般用两种方式:一是 Ymodem 本身的 CRC16 校验,每帧都校验,能保证传输过程没错;二是在固件末尾附加一个 CRC32 值,Bootloader 写完后对整个 APP 区算一遍 CRC32,和附加的值比对。

import zlib def append_crc32(filepath): with open(filepath, 'rb') as f: data = f.read() crc = zlib.crc32(data) & 0xFFFFFFFF with open(filepath + '.crc', 'wb') as f: f.write(data) f.write(struct.pack('<I', crc)) return crc

Bootloader 端收到固件后,先按 Ymodem 协议写入 Flash,写完后从 Flash 里读出来算 CRC32,和固件末尾 4 个字节比对。如果一致就跳转,不一致就报错。这样双重保险,基本不会出现写入错误还跳转的情况。

6.3 批量升级的自动化思路

如果你有多台设备要升级,可以写一个循环,每台设备分配一个串口号,依次调用send_ymodem。更高效的做法是用多线程,每个串口一个线程,同时升级多台。但要注意,如果设备共用电源,同时擦写 Flash 可能导致电流不够,建议分批或者加缓冲电容。

我自己的习惯是:每次升级前先读一下设备的当前固件版本(APP 里实现一个版本查询命令),升级后再读一次确认版本变了。这样即使升级过程没报错,也能确认新固件真的跑起来了。这个习惯帮我省过好几次"以为升级成功结果跳转失败"的后悔药。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询