1. 嵌入式 MQTT 选型的核心矛盾与拆解思路
STM32 上跑 MQTT,看起来是个很具体的技术问题,但真正动过手的人都知道,这里面的坑远比想象中多。我最早接触这个需求是在一个工业数据采集项目上,主控是 STM32F407,跑 LwIP 协议栈,需要通过 MQTT 把采集到的传感器数据上报到平台,同时还要接收下行指令控制继电器。当时第一反应是找个现成的 MQTT 客户端库移植进去就完事了,结果一上手才发现,嵌入式环境下的 MQTT 客户端选型远不是“找个库编译一下”这么简单。
核心矛盾在于三个维度的拉扯:资源占用、功能完整度、移植成本。STM32 的 RAM 从几十 KB 到几百 KB 不等,Flash 也是从 64KB 到 2MB 都有,而 MQTT 协议本身虽然不算复杂,但一个完整的客户端实现要处理连接管理、心跳保活、QoS 等级、重传机制、主题订阅匹配等一堆事情。你选一个功能全的库,可能 RAM 直接爆掉;选一个精简的,可能 QoS 1 都不支持,或者断线重连要自己写。
所以这篇文章不是要给你一个“标准答案”,而是把 STM32 上跑 MQTT 这件事拆开揉碎,从协议栈底层到客户端实现,从内存管理到网络接口适配,把选型逻辑和实操细节讲清楚。适合正在做物联网终端开发、需要把 STM32 接入 MQTT 平台的嵌入式工程师,也适合刚接触嵌入式网络编程、想搞清楚 MQTT 在资源受限设备上怎么落地的朋友。
1.1 为什么不能直接拿 Linux 上的方案来用
很多人在 PC 或 Linux 上用过 Mosquitto 的客户端库,或者用过 Paho MQTT,觉得移植到 STM32 上应该差不多。这个想法很危险。Linux 上的 MQTT 客户端库默认假设你有完整的操作系统支持,有动态内存分配、有 socket 接口、有多线程能力。而 STM32 上跑裸机或者 RTOS,情况完全不同。
最典型的差异是网络接口层。Linux 上你直接用 BSD socket 就行,connect()、send()、recv()一套搞定。但在 STM32 上,如果你用的是 LwIP 协议栈,虽然 LwIP 提供了 socket API,但那个 socket 是 LwIP 自己实现的,跟 Linux 的 socket 在行为上有微妙差异,比如非阻塞模式下的错误码、超时处理、连接建立流程等。更别说如果你用的是裸机 LwIP 的 raw API,那连 socket 都没有,得直接操作 pcb 结构体。
另一个差异是内存管理。Linux 上malloc和free随便用,STM32 上你得考虑堆的大小、碎片问题、是否用内存池。一个 MQTT 客户端库如果内部大量使用动态内存分配,在 STM32 上跑久了很容易出现内存碎片,最后导致分配失败。所以选型时一定要看这个库的内存策略,是全部静态分配,还是可配置的内存池,还是直接用malloc。
还有一个容易被忽略的点是线程安全。Linux 上 MQTT 客户端通常跑在独立线程里,用互斥锁保护共享数据。STM32 上如果你用 FreeRTOS,也有互斥锁,但如果你跑裸机,那就得靠状态机来管理,不能阻塞。很多 MQTT 库在设计时假设了多线程环境,移植到裸机上就会出问题。
1.2 选型前必须明确的几个问题
在动手选库之前,先把这几个问题想清楚,能帮你省掉大量试错时间。
你的 STM32 型号和资源情况。F0、F1 系列 RAM 可能只有 20KB 甚至更少,F4、F7、H7 系列就好很多。MQTT 客户端本身占用的 RAM 取决于你选的 QoS 等级和同时订阅的主题数量。一般来说,一个精简的 MQTT 客户端在 QoS 0 下占用 2-5KB RAM,QoS 1 下可能要 5-10KB,如果加上 TLS 那就要几十 KB 了。
你用的网络方案是什么。是 LwIP 还是其他协议栈?是裸机跑还是跑 RTOS?是以太网还是 4G 模块?不同的网络方案对 MQTT 客户端的接口适配要求完全不同。比如用 4G 模块的话,你可能要通过 AT 指令建立 TCP 连接,然后 MQTT 客户端跑在 TCP 之上,这时候 MQTT 库需要适配的是 AT 指令的收发接口,而不是 socket。
你需要哪些 MQTT 特性。QoS 0 够不够?要不要支持 QoS 1 和 QoS 2?要不要支持遗嘱消息?要不要支持保留消息?要不要支持 TLS 加密?这些特性直接决定了你能选哪些库。很多精简的 MQTT 库只支持 QoS 0,如果你需要可靠传输,那就得换。
你的开发环境和工具链。是 Keil、IAR 还是 GCC?是 STM32CubeIDE 还是自己搭的 Makefile?不同的工具链对库的编译要求不同,有些库可能只提供了 GCC 的编译配置,移植到 Keil 上要自己改。
2. 主流嵌入式 MQTT 客户端 C 语言实现横向对比
市面上能在 STM32 上跑的 MQTT 客户端库不算多,但也不少。我这些年实际用过或者深入研究过的有这么几个:Eclipse Paho Embedded C、MQTT-C、wolfMQTT、coreMQTT、lwMQTT,还有一些国内开发者自己写的精简版。下面逐个说。
2.1 Eclipse Paho Embedded C:功能全但偏重
Paho 是 Eclipse 基金会下的 MQTT 项目,有多个语言的实现。Embedded C 版本是专门为嵌入式设备设计的,支持 QoS 0、1、2,支持遗嘱消息、保留消息,功能算是比较完整的。
它的架构是分层的,底层是网络抽象层,你可以自己实现网络接口,上层是 MQTT 协议逻辑。这个设计的好处是移植性不错,你只需要实现几个网络相关的函数,比如连接、发送、接收,就能把 Paho 跑起来。
但问题也很明显。首先是代码体积偏大,编译出来 Flash 占用大概在 30-50KB 左右,RAM 占用也不小。对于 F103 这种资源紧张的芯片来说,压力比较大。其次是内存管理,Paho Embedded C 内部使用动态内存分配,虽然可以配置,但在长时间运行的场景下还是有碎片风险。
还有一个坑是它的文档和示例。Paho Embedded C 的文档不算特别详细,示例代码也比较老,有些 API 在不同版本之间有变化,网上搜到的教程可能跟最新代码对不上。我当初移植的时候,光是搞清楚它的网络接口怎么实现就花了不少时间。
不过如果你用的是 F4 以上的芯片,资源比较充裕,又需要完整的 MQTT 功能,Paho 还是个不错的选择。它的社区活跃度还可以,遇到问题能在 GitHub 上找到一些讨论。
2.2 MQTT-C:轻量但需要自己补全
MQTT-C 是一个单文件的 MQTT 客户端实现,整个库就是一个mqtt.c和一个mqtt.h,代码量很小,编译出来 Flash 占用大概 10-15KB。它的设计目标是可移植性和轻量级,不依赖任何特定的网络栈或操作系统。
这个库的特点是接口简单,核心就是几个函数:mqtt_init()、mqtt_connect()、mqtt_publish()、mqtt_subscribe()、mqtt_sync()。你需要自己提供一个发送和接收的回调函数,把数据通过你的网络接口发出去。
但它的功能相对基础,QoS 支持到 1,QoS 2 好像不支持(具体看版本)。遗嘱消息、保留消息这些是支持的,但 TLS 需要自己集成。另外它的重连机制需要自己实现,库本身不提供自动重连。
我用 MQTT-C 做过一个项目,跑在 STM32F103 + LwIP 上,整体感觉是够用,但很多东西要自己补。比如断线检测、自动重连、订阅恢复,这些都要在应用层写。如果你愿意花时间自己封装,MQTT-C 是个很灵活的选择。
2.3 wolfMQTT:商业级但学习曲线陡
wolfMQTT 是 wolfSSL 团队出的 MQTT 客户端库,跟 wolfSSL 配合可以支持 TLS。它的代码质量很高,功能也完整,支持 QoS 0、1、2,支持遗嘱、保留消息,还支持 MQTT over WebSocket。
这个库的最大优势是 TLS 支持,如果你需要加密连接,wolfMQTT + wolfSSL 是个很成熟的方案。但代价是资源占用大,wolfSSL 本身就不小,加上 wolfMQTT,Flash 占用可能要到 100KB 以上,RAM 也要几十 KB。一般只有 F7、H7 这种高端型号才扛得住。
另外它的授权模式需要注意,wolfMQTT 是 GPLv2 或者商业授权,如果你做商业产品,要么开源你的代码,要么买商业授权。这一点在选型时一定要考虑清楚。
2.4 coreMQTT:FreeRTOS 生态的首选
coreMQTT 是 AWS 出的 MQTT 客户端库,属于 FreeRTOS 生态的一部分。它的设计很干净,代码质量高,支持 QoS 0、1、2,支持遗嘱、保留消息。最重要的是它跟 FreeRTOS 的集成很好,如果你本来就用 FreeRTOS,那 coreMQTT 是个很自然的选择。
它的内存管理做得不错,所有内存都是调用者提供的,库本身不动态分配内存。这意味着你可以用静态数组或者内存池来给它提供缓冲区,完全可控。这一点在嵌入式场景下非常重要。
但它的网络接口需要自己适配,coreMQTT 不直接依赖 socket,而是通过一个传输接口来收发数据。你需要实现TransportInterface_t结构体里的send和recv函数。如果你用 LwIP,那就把 LwIP 的 socket 或者 raw API 封装进去。
coreMQTT 的文档和示例比较完善,AWS 提供了详细的移植指南,GitHub 上也有不少示例项目。如果你用 STM32 + FreeRTOS + LwIP 这套组合,coreMQTT 值得优先考虑。
2.5 lwMQTT:跟 LwIP 绑定的轻量方案
lwMQTT 是 LwIP 协议栈自带的一个 MQTT 客户端实现,代码量很小,跟 LwIP 的集成度很高。如果你本来就用 LwIP,那 lwMQTT 可以省掉很多适配工作。
但它的功能比较有限,主要支持 QoS 0 和 QoS 1,QoS 2 好像不支持。而且它的维护活跃度不高,LwIP 的更新频率本身就不算快,lwMQTT 的更新就更少了。如果你遇到 bug,可能要自己修。
我用 lwMQTT 做过一个简单的数据上报项目,跑在 STM32F107 + LwIP 上,基本功能没问题,但遇到网络抖动时重连逻辑要自己写。如果你只是做个 demo 或者对可靠性要求不高的场景,lwMQTT 够用。
2.6 横向对比表格
| 特性 | Paho Embedded C | MQTT-C | wolfMQTT | coreMQTT | lwMQTT |
|---|---|---|---|---|---|
| Flash 占用(约) | 30-50KB | 10-15KB | 50-100KB+ | 15-25KB | 8-12KB |
| RAM 占用(约) | 5-10KB | 2-5KB | 10-30KB | 3-8KB | 2-4KB |
| QoS 0/1/2 | 支持 | 0/1 | 支持 | 支持 | 0/1 |
| 遗嘱消息 | 支持 | 支持 | 支持 | 支持 | 部分 |
| 保留消息 | 支持 | 支持 | 支持 | 支持 | 部分 |
| TLS | 需集成 | 需集成 | 原生支持 | 需集成 | 不支持 |
| 内存策略 | 动态分配 | 动态分配 | 动态分配 | 调用者提供 | 动态分配 |
| 自动重连 | 需自己写 | 需自己写 | 需自己写 | 需自己写 | 需自己写 |
| 授权 | EPL/EDL | MIT | GPLv2/商业 | MIT | BSD |
| 适用场景 | 资源充裕、功能全 | 资源紧张、灵活 | 需要 TLS | FreeRTOS 生态 | LwIP 简单场景 |
这个表格里的数据是基于我实际编译和运行的经验,不同编译选项和配置下会有差异,仅供参考。
3. 基于 LwIP 的 MQTT 客户端移植实操
选好了库,接下来就是移植。这里以coreMQTT + LwIP + FreeRTOS这套组合为例,把完整的移植过程走一遍。选这个组合是因为它在 STM32 上比较有代表性,而且 coreMQTT 的设计比较规范,适合作为教学案例。
3.1 网络接口层的适配
coreMQTT 不直接调用 socket,而是通过TransportInterface_t来收发数据。这个结构体定义在transport_interface.h里,核心是两个函数指针:
typedef struct TransportInterface { TransportRecv_t recv; TransportSend_t send; void *pNetworkContext; } TransportInterface_t;send函数的原型是int32_t (*TransportSend_t)(NetworkContext_t *pNetworkContext, const void *pBuffer, size_t bytesToSend),recv函数的原型是int32_t (*TransportRecv_t)(NetworkContext_t *pNetworkContext, void *pBuffer, size_t bytesToRecv)。
如果你用 LwIP 的 socket API,pNetworkContext可以指向一个包含 socket 文件描述符的结构体。send函数直接调用lwip_send(),recv函数调用lwip_recv()。但要注意,LwIP 的 socket 在非阻塞模式下的行为跟 Linux 不完全一样,lwip_recv()返回 0 表示连接关闭,返回 -1 时要检查errno来判断是EAGAIN还是真正的错误。
如果你用 LwIP 的 raw API,那就更底层一些。你需要自己管理 TCP PCB,在tcp_recv回调里把数据存到缓冲区,然后让recv函数从缓冲区里读。这种方式更省资源,但实现起来复杂不少。
我一般推荐用 socket API,因为开发效率高,而且 LwIP 的 socket 层已经处理了很多细节。但要注意 socket 的数量限制,LwIP 默认的MEMP_NUM_NETCONN可能不够用,需要在lwipopts.h里调大。
3.2 MQTT 连接建立与保活机制
coreMQTT 的连接建立流程是这样的:先调用MQTT_Init()初始化上下文,然后调用MQTT_Connect()发送 CONNECT 报文,等待 CONNACK。MQTT_Connect()是个阻塞函数,它会一直等到收到 CONNACK 或者超时。
这里有个细节要注意:MQTT_Connect() 的超时时间。coreMQTT 的MQTT_Connect()接受一个timeoutMs参数,这个参数决定了等待 CONNACK 的最长时间。如果网络延迟大,这个值要设大一点,比如 5000ms 或 10000ms。但设太大又会导致连接失败时阻塞太久,影响系统响应。我一般设 5000ms,配合重试机制。
连接建立后,保活机制是关键。MQTT 协议要求客户端在 keep-alive 间隔内至少发送一次报文(PINGREQ 或者 PUBLISH 等),否则 broker 会认为客户端离线。coreMQTT 提供了MQTT_KeepAlive()函数,你需要在主循环或者定时器里定期调用它。
但这里有个坑:MQTT_KeepAlive()只是发送 PINGREQ,它不处理 PINGRESP。你需要调用MQTT_ProcessLoop()来接收和处理来自 broker 的报文,包括 PINGRESP。如果只发 PINGREQ 不处理 PINGRESP,broker 可能会因为没收到确认而断开连接。
我的做法是在 FreeRTOS 里创建一个 MQTT 任务,任务里循环调用MQTT_ProcessLoop(),同时用一个软件定时器或者xTaskGetTickCount()来判断是否该发送 PINGREQ。MQTT_ProcessLoop()的超时时间设短一点,比如 100ms 或 500ms,这样既能及时处理 incoming 报文,又不会阻塞太久。
3.3 订阅与发布的消息处理
订阅和发布是 MQTT 的核心操作。coreMQTT 的MQTT_Subscribe()和MQTT_Publish()都是阻塞函数,它们会等待 SUBACK 或 PUBACK(QoS 1 时)。
这里有个重要的注意事项:MQTT_Subscribe()和MQTT_Publish()在等待确认的过程中,会调用MQTT_ProcessLoop()来处理 incoming 报文。这意味着如果你在订阅回调里又调用了MQTT_Subscribe(),可能会导致递归调用,栈溢出。所以不要在回调函数里做阻塞操作,回调函数应该尽快返回,把耗时的处理放到主循环里。
消息接收是通过回调函数实现的。在MQTT_Init()时你需要注册一个MQTTEventCallback_t,当收到 PUBLISH 报文时,coreMQTT 会调用这个回调。回调函数的参数里包含了主题名、主题长度、 payload、payload 长度等信息。
处理订阅消息时,主题匹配是个容易出错的地方。MQTT 的主题是大小写敏感的,而且支持通配符+和#。如果你在回调里用strcmp()来匹配主题,要注意主题可能不是以\0结尾的,需要用主题长度来比较。我一般用strncmp()加上长度判断。
3.4 断线重连与状态恢复
嵌入式网络环境不稳定,断线重连是必须的。coreMQTT 本身不提供自动重连,需要你自己实现。
我的做法是维护一个连接状态机,状态包括:DISCONNECTED、CONNECTING、CONNECTED、RECONNECTING。在 MQTT 任务里根据当前状态执行不同的操作。
当MQTT_ProcessLoop()返回错误,或者MQTT_KeepAlive()失败时,把状态切到RECONNECTING。在RECONNECTING状态下,先关闭 socket,等待一段时间(比如 1 秒、2 秒、4 秒,指数退避),然后重新建立 TCP 连接,再调用MQTT_Connect()。
重连成功后,需要重新订阅之前的主题。因为 broker 不会保留客户端的订阅关系(除非你用了持久会话)。所以你需要维护一个订阅列表,重连后遍历这个列表重新订阅。
这里有个经验技巧:在 CONNECT 报文里设置clean session标志。如果设为 1,broker 会清除之前的会话状态,每次重连都是全新的会话。如果设为 0,broker 会保留会话,包括未确认的消息和订阅关系。对于资源受限的设备,我一般建议设clean session = 1,然后自己在应用层维护订阅列表,这样更可控。
4. 常见问题排查与避坑经验实录
这一部分是我这些年踩过的坑和总结出来的经验,有些是 coreMQTT 特有的,有些是 STM32 + LwIP 环境下的通用问题。
4.1 连接建立失败的原因排查
连接建立失败是最常见的问题,可能的原因有很多。我整理了一个排查表:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| TCP 连接都建不起来 | IP 地址、端口不对 | ping broker 地址,检查端口是否开放 |
| TCP 连接成功但 MQTT 连接失败 | Client ID 冲突 | 换一个唯一的 Client ID |
| MQTT 连接返回 CONNACK 拒绝 | 用户名密码错误 | 检查认证信息,用 MQTT 客户端工具验证 |
| MQTT 连接超时 | 网络延迟大或 broker 响应慢 | 增大 timeoutMs,检查 broker 负载 |
| 连接建立后马上断开 | Keep-alive 设置太短 | 增大 keep-alive 间隔,检查 PINGREQ 是否正常发送 |
我遇到过一次很诡异的情况:TCP 连接能建立,CONNECT 报文也发出去了,但就是收不到 CONNACK。后来抓包发现,broker 其实回了 CONNACK,但 LwIP 的接收缓冲区太小,CONNACK 报文被截断了。解决办法是增大TCP_WND和MEMP_NUM_TCP_SEG。
4.2 内存不足与碎片问题
STM32 上跑 MQTT,内存问题是最头疼的。coreMQTT 虽然不动态分配内存,但 LwIP 本身会动态分配。如果 LwIP 的堆设置太小,或者长时间运行后碎片严重,就会出现分配失败。
我的建议是:尽量用静态分配。LwIP 的lwipopts.h里可以把MEM_LIBC_MALLOC设为 0,然后用 LwIP 自己的内存池。还可以把MEMP_MEM_MALLOC设为 0,强制使用内存池而不是堆。这样虽然灵活性差一点,但稳定性好很多。
另外,MQTT 的发送和接收缓冲区也要注意。coreMQTT 需要你提供发送和接收缓冲区,大小要能容纳最大的 MQTT 报文。如果你要发布较大的 payload,缓冲区就要相应增大。但缓冲区太大会浪费 RAM,太小又会导致报文被截断。我一般设发送缓冲区 512 字节,接收缓冲区 1024 字节,对于大多数传感器数据上报场景够用了。
4.3 网络抖动与超时处理
嵌入式设备的网络环境往往不如有线网络稳定,4G 模块、WiFi 模块都可能出现短暂的网络抖动。如果 MQTT 客户端没有处理好超时,可能会导致任务卡死。
关键原则:所有网络操作都要设超时。LwIP 的 socket 可以设置SO_RCVTIMEO和SO_SNDTIMEO,这样recv()和send()不会无限阻塞。coreMQTT 的MQTT_ProcessLoop()也有超时参数,不要传 0 或者很大的值,我一般传 100-500ms。
还有一个技巧是用 select 或者 poll 来检测 socket 可读。在调用MQTT_ProcessLoop()之前,先用lwip_select()检查 socket 是否有数据可读,如果没有就直接返回,避免阻塞。这样可以提高任务的响应性。
4.4 订阅消息丢失与 QoS 选择
如果你发现订阅的消息偶尔会丢失,首先要检查 QoS 等级。QoS 0 是“最多一次”,消息可能丢失;QoS 1 是“至少一次”,消息不会丢失但可能重复;QoS 2 是“恰好一次”,不会丢失也不会重复,但开销最大。
对于传感器数据上报,如果数据可以容忍偶尔丢失,用 QoS 0 就够了,开销最小。如果数据很重要不能丢,用 QoS 1,但在应用层要做去重处理。QoS 2 在嵌入式场景下用得比较少,因为握手过程太复杂,开销大。
还有一个容易忽略的点是订阅时的 QoS 和发布时的 QoS。订阅时指定的 QoS 是 broker 向客户端发送消息时使用的最大 QoS,发布时指定的 QoS 是客户端向 broker 发送消息时使用的 QoS。两者可以不同。如果你订阅时用了 QoS 0,那 broker 发给你的消息就是 QoS 0,即使发布者用的是 QoS 1。
4.5 与 485 设备联动的实操细节
很多工业场景下,STM32 需要通过 485 总线跟其他设备通信,然后把数据通过 MQTT 上报。这里有个典型的坑:485 是半双工,发送和接收不能同时进行。如果你在 MQTT 回调里直接去读 485 数据,可能会跟 MQTT 的发送操作冲突。
我的做法是用消息队列解耦。MQTT 回调里只负责把下行指令放到队列里,另一个任务从队列里取指令,通过 485 发给设备,等设备响应后再把数据放到另一个队列,由 MQTT 任务读取并发布。这样各个任务职责清晰,不会互相阻塞。
485 的收发切换也要注意。发送前要把 485 芯片的 DE/RE 引脚拉高,发送完再拉低。这个切换需要根据波特率计算延时,确保最后一个字节发送完成后再切换。我一般用定时器或者us级延时来实现。
5. 不同资源条件下的选型建议
最后说一下不同 STM32 型号和资源条件下的选型思路,这部分是我个人的经验总结,不一定适用于所有场景,但可以作为一个参考起点。
5.1 资源紧张型(F0/F1 系列,RAM < 20KB)
这类芯片 RAM 很紧张,Flash 也不大,选型时要优先考虑资源占用。lwMQTT或者MQTT-C是比较合适的选择,Flash 占用在 10KB 左右,RAM 占用可以控制在 3KB 以内。
功能上要妥协,QoS 0 为主,QoS 1 谨慎使用。TLS 基本不用考虑,跑不动。订阅的主题数量要控制,不要超过 3-5 个。payload 大小也要限制,单条消息不要超过 256 字节。
如果一定要用 coreMQTT,也不是不行,但要把缓冲区设到最小,比如发送 256 字节、接收 512 字节,并且关闭不必要的功能。
5.2 资源适中(F4/F7 系列,RAM 64KB-256KB)
这类芯片资源比较充裕,可以选择的方案就多了。coreMQTT或者Paho Embedded C都可以,功能上可以支持 QoS 1,TLS 也可以考虑(但 wolfSSL 的 RAM 占用要仔细评估)。
我一般推荐 coreMQTT,因为它的内存策略更可控,跟 FreeRTOS 的集成也更好。Paho 功能更全,但代码体积和内存占用都偏大,而且文档不如 coreMQTT 清晰。
这个资源级别下,可以同时订阅 10 个以上的主题,payload 可以到 1KB 甚至更大。如果跑 TLS,建议用硬件加密引擎(如果芯片支持)来减轻 CPU 负担。
5.3 资源充裕(H7 系列,RAM > 512KB)
H7 系列资源很充裕,基本上想用什么库都行。wolfMQTT + wolfSSL可以跑起来,TLS 加密没问题。也可以跑Paho Embedded C的全功能版本。
这个级别下,可以考虑更复杂的应用场景,比如同时维护多个 MQTT 连接、支持 MQTT over WebSocket、做边缘计算后再上报等。但也要注意,资源充裕不代表可以随便浪费,良好的内存管理习惯还是要保持。
5.4 选型决策流程图(文字版)
由于不能画图,我用文字描述一下决策逻辑:
第一步,看 RAM 大小。小于 20KB 选 lwMQTT 或 MQTT-C;20-64KB 选 coreMQTT 或 MQTT-C;大于 64KB 可以选 coreMQTT、Paho 或 wolfMQTT。
第二步,看是否需要 TLS。需要 TLS 且 RAM 大于 128KB,选 wolfMQTT + wolfSSL;需要 TLS 但 RAM 不够,考虑用硬件加密模块或者放弃 TLS。
第三步,看是否用 FreeRTOS。用 FreeRTOS 优先选 coreMQTT;不用 RTOS 选 MQTT-C 或 lwMQTT。
第四步,看功能需求。需要 QoS 2 选 Paho 或 wolfMQTT;只需要 QoS 0/1 选 coreMQTT 或 MQTT-C。
这套逻辑不是绝对的,实际选型还要考虑团队熟悉度、社区支持、授权协议等因素。但作为一个起点,可以帮你快速缩小选择范围。
我在实际项目中最常用的组合是STM32F407 + FreeRTOS + LwIP + coreMQTT,这套组合在资源占用、功能完整度、开发效率之间取得了比较好的平衡。当然,具体项目还要具体分析,希望这篇文章能帮你在 STM32 上跑 MQTT 时少走一些弯路。