本文为 HinGwenWoong 原创,如果这篇文章对您有帮助,欢迎转载,转载请阅读文末的【授权须知】,感谢您对 HinGwenWoong 文章的认可!
如今的时代发展很快,万物互联成为趋势,每个产品都需要连接到网络,MQTT
这种及其轻量级的传输协议逐渐使用广泛,下面我为大家介绍如何使用 STM32
使用 LWIP
实现 MQTT
。
我是 HinGwenWoong ,一个有着清晰目标不停奋斗的程序猿,热爱技术,喜欢分享,码字不易,如果帮到您,请帮我在屏幕下方点赞 ,您的点赞可以让技术传播得更远更广,谢谢!
MQTT
是机器对机器(M2M
)/物联网(IoT
)连接协议。它被设计为一个极其轻量级的发布/订阅消息
传输协议。对于需要较小代码占用空间和/或网络带宽非常宝贵的远程连接非常有用,是专为受限设备和低带宽、高延迟或不可靠的网络而设计。这些原则也使该协议成为新兴的“机器到机器”(M2M
)或物联网(IoT
)世界的连接设备,以及带宽和电池功率非常高的移动应用的理想选择。例如,它已被用于通过卫星链路与代理通信的传感器、与医疗服务提供者的拨号连接,以及一系列家庭自动化和小型设备场景。它也是移动应用的理想选择,因为它体积小,功耗低,数据包最小,并且可以有效地将信息分配给一个或多个接收器。
——摘自 MQTT中文网
本项目使用的是 LAN8720
芯片,需要修改 PHY Address
为 0
Cube 生成的 MAC
地址是固定的,防止和测试环境中的其他设备相撞,需要打开文件 ethernetif.c
手动修改 MAC
地址,我这里提取了 芯片ID
作为MAC
地址的最后几位,这里是 STM32F767
的芯片ID的地址 0x1FF0F420
uint32_t sn0 = *(uint32_t *)(0x1FF0F420);//STM32 cpu id
MACAddr[3] = (sn0 >> 16) & 0xFF;
MACAddr[4] = (sn0 >> 8) & 0xFFF;
MACAddr[5] = sn0 & 0xFF;
编译 -> 烧录 到单片机里面,拿一条和 PC 在同一局域网内的网线,根据 MX_LWIP_Init()
函数下面设置的 IP
测试 ping
功能,下面是成功的结果图:
打开文件 lwipopts.h
加入如下代码,如何使用 JLink
实现 Segger log
可以按参考我之前的文章: [【嵌入式小技巧】stm32 实现 Segger RTT 打印(超详细)]
#define LWIP_DEBUG
#include "bsp_printlog.h"
#undef LWIP_PLATFORM_DIAG
#define LWIP_PLATFORM_DIAG(x) do {print_log x;} while(0)
//打开 MQTT DEBUG 模式以便更好观察
#define MQTT_DEBUG LWIP_DBG_ON
这里使用的是 通信猫 共享MQTT服务器 在线客户端,方便快捷
首先使用电脑 CMD
根据 通信猫 的域名解析出 IP,这里暂时没用到 lwip 的自动域名解析,获得 IP = 120.76.100.197
打开文件 cc.h
,修改 LWIP_PLATFORM_ASSERT
宏定义,否则没有重定义 printf
的前提下,LWIP
出错的时候会导致系统直接死机
#include "bsp_printlog.h"
#define LWIP_PLATFORM_ASSERT(x) do {print_log("Assertion \"%s\" failed at line %d in %s\n", \
x, __LINE__, __FILE__); } while(0)
将 lwip 接口的返回值放到 lwip_strerr
函数,会打印函数的错误提示,前提是需要开启 LWIP_DEBUG
print_log("mqtt state : %s",lwip_strerr(ret));
/*!
* @brief MQTT 连接成功的处理函数,需要的话在应用层重新定义
*
* @param [in1] : MQTT 连接句柄
* @param [in2] : MQTT 连接参数指针
*
* @retval: None
*/
__weak void mqtt_conn_suc_proc(mqtt_client_t *client, void *arg)
{
//这里是连接成功之后进行 订阅操作
char test_sub_topic[] = "/public/TEST/AidenHinGwenWong_sub"; //订阅 topic
bsp_mqtt_subscribe(client, test_sub_topic, 0); //订阅接口函数
}
/*!
* @brief MQTT 处理失败调用的函数
*
* @param [in1] : MQTT 连接句柄
* @param [in2] : MQTT 连接参数指针
*
* @retval: None
*/
__weak void mqtt_error_process_callback(mqtt_client_t *client, void *arg)
{
/* 这里可以做重连操作,根据 clent 指针可以知道是哪个MQTT的连接句柄 */
}
/*!
* @brief MQTT 连接状态的回调函数
*
* @param [in] : MQTT 连接句柄
* @param [in] : 用户提供的回调参数指针
* @param [in] : MQTT 连接状态
* @retval: None
*/
static void bsp_mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status)
{
if (client == NULL)
{
//错误返回
print_log("bsp_mqtt_connection_cb: condition error @entry\n");
return;
}
if (status == MQTT_CONNECT_ACCEPTED) //Successfully connected
{
print_log("bsp_mqtt_connection_cb: Successfully connected\n");
// 注册接收数据的回调函数
mqtt_set_inpub_callback(client, bsp_mqtt_incoming_publish_cb, bsp_mqtt_incoming_data_cb, arg);
//成功处理函数
mqtt_conn_suc_proc(client, arg);
}
else
{
print_log("bsp_mqtt_connection_cb: Fail connected, status = %s\n", lwip_strerr(status));
//错误处理
mqtt_error_process_callback(client, arg);
}
}
/*!
* @brief 连接到 mqtt 服务器
* 执行条件:无
*
* @param [in] : None
*
* @retval: 连接状态,如果返回不是 ERR_OK 则需要重新连接
*/
static err_t bsp_mqtt_connect(void)
{
print_log("bsp_mqtt_connect: Enter!\n");
err_t ret;
struct mqtt_connect_client_info_t mqtt_connect_info = {
"AidenHinGwenWong_MQTT_Test", /* 这里需要修改,以免在同一个服务器两个相同ID会发生冲突 */
NULL, /* MQTT 服务器用户名 */
NULL, /* MQTT 服务器密码 */
60, /* 与 MQTT 服务器保持连接时间,时间超过未发送数据会断开 */
"/public/TEST/AidenHinGwenWong_pub", /* MQTT遗嘱的消息发送topic */
"Offline_pls_check", /* MQTT遗嘱的消息,断开服务器的时候会发送 */
0, /* MQTT遗嘱的消息 Qos */
0 /* MQTT遗嘱的消息 Retain */
};
ip_addr_t server_ip;
ip4_addr_set_u32(&server_ip, ipaddr_addr("120.76.100.197")); //MQTT服务器IP
uint16_t server_port = 18830; //注意这里是 MQTT 的 TCP 连接方式的端口号!!!!
if (s__mqtt_client_instance == NULL)
{
// 句柄==NULL 才申请空间,否则无需重复申请
s__mqtt_client_instance = mqtt_client_new();
}
if (s__mqtt_client_instance == NULL)
{
//防止申请失败
print_log("bsp_mqtt_connect: s__mqtt_client_instance malloc fail @@!!!\n");
return ERR_MEM;
}
//进行连接,注意:如果需要带入 arg ,arg必须是全局变量,局部变量指针会被回收,大坑!!!!!
ret = mqtt_client_connect(s__mqtt_client_instance, &server_ip, server_port,\
bsp_mqtt_connection_cb, NULL, &mqtt_connect_info);
/******************
小提示:连接错误不需要做任何操作,mqtt_client_connect 中注册的回调函数里面做判断并进行对应的操作
*****************/
print_log("bsp_mqtt_connect: connect to mqtt %s\n", lwip_strerr(ret));
return ret;
}
/*!
* @brief mqtt 接收数据处理函数接口,需要在应用层进行处理
* 执行条件:mqtt连接成功
*
* @param [in1] : 用户提供的回调参数指针
* @param [in2] : 接收的数据指针
* @param [in3] : 接收数据长度
* @retval: 处理的结果
*/
__weak int mqtt_rec_data_process(void *arg, char *rec_buf, uint64_t buf_len)
{
print_log("recv_buffer = %s\n", rec_buf);
return 0;
}
/*!
* @brief MQTT 接收到数据的回调函数
* 执行条件:MQTT 连接成功
*
* @param [in1] : 用户提供的回调参数指针
* @param [in2] : MQTT 收到的分包数据指针
* @param [in3] : MQTT 分包数据长度
* @param [in4] : MQTT 数据包的标志位
* @retval: None
*/
static void bsp_mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags)
{
if ((data == NULL) || (len == 0))
{
//错误返回
print_log("mqtt_client_incoming_data_cb: condition error @entry\n");
return;
}
if (s__mqtt_recv_buffer_g.recv_len + len < sizeof(s__mqtt_recv_buffer_g.recv_buffer))
{
//将接收到的数据加入buffer中
snprintf(&s__mqtt_recv_buffer_g.recv_buffer[s__mqtt_recv_buffer_g.recv_len], len, "%s", data);
s__mqtt_recv_buffer_g.recv_len += len;
}
if ((flags & MQTT_DATA_FLAG_LAST) == MQTT_DATA_FLAG_LAST) //接收的是最后的分包数据——接收已经完成
{
//处理数据
mqtt_rec_data_process(arg, s__mqtt_recv_buffer_g.recv_buffer, s__mqtt_recv_buffer_g.recv_len);
//已接收字节计数归0
s__mqtt_recv_buffer_g.recv_len = 0;
//清空接收buffer
memset(s__mqtt_recv_buffer_g.recv_buffer, 0, sizeof(s__mqtt_recv_buffer_g.recv_buffer));
}
print_log("mqtt_client_incoming_data_cb:reveiving incomming data.\n");
}
/*!
* @brief MQTT 接收到数据的回调函数
* 执行条件:MQTT 连接成功
*
* @param [in] : 用户提供的回调参数指针
* @param [in] : MQTT 收到数据的topic
* @param [in] : MQTT 收到数据的总长度
* @retval: None
*/
static void bsp_mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len)
{
if ((topic == NULL) || (tot_len == 0))
{
//错误返回
print_log("bsp_mqtt_incoming_publish_cb: condition error @entry\n");
return;
}
print_log("bsp_mqtt_incoming_publish_cb: topic = %s.\n", topic);
print_log("bsp_mqtt_incoming_publish_cb: tot_len = %d.\n", tot_len);
s__mqtt_recv_buffer_g.recv_total = tot_len; //需要接收的总字节
s__mqtt_recv_buffer_g.recv_len = 0; //已接收字节计数归0
//清空接收buffer
memset(s__mqtt_recv_buffer_g.recv_buffer, 0, sizeof(s__mqtt_recv_buffer_g.recv_buffer));
}
/*!
* @brief MQTT 发送数据的回调函数
* 执行条件:MQTT 连接成功
*
* @param [in] : 用户提供的回调参数指针
* @param [in] : MQTT 发送的结果:成功或者可能的错误
* @retval: None
*/
static void mqtt_client_pub_request_cb(void *arg, err_t result)
{
//传进来的 arg 还原
mqtt_client_t *client = (mqtt_client_t *)arg;
if (result != ERR_OK)
{
print_log("mqtt_client_pub_request_cb: c002: Publish FAIL, result = %s\n", lwip_strerr(result));
//错误处理
mqtt_error_process_callback(client, arg);
}
else
{
print_log("mqtt_client_pub_request_cb: c005: Publish complete!\n");
}
}
/*!
* @brief 发送消息到服务器的接口函数
* 执行条件:无
*
* @param [in1] : mqtt 连接句柄
* @param [in2] : mqtt 发送 topic 指针
* @param [in3] : 发送数据包指针
* @param [in4] : 数据包长度
* @param [in5] : qos
* @param [in6] : retain
* @retval: 发送状态
* @note: 有可能发送不成功但是现实返回值是 0 ,需要判断回调函数 mqtt_client_pub_request_cb 是否 result == ERR_OK
*/
err_t bsp_mqtt_publish(mqtt_client_t *client, char *pub_topic, char *pub_buf, uint16_t data_len, uint8_t qos, uint8_t retain)
{
if ((client == NULL) || (pub_topic == NULL) || (pub_buf == NULL) || \
(data_len == 0) || (qos > 2) || (retain > 1))
{
print_log("bsp_mqtt_publish: input error@@");
return ERR_VAL;
}
//判断是否连接状态
if (mqtt_client_is_connected(client) != pdTRUE)
{
print_log("bsp_mqtt_publish: client is not connected\n");
return ERR_CONN;
}
err_t err;
#ifdef USE_MQTT_MUTEX
// 创建 mqtt 发送互斥锁
if (s__mqtt_publish_mutex == NULL)
{
print_log("bsp_mqtt_publish: create mqtt mutex ! \n");
s__mqtt_publish_mutex = xSemaphoreCreateMutex();
}
if (xSemaphoreTake(s__mqtt_publish_mutex, portMAX_DELAY) == pdPASS)
#endif /* USE_MQTT_MUTEX */
{
err = mqtt_publish(client, pub_topic, pub_buf, data_len,\
qos, retain, mqtt_client_pub_request_cb, (void *)client);
print_log("bsp_mqtt_publish: mqtt_publish err = %s\n", lwip_strerr(err));
#ifdef USE_MQTT_MUTEX
print_log("bsp_mqtt_publish: mqtt_publish xSemaphoreTake\n");
xSemaphoreGive(s__mqtt_publish_mutex);
#endif /* USE_MQTT_MUTEX */
}
return err;
}
/*!
* @brief MQTT 订阅的回调函数
* 执行条件:MQTT 连接成功
*
* @param [in] : 用户提供的回调参数指针
* @param [in] : MQTT 订阅结果
* @retval: None
*/
static void bsp_mqtt_request_cb(void *arg, err_t err)
{
if (arg == NULL)
{
print_log("bsp_mqtt_request_cb: input error@@\n");
return;
}
mqtt_client_t *client = (mqtt_client_t *)arg;
if (err != ERR_OK)
{
print_log("bsp_mqtt_request_cb: FAIL sub, sub again, err = %s\n", lwip_strerr(err));
//错误处理
mqtt_error_process_callback(client, arg);
}
else
{
print_log("bsp_mqtt_request_cb: sub SUCCESS!\n");
}
}
/*!
* @brief mqtt 订阅
* 执行条件:连接成功
*
* @param [in1] : mqtt 连接句柄
* @param [in2] : mqtt 发送 topic 指针
* @param [in5] : qos
* @retval: 订阅状态
*/
static err_t bsp_mqtt_subscribe(mqtt_client_t *mqtt_client, char *sub_topic, uint8_t qos)
{
print_log("bsp_mqtt_subscribe: Enter\n");
if ((mqtt_client == NULL) || (sub_topic == NULL) || (qos > 2))
{
print_log("bsp_mqtt_subscribe: input error@@\n");
return ERR_VAL;
}
if (mqtt_client_is_connected(mqtt_client) != pdTRUE)
{
print_log("bsp_mqtt_subscribe: mqtt is not connected, return ERR_CLSD.\n");
return ERR_CLSD;
}
err_t err;
//订阅及注册回调函数
err = mqtt_subscribe(mqtt_client, sub_topic, qos, bsp_mqtt_request_cb, (void *)mqtt_client);
if (err != ERR_OK)
{
print_log("bsp_mqtt_subscribe: mqtt_subscribe Fail, return:%s \n", lwip_strerr(err));
}
else
{
print_log("bsp_mqtt_subscribe: mqtt_subscribe SUCCESS, reason: %s\n", lwip_strerr(err));
}
return err;
}
/*!
* @brief 封装 MQTT 初始化接口
* 执行条件:无
*
* @retval: 无
*/
void bsp_mqtt_init(void)
{
print_log("Mqtt init...\n");
// 连接服务器
bsp_mqtt_connect();
// 发送消息到服务器
char message_test[] = "Hello mqtt server";
for (int i = 0; i < 10; i++)
{
bsp_mqtt_publish(s__mqtt_client_instance, "/public/TEST/AidenHinGwenWong_pub",\
message_test, sizeof(message_test), 1, 0);
vTaskDelay(1000);
}
}
打开 RTT 窗口,连接 RTT
到 STM32
,烧录代码,
如何使用 JLink
实现 Segger log
可以按参考我之前的文章: 【嵌入式小技巧】stm32 实现 Segger RTT 打印(超详细)
可以在服务器 通信猫 共享MQTT服务器 在线客户端 接收到数据:
可以在 RTT
看到接收到的数据和在服务器发送下来的数据相同,成功!!!!
一般的 MQTT
服务器都会有 2
个端口号,一个是 TCP
协议的,另外一个是 Websocket
协议的,使用 LWIP
的 MQTT
需要使用 TCP
的端口号。
arg 指针必须是全局变量,局部变量指针会被回收!!!!!
打开 mqtt_opts.h
,里面有关于 MQTT 的配置,默认发送字节最大是 256
,增大发送字节数只需要修改 MQTT_OUTPUT_RINGBUF_SIZE
宏定义后面的数字即可
以上是 STM32+Lwip 实现 MQTT
的全部内容。项目文件已经上传到 GitHub :STM32_LWIP_MQTT,如果有帮助请大家帮忙右上角点个 star ✨✨✨ ,满足下我的虚荣心,谢谢!!!
作者:HinGwenWoong
一个有着清晰目标不停奋斗的程序猿,热爱技术,喜欢分享,共同进步!
CSDN: HinGwenWoong
原文链接:【嵌入式实战】STM32+Lwip 实现 MQTT(超详细步骤+代码注释,内含避坑提示)