)
完整源码https://github.com/uiuiiii1/stm32f407-iot-gateway最新版本https://github.com/uiuiiii1/stm32f407-iot-gateway/releases/tag/v0.5基于STM32F407VGT6 FreeRTOS V11.3.0 W5500RS485 采集温湿度 → MQTT 上云 → LCD 仪表盘断网期间数据缓存进外部 Flash 并按天 CSV 归档到 SD 卡联网后自动补传以太网断开时自动切换到 Air780E 4G 备份通道支持 MQTT 远程 OTA 升级Bootloader 双固件槽 失败自动回退。一、v0.5 相比之前做了什么版本里程碑v0.1裸机全功能网关Modbus → MQTT → LCDv0.2FreeRTOS 多任务4 任务 队列/互斥锁 IWDG 心跳v0.3断网缓存 SNTP 对时 联网自动补传5 任务v0.4MQTT OTA 远程升级Bootloader Slot B 备份 自动回滚v0.5SD 卡按天 CSV 归档 Air780E 4G 备份链路v0.5 一次性把两条上云/存储增强塞进来了SPL 与 HAL 两工程功能对齐SD 卡按天 CSV 归档阶段 11走独立的 GPIO 软件 SPI 总线与 W25Q64 硬件 SPI 物理隔离FatFS 挂载YYYYMMDD.CSV每天一个文件拔卡自动降级、插回自动恢复。4G 备份链路阶段 13Air780E 合宙 AT 固件以太网断线超过阈值后自动接管 MQTT 发布网线恢复自动切回RST 引脚硬复位自愈实测约 26 秒自动恢复。二、系统架构温湿度变送器 ──RS485(Modbus RTU)──┐ │ USART2 ┌─────────────▼─────────┐ LCD 仪表盘 ◄──SPI3──┤ STM32F407VGT6 ├──SPI2──W25Q64(断网缓存 OTA 固件槽) (SPI3/1.54寸) │ (FreeRTOS, 5 任务) │ └──┬───────────────┬────┘ SPI1│ │软件 SPI(PC6~9) ┌────────▼───────┐ ┌────▼─────────┐ │ W5500 模块 │ │ microSD │──FatFS 按天 CSV 归档 │ (硬件 TCP/IP) │ │ │ └───────┬────────┘ └───────────────┘ │网线── 路由器 ── MQTT Broker主链路 Air780E 4G (USART3 PC10/11)以太网断线时自动接管的备份通道 AT24C64 (软件 I2C PB10/PB11)补传书签 OTA 升级标志掉电安全FreeRTOS 任务5 个 队列/互斥任务职责IWDG看门狗巡检喂 IWDG 检查全任务心跳COLModbus 采集2 秒周期往队列塞一条数据NETW5500 网络 MQTT 主发布 4G 备份 OTA 分块接收STGW25Q64 断网缓存写入 SD 卡按天 CSV 补传泵LCD1.54 寸 TFT 仪表盘刷新 网络/4G 状态指示三、存储分区布局片内 Flash 0x08000000 - 0x08007FFF Bootloader32KB独立工程 0x08008000 - 0x0807FFFF 应用SPL 或 HAL W25Q648MB硬件 SPI2 0x000000 - 0x07FFFF Slot AOTA 新固件头 8B 大端元数据 0x080000 - 0x0FFFFF Slot B升级前旧版备份回滚用 0x100000 - 0x10FFFF 参数区备用 0x110000 - 0x7FFFFF 断网缓存32B/条环形 CRC16 SD 卡软件 SPIPC6~9独立总线 根目录 /YYYYMMDD.CSV 每天一个 CSVFatFS 挂载 AT24C64软件 I2C掉电不丢 0x00/0x10 补传书签双槽乒乓 序号 CRC 0x20/0x23/0x24/0x30 OTA 升级标志/待确认锁存/启动计数/确认版本关键点SD 卡是独立物理总线GPIO 位拍挂了不影响 W25Q64 的硬件 SPI也不影响断网缓存主链路。四、硬件清单与接线部件型号主控鹿小班 LXB407VG-P1 核心板STM32F407VGT6HSE 8MHz以太网USR-ES1W5500 硬件 TCP/IP 协议栈温湿度RS485 导轨式变送器Modbus RTU 从站DC 5~28V显示1.54 寸 TFT 240×240ST7789SPI存储W25Q64SPI Flash8MBSD 卡microSD 模块软件 SPIPC6~94G银尔达 YED-M100M-C2Air780E合宙 AT 固件EEPROMAT24C64软件 I2CPB10/PB11关键接线W5500SPI1PA5/6/7 片选 PC4 复位 PC5SD 卡软件 SPICSPC6、SCKPC7、MISOPC8、MOSIPC9Air780EUSART3 PC10(TX)/PC11(RX) 115200-8N1RDYPC12 输入RSTPE0 开漏AT24C64软件 I2C SCLPB10、SDAPB11模块无板上拉则各补 4.7k调试串口CH340 TX→PA10、RX→PA9、GND 共地CH340 的 3.3V/5V 不接供电注意所有模块的 3.3V/GND 从核心板取电同源W5500 峰值 200mA 需就近并联 470µF。五、断网环形缓存datalog.cv0.3 引入断网期间每条数据写进 W25Q64 的 0x110000 起扇区32 字节一条typedefstruct{uint32_tmagic;// 0xD1A7C300uint32_tts;// Unix 时间戳int16_tt10;// 温度 x 10uint16_th10;// 湿度 x 10uint16_tcrc16;// 前 12 字节的 CRC16}DatalogRec;布局参数参数值缓存区起点DL_BASE_ADDR 0x110000扇区数DL_SECTORS 1775每扇区条数128总容量227,200 条采样周期2 秒断网续航≈ 126 小时 ≈ 5.3 天书签掉电安全书签存 AT24C64 0x00/0x10 双槽乒乓 序号 CRC掉电撕裂也不会指向错误位置。关键坑v0.3 修复缓存区起点 0x110000 与 OTA Slot A/B0x000000~0x0FFFFF物理不冲突只有网络离线且 SNTP 已对时gTimeSynced1才缓存ts0直接丢板载未装 CR1220 电池掉电即丢 RTC 时间重启后要等 SNTP 重新对时六、SNTP 对时#defineSNTP_SERVER_HOSTntp.aliyun.com#defineSNTP_SERVER_IP{203,107,6,88}// DNS 失败时回退对时成功后更新RTCF407 内部 RTC LSE 32.768kHz置位gTimeSynced 1断网缓存恢复可用SD 卡 CSV 换出NODATE.CSV写入正常的日期文件七、MQTT 主链路以太网 W5500主题主题方向内容gateway/gw001/data上行实时数据每 5 秒gateway/gw001/replay上行断网缓存补传带原始时间戳gateway/gw001/cmd下行命令 升级确认gateway/gw001/ota下行OTA 分块数据ClientIDgw001-lxb407发布 JSON示例{ts:1728230400,t:-10,h:45}重连策略v0.4 加入的稳态保护MQTT_SEND_FAIL_LIMIT 3连续 3 次 send 失败才断开重连瞬时失败不立刻断线防公网重连风暴八、断网缓存补传v0.3 引入补传泵在 STG 任务里跑只在以太网恢复后触发while(在线书签有未补传记录){读书签指向的记录MQTT_Publish(gateway/gw001/replay,json,长度);节流每条20ms 更新书签到下一个 喂 IWDG 心跳}关键点补传 JSON 用原始时间戳ts字段客户端可看出这条是断网期间采的补传失败不动书签下次继续4G 通道不发补传只发实时数据补传仍走以太网恢复后执行九、SD 卡按天 CSV 归档v0.5 新增阶段 119.1 总线与挂载独立 GPIO 软件 SPI与 W5500/W25Q64 的硬件 SPI物理隔离引脚功能PC6CSPC7SCKPC8MISOPC9MOSIFatFS 配置ffconf.hUSE_LFN 0LFN 关闭USE_8DOT3 1只用 8.3 短名未开重入保护单写者模型只允许 STG 任务调用9.2 文件命名每天一个文件YYYYMMDD.CSV例如20261007.CSV放在根目录。RTC 无效未对时或掉电丢了时间时全部写进NODATE.CSVSNTP 对时后自动换到日期文件。9.3 行格式1728230400,-100,452\r\n三列Unix 时间戳、温度 x 10、湿度 x 10。逗号分隔一行一条。9.4 写入策略文件常开FA_WRITE | FA_OPEN_APPEND每次写都是一次f_write每 10 条f_sync一次SDLOG_SYNC_EVERY 10跨天自动换文件检测到日期变先f_close关旧文件再f_open新文件每 60 秒打印一行归档统计[SD] recsN total写失败/挂失败 → 关闭文件置s_ready060 秒后自动重探测拔插卡自恢复9.5 容量估算项值采样频率2 秒每条~28 字节日文件~1.2MBSD 卡磨损可忽略写频率 0.5Hz9.6 与 W25Q64 缓存的关系W25Q64 缓存SD 卡 CSV掉电安全✅AT24C64 书签❌掉电最多丢最近 10 条用途补传事后归档上云走 replay 主题不走离线 CSV拔卡影响无主链路不受影响两层互不背锅W25Q64 保证补传数据不丢SD 卡提供一份可读的 CSV 归档。9.7 核心代码骨架/* SDLOG_Log(ts, t10, h10) */if(!s_ready){if((GetTick()-s_lastTry)SDLOG_RETRY_MS)return;s_lastTryGetTick();SDLOG_Init();if(!s_ready)return;}/* 跨天检测 */if(RTC_IsValid()日期变了){f_close(s_file);SDLOG_OpenDayFile();}/* 写一行 */sprintf(line,%lu,%d,%u\r\n,ts,t10,h10);f_write(s_file,line,len,bw);if(s_sinceSyncSDLOG_SYNC_EVERY)f_sync(s_file);十、4G 备份链路v0.5 新增阶段 13Air780E10.1 硬件与固件模块银尔达 YED-M100M-C2Air780E 核心板跑合宙原厂 AT 固件接线USART3 PC10(TX)/PC11(RX) AF7115200-8N1RDYPC12 输入模块连上服务器后输出 3.3VRSTPE0 开漏输出不接 PWRKEY——那是开关机键拉低 2s 是关机10.2 MQTT 命令序列合宙 AT 固件ATMIPSTARTbroker.emqx.io,1883 → OK CONNECT OKTCP 层 ATMCONNECT1,300 → OK CONNACK OKMQTT 握手 ATMPUBtopic,0,0,payload → OK ATMQTTSTATU → MQTTSTATU :1已连接关键坑MIPSTART返回OK只是收到了必须等CONNECT OKURC再走MCONNECTATMPUB的 payload必须用双引号括住否则 JSON 里的逗号会被当成参数分隔符——v0.5 修复项之一消息内特殊字符要按\hex转义→\22、\r→\0D、\n→\0A、\→\5C10.3 切换策略切 4G 阈值AIR780E_SWITCH_MS 5000UL联调期 5s正式演示建议 30s~5min重试间隔AIR780E_RETRY_MS 5000UL切回以太网网线恢复后NET 任务在下一次发布时切回主链路10.4 硬复位自愈触发条件模块无应答AT 无返回连续发布失败MIPSTART或MPUB多次 ERROR执行/* AIR780E_HardReset() */PE0 先置 ODR1开漏空闲高再配模式 PE0 拉低300ms手册要求 ≥100ms留余量 PE0 释放 等模块启动模块重启要十几秒节流AIR780E_RST_MIN_GAP_MS 60000UL60 秒内不重复复位。实测以太网断线 4G 无信号 情况下60 秒内自动完成复位 → 启动 → 重新握手约 26 秒自动恢复。10.5 LCD 状态指示LCD 显示含义OFF以太网和 4G 都断ONLINE绿以太网在线4G OK4G 已接管发布10.6 4G 通道限制只发实时数据gateway/gw001/data不发补传补传仍走以太网恢复后执行不参与 OTAOTA 走以太网 MQTT 分块4G 链路太慢不做下行命令解析订阅/确认回包走以太网十一、MQTT OTA 远程升级v0.4 引入v0.5 沿用11.1 一句话流程PC 端发 MQTT 消息 → 设备端ota.c分块下载 → 校验 → 写 Slot A → 复位 → Bootloader 搬到 0x08008000 → 新固件发{ota:ok}确认。未确认则启动计数≥3 时自动回滚 Slot B 旧版。11.2 关键参数ota.h#defineOTA_CHUNK_BYTES512// 每块 512 字节#defineOTA_TIMEOUT_MS90000// 整包超时 90s#defineOTA_SIZE_MAX0x78000// 480KB#defineOTA_VER_STR0.5// 固件内部版本号#defineOTA_SLOT_A0x000000// 新固件槽#defineOTA_SLOT_B0x080000// 旧版备份槽#defineOTA_TOPIC_CMDgateway/gw001/cmd#defineOTA_TOPIC_DATAgateway/gw001/ota#defineBL_CONFIRM_BOOTS3// 连续 3 次启动未确认 → 回滚版本号口径v0.4/v0.5 是工程 Release tag阶段 10 OTA 完成固件内部OTA_VER_STR 0.5是固件自身版本号临时改上去测试 OTA 流程的值方便观察升级效果。两者不是一个维度以 Release tag 为准。11.3 Bootloader 独立工程bootloader/是独立 Keil 工程链接到0x08000000前 32KB。应用工程链接到0x08008000三重重定位IROM1 起始地址改0x08008000启动文件SCB-VTOR 0x08008000中断向量表偏移VECT_TAB_OFFSET11.4 PC 端工具两工程目录各一套工具用途gen_ota_msgs.py 固件.bin --ver 0.5把 .bin 切成 512B/块 分块报文输出 ota_msgs.txtota_send.py 块间隔ms broker IP 报文文件paho-mqtt 发送start_broker.cmd启动本地测试 brokeramqttbroker.yaml本地 broker 配置注生成的.bin、ota_msgs*.txt按惯例不入库工具一跑就能重新生成。11.5 两工程 bin 大小差异工程bin 大小块数SPL 标准库≈ 66KB130 块HAL 库≈ 43KB85 块块间隔建议SPL 用 250ms/块约 32.5 秒HAL 用 200ms/块约 17 秒。以实际 .bin 为准。11.6 首刷说明鸡生蛋问题OTA 依赖当前运行的旧固件里的ota.c下载器才能下载新固件。但 v0.3 及更早的固件没有ota.c——它根本收不了ota_begin消息。首刷只能 ST-Link 烧1. ST-Link 烧 Bootloader 到 0x08000000 2. ST-Link 烧应用到 0x08008000选 Erase Sectors不能全片擦 3. 之后才能通过 MQTT 远程升级11.7 升级成功怎么确认刷完复位后串口应看到 gw001 bootloader v1.0 →update pending→verify CRC: OK→flash to 0x08008000→jump app应用启动LCD 显示ONLINEMQTT 上线后应发{ota:ok,ver:0.5}到cmd主题用 MQTTX 订阅cmd主题看到 ok 消息即升级成功拔网线验证LCD 变红、数据进 W25Q64 缓存插回网线自动补传到replay主题如果 3 次启动后没收到确认Bootloader 会自动从 Slot B 回滚到升级前的旧版十二、已知限制未做长时间 soak 测试目前验证都是短测分钟级没有连续几天在线的证据CR1220 电池未装掉电后 RTC 时间丢失要等 SNTP 对时才恢复缓存补传耗时未做秒级日志理论 20ms/条1000 条约 20s实测数据不公开W5500 接收窗口 2KB跨国 broker 高峰丢块建议局域网 broker 测试CAN 驱动骨架保留在工程内未启用如需启用要按官方教程补 TJA1050 硬件AT 固件依赖合宙原厂如果换固件版本MIPSTART/MCONNECT/MPUB命令兼容性要重新验证单写者模型FatFS 未开重入保护SD 卡写入只能由 STG 任务做十三、版本演进版本内容v0.1裸机全功能网关Modbus → MQTT → LCDv0.2FreeRTOS V11.3.04 任务 队列/互斥锁 IWDG 心跳v0.3阶段 9断网缓存 SNTP 自动补传5 任务v0.4阶段 10MQTT OTA Bootloader Slot B 备份 自动回滚v0.5阶段 1113SD 卡按天 CSV 归档 Air780E 4G 备份链路十四、路线图裸机全功能网关Modbus → MQTT → LCDFreeRTOS 多任务5 任务 队列/互斥锁 IWDG断网缓存W25Q64 环形 CRC 掉电书签SNTP 对时ntp.aliyun.com 备用 IP联网自动补传replay 主题 原始时间戳OTA 远程升级Bootloader Slot B 备份 自动回滚SD 卡按天 CSV 归档FatFS 独立软件 SPI 拔插自愈4G 备份链路Air780E AT MQTT RST 自愈 自动切换十五、源码结构├── bootloader/ Bootloader 独立工程32KB0x08000000 ├── 标准库基础工程/ SPL V1.8.0 应用0x08008000 │ ├── Hardware/ │ │ ├── mqtt.c/h MQTT 3.1.1 下行解析 │ │ ├── modbus.c/h Modbus RTU 主站 │ │ ├── w5500_bsp.c/h W5500 板级 BSP │ │ ├── w25q64.c/h W25Q64 驱动 │ │ ├── datalog.c/h 断网环形缓存 │ │ ├── sdcard.c/h SD 卡软件 SPI 驱动 │ │ ├── sdlog.c/h 按天 CSV 归档v0.5 新增 │ │ ├── diskio_sd.c FatFS diskio 适配层 │ │ ├── air780e.c/h 4G 模块驱动v0.5 新增 │ │ ├── at24c64.c/h EEPROM 双槽书签 │ │ ├── ota.c/h OTA 分块下载器 │ │ ├── lcd_spi_154.c/h 1.54 寸 TFT │ │ ├── sntp.c/h SNTP 对时 │ │ ├── rtc.c/h RTC 应用层 │ │ └── FatFs/ FatFS R0.12cv0.5 新增 │ ├── User/app.c 5 任务接线 │ ├── gen_ota_msgs.py OTA 报文生成 │ ├── ota_send.py OTA 发送 │ ├── start_broker.cmd 本地 broker │ └── broker.yaml broker 配置 ├── HAL库工程/ HAL CubeMX 应用与 SPL 功能对齐 └── docs/ 开发计划等文档十六、踩坑记录v0.5 部分SD 卡相关FatFS R0.12c 会把非 FAT 卷误挂载介质预检要先确认卡有 FAT 分区否则挂载成功但读写失败LFN 关闭 8.3 短名文件名叫20261007.CSV就够用不用长文件名占内存未开重入保护FatFS 默认不允许多任务并发访问本工程通过单写者STG 任务规避4G 相关MIPSTART返回 OK 只是收到命令必须等CONNECT OKURC 才能真正MCONNECT——v0.5 的MQTTStart修复项ATMPUBpayload 不加双引号会翻车JSON 里的逗号会被当成参数分隔符导致MPUB FAIL——v0.5 修复项代码里MPTTPublishJson自动加AT 命令超时不能无限等驱动层用AIR780E_At(cmd, expect, resp, len, timeoutMs)强制超时返回 0成功RST 引脚开漏输出要先置 ODR1如果初始化时不先拉高模式配置阶段会被瞬间拉低触发一次意外复位别把 RST 接到 PWRKEYPWRKEY 拉低约 2s 是关机不是复位反而更麻烦模块启动慢上电后协议栈就绪约需 30sAIR780E_WaitReady()会主动等待OTA 相关v0.4 引入v0.5 沿用片内 Flash 分区改错了会抹掉 Bootloader应用工程链接到 0x08008000烧录时必须选Erase Sectors不能全片擦bootloader 跳 app 后尽早恢复全局中断__disable_irq()之后依赖中断的延时函数会无声挂死参考仓库https://github.com/uiuiiii1/stm32f407-iot-gatewayv0.5 Releasehttps://github.com/uiuiiii1/stm32f407-iot-gateway/releases/tag/v0.5参考教程江科大自化协江科大众志STM32 教程系列