尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

STM32 FreeRTOS + LwIP 集成实践:基于 MQTT 的通信示例 - 实践

STM32 FreeRTOS + LwIP 集成实践:基于 MQTT 的通信示例 - 实践
📅 发布时间:2026/6/19 16:40:46

1. LWIP简介

一个 轻量级 TCP/IP 协议栈搭建,最初由 Adam Dunkels 在瑞典计算机科学研究所(CNA 实验室)编写,目前由 Simon Goldschmidt 和 Dirk Ziegelmeier 领导的全球编写团队持续维护就是LWIP(Lightweight IP)

1.1 重要特点

  • 轻量级设计:优化 RAM 占用(可在几十 KB 内存中运行),代码占用约 40 KB ROM。
  • 开源免费:采用 BSD 风格许可,C 源码形式提供。
  • 多平台承受:可移植到多种 MCU、嵌入式平台,也可在有或无操作系统的环境下运行。
  • 协议完整:支持 IP、ICMP、IGMP、UDP(含 UDP-lite)、TCP(含拥塞控制、RTT 估算、快速恢复/快速重传)、ARP、PPP 等。
  • 网络服务帮助:可选 DNS、DHCP、AUTOIP、SNMP 等功能。
  • 接口灵活:供应原生 Raw API、可选 Berkeley-style Socket API,兼顾性能和易用性。

1.2 应用场景

  • IoT 设备(智能家居、传感器网关)
  • 工业控制系统
  • 网络摄像头、音视频传输设备
  • 车载信息娱乐系统

2. 在 FreeRTOS 中集成 LwIP 并完成 MQTT 通信

2.1 工具准备

  • MQTTX:桌面版 MQTT 客户端,操作方便,适合测试。
  • MQTT Broker:使用 broker.emqx.io (EMQX 团队 提供的一个 公共 MQTT Broker 服务,主要用于学习、测试和验证 MQTT 协议的功能)。

2.2 目标

经过 STM32 开发板发送 MQTT 消息到 Broker,并使用桌面客户端接收消息。

2.3 工程配置

1. 开始参考这篇博文创建一个 FreeRTOS 工程:STM32 FreeRTOS工程创建

在此基础上进行以下修改:

  • 接口选择

    • 原工程接口为 CMSIS_V2,这里改为 CMSIS_V1。
    • 测试发现,在 STM32F746 板子上选择 CMSIS_V1 时,LwIP 初始化才能正常运行。
  • 启用 Counting Semaphore

    • 打开 USE_COUNTING_SEMAPHORES,后续在代码中配置信号量时应该用到。

2.启用LWIP。

  • 网络配置

    • DHCP 默认可保持启用,这里为了测试网络连接,改用静态地址。
    • 启用DNS模块。
    • 配置 Ethernet PHY 驱动。

3. 配置 Ethernet 接口(参考开发板手册)。

4. 生成工程,并在 STM32CubeIDE 中打开。

2.4 添加MQTT任务

1. 在 main() 中删除自动生成的任务,改为调用 mqtt_setup()。

#include "lwip.h"
#include "mqtt.h"
#define HOST  "broker.emqx.io"              // MQTT 服务器域名
#define PORT  1883                          // MQTT 服务器端口
#define TOPIC "stm32f746g-disco/mqtt/test"  // 发布的主题
#define SIGNAL_CONNECTED 0x01               // MQTT 连接成功信号
#define SIGNAL_HOST_RESOLVED 0x02           // DNS 解析完成信号
// 定义并声明一个信号量,用于发布消息控制
osSemaphoreDef (publish_semaphore);
osSemaphoreId  (publish_semaphore_id);
static osThreadId mqtt_thread_id;
static ip_addr_t host_ip;
// MQTT 任务入口函数
void start_mqtt_task(void *argument);
// DNS 解析完成后的回调函数
void dns_found_callback(const char *name, ip_addr_t *ipaddr, void *arg)
{
if (ipaddr)
{
// 保存解析到的 IP 地址
host_ip = *ipaddr;
// 发送 DNS 解析完成信号,唤醒 MQTT 线程
osSignalSet(mqtt_thread_id, SIGNAL_HOST_RESOLVED);
}
}
// 创建 MQTT 任务
void mqtt_setup(){
osThreadDef(mqtt_task, start_mqtt_task, osPriorityNormal, 0, 1025);
mqtt_thread_id = osThreadCreate(osThread(mqtt_task), NULL);
}
// MQTT 连接回调函数
static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status)
{
if(status == MQTT_CONNECT_ACCEPTED) {
// 连接成功,发送信号通知任务
osSignalSet(mqtt_thread_id, SIGNAL_CONNECTED);
}
}
/* 发布消息完成后的回调(成功或失败都会调用) */
static void mqtt_pub_request_cb(void *arg, err_t result)
{
// 释放信号量,允许下一条消息发布
osSemaphoreRelease(publish_semaphore_id);
}
// MQTT 任务主体函数
void start_mqtt_task(void *argument)
{
/* 初始化 LwIP 网络协议栈 */
MX_LWIP_Init();
ip_addr_t dnsserver;
// 设置 DNS 服务器为 8.8.8.8
IP4_ADDR(&dnsserver,8,8,8,8);
dns_setserver(0, &dnsserver);
// 异步解析 MQTT 服务器域名
err_t result = dns_gethostbyname(HOST, &host_ip, dns_found_callback, NULL);
if(result == ERR_OK){
// 主机名已存在于本地缓存,解析立即成功
} else if (result == ERR_INPROGRESS){
// 向 DNS 服务器发送请求,等待解析完成信号
osSignalWait(SIGNAL_HOST_RESOLVED, osWaitForever);
} else{
// 解析失败,直接返回
return;
}
// 创建 MQTT 客户端对象
mqtt_client_t *client = mqtt_client_new();
// 配置 MQTT 客户端连接信息
struct mqtt_connect_client_info_t ci;
memset(&ci, 0, sizeof(ci));
ci.client_id = "lwip_mqtt_test"; // 客户端 ID
// 创建发布信号量,控制同时发布的消息数
publish_semaphore_id = osSemaphoreCreate(osSemaphore(publish_semaphore), MQTT_REQ_MAX_IN_FLIGHT);
// 建立与 MQTT 服务器的连接
mqtt_client_connect(client, &host_ip, PORT, mqtt_connection_cb, NULL, &ci);
char payload[100];
u8_t qos = 1; /* 0 1 or 2, see MQTT specification */
u8_t retain = 0; /* No don't retain such crappy payload... */
// 等待mqtt连接建立
osSignalWait(SIGNAL_CONNECTED, osWaitForever);
// 无限循环,持续发布消息
for(int i = 0; ;i++)
{
osSemaphoreWait(publish_semaphore_id, osWaitForever);
sprintf(payload, "Hello Woirld %d", i);
mqtt_publish(client, TOPIC, payload, strlen(payload), qos, retain, mqtt_pub_request_cb, NULL);
}
}

2. 整体逻辑概括

  • 初始化网络栈 (LWIP)

调用 MX_LWIP_Init(),让 STM32 的网络功能(以太网 + TCP/IP 协议栈)启动。

  • DNS 域名解析

    • 使用 dns_gethostbyname() 异步解析 broker.emqx.io 域名。
    • 假如立即解析成功 (ERR_OK),直接得到 IP 地址。
    • 如果需要等待 (ERR_INPROGRESS),就阻塞在 osSignalWait(SIGNAL_HOST_RESOLVED, osWaitForever),等回调函数 - dns_found_callback 通知完毕。
  • MQTT 客户端连接

    • 创建 MQTT 客户端 mqtt_client_new()。
    • 配置客户端信息(如 client_id)。
    • 调用 mqtt_client_connect() 发起连接,成功后由 mqtt_connection_cb() 回调函数通知。
    • 任务里会等待 SIGNAL_CONNECTED 信号,确保连接成功后才进入发布循环。
  • 发布消息循环

    • 创建信号量 publish_semaphore,用于控制消息发布并发数量。
    • 每次发布消息前,先 osSemaphoreWait()(等待可用 slot)。
    • 调用 mqtt_publish() 发送消息。
    • 消息发布结束后,回调函数 mqtt_pub_request_cb() 释放信号量 (osSemaphoreRelease()),允许下一条消息发送。
    • 循环构造 "Hello World X" 消息并持续发布。

3. 关键同步机制

  • 信号 (osSignal)

    • SIGNAL_HOST_RESOLVED:DNS 解析做完后由回调设置 → 唤醒 MQTT 线程继续执行。
    • SIGNAL_CONNECTED:MQTT 连接建立成功时设置 → 唤醒 MQTT 线程开始发布消息。
  • 信号量 (osSemaphore)

    • 控制并发的 MQTT 消息发布数(受 MQTT_REQ_MAX_IN_FLIGHT 限制)。
    • 每次发布需要先 wait,完成后在回调里 release。
    • 避免发送过快、超过协议栈处理能力。

2.5 采用MQTTX测试

  1. 连接到 Broker。

  2. 创建订阅 

  3. 设置订阅主题和 QoS.

  4. 将工程下载到开发板运行后,可在 MQTTX 中看到发布的消息:

相关新闻

  • 数分3
  • 基于模拟退火算法解决带容量限制车辆路径问题(CVRP)的MATLAB实现
  • 完整教程:分片后的聚合分页处理

最新新闻

  • 石家庄黄金回收正规军在哪?2026实测门店星级榜,卖金前看一眼 - 奢侈品回收测评
  • 深度学习进阶(三十一)FlashAttention:IO 感知的精确注意力
  • 6个免费方法让你的手机视频秒变MP4 - 软件工具教程方法
  • Kali Linux实战:ARP欺骗攻击原理、环境搭建与Wireshark流量分析
  • 杭州靠谱品牌首饰回收排行,光谱验金透明称重全款现结 - 奢品小当家
  • 2026年安徽省合肥市合肥医药卫生学校招生简章官网发布:报名入口+报考指南 - cc江江

日新闻

  • 5分钟掌握Python进化算法:Geatpy高性能优化工具完全指南
  • Microchip 24AA044 EEPROM选型与应用全指南:从参数解析到实战编程
  • 华为的鸿蒙到底有多牛?为什么称作遥遥领先?

周新闻

  • 3步解锁iOS设备:applera1n激活锁绕过完全指南
  • 39 2026 人工智能证书终极盘点,普通人选 AI 证书可以从这些方向入手
  • Redis 暴露公网有多危险?从端口检查到补救步骤

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号