工业物联网边缘网关
本文介绍了一种基于STM32F407的工业级物联网边缘计算网关设计方案。系统采用FreeRTOS实时操作系统,集成ModbusRTU传感器采集、LVGL图形界面显示、MQTT云端通信等功能。硬件架构包含STM32F407主控、ESP8266Wi-Fi模块、SP3485RS485收发器等组件。软件实现了ModbusRTU协议栈、MQTT3.1.1协议栈、SPIFlash断网缓存等核心功能,并采用Fr
基于 STM32F407、FreeRTOS、LVGL 的工业级物联网边缘计算网关,具备 Modbus RTU 传感器采集、本地触控显示、MQTT 云端上报及断网数据缓存与手动补传功能。
---
## 系统架构
```
┌──────────────────────────────────────────────────────────────┐
│ 巴法云 MQTT 服务器 │
│ (bemfa.com:9501) │
└──────────────────────────────────────────────────────────────┘
▲
│ Wi-Fi (MQTT 透传模式)
▼
┌──────────────────────────────────────────────────────────────┐
│ 边缘网关 (STM32F407VET6) │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 3.5" SPI 触摸屏 (LVGL v8.3) │ │
│ │ 温湿度仪表盘 / 在线状态 / 运行时间 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ FreeRTOS │ │ W25Q64 │ │ ESP8266 │ │
│ │ 4 任务调度 │ │ 断网缓存 │ │ Wi-Fi 模块 │ │
│ └────────────┘ │ 后 4MB 空间 │ │ AT 固件 │ │
│ └────────────┘ └────────────┘ │
│ │ │
│ SP3485 模块 │
│ (TTL ↔ RS485) │
│ │ │
└─────────────────────────┼─────────────────────────────────────┘
│ RS485 总线 (A+/B- 双绞线)
┌─────────────┼─────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│温湿度传感器│ │光照度传感器│ │继电器模块 │
│地址: 01 │ │(可扩展) │ │(可扩展) │
└──────────┘ └──────────┘ └──────────┘
```
---
## 硬件清单
| 物料 | 型号/规格 | 用途 |
|:---|:---|:---|
| 主控开发板 | STM32F407VET6 (正点原子探索者) | 运行 FreeRTOS 和所有业务代码 |
| 显示屏 | 3.5 寸 SPI 触摸屏 (ILI9488) | LVGL 图形界面 |
| Wi-Fi 模块 | ESP8266 NodeMCU (AT 固件) | MQTT 联网通信 |
| RS485 收发器 | SP3485 | TTL ↔ RS485 电平转换 |
| 温湿度传感器 | 恒智微鑫 HZ8706-485 | Modbus RTU 从机 |
| SPI Flash | W25Q64 (板载) | 8MB 容量,后 4MB 用于断网缓存 |
| 调试器 | ST-Link V2 | 程序下载与调试 |
| 电源适配器 | 12V/1A DC | 传感器独立供电 |
| 线材 | 杜邦线、22AWG 导线 | 电路连接 |
| 面包板 | — | 接线 |
| 120Ω 电阻 | — | RS485 终端电阻 |
### 引脚分配
| 外设 | 引脚 | 说明 |
|:---|:---|:---|
| USART1 (ESP8266) | PA9(TX), PA10(RX) | 115200-8-N-1 |
| USART2 (RS485) | PA2(TX), PA3(RX) | 9600-8-N-1 |
| RS485 方向控制 | PB0 | 高=发送,低=接收 |
| SPI1 (LCD) | PB3(SCK), PB4(MISO), PB5(MOSI) | 触摸屏 |
| SPI2 (W25Q64) | PB12(CS), PB13(SCK), PB14(MISO), PB15(MOSI) | Flash 存储 |
---
## 软件架构
### FreeRTOS 任务设计
| 任务 | 优先级 | 栈 | 职责 |
|:---|:---:|:---|:---|
| **UI Task** | AboveNormal | 1024 | LVGL 刷新+触摸响应 |
| **Sensor Task** | Normal | 512 | Modbus 采集温湿度 |
| **MQTT Task** | Normal | 512 | 在线上报/离线缓存+补传 |
| **AT Task** | Normal | 512 | 初始化+心跳检测+断线处理 |
### 任务间通信
| 通信路径 | 机制 | 用途 |
|:---|:---|:---|
| SensorTask → MQTTTask | `data_queue` (长度1,覆盖写入) | 传递温湿度数据 |
| MQTTTask → UITask | `lvgl_data_queue` (长度1) | 通知 UI 更新 |
| MQTTTask ↔ ATTask | `uart1_mutex` 互斥锁 | 保护 ESP8266 串口 |
| 全局 | `g_mqtt_connected` 标志 | 网络状态判断 |
---
## 核心功能实现
### 1. Modbus RTU 协议栈 (手写)
- **CRC16 校验**:同时实现按位计算法和查表法。查表法预计算 256 项高低字节表,处理速度约为按位计算的 8 倍。
- **03 功能码帧构建**:地址(1B) + 功能码(1B) + 起始寄存器(2B) + 寄存器数量(2B) + CRC(2B,低字节在前)。
- **手动逐字节轮询接收**:发送完成后立即切回接收模式,通过检测 `RXNE` 标志位直接读取 `DR` 寄存器,不经过 HAL 库的阻塞/中断接收 API。
- **双重超时保护**:帧间隔 50ms(远大于 Modbus 标准 3.5 字符/4ms),绝对超时 800ms。
- **数据解析**:温度寄存器 `0x0001`,湿度寄存器 `0x0002`,寄存器值 ÷10 = 实际物理量。
### 2. MQTT 3.1.1 协议栈 (手写)
全部控制报文均手动构建二进制帧,不依赖任何第三方 MQTT 库:
| 报文类型 | 固定报头 | 关键字段 |
|:---|:---|:---|
| **CONNECT** | `0x10` | 协议名 `MQIsdp`、版本 3、Clean Session、Keep Alive 120s、ClientID=UID |
| **SUBSCRIBE** | `0x82` | 报文标识符 `0x0001`、主题 `stm32`、QoS 1 |
| **PUBLISH** | `0x32` | 主题 `stm32`、报文标识符 `0x0001`、JSON 载荷 |
| **PINGREQ** | `0xC0` | 仅 2 字节 |
**关键技术要点**:
- **剩余长度变长编码**:小于 128 字节用 1 字节,大于等于 128 用多字节,每字节低 7 位存数据,最高位标记"是否还有后续"。
- **CONNECT 剩余长度精确计算**:可变报头 12 字节 + ClientID(2+32)=34 字节,总计 46 字节=`0x2E`。
- **透传模式**:TCP 连接建立后进入透传 (`AT+CIPMODE=1` → `AT+CIPSEND`),所有 MQTT 报文直接通过 `HAL_UART_Transmit` 发送,无需 AT 指令交互。
### 3. SPI Flash 断网续传
**缓存区设计**:
- 使用 W25Q64 后 4MB 空间(地址 `0x400000~0x7FFFFF`)作为环形缓存区。
- 每条记录固定 128 字节结构体:魔数(4B) + 时间戳(4B) + 数据长度(2B) + JSON 载荷(116B)。
- `write_addr` 和 `read_addr` 独立管理,到达 8MB 边界自动回绕。
**魔数状态机**:
- `0xDEADBEEF` (MAGIC_VALID):有效数据,等待补传。
- `0xFFFFFFFF` (MAGIC_EMPTY):已补传或空白区域。
- 补传时直接改写 4 字节魔数(通过页写入),**不擦除扇区**,保护同扇区内其他有效数据。
**分批补传**:
- 每次最多补传 5 条,每条间隔 800ms,不长时间占用互斥锁。
- 配合静态计数器保护,即使读写指针异常也不会陷入无限循环。
### 4. 网络检测与断线处理
- **主动心跳检测**:AT 任务每 10 秒发送 PINGREQ,等待 PINGRESP(1 秒超时),连续 2 次失败判定离线。
- **离线缓存**:MQTT 任务检测到离线后,将温湿度 JSON 写入 SPI Flash,不丢失数据。
- **手动重连**:断网后需按复位键重新执行 `MQTT_Init`。采用手动重连方案换取系统极致稳定性,避免自动重连时透传混乱、AT 指令冲突等问题。
- **手动补传**:重连后,MQTT 任务在线分支自动补传缓存数据(分批,每次最多 5 条)。
### 5. LVGL 图形界面
- **单个仪表盘页面**:温度卡片 + 湿度卡片 + 进度条动画 + 底部状态栏(在线状态 + 运行时间)。
- **自定义中文字体**:通过 LVGL 在线工具生成 4bpp 抗锯齿中文位图字体,包含界面所需所有汉字、数字和符号。
- **按需更新机制**:UI 任务仅在数据实际变化时才刷新 LVGL 控件,避免频繁重绘导致 CPU 占用过高。
---
## 踩坑与解决
| 问题 | 原因 | 解决方案 |
|:---|:---|:---|
| Modbus 接收全 0 | RS485 A/B 总线极性反了 | 对调传感器端 A/B 线 |
| MQTT 连接返回 0x05 | CONNECT 报文剩余长度写错 (`0x26`→应为 `0x2E`) | 逐字节重算剩余长度 |
| 透传模式 `>` 匹配失败 | `>` 单独成行,逐行解析失败 | 改用 `ESP_WaitFor` 滑动匹配原始字节流 |
| 心跳检测时卡死 | 持锁期间等 PINGRESP,形成死锁 | 改为"只发不等"或快速扫描 |
| 补传只发一条 | `FlashCache_MarkSent` 重复后移读指针 | 删除 `MarkSent` 中的指针后移操作 |
| 任务栈增大反而溢出 | 总堆容量不足,超出 `configTOTAL_HEAP_SIZE` | 保持 512 字栈大小不变 |
| 重连永远失败 | `MQTT_ReConnect` 开头 `AT+CIPCLOSE` 关闭了刚建立的 TCP | 重连时只发 `AT+CIPSTART`,不做任何关闭操作 |
| 断网发现耗时约 1 分钟 | MQTT 任务被动等待服务器推送数据 | AT 任务主动发 PINGREQ + 等 PINGRESP |
| 界面中文乱码 | LVGL 内置字体不含中文字模 | 用在线工具生成自定义中文位图字体 |
| UI 任务导致系统卡死 | UI 优先级过高频繁刷新抢占 CPU | UI 改为按需更新,MQTT 优先级调至最高 |
---
## 快速开始
### 1. 硬件准备
按照引脚分配表连接各模块。特别注意:
- ESP8266 采用独立 USB 供电,**不要从 STM32 取电**。
- RS485 A+/B- 之间并联 120Ω 终端电阻。
- 所有设备的 GND 共地。
### 2. 固件烧录
- ESP8266 需烧录安信可 **AT 固件** (`ESP8266 NonOS AT Bin V1.7.5`),确保支持透传模式。
- STM32 通过 ST-Link 烧录本项目代码。
### 3. 云平台配置
- 注册 [巴法云](https://cloud.bemfa.com) 账号,创建 MQTT 设备云主题(如 `stm32`)。
- 获取 32 位 UID 私钥,替换 `mqtt.c` 中 `BEMFA_UID` 宏的值。
- 修改 `mqtt.c` 中 Wi-Fi 名称和密码。
### 4. 编译与运行
- 使用 Keil MDK 打开工程,编译后烧录到 STM32F407。
- 上电后设备自动连接 Wi-Fi → TCP → MQTT 登录 → 订阅主题。
- 屏幕显示实时温湿度,巴法云控制台同步看到数据上报。
### 5. 断网续传验证
| 步骤 | 操作 | 预期现象 |
|:---:|:---|:---|
| 1 | 正常联网 | 串口打印实时温湿度,巴法云数据实时刷新 |
| 2 | **拔掉 ESP8266 USB 线** | 屏幕显示"离线",数据自动写入 SPI Flash |
| 3 | **插回 ESP8266 线 + 按复位键** | 设备重连成功,屏幕显示"在线",巴法云出现断网期间补传数据 |
---
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)