资讯详情

STM32F103基于4G模块的OTA远程固件升级实战:BootLoader与Flash分区设计

发布时间:2026/9/16 15:45:34

500+
企业客户服务经验
120+
行业领域内容覆盖
3000+
原创页面设计沉淀
98%
客户满意度

STM32F103基于4G模块的OTA远程固件升级实战:BootLoader与Flash分区设计

简介面向STM32F103单片机的嵌入式开发者这份资料演示了通过A7680C 4G模块实现远程固件升级OTA的完整工程。代码基于KEIL标准库编写已在STM32F103上运行同系列其他型号也可通过修改芯片型号和Flash容量适配。压缩包共456个文件其中96个.h头文件与84个.c源文件构成主体逻辑配合uvprojx、uvoptx等工程文件可直接打开编译bin、hex固件便于烧录验证sct、map、lst等则记录了链接与编译细节适合深入分析。资源整体约12.35MB已有364人学习。工程内包含Boot区和APP区程序并标注了与A7680C的接线引脚还附带一键清除KEIL编译残余的批处理脚本。对于需要快速搭建4G远程升级链路、避开常见移植问题的开发者这份例程能有效缩短调试周期。1. 为什么拿 STM32F103 和 A7680C 搭 4G OTA 升级我第一次按下 A7680C 的 PWRKEY 时想法和大多数人一样一块几块钱的 STM32F103加一块 4G 模块能不能真的把固件远程刷上去答案是肯定的而且这套例程已经把链路跑通了。它要解决的场景很常见设备装在配电箱、农田大棚、工地塔吊上返厂刷程序根本不现实而 STM32F103 又是存量最大的控制核心。与其把老设计推倒重来做 NB-IoT 或 WiFi不如外挂 4G 模块通过 AT 指令把新固件拉回来走 BootLoader 跳转更新。适合正在做数据采集、远程控制、物联网网关的单片机开发者也适合手里攥着标准库工程、不想迁移到 HAL 库的老手。2. BootLoader 与 Flash 分区设计把升级标志位藏进最后 2KBOTA 升级的核心不是一个下载动作而是系统结构上的重新分工。如果不加 BootLoader下载下来的二进制根本不知道该往哪放程序自己覆盖自己也是极其危险的。我的做法是把整个工程拆成 BootLoader 和 App 两个独立固件App 负责业务BootLoader 负责启动和远程更新两者用 Flash 地址硬隔离。下面先定义分区再解决启动判断最后串起状态机。2.1 为什么 OTA 一定要有 BootLoaderBootLoader 的本质是“升级执行器”App 跳转和下载过程的可靠性都由它兜底。对于 STM32F103 这种内部 Flash 只有几十到上百 KB 的 MCU外挂 4G 模块后BootLoader 要做的事反而要精简上电判断是否进入升级模式若需要升级则初始化 A7680C 下载固件写入 App 区最后跳转。不需要把 TCP/IP 协议栈跑在单片机里也不用跑复杂文件系统这样才能把 BootLoader 压到 16KB 以下。另一个容易被忽略的原因是App 运行期间如果直接擦写自身 Flash 来升级指令预取会有机会执行到擦了一半的代码导致 HardFault。所以升级动作必须从 BootLoader 这个独立的地址空间发起App 只能把升级请求写到固定标志区然后软复位把控制权交还给 BootLoader。这个设计是 OTA 能稳定工作的前提。2.2 Flash 分区与升级标志位分区设计我按 STM32F103C8T664KB Flash来写如果你用的是 RCT6 或别的型号只需要按比例平移地址。下面是一张常用的分区表。区域地址范围大小功能BootLoader0x08000000 - 0x08003FFF16KB上电启动、升级逻辑、跳转判断App 业务区0x08004000 - 0x0800F3FF44KB用户业务程序编译时 IROM1 从 0x08004000 开始OTA 标志区0x0800F400 - 0x0800F7FF1KB保存升级请求和固件校验信息保留/日志区0x0800F800 - 0x0800FFFF2KB可存版本号、运行日志也可留给 BootLoader 做标记注意 STM32F103 的 Flash 页大小为 1KB擦除以页为单位所以我把标志区对齐到整页。升级标志用一个结构体放在一个页里避免频繁擦写把 Flash 磨坏。#define OTA_FLAG_ADDR 0x0800F400U #define OTA_MAGIC 0xA5A5A5A5U typedef struct { uint32_t magic; /* 固定魔数用于判断标志区是否有有效内容 */ uint32_t app_len; /* 待升级 App 固件的长度 */ uint32_t app_crc; /* App 固件的 CRC32 校验值 */ uint32_t upgrade_state; /* 升级状态0无请求1请求升级2下载完成 */ } ota_flag_t;这段结构体是整个升级流程的“通信协议”。App 端收到云平台指令后先填充 app_len 和 app_crc再把 upgrade_state 置为 1最后写进 0x0800F400。BootLoader 每次启动只读这一个结构体就能决定是进升级流程还是直接跳 App。之所以不用外部 EEPROM是因为 F103 内部没有 EEPROM额外挂 24C02 会增加成本和接线直接用一个 Flash 页足够。2.3 升级状态机与启动流程整个升级流程用状态机描述BootLoader 和 App 各占一半。App 运行时如果收到云平台下发的升级指令先通过 4G 模块确认有新版本把上述元数据写进 OTA 标志区然后软复位进入 BootLoader。BootLoader 上电后在 main 函数最开始读标志区magic 对应且 upgrade_state 为 1 时进入升级流程否则直接跳转 App。下载过程中 App 区的旧固件可能已经被擦掉如果中途断电下次上电因为 App 区栈顶指针无效BootLoader 的跳转函数检测到非法地址同样会进入升级模式防止设备变砖。判断栈顶的代码放在 BootLoader 里是一个关键点。uint32_t app_msp *(volatile uint32_t *)APP_BASE_ADDR; if ((app_msp 0xFFFF0000) 0x20000000) { jump_to_app(APP_BASE_ADDR); } else { /* App 不完整进入 4G 升级模式 */ ota_enter_upgrade(); }这里 APP_BASE_ADDR 对应 0x080040000x20000000 是 STM32F103 SRAM 的起始地址。判断栈顶地址落在 RAM 范围内说明 App 的向量表头没有被擦掉或没有变成全 0xFF如果栈顶非法就说明 App 区不可信必须进入升级模式。这个判断比重置向量更可靠因为某些调试器会填零而不是填 FFFF。3. 用 A7680C AT 指令把固件抓到 F103建连、HTTP 下载、分包接收A7680C 是 4G Cat.1 模块内部自带 TCP/IP 协议栈STM32F103 只需要通过串口发 AT 指令就能拉取固件。这一章把接线、网络建立、HTTP 取固件和分包接收拆开讲每一步都对应例程里的实际代码。3.1 A7680C 的启动与 UART 接线A7680C 工作电压 3.4-4.2V跟 STM32F103 不在一个电源轨上而且模块的串口电平通常不兼容 3.3V 直接输入。例程代码里把 RX、TX 定义在某个 USART 上接线定义都写在注释里用之前先确认三件事模块供电能拉到 2A 峰值PWRKEY 拉低 600ms 以上启动串口 TTL 电平需要做转换。很多开发板直接连 F103 也能跑但功耗一高就容易重启。STM32F103A7680C说明PA2 (USART2_TX)UART_RX主控发送 AT 指令到模块PA3 (USART2_RX)UART_TX模块返回应答和固件数据GNDGND必须共地3.3V → 电平转换UART_RX/ UART_TX建议用 1.8V 电平转换芯片A7680C 的 UART 接口一般支持 1.8V 逻辑和 F103 的 3.3V 引脚直连存在长期损伤风险。稳妥办法是加一块 TXS0108 或电平转换小板至少用两个电阻分压否则长时间运行会出现偶发乱码升级文件一错就是一串地址错误。3.2 先调通网络再谈下载AT 命令序列模块上电后首先要确认 AT 握手成功。A7680C 的 AT 指令与常见 4G 模组兼容度很高用串口工具逐个发送下面的指令即可。网络建立序列是指令期望应答作用AT\r\nOK检查串口和模块状态ATE0\r\nOK关闭回显减少干扰ATCGDCONT1,IP,cmnetOK设置 APNcmnet 是中国移动默认ATCGACT1,1OK激活 PDP 上下文ATCSQCSQ: 20,99查询信号强度第一值 0-3120 以上比较稳ATCOPS?COPS: 0,0,CHINA MOBILE,7确认已注册网络如果你的卡是电信或联通APN 要改成 ctlte 或 3gnet。注意 ATCSQ 只返回信号强度不代表网络已注册所以还要用 ATCOPS? 确认注册上。模块内部自带的 APN 配置不一定能和后续 HTTP 链路配合稳妥做法是在上下文里显式指定 APN。串口发送函数我封装成带期望值判断的形式避免每条指令都要写一遍超时循环。static uint8_t at_cmd(const char *cmd, const char *expect, uint32_t timeout) { uart_put_str(cmd); ringbuf_reset(g_rxbuf); uint32_t start now_ms(); while (now_ms() - start timeout) { if (strstr((const char *)g_rxbuf.data, expect) ! NULL) { return 0; } } return 1; /* 超时返回失败 */ }用法是at_cmd(ATCGACT1,1\r\n, OK, 3000)返回 0 表示成功返回 1 就打印错误码。这个函数背后依赖一个环形缓冲区不要一边从串口中断里收数据一边在主循环里 strstr那样会出现半个包匹配不到的问题。CSTX2023 例程里用的是中断加缓冲区的经典结构调用前先 reset 一次保证检测的是本次命令的应答。3.3 用 HTTP 把固件 body 一节节拉下来网络通了以后用模块的 HTTP 功能下载 APP1.bin 或 APP3.bin。A7680C 支持 HTTP 客户端流程是ATHTTPINIT 初始化ATHTTPPARA 设置 URLATHTTPACTION0 发起 GETATHTTPREAD 读取响应 body。但模块内部 RAM 有限固件动辄几十 KB不能一次性全读回来需要边读边写 Flash。我采用的序列是先 GET 拿到固件长度再用 HTTPREAD 按 1024 字节分段读取凑满一页就写入 App 区。这里有一个关键参数ATHTTPPARA 里的 READTIMEOUT如果设太短大文件下载容易被截断设太长升级失败又要在串口傻等。我一般设 15 秒。uint32_t file_len get_http_file_len(http://192.168.1.10/ota/app.bin); for (uint32_t offset 0; offset file_len; offset 1024) { memset(g_buf, 0xFF, sizeof(g_buf)); snprintf((char*)tmp, sizeof(tmp), ATHTTPREAD%lu,%lu\r\n, offset, 1024); at_cmd((char*)tmp, HTTPREAD:, 5000); /* 从模块返回的数据帧中解析出 1024 字节 */ parse_httpread_bytes(g_buf, 1024); /* 写入 APP 区地址 APP_BASE_ADDR offset */ ota_write_flash(APP_BASE_ADDR offset, g_buf, 1024); }ATHTTPREAD 的偏移和长度要自己拼有些模块支持ATHTTPREADlen但它会从缓冲头开始读不利于分包续传所以我统一用带偏移的方式。每写完一页就打印一次进度方便从串口确认卡在哪一包。如果模块返回 CONNECT FAIL先不要反复发指令用ATCGACT0,1再恢复激活一次通常是 DNS 解析失败重置 PDP 上下文比重启模块更快。4. F103 标准库 Flash 写入与 App 跳转代码拆解固件数据通过串口进来后接下来的硬骨头是 Flash 写入和跳转。STM32F103 标准库 V3.5 的 Flash 操作接口不多但用错地址和时序会把自己锁住。这一章把跳转、擦写、校验三个关键函数拆开讲。4.1 跳转前的关键检查栈顶和复位向量从 BootLoader 跳转 App 前必须检查两个 32 位内容App 区地址处的栈顶指针MSP和偏移 0x04 的复位向量。很多人只检查第一个字第二个字不检查结果跳到一个空地址上死循环。我写的跳转函数会把两个条件都验证。void jump_to_app(uint32_t app_base) { uint32_t msp *(volatile uint32_t *)app_base; uint32_t reset *(volatile uint32_t *)(app_base 4); if ((msp 0xFFFF0000) ! 0x20000000) { return; /* 无效 App停留在 BootLoader */ } if ((reset 0xFFFF0000) ! 0x08000000) { return; /* reset 向量不在 Flash 范围放弃跳转 */ } __disable_irq(); NVIC_SetVectorTable(NVIC_VectTab_FLASH, app_base); /* 标准库写法 */ __set_MSP(msp); ((void (*)(void))reset)(); }NVIC_SetVectorTable 是老标准库函数作用是把 Cortex-M3 的向量表基地址重定位到 0x08004000避免 App 中断进来时查到 BootLoader 的向量表。旧版本标准库里也有 SCB-VTOR 直接写的方法但 F103 某些早期芯片对 VTOR 偏移有限制用库函数更稳。跳转前关掉全局中断是因为外设中断状态在跳转瞬间不可预测App 启动代码会重新初始化 NVIC。4.2 按页擦除、半字写入的细节Flash 写入的坑比想的多。F103 擦除按页进行C8T6 每页 1KB而写入必须以半字16 位为单位。先擦后写顺序不能反否则数据写不进去。标准库的流程固定在五个函数之间。void ota_write_flash(uint32_t addr, const uint8_t *data, uint32_t len) { FLASH_Unlock(); FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); /* 按 1KB 页对齐擦除 */ uint32_t page_start addr ~(FLASH_PAGE_SIZE - 1); for (uint32_t p page_start; p addr len; p FLASH_PAGE_SIZE) { FLASH_ErasePage(p); } /* 半字写入注意一次只能写 16 bit */ for (uint32_t i 0; i len; i 2) { uint16_t halfword data[i]; if (i 1 len) { halfword | (uint16_t)((uint16_t)data[i 1] 8); } FLASH_ProgramHalfWord(addr i, halfword); } FLASH_Lock(); }为什么必须半字写F103 的 Flash 控制器不支持单字节编程写 0xFF 是擦除后的状态写其他字节要先把对应半字整体写入。如果 len 是奇数最后一个字节要补 0xFF否则会越界访问 data 数组。擦除整页很慢一页大约 20ms下 44KB 固件要擦 43 页累计接近 1 秒这个期间要关掉外部看门狗喂狗否则会被打断。注意 FLASH_ClearFlag 这一步不是形式上的之前如果发生写保护或编程错误标志位不清理后续擦写会一直失败。特别是调试 Halt 在 Flash 操作中途时很容易留下 PGERR 标志。4.3 升级完成后的校验与软件复位写完不校验就是耍流氓。我的做法是下载完成后对 App 区整体读回算 CRC32和 OTA 标志区里的 app_crc 比对。app_crc 在下载开始前由服务器侧算好写进标志区如果比对失败说明这次升级内容有问题保留升级请求并复位重试。uint32_t calc_crc32(uint32_t addr, uint32_t len); if (calc_crc32(APP_BASE_ADDR, file_len) ! flag-app_crc) { flag-upgrade_state 1; /* 保留升级请求下次重试 */ save_ota_flag(flag); NVIC_SystemReset(); } flag-upgrade_state 2; save_ota_flag(flag); jump_to_app(APP_BASE_ADDR);save_ota_flag 内部先擦标志页再写新数据。这里有个容易中招的细节如果写入过程中断电标志区会变成一半新、一半旧下次读出来的结构体可能既不是升级请求也不是完成状态。解决方法是调整结构体成员的写入顺序先写 magic 和 app_crc最后写 stateBootLoader 判断时只认 state 的值中间态一律视为未完成升级。例程里给了 APP1.bin 和 APP3.bin 两个文件实际使用中分别代表验证版本和正式版本。通过 HTTP 请求参数指定不同 bin就能在同一个 BootLoader 上反复测试升级流程。5. 远程升级避坑Keil 编程器选择、启动文件匹配和防变砖技巧这一章整理几个最容易让项目卡壳的实操点全部来自我跑 CSTX2023 例程时踩过的坑每一条都能直接套用。5.1 Keil 里 JLINK、STLINK 选择决定你能否一次下载成功拿到工程第一件事是看 Debug 配置。如果电脑上插的是 ST-Link而工程里选的还是 J-LINK一按下载就会报 RDDI-DAP Error。打开 Options for Target → Debug右侧下拉框选成自己的调试器再确认 Settings 里的 Flash Download 算法。算法容量选错也会报 Programming Failed at address 0x08000000。STM32F103C8T6 用 Medium-density 算法RCT6 和 ZET6 用相应容量型号不要盲目选 High-density。5.2 启动文件与 Flash 容量匹配标准库 V3.5 的工程里startup_stm32f10x_hd.s 和 startup_stm32f10x_md.s 不能混用。MD 用于中等容量片内 FlashHD 用于高密度。容量不匹配时启动代码里的 SystemInit 调用没问题但中断向量表地址在链接阶段就偏了最常见的表现是程序能下载但跑飞。如果下载时提示 No target connected先检查 BOOT0 是否被拉高而不是怀疑芯片坏了BOOT01 时芯片跳到系统存储器调试口默认不响应。另一个坑是 App 工程 IROM1 地址。App 的 Options for Target → Target 里IROM1 Start 必须写成 0x08004000Size 写 0xB000对应 44KB不能保持 0x08000000。如果忘了改App 编出来的向量表在 BootLoader 区跳转后执行的是 App 代码但中断向量还是 BootLoader 的一开中断就跑飞。5.3 防变砖三板斧OTA 最怕升级一半断电。我的习惯是第一BootLoader 启动后 5 秒内检查 OTA 标志没有升级请求就立即跳转 App把升级窗口压到最短第二App 区栈顶非法时不要硬跳停留在 BootLoader同时串口打印错误码第三在 BootLoader 里启用独立看门狗喂狗周期短于下载超时时间程序卡在 HTTP 解析上时自动复位。如果不幸把 App 区和标志区都刷坏了还有最后一招通过串口强制让 BootLoader 进入升级模式。CSTX2023 例程的 BootLoader 上电时会在串口打印一个短窗口窗口期内收到任意字符就进入 AT 升级流程。此时即使 App1.bin 已经废了也可以用 APP3.bin 恢复。工程里同时给出这两个 bin目的就是让你验证多版本回退。恢复新固件前先用 ATCGATT1 确认网络注册完成再走 HTTP 下载。如果因为中途断电导致标志区数据混乱先把 ota_flag_t 所在页整体擦除一次让 BootLoader 判定 App 无效然后主动进入升级模式等待新固件。只要 BootLoader 还在设备就不会彻底变砖。远程升级做完后我习惯把 APP1.bin 对应的版本号写进保留区每次上电通过标志区比对这样下次升级前能确认固件是从旧版本升级上来而不是重复刷同一个版本。用一个简单的 4 字节版本号就能实现。本文还有配套的精品资源点击获取
热门专题

继续阅读更多专题内容

围绕企业服务、数字化转型与官网运营的常青话题,持续输出深度内容

企业官网建设指南 企业托管服务模式 财税政策与解读 企业数字化转型 官网SEO与获客 网站安全与运维
配套服务

读完这篇文章,了解更多服务

从整站搭建到SEO布局,17项核心服务助您打造高转化的企业官网

01

企业托管整站搭建

从信息架构到栏目预留,搭建可生长的企业站点骨架,每个页面独立原创设计。...

了解详情
02

规整可信网页设计

雪地靴温暖风原创设计,金属铜线条贯穿全页,拒绝通用模板与AI流水线。...

了解详情
03

企业服务SEO布局

关键词体系与语义化结构,从建站源头为搜索排名而生。...

了解详情
04

业务预约咨询表单

多场景表单与线索收集体系,把访问流量转化为可追踪的销售线索。...

了解详情
05

企业服务站点运维

安全巡检、数据备份与内容更新支持,全年守护网站稳定运行。...

了解详情
06

全终端商务适配

电脑、平板、手机一致呈现,移动端体验与转化同样出色。...

了解详情
需要专业建议?

让专业顾问为您解读行业趋势

关于企业官网建设、SEO获客与数字化转型的任何疑问,欢迎一对一咨询我们的专业顾问。