公司动态
GD32F407 基于 ESP8266 的 OTA 无线升级实战(Bootloader + App + PC服务器全流程)
本文基于立创梁山派天空星GD32F407VET68MHz 晶振开发板用 ESP8266 实现了一套完整的 OTA 无线固件升级。全程从零搭建踩了大量坑串口编号、跳转汇编、ACK 错位……方法、协议、代码都给你讲透。一、为什么做 OTA单片机固件通常用下载器ST-Link/J-Link/DAP离线烧录。产品一旦装机要升级就得拆壳连线非常麻烦。OTAOver-The-Air就是让设备通过 WiFi/网络自己下载新固件、自己写进 Flash、自己重启——远程升级、无人值守。本文方案GD32F407 ESP8266AT 固件PC 端用 Python 当固件服务器板子通过 WiFi 拉取新固件在线升级。文章底部附上了Git 仓库链接有需要的朋友可以自行下载。二、整体架构-------------------- USART1 ----------- WiFi/TCP ------------------ | GD32F407 Bootloader| ------------------ | ESP8266 | ---------- | PC (ota_server.py)| | 0x08000000 (32KB) | PA2-TX / PA3-RX | IPD帧 | | 192.168.x.x:9000 | -------------------- 115200 ----------- ------------------ | GD32F407 App | (引导/升级/跳转) (发送 app.bin) | 0x08008000 (480KB) | --------------------三层的角色GD32 板子总导演。平时跑 App上电先跑 Bootloader 决定升级还是跳 App。ESP8266透明无线串口线。由板子用 AT 指令指挥它连 WiFi、连服务器连上后只负责在 WiFi 和串口之间搬运字节不理解任何内容。PC 服务器仓库管理员。把固件app.bin按协议一块块发给板子并核对确认。物理链路服务器 ←WiFi/TCP→ ESP8266 ←UART(115200)→ GD32。三、Flash 分区512KB区域地址范围大小占用扇区内容Bootloader0x08000000 ~ 0x08007FFF32KB扇区 0~1引导 OTA 逻辑约 15KBApp0x08008000 ~ 0x0807FFFF480KB扇区 2~7业务固件分界线 0x08008000 正好是扇区 2 起始16KB 对齐擦除和链接都干净。GD32F407 扇区0~3 各 16KB4 号 64KB5~7 各 128KB。升级标志用 RTC 备份寄存器 BKP0RTC_BASE0x50写魔数0x4F544131(“OTA1”)。软复位后保留备份域不受 NRST 影响断电才清——正好用于App 请求升级 → 复位 → Bootloader 看到标志进入 OTA。四、Bootloader 工程gd32f4_bootBootloader 是看门人只管引导 升级不做业务。主流程intmain(void){intupdate0;systick_config();dbg_init();led_init();// 时钟/调试串口/LED// 入口1升级标志App 请求过if(*(volatileuint32_t*)OTA_FLAG_ADDROTA_FLAG_MAGIC)update1;// 入口2上电 2 秒内调试串口收到 uif(!update){/* 轮询 2s */}if(update){if(ota_run()0)打印成功;else打印失败;}if(ota_app_valid())ota_jump_to_app();// 有效则跳转while(1){/* 无有效App则等 u 重试永不砖 */}}核心文件ota.c升级主体——包头解析、擦除 App 扇区、逐块收写、CRC32 校验、App 有效性检查、跳转。esp8266.cAT 驱动连 WiFi、TCP、IPD 帧解析、发送 ACK。dbg.cUSART0 调试打印。ota.h分区地址、升级标志、协议魔数、块大小等。OTA 传输流程ota_run1. esp8266_connect() 连 WiFi → TCP 连服务器 2. 发就绪握手 0x06 服务器收到后开始 3. 收 10 字节包头 A5 5A 长度(u32 LE) CRC32(u32 LE) 4. 擦除 App 区 扇区 2~7擦除前无数据在途 5. 发擦除完成握手 0x06 服务器放行 6. 逐块收(128B)写Flash回ACK 0x06 7. 全收完比对 CRC32 一致才跳转五、App 工程改造gd32f4App 是 OTA 架构里的业务程序需要两处改造链接到 0x08008000KeilOptions → Target里 IROM1 起点 0x08008000、大小 0x78000。向量表偏移system_gd32f4xx.c的VECT_TAB_OFFSET改成0x8000否则中断全跑飞。升级触发串口 USART0 收到up回车 → 写 BKP0 “OTA1” →NVIC_SystemReset()复位进 Bootloader。编译后自动生成 app.bin在Options → User → After Build加一句D:\Program Files\Keil\ARM\ARMCLANG\Bin\fromelf.exe --bin -o .\Objects\app.bin .\Objects\GD32F407_Template.axf六、PC 服务器ota_server.py一个纯 Python 的 TCP 服务器约 80 行。核心循环# 就绪握手readyconn.recv(1)# 等板子发 0x06# 发 10 字节包头conn.sendall(MAGICstruct.pack(I,len(data))struct.pack(I,crc))# 等擦除完成goconn.recv(1)# 等板子发 0x06# 分块发送每块等一个 ACK收到后延时 50ms 再发下一块sent0whilesentlen(data):chunkdata[sent:sentCHUNK]conn.sendall(chunk)ackconn.recv(1)# 等板子 ACKifack!b\x06:breaksentlen(chunk)time.sleep(0.05)# 发完等 2 秒再关连接给 ESP8266 时间把尾部数据吐给板子time.sleep(2)conn.close()七、传输协议详解通俗版整个通信像打电话对暗号① 板子→服务器: 0x06 我准备好了 ② 服务器→板子: 10字节包头 A5 5A 长度 CRC32 ③ 板子→服务器: 0x06 我擦除完了放行 ④ 服务器→板子: 数据块(128B) 一块一块发 板子→服务器: 0x06 这块写好了 重复直到最后一块 ⑤ 板子校验 CRC32 对上了才跳转关键设计包头先行长度收多少 CRC32收完怎么验收。停等协议发一块等一个 ACK板子写 Flash 会暂停 CPU一发一答保证数据不溢出、不丢。擦除先打招呼擦除期间不能有数据在途。CRC32 全程边收边算最后一次性比对一个错字节都逃不掉。八、烧录与使用流程先烧 Bootloader打开GD32F407_Boot.uvprojxF7 F8。再烧 App打开GD32F407_Template.uvprojxF7自动生成 app.bin F8。改esp8266.h里的 WiFi 名称/密码/服务器 IP。PC 跑服务器python ota_server.py Objects\app.bin。触发升级串口助手发up回车App 里或上电 2 秒内发uBootloader。完整链路连 WiFi → 连服务器 → 交互协议 → 写 Flash → 校验 → 跳转新 App。九、踩坑记录这才是本文的精华这套方案看似简单实际从零调通翻了大车。下面每个坑都是真实踩出来的供大家避坑。坑 1跳转 App 时汇编级崩溃最难现象Bootloader 打印跳转成功App 却完全没跑。根因__set_MSP(sp); ((void(*)(void))rv)();这两句被 armclang 优化后编译器把跳转地址暂存在旧栈[sp,#4]切栈之后才从新栈读地址 → 读到垃圾值直接跳飞。反汇编实锤MSR MSP,r0 ; ← 已切到 App 的栈 LDR r0,[sp,#4] ; ← 从这里读跳转地址读到 App 栈的残留数据 BLX r0 ; ← 跳飞修复换栈 跳转合并成一条内联汇编地址全程在寄存器里不经过栈__asmvolatile(MSR msp, %0\nBX %1::r(sp),r(rv):memory);教训凡先切栈指针、再用局部变量的代码编译器可能在切栈后读旧栈 —— 用寄存器或内联汇编钉死。坑 2跳转后全局中断没开现象App 能进main卡死在delay_1ms。根因Bootloader 跳转前__disable_irq()跳转 ≠ 复位PRIMASK 残留为 1所有中断含 SysTick被屏蔽延时计数器没人递减 → 死循环。修复App 的main里加__enable_irq()。坑 3块/ACK 错位 幽灵 ACK导致 OTA 反复失败现象服务器凭空收到一个 ACK全程错位最后一块永远等不到、超时。根因板子在擦除前后发了三个握手0x06服务器只读两个。多余的字节滞留 TCP 缓冲区被服务器误读成第一块的 ACK从此服务器永远比板子快一块 → 提前发完关闭 → 尾块丢失。修复删除多余的收到包头字节只保留就绪擦除完成两个握手服务器发完再等 2 秒关连接。坑 4ESP8266 透传模式不可靠部分 AT 固件的透传模式ATCIPMODE1收发大数据不可靠——小数据能通、大数据一传就断。改用最经典的IPD数据帧模式ATCIPMUX0 逐帧解析IPD,长度:数据兼容性最好。坑 5等 “SEND OK” 吃掉下一帧头发完 ACK 等SEND OK应答但服务器收到 ACK 后会立刻发下一块IPD帧头可能先到并被扫描函数吞掉 → 收帧超时。修复不再等SEND OK让帧解析自动跳过残留文本。坑 6Keil 下载后程序不运行不是代码问题Keil 的 “Reset and Run” 没勾选下载后芯片停在暂停态。Options → Utilities → Settings → Flash Download勾选Reset and Run即可。十、总结这套 OTA 方案跑通的四步分区Bootloader(32KB) App(480KB)升级标志放备份寄存器。Bootloader引导 联网 擦写 校验 跳转所有脏活都它干。App只负责请求升级收到命令置标志复位不参与接收。协议包头(长度CRC32) 分块停等 ACK 擦除握手保证不丢不坏。代码侧最难的两个点boot跳转app、ACK 错位。把这几个悟透任何单片机的 Bootloader OTA 基本都能拿下。希望这篇文章帮你少走我走的弯路。附工程文件清单供参考https://gitee.com/born-again-i-am-a-handsome-man/gd32f407-ota文件作用gd32f4_boot\GD32F407_Boot.uvprojxBootloader 工程32KB0x08000000gd32f4_boot\User\ota.c/.h升级协议、擦写、CRC、跳转gd32f4_boot\User\esp8266.c/.hESP8266 AT 驱动IPD 帧模式gd32f4_boot\User\main.c引导流程gd32f4\GD32F407_Template.uvprojxApp 工程480KB0x08008000gd32f4\ota_server.pyPC 固件服务器gd32f4\OTA_README.md使用说明