# iot_ota_app **Repository Path**: xuedejin/iot_ota_app ## Basic Information - **Project Name**: iot_ota_app - **Description**: 这是运行在 STM32F103C8T6上的设备应用程序,配合同一 OTA 方案中的 Bootloader 使用。项目的网页管理端和设备接入平台由自行部署的 ThingsBoard 开源 IoT 平台提供,负责设备数据展示、RPC 控制和 OTA 固件管理;设备通过 ESP8266 接入 Wi-Fi 和 MQTT 与平台通信。新镜像写入非活动槽后,应用请求 Bootloader 在下次复位时试启动。 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-10-12 - **Last Updated**: 2026-10-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: IOT, OTA, stm32, esp8266, bootloader ## README # STM32 IoT OTA Application 这是运行在 STM32F103C8T6(Blue Pill / C8T6)上的设备应用程序,配合同一 OTA 方案中的 Bootloader 使用。项目的网页管理端和设备接入平台由自行部署的 ThingsBoard 开源 IoT 平台提供,负责设备数据展示、RPC 控制和 OTA 固件管理;设备通过 ESP8266 接入 Wi-Fi 和 MQTT 与平台通信。新镜像写入非活动槽后,应用请求 Bootloader 在下次复位时试启动。 本仓库仅包含 STM32 设备端固件,不包含 ThingsBoard 服务端或网页端源码,也不是 Bootloader。工程基于 STM32CubeMX 外设初始化代码,使用 CMake、Ninja 和 ST ARM Clang 工具链构建;A/B 槽位链接脚本和 OTA Flash 接口需与配套 Bootloader 的存储布局一致。 配套 Bootloader 源码仓库:[https://gitee.com/xuedejin/bootloader](https://gitee.com/xuedejin/bootloader)。 ## 1. 功能概览 - A/B 双槽应用:可分别构建链接到 Slot A 或 Slot B 的固件。 - MQTT 设备通信:对接自部署的 ThingsBoard 开源 IoT 平台,支持传感器遥测、RPC 控制、客户端属性同步和 OTA 固件主题。 - OTA 下载和写入:校验元数据,擦除非活动槽,接收固件分块并写入 Flash。 - 镜像校验和切换:对下载镜像计算 CRC32,检查镜像有效性后调用 `Boot_SetPending()` 设置待启动槽。 - 启动确认:应用启动时检查当前槽;若它是 pending 槽,则调用 `Boot_ConfirmPending()` 确认升级。 - 传感器遥测:每 500 ms 刷新 AHT20、INA226 和 ADC 数据缓存;MQTT 已连接时按 5 s 节拍上传。 - 远程设备控制:处理 RPC 命令以控制 LED、蜂鸣器和继电器,并回传命令应答及设备属性。 - 串口调试:USART1 输出日志;USART3 连接 ESP8266,使用 DMA 接收数据。 ## 2. 目标硬件与 Flash 布局 工程按 STM32F103C8T6 的 64 KB Flash、20 KB RAM 配置。应用使用的 Flash 地址必须与 Bootloader 保持一致: | 区域 | 起始地址 | 大小 | 说明 | | --- | --- | --- | --- | | Bootloader | `0x08000000` | 8 KB | 引导程序 | | App Slot A | `0x08002000` | 24 KB | A 槽应用镜像 | | App Slot B | `0x08008000` | 24 KB | B 槽应用镜像 | | OTA Flag | `0x0800E000` | 4 KB | OTA 状态记录区 | | Reserve | `0x0800F000` | 4 KB | 预留区 | Slot A 和 Slot B 分别由 [linker/app_slot_a.ld](linker/app_slot_a.ld) 和 [linker/app_slot_b.ld](linker/app_slot_b.ld) 链接。每个应用镜像最大为 24 KB;OTA 元数据中的 `fw_size` 不得超过该上限。槽位地址及状态接口定义在 [Modules/Inc/boot.h](Modules/Inc/boot.h)。 ## 3. 主要代码结构 ### 3.1 应用入口与外设初始化 - [Core/Src/main.c](Core/Src/main.c):设置向量表偏移、初始化 HAL/时钟和外设,按启动顺序调用应用启动管理模块,连接 MQTT 并进入轮询主循环。 - [Modules/Src/app_boot.c](Modules/Src/app_boot.c):应用启动层,统一判断当前运行槽、首次启动时初始化活动槽状态,并在 MQTT 连接成功后确认当前 pending 槽。它调用 `boot.c` 的状态/Flash 接口,但不处理网络下载。 - [Modules/Src/boot.c](Modules/Src/boot.c):Bootloader 协议对应的底层 OTA 管理接口,负责镜像校验、CRC、Flash 槽擦写和 OTA 状态记录读写。 - [Core/Src/usart.c](Core/Src/usart.c)、[Core/Src/i2c.c](Core/Src/i2c.c)、[Core/Src/gpio.c](Core/Src/gpio.c):STM32CubeMX 生成的 UART、I2C 和 GPIO 初始化代码。 - [Modules/Src/serial.c](Modules/Src/serial.c):串口日志输出。 ### 3.2 ESP8266 与 MQTT - [Modules/Src/esp8266.c](Modules/Src/esp8266.c):ESP8266 AT 命令、Wi-Fi/MQTT 连接和 DMA 接收缓冲处理。 - [Modules/Src/mqtt_core.c](Modules/Src/mqtt_core.c):连接管理、消息发布订阅、接收数据分发和主轮询。 - [Modules/Src/mqtt_device.c](Modules/Src/mqtt_device.c):RPC 命令处理、GPIO 输出更新、设备属性同步和 RPC 应答。 - [Modules/Src/mqtt_telemetry.c](Modules/Src/mqtt_telemetry.c):传感器读取、遥测缓存和 MQTT 上传。 - [Modules/Src/mqtt_ota.c](Modules/Src/mqtt_ota.c):OTA 元数据解析、固件分块请求/接收、Flash 写入、校验和待启动状态设置;通过 `AppBoot_GetRunningSlot()` 获取当前槽,选择非活动槽。 MQTT 主题定义集中在 [Modules/Inc/mqtt_common.h](Modules/Inc/mqtt_common.h),包括: | 用途 | 主题 | | --- | --- | | 设备 RPC 命令 | `v1/devices/me/rpc/request/+` | | OTA 元数据(ThingsBoard 共享属性更新) | `v1/devices/me/attributes` | | OTA 固件分块响应 | `v2/fw/response/+/chunk/#` | | 遥测及 OTA 状态 | `v1/devices/me/telemetry` | | 设备状态客户端属性 | `v1/devices/me/attributes` | | OTA 固件分块请求 | `v2/fw/request/+/chunk/#` | RPC 设备控制支持 `led1Set`、`led2Set`、`beepSet` 和 `relaySet` 四种方法,`params` 使用布尔值控制开关。OTA 元数据需通过 ThingsBoard 共享属性下发,包含字符串字段 `fw_title`、`fw_version`、`fw_checksum`,以及整数 `fw_size`。其中 `fw_checksum` 必须是 8 位十六进制 CRC32 字符串;代码会对解析出的数值进行 32 位字节序转换,因此生成元数据时必须与本实现的字节序约定一致。版本号与当前固件版本相同的升级会被跳过。设备按每块 256 字节请求固件,单块响应等待超时为 20 s,最多重试 5 次。OTA 成功后应用会尝试上报 `fw_state: UPDATED` 并复位;失败时会尝试上报 `fw_state: FAILED`。 ### 3.3 传感器和设备控制 - AHT20、INA226 通过 I2C1 读取;I2C1 使用 PB6(SCL)和 PB7(SDA)。 - USART1(PA9 TX / PA10 RX,115200、8N1)用于调试输出。 - USART3(PB10 TX / PB11 RX,115200、8N1)连接 ESP8266。 - LED1/LED2:PB9/PB8;蜂鸣器:PC13;继电器:PB1。 - 按键输入:PB12(OK)、PB13(UP)、PB14(DOWN)、PB15(RIGHT)、PB5(LEFT)、PA8(ESC)。 按键目前配置为上拉输入,但主程序尚未调用按键模块进行扫描,因此当前固件没有按键交互功能。请按实际开发板原理图确认引脚、电平和外设地址;不同板卡可能需要修改 CubeMX 配置及相应驱动。 ## 4. 启动与 OTA 流程 1. Bootloader 根据持久化状态选择活动槽或 pending 槽,并校验镜像后跳转到应用。 2. `main()` 调用 `AppBoot_ProvisionActiveIfNeeded()`;若 OTA 状态记录不可读,则由应用启动层根据当前运行槽调用 `Boot_ProvisionActive()` 初始化活动槽。此步骤不依赖网络连接。 3. 应用连接 Wi-Fi/MQTT 并订阅相关主题;连接成功后,`main()` 调用 `AppBoot_ConfirmPending()`。只有当前槽与 pending 槽相同时,应用启动层才调用 `Boot_ConfirmPending()`。 4. 主循环轮询处理 RPC、遥测和 OTA 消息。 5. 收到有效 OTA 元数据后,应用选择非活动槽,擦除该槽并向服务器请求固件分块。 6. 每块数据写入 Flash,完成后校验固件 CRC32 及镜像有效性。 7. 校验通过后调用 `Boot_SetPending()`,上报升级成功并复位;Bootloader 下次启动时试启动新槽。 8. 若新应用未能启动并确认,Bootloader 根据自身 pending 启动计数策略回滚到已确认的活动槽。 当前实现将 MQTT 连接及主题订阅成功作为 pending 镜像确认的门槛;若 MQTT 不可用,活动槽初始化仍会执行,但 pending 镜像保持未确认。网络连通不等同于完整业务自检或用户确认;若需要更严格的确认标准,应在 `MQTT_Connect_Server()` 成功后增加本地业务健康检查,并只在检查通过时调用 `App_ConfirmIfPending()`。 ### 4.1 OTA 非阻塞状态机 OTA 下载由 [Modules/Src/mqtt_ota.c](Modules/Src/mqtt_ota.c) 中的状态机驱动。`MQTT_Poll()` 每轮调用 `MQTT_OTA_Poll()`,状态机根据当前状态推进一个阶段后返回;等待服务器响应时不会在主循环中持续轮询或等待整份固件下载完成。ESP8266 接收处理收到元数据或完整分块后设置标志,后续轮询再处理该数据。因此,在等待网络分块期间,主循环仍可继续运行传感器刷新、设备命令和遥测处理。 状态转换顺序如下: | 状态 | 处理内容 | | --- | --- | | `OTA_ST_IDLE` | 等待 OTA 元数据就绪标志。 | | `OTA_ST_BEGIN` | 检查 MQTT/固件参数与 Boot 状态,选择非活动槽并初始化下载进度。 | | `OTA_ST_ERASE` | 擦除目标槽;成功后进入分块请求。 | | `OTA_ST_REQUEST` | 请求当前固件分块;网络断开时按间隔尝试重连。每块默认 256 字节。 | | `OTA_ST_WAIT` | 等待分块接收标志;超时或断连时重试当前块,单块最多重试 5 次。 | | `OTA_ST_WRITE` | 校验接收长度,将当前块写入目标槽并更新接收进度。 | | `OTA_ST_VERIFY` | 校验整镜像 CRC32 和镜像有效性,通过后调用 `Boot_SetPending()`。 | | `OTA_ST_DONE` | 上报 `UPDATED` 并触发系统复位。 | | `OTA_ST_ERROR` | 上报 `FAILED`,清理本次 OTA 流程并回到空闲状态。 | 这里的“非阻塞”指网络分块等待采用轮询状态和超时/重试机制,不会为了等待整段网络响应而停住主循环;它不代表每个状态内的所有操作都完全异步。Flash 擦除/编程、MQTT 发布及连接操作仍通过现有驱动接口执行,单次调用可能耗时或等待其配置的超时。因此 OTA 阶段仍可能对其他轮询任务造成短暂延迟。 ## 5. 配置 Wi-Fi 与 MQTT 凭据 本地凭据头文件为 `Modules/Inc/secretkey.h`,已由 `.gitignore` 忽略,不应提交到版本库。若本地没有该文件,可复制 `Modules/Inc/secretkey.example.h` 后填写以下配置: - `WIFI_SSID`、`WIFI_PASSWORD` - `MQTT_CLIENT_ID`、`MQTT_USERNAME`、`MQTT_PASSWORD` - `MQTT_SERVICE_IP`、`MQTT_SERVICE_PORT` ## 6. 构建 ### 推荐:构建 PowerShell 脚本 工程提供 [build-app.ps1](build-app.ps1),会从本机 STM32Cube bundles 中查找 CMake、Ninja 和 ST ARM Clang。需要先安装包含这些工具的 STM32Cube 工具包。可在项目根目录执行: ```powershell # Release 版本,同时构建 A/B 两个槽 .\build-app.ps1 -Slot Both -Configuration Release -Version v1.2.0 # 仅构建 A 槽 Debug 版本 .\build-app.ps1 -Slot A -Configuration Debug -Version v1.2.0 # 仅构建 B 槽 Release 版本 .\build-app.ps1 -Slot B -Configuration Release -Version v1.2.0 ``` 脚本支持的槽位为 `A`、`B`、`Both`,配置为 `Debug` 或 `Release`。版本号会编译进应用,并用于生成固件文件名。 ### CMake Presets 工程也定义了 `Debug`、`Release`、`slot-a` 和 `slot-b` 预设(见 [CMakePresets.json](CMakePresets.json))。`APP_SLOT` 选择链接槽位,`APP_VERSION` 设置固件版本。构建工具链使用 [cmake/starm-clang.cmake](cmake/starm-clang.cmake);请确保 CMake、Ninja、ST ARM Clang 可用且版本与工程环境兼容。 ### 构建产物 每个槽构建都会生成 ELF 和槽位专属的 OTA BIN 文件,例如: ```text build/slot-a/iot_ota_app-v1.2.0-slot-a.bin build/slot-b/iot_ota_app-v1.2.0-slot-b.bin ``` BIN 文件按对应槽位地址链接。发布 OTA 固件时,必须将匹配目标槽位的镜像及其正确的文件大小、版本号和 CRC32 元数据配置到固件服务;不要把一个槽位链接的镜像当作另一个槽位的镜像使用。 ## 7. VS Code 下载与调试 可在本地 `.vscode/launch.json` 配置中使用 STM32 扩展的 `stlinkgdbtarget` 调试配置。当前 `.vscode/` 目录被 `.gitignore` 排除,不会随仓库分发;克隆项目后需自行创建或配置调试文件。安装并配置项目所需的 STM32Cube 扩展、ST ARM Clang 和 ST-Link GDB Server 后: 1. 使用 VS Code 打开应用工程目录,连接目标板和 ST-Link。 2. 在“运行和调试”中选择 `STM32Cube: Launch ST-Link GDB Server`。 3. 按 **F5**;若本地调试配置包含 `preBuild`,会先构建再启动调试。 该调试配置依赖 STM32 扩展提供的构建命令和项目上下文;配置包含 `preBuild` 时,按 F5 会先构建再启动调试。如果构建或连接失败,请查看 VS Code“输出”面板及调试控制台。下载调试时确认构建所选槽位与当前 Bootloader 状态和测试目标相符。 ## 8. 典型目录 ```text . ├── CMakeLists.txt ├── CMakePresets.json ├── build-app.ps1 ├── linker/ │ ├── app_slot_a.ld │ └── app_slot_b.ld ├── Core/ │ ├── Inc/ # CubeMX 外设及 HAL 配置头文件 │ └── Src/ # 应用入口、外设初始化和中断 ├── Modules/ │ ├── Inc/ # app_boot、boot、MQTT、ESP8266、传感器等模块接口 │ └── Src/ # 应用启动管理、OTA/MQTT、驱动和传感器模块 ├── Drivers/ # STM32 HAL 与 CMSIS └── cmake/ # 工具链及 CubeMX CMake 配置 ``` ## 9. 使用注意事项 - 本工程需要与匹配的 Bootloader 一起使用;应用链接地址、槽大小、Flash 状态区及 OTA 接口必须保持一致。 - STM32F103C8T6 的两个应用槽各为 24 KB。构建或 OTA 时请检查固件大小不超过目标槽容量。 - OTA 更新会擦除所选非活动槽;更新期间应保证供电稳定,不要手动向活动槽或 OTA 状态区写入无关数据。 - 固件服务上的 CRC32 必须与应用对固件内容计算得到的值一致,`fw_size` 必须是固件实际字节数。 - 当前应用启动后较早确认 pending 镜像;如需在联网或设备自检完成后才确认,应在业务层增加检查并调整确认流程。 - 更改引脚、传感器或无线模块时,同时检查 CubeMX 初始化、模块驱动及硬件连线。 ## 10. 适用场景 - 基于 STM32F1 的联网设备应用 - 通过 ESP8266 和 MQTT 进行遥测及远程 GPIO 控制 - 与双槽 Bootloader 配合实现固件分块下载、预览启动与失败回滚