STM32 + MQTT 实现物联网设备数据上报与远程控制(实战教程)
🚀 STM32 + MQTT 实现物联网设备数据上报与远程控制(实战教程)
关键词:STM32、MQTT、物联网、W5500、ESP8266、LwIP、Paho、Mosquitto
适合读者:有 STM32 开发经验,想把设备接入物联网并实现上下行(数据上报 + 远程控制)

一、总体方案与选型(为什么用MQTT)
-
MQTT 优点:轻量、发布/订阅、QoS、保留消息、遗嘱消息、支持断连重连 —— 非常适合 IoT 网络波动场景。
-
典型硬件路径:
- 方案 A(企业/生产级、推荐):STM32 + W5500(以太网)或 STM32 + ESP32/ESP8266(Wi-Fi,模块当网络接口),内部运行 LwIP + Paho Embedded MQTT 客户端(设备端实现MQTT)。
- 方案 B(入门更快):STM32 通过串口与 ESP8266/ESP32 互联,把网络与 MQTT 客户端逻辑放在 ESP 模块(NodeMCU 或 Arduino core),STM32 负责传感/控制。优点快、可靠;缺点 STM32 无法直接做 MQTT 客户端逻辑(逻辑在 ESP)。
本教程以 方案 A(STM32 + W5500 + LwIP + Paho Embedded C) 为主线(可在企业/产品部署),并在结尾给出方案 B 的快速实现提示。
二、系统架构
┌───────────────┐ TCP/IP/MQTT ┌───────────────┐
│ STM32 + W5500│ ────────────────────▶│ MQTT Broker │
│ (Paho+LwIP) │ publish/subscribe │(Mosquitto/NMS) │
└──────┬────────┘ └──────┬────────┘│ ││ Local serial / web UI or Python client │▼ ▼
┌───────────────┐ ┌──────────────────┐
│Sensors/Actuators│ │ Dashboard / App │
└────────────────┘ └──────────────────┘
主题(Topic)示例:
- 设备上报:
devices/{deviceId}/telemetry(payload JSON, qos=1) - 控制命令:
devices/{deviceId}/cmd(服务端发布命令,设备订阅) - 状态/保留:
devices/{deviceId}/status(在线/离线; retained)
三、准备工作(硬件 & 软件)
硬件
- STM32(推荐 STM32F4 / STM32F7 / STM32H7;本示例用 STM32F407)
- WIZnet W5500 Ethernet 模块(SPI)或类似网口模块
- 电源、连线、若干传感器(温度/湿度模拟)
- PC(运行 Mosquitto Broker + MQTT 客户端显示)
软件
- STM32CubeMX + STM32CubeIDE(生成 HAL、FreeRTOS、LWIP)
- W5500 驱动(Cube 中或 WIZnet 官方库)
- Paho Embedded C(Eclipse Paho 的嵌入式MQTT客户端)
- Mosquitto(Broker,或使用云端 Broker)
四、实战项目:STM32F4 + W5500 + LwIP + Paho 实现数据上报与远程控制(详尽步骤)
目标:STM32 每 10 秒上报一次传感数据到 Broker;当 Broker 向
devices/{id}/cmd发布{"action":"gpio","pin":5,"value":1},STM32 接收并控制 GPIO。
1)工程创建(STM32CubeMX)
-
新建 STM32F407 项目。
-
配置外设:
- SPI:用于 W5500(例如 SPI1)。
- ETH(可选)或使用 SPI/W5500。
- USART:用于调试(printf)。
- GPIO:控制 LED 或继电器。
- FreeRTOS:启用(推荐任务化设计)。
-
中间件启用:LwIP(选择 DHCP 或静态 IP),生成代码。

CubeMX 会生成 LwIP 网络接口 skeleton(netif),需要将 W5500 驱动适配到 netif 层(示例里我们用 W5500 的 socket API 或封装成 netif)。
2)集成 W5500 驱动 & LwIP netif(关键点)
- W5500 通过 SPI 与 STM32 连接。要点:正确设置 SPI 引脚,确保 CS、INT、RST 接线。
* 有两条实现路径:
- 路径 A(简单):使用 WIZnet Socket API(封装好的库)实现 BSD 风格 socket,然后用 Paho 的
MQTTClient基于该 socket 实现 TCP。 - 路径 B(LwIP):将 W5500 适配为 LwIP netif(更复杂,但更系统化,利于未来扩展)。
本文以 路径 A 为例(更容易复现):W5500 提供的 socket API + Paho Embedded C(基于底层 TCP 接口)。
3)集成 Paho Embedded C(MQTT 客户端)
-
在项目中引入 Paho Embedded C 的
MQTTClient(或MQTTClient-C的嵌入式版本)。 -
基本使用流程:
- 建立 TCP socket(W5500 socket)并 connect 到 broker:1883。
- 用
MQTTClient封装 socket,实现 read/write 回调。 - 调用
MQTTConnect,订阅命令 topic,循环调用MQTTYield或在单独任务中处理。
关键结构示例(伪代码):
/* network wrapper based on W5500 socket */
Network nw;
NetworkInit(&nw);
int rc = NetworkConnect(&nw, "192.168.1.100", 1883);
if (rc != 0) { /* handle error */ }/* MQTT client */
MQTTClient client;
MQTTPacket_connectData options = MQTTPacket_connectData_initializer;
options.MQTTVersion = 4;
options.clientID.cstring = "stm32-device-01";
options.username.cstring = "user";
options.password.cstring = "pass";MQTTClientInit(&client, &nw, 1000, sendbuf, sizeof(sendbuf), readbuf, sizeof(readbuf));
rc = MQTTConnect(&client, &options);
if (rc != 0) { /* error */ }/* subscribe to command topic */
MQTTSubscribe(&client, "devices/stm32-01/cmd", QOS1, cmd_handler);/* publish telemetry */
char payload[128];
sprintf(payload, "{\"temp\":%.2f, \"hum\":%.2f}", 25.3, 60.2);
MQTTMessage message;
message.qos = QOS1;
message.payload = payload;
message.payloadlen = strlen(payload);
MQTTPublish(&client, "devices/stm32-01/telemetry", &message);
注意:
Network需要实现mqtt库期望的read/write接口(阻塞或非阻塞)。
4)实现命令处理回调(远程控制)
当订阅到 devices/stm32-01/cmd,回调处理 JSON。建议用轻量 JSON 解析(cJSON):
void cmd_handler(MessageData* md) {MQTTMessage* msg = md->message;// msg->payload is bytes, not null-terminatedchar jsonbuf[128];memcpy(jsonbuf, msg->payload, msg->payloadlen);jsonbuf[msg->payloadlen] = '\0';cJSON* root = cJSON_Parse(jsonbuf);const cJSON* action = cJSON_GetObjectItem(root, "action");if (action && strcmp(action->valuestring, "gpio") == 0) {int pin = cJSON_GetObjectItem(root, "pin")->valueint;int value = cJSON_GetObjectItem(root, "value")->valueint;// 操作对应GPIOHAL_GPIO_WritePin(GPIOx, PIN_MAP(pin), value ? GPIO_PIN_SET : GPIO_PIN_RESET);}cJSON_Delete(root);
}
5)FreeRTOS 任务划分(推荐)
- net_task:负责网络连接、MQTT 连接与
MQTTYield循环(保持会话、处理回调) - sensor_task:读取传感器并把数据加入队列或直接发布(通过 MQTT publish)。
- watchdog_task:检测网络状态并重连(若断连则 NetworkDisconnect + NetworkConnect + MQTTConnect)。
6)Broker(Mosquitto)搭建与测试
本地或树莓派上安装 Mosquitto:
sudo apt-get install mosquitto mosquitto-clients
sudo systemctl start mosquitto
测试订阅:
mosquitto_sub -h 192.168.1.100 -t "devices/stm32-01/telemetry" -v
测试发布命令(远程控制):
mosquitto_pub -h 192.168.1.100 -t "devices/stm32-01/cmd" -m '{"action":"gpio","pin":5,"value":1}' -q 1
7)完整示例:主要文件与关键代码(精简版)
network_w5500.c(核心网络读写)
(实现 Network 的 read / write,基于 W5500 socket API)
int NetworkConnect(Network* n, char* addr, int port) {n->socket = socket(SOCK_STREAM, 0, 0); // W5500 APIif (connect(n->socket, addr, port) != 0) return -1;return 0;
}
int NetworkRead(Network* n, unsigned char* buffer, int len, int timeout_ms) {// wrap W5500 recv with timeoutreturn recv(n->socket, buffer, len);
}
int NetworkWrite(Network* n, unsigned char* buffer, int len, int timeout_ms) {return send(n->socket, buffer, len);
}
main.c(任务与初始化)
- 初始化 HAL、SPI、W5500、FreeRTOS
- 启动
net_task和sensor_task
8)测试与验证步骤(详细)
- 物理连线检查:确认 W5500 SPI 接线(MOSI/MISO/SCK/CS/INT/RST)。
- DHCP 或 静态 IP:若使用静态 IP,先在 CubeMX LwIP 配置里设置 IP;或使用 DHCP。
- 启动 Broker(本地或云端)并确认可达:
ping broker_ip。 - 调试串口输出:在各关键点打印日志(网络连接成功/失败、MQTT CONNECT 成功/失败、订阅成功、收到命令)。
- 发布测试命令:使用
mosquitto_pub发送控制命令,观察 STM32 GPIO 响应。 - 上报数据:使用
mosquitto_sub查看/telemetrytopic 是否收到 JSON。 - 断网重连测试:断开网线或重启 Broker,确认设备能在恢复后自动重连(watchdog_task)。
9)安全与稳定性建议
- 认证:MQTT Broker 启用用户名/密码或 TLS。Paho Embedded 支持用户名/密码;若需 TLS,需使用带 TLS 的 socket 层(mbedTLS/mbedtls+WIZnet supports?)。
- Last Will:在
connect时设置遗嘱消息will,用于在线/离线检测。 - QoS 选择:Telemetry 建议
QoS=1,Command 使用QoS=1或QoS=2(如果需要更强保证)。 - 保活与心跳:设置合理 keepalive(如 60s)并在设备端实现 Ping/Keepalive 逻辑。
- 限流/缓冲:当网络拥堵或 Broker 不可用时,请把数据临时缓存(环形缓冲),避免丢失重要数据。
五、方案 B:STM32 + ESP8266(串口桥 / MQTT 在 ESP 上)
如果你想更快验证(特别是没有 W5500),可使用 ESP8266/ESP32:
-
做法 1(推荐入门):把MQTT逻辑移到 ESP(用 Arduino/ESP-IDF 的 PubSubClient / esp-mqtt),STM32 与 ESP 用 UART 交换传感与控制数据(自定义串口协议)。
- 优点:实现快、ESP 对 Wi-Fi 和 TLS 支持好;
- 缺点:STM32 不直接接入 Broker,逻辑分离。
-
做法 2:烧写 ESP 的 AT 固件(或用 ESP8266 AT 支持的 MQTT AT 指令),STM32 通过 AT 命令让 ESP 做 MQTT。AT 版通常功能有限且命令复杂,不推荐用于长期项目。
六、常见问题与排查(必备)
- 无法连接 Broker:检查 IP/端口、防火墙、网关、DNS(若用域名)。
- MQTT CONNECT 失败:检查 clientID、username/password、是否启用 TLS(端口要 8883)。
- 订阅不到消息:确认 topic 名称是否一致,是否使用了
+或#通配符误匹配;确认 QoS。 - 网络掉线重连失败:确保
NetworkDisconnect+NetworkConnect的流程正确,且任务中有重连重试策略。 - 性能问题(高并发):缩短 publish 频率、合并数据、使用 QoS 0/1 的合适配比、提升 Broker 性能。
七、辅助:服务端脚本示例(用于调试)
Python (paho-mqtt) 订阅 telemetry 并下发命令:
import paho.mqtt.client as mqtt
import time
import jsonBROKER = '192.168.1.100'
client = mqtt.Client("controller1")
client.username_pw_set('user','pass')
client.connect(BROKER, 1883, 60)def on_message(client, userdata, msg):print(f"RECV {msg.topic} {msg.payload}")client.on_message = on_message
client.subscribe("devices/stm32-01/telemetry", qos=1)
client.loop_start()# 等待数据
time.sleep(5)
# 下发命令:打开 pin 5
cmd = json.dumps({"action":"gpio","pin":5,"value":1})
client.publish("devices/stm32-01/cmd", cmd, qos=1)
八、拓展方向(生产级方案)
- 使用 TLS + 客户端证书 实现高安全性连接(在 Paho 中配置 TLS socket)。
- 集成 Device Registry(设备认证、证书管理)。
- 使用 MQTT Bridge/ACL 做权限控制,或用云厂商(如 EMQX, AWS IoT)替代本地 Broker。
- 支持 OTA 升级(复杂: 需要 bootloader + 镜像签名)。
九、总结
- 选择硬件(W5500/ESP32)并完成物理接线。
- 用 CubeMX 生成基础项目(启用 LwIP/FreeRTOS)。
- 集成 W5500 的 socket API 或 netif,测试 TCP 连接到 Broker。
- 引入 Paho Embedded C 并实现 Network read/write 接口。
- 实现 MQTT connect/subscribe/publish 的任务逻辑。
- 测试 publish/subscribe,使用 mosquitto_sub/publish 或 Python 来验证。
- 增加重连、遗嘱消息、QoS 与安全策略。
