STM32实战:基于STM32F103的MQTT协议通信(EMQ X Broker)

文章目录

    • 一、前言
      • [1.1 技术背景](#1.1 技术背景)
      • [1.2 MQTT协议特点](#1.2 MQTT协议特点)
      • [1.3 本文目标](#1.3 本文目标)
    • 二、环境准备
      • [2.1 EMQ X服务器搭建](#2.1 EMQ X服务器搭建)
      • [2.2 硬件准备](#2.2 硬件准备)
      • [2.3 软件环境](#2.3 软件环境)
    • 三、核心实现
      • [3.1 MQTT协议栈移植](#3.1 MQTT协议栈移植)
      • [3.2 ESP8266 TCP驱动](#3.2 ESP8266 TCP驱动)
      • [3.3 主程序实现](#3.3 主程序实现)
    • 四、测试验证
      • [4.1 EMQ X Dashboard测试](#4.1 EMQ X Dashboard测试)
      • [4.2 功能测试](#4.2 功能测试)
    • 五、故障排查
      • [5.1 连接问题](#5.1 连接问题)
      • [5.2 通信问题](#5.2 通信问题)
    • 六、总结
      • [6.1 核心知识点](#6.1 核心知识点)
      • [6.2 扩展学习](#6.2 扩展学习)

一、前言

1.1 技术背景

MQTT(Message Queuing Telemetry Transport)是一种轻量级的发布/订阅消息协议,专为低带宽、高延迟或不可靠网络设计。它是物联网(IoT)领域最常用的通信协议之一。

EMQ X是一款开源的高性能MQTT消息服务器,支持百万级并发连接,提供企业级的稳定性。使用STM32连接EMQ X,可以构建可靠的物联网通信系统。

1.2 MQTT协议特点

  • 轻量级:协议头部最小仅2字节
  • 发布/订阅模式:解耦消息生产者和消费者
  • QoS机制:支持三种服务质量等级
  • 遗嘱消息:设备异常断开时通知其他客户端
  • 保留消息:新订阅者立即收到最新消息

QoS等级:

  • QoS 0:最多一次(At most once)
  • QoS 1:至少一次(At least once)
  • QoS 2:恰好一次(Exactly once)

1.3 本文目标

通过本文,你将学会:

  • MQTT协议核心概念和工作原理
  • EMQ X服务器的搭建和配置
  • STM32 MQTT客户端实现
  • 发布/订阅模式的实际应用
  • QoS等级选择和实现
  • 遗嘱消息和保留消息的使用

技术栈:

  • 开发板:STM32F103C8T6
  • WiFi模块:ESP8266
  • MQTT服务器:EMQ X
  • 客户端库:Paho MQTT Embedded

二、环境准备

2.1 EMQ X服务器搭建

方式1:本地部署(Docker)

bash 复制代码
# 拉取EMQ X镜像
docker pull emqx/emqx:5.0.0

# 运行容器
docker run -d --name emqx \
  -p 1883:1883 \
  -p 8083:8083 \
  -p 8084:8084 \
  -p 8883:8883 \
  -p 18083:18083 \
  emqx/emqx:5.0.0

# 访问Dashboard
# http://localhost:18083
# 默认账号:admin / public

方式2:使用公共测试服务器

复制代码
服务器:broker.emqx.io
端口:1883(MQTT)/ 8083(WebSocket)

方式3:云服务部署

  • EMQ X Cloud(托管服务)
  • AWS IoT Core
  • 阿里云物联网平台

2.2 硬件准备

器件 数量 说明
STM32F103C8T6 1 主控芯片
ESP8266-01S 1 WiFi模块
LED 2 状态指示
杜邦线 若干 连接

硬件连接:

复制代码
STM32F103C8T6          ESP8266-01S
    3.3V  --------------- VCC
    GND   --------------- GND
    PA9   --------------- RXD
    PA10  --------------- TXD
    
STM32F103C8T6          LED
    PB12  --------------- LED1 (连接状态)
    PB13  --------------- LED2 (数据指示)

2.3 软件环境

STM32CubeMX配置:

  • USART1:调试串口(PA9/PA10)
  • USART2:ESP8266通信(PA2/PA3)
  • TIM2:1ms定时器(MQTT保活)

三、核心实现

3.1 MQTT协议栈移植

📄 创建文件:Inc/MQTTClient.h

c 复制代码
#ifndef __MQTT_CLIENT_H
#define __MQTT_CLIENT_H

#include "stm32f1xx_hal.h"
#include <string.h>
#include <stdio.h>

// MQTT配置
#define MQTT_MAX_PACKET_SIZE    512
#define MQTT_KEEPALIVE          60
#define MQTT_MAX_RECONNECT      3

// QoS等级
#define MQTT_QOS0   0
#define MQTT_QOS1   1
#define MQTT_QOS2   2

// 消息回调函数类型
typedef void (*MQTTMessageCallback)(char *topic, uint8_t *payload, uint16_t len);
typedef void (*MQTTConnectCallback)(uint8_t connected);

// MQTT客户端结构体
typedef struct
{
    char client_id[32];
    char username[32];
    char password[32];
    char will_topic[64];
    char will_msg[64];
    uint8_t will_qos;
    uint8_t will_retain;
    uint8_t clean_session;
    uint16_t keepalive;
    
    // 回调函数
    MQTTMessageCallback msg_callback;
    MQTTConnectCallback conn_callback;
    
    // 状态
    uint8_t connected;
    uint32_t last_ping;
} MQTTClient_TypeDef;

// 函数声明
uint8_t MQTT_Init(MQTTClient_TypeDef *client);
uint8_t MQTT_Connect(char *broker, uint16_t port);
void MQTT_Disconnect(void);
uint8_t MQTT_Publish(char *topic, uint8_t *payload, uint16_t len, uint8_t qos, uint8_t retain);
uint8_t MQTT_Subscribe(char *topic, uint8_t qos);
uint8_t MQTT_Unsubscribe(char *topic);
void MQTT_Process(void);
uint8_t MQTT_IsConnected(void);

#endif

📄 创建文件:Src/MQTTClient.c

c 复制代码
#include "MQTTClient.h"
#include "esp8266.h"

// MQTT固定头部标志
#define MQTT_CONNECT        1
#define MQTT_CONNACK        2
#define MQTT_PUBLISH        3
#define MQTT_PUBACK         4
#define MQTT_PUBREC         5
#define MQTT_PUBREL         6
#define MQTT_PUBCOMP        7
#define MQTT_SUBSCRIBE      8
#define MQTT_SUBACK         9
#define MQTT_UNSUBSCRIBE    10
#define MQTT_UNSUBACK       11
#define MQTT_PINGREQ        12
#define MQTT_PINGRESP       13
#define MQTT_DISCONNECT     14

static MQTTClient_TypeDef *mqtt_client = NULL;
static uint8_t packet_buffer[MQTT_MAX_PACKET_SIZE];

/**
 * @brief  编码剩余长度
 */
static uint16_t EncodeRemainingLength(uint8_t *buf, uint32_t length)
{
    uint16_t i = 0;
    uint8_t encoded_byte;
    
    do
    {
        encoded_byte = length % 128;
        length = length / 128;
        if(length > 0)
            encoded_byte |= 128;
        buf[i++] = encoded_byte;
    } while(length > 0);
    
    return i;
}

/**
 * @brief  解码剩余长度
 */
static uint16_t DecodeRemainingLength(uint8_t *buf, uint32_t *length)
{
    uint16_t i = 0;
    uint8_t encoded_byte;
    uint32_t multiplier = 1;
    
    *length = 0;
    do
    {
        encoded_byte = buf[i++];
        *length += (encoded_byte & 127) * multiplier;
        multiplier *= 128;
        if(multiplier > 128 * 128 * 128)
            return 0;  // 错误
    } while((encoded_byte & 128) != 0);
    
    return i;
}

/**
 * @brief  写入字符串(2字节长度+数据)
 */
static uint16_t WriteString(uint8_t *buf, char *str)
{
    uint16_t len = strlen(str);
    buf[0] = (len >> 8) & 0xFF;
    buf[1] = len & 0xFF;
    memcpy(&buf[2], str, len);
    return len + 2;
}

/**
 * @brief  MQTT初始化
 */
uint8_t MQTT_Init(MQTTClient_TypeDef *client)
{
    mqtt_client = client;
    client->connected = 0;
    client->last_ping = 0;
    
    return 0;
}

/**
 * @brief  构建CONNECT报文
 */
static uint16_t BuildConnectPacket(uint8_t *buf, MQTTClient_TypeDef *client)
{
    uint16_t i = 0;
    uint16_t payload_len = 0;
    uint16_t connect_flags = 0;
    
    // 固定头部
    buf[i++] = MQTT_CONNECT << 4;
    
    // 计算载荷长度
    payload_len += 2 + 4;  // 协议名长度 + "MQTT"
    payload_len += 1;      // 协议级别
    payload_len += 1;      // 连接标志
    payload_len += 2;      // 保活时间
    payload_len += 2 + strlen(client->client_id);  // 客户端ID
    
    if(strlen(client->username) > 0)
    {
        connect_flags |= 0x80;
        payload_len += 2 + strlen(client->username);
    }
    if(strlen(client->password) > 0)
    {
        connect_flags |= 0x40;
        payload_len += 2 + strlen(client->password);
    }
    if(strlen(client->will_topic) > 0)
    {
        connect_flags |= 0x04 | (client->will_qos << 3);
        if(client->will_retain)
            connect_flags |= 0x20;
        payload_len += 2 + strlen(client->will_topic);
        payload_len += 2 + strlen(client->will_msg);
    }
    if(client->clean_session)
        connect_flags |= 0x02;
    
    // 编码剩余长度
    i += EncodeRemainingLength(&buf[i], payload_len);
    
    // 可变头部
    i += WriteString(&buf[i], "MQTT");  // 协议名
    buf[i++] = 4;  // 协议级别(MQTT 3.1.1)
    buf[i++] = connect_flags;
    buf[i++] = (client->keepalive >> 8) & 0xFF;
    buf[i++] = client->keepalive & 0xFF;
    
    // 载荷
    i += WriteString(&buf[i], client->client_id);
    
    if(strlen(client->will_topic) > 0)
    {
        i += WriteString(&buf[i], client->will_topic);
        i += WriteString(&buf[i], client->will_msg);
    }
    if(strlen(client->username) > 0)
    {
        i += WriteString(&buf[i], client->username);
    }
    if(strlen(client->password) > 0)
    {
        i += WriteString(&buf[i], client->password);
    }
    
    return i;
}

/**
 * @brief  连接MQTT服务器
 */
uint8_t MQTT_Connect(char *broker, uint16_t port)
{
    char port_str[8];
    uint16_t packet_len;
    uint32_t timeout;
    
    if(mqtt_client == NULL)
        return 1;
    
    printf("Connecting to MQTT broker: %s:%d\r\n", broker, port);
    
    // 建立TCP连接
    snprintf(port_str, sizeof(port_str), "%d", port);
    if(ESP8266_TCPConnect(broker, port_str) != 0)
    {
        printf("TCP connect failed!\r\n");
        return 1;
    }
    
    // 发送CONNECT报文
    packet_len = BuildConnectPacket(packet_buffer, mqtt_client);
    if(ESP8266_TCPSend(packet_buffer, packet_len) != 0)
    {
        printf("Send CONNECT failed!\r\n");
        return 1;
    }
    
    // 等待CONNACK
    timeout = HAL_GetTick() + 5000;
    while(HAL_GetTick() < timeout)
    {
        uint16_t rx_len = ESP8266_TCPReceive(packet_buffer, MQTT_MAX_PACKET_SIZE, 100);
        if(rx_len > 0)
        {
            // 解析CONNACK
            if((packet_buffer[0] >> 4) == MQTT_CONNACK)
            {
                uint8_t return_code = packet_buffer[3];
                if(return_code == 0)
                {
                    mqtt_client->connected = 1;
                    mqtt_client->last_ping = HAL_GetTick();
                    
                    if(mqtt_client->conn_callback)
                        mqtt_client->conn_callback(1);
                    
                    printf("MQTT connected!\r\n");
                    return 0;
                }
                else
                {
                    printf("CONNACK error code: %d\r\n", return_code);
                    return 1;
                }
            }
        }
    }
    
    printf("MQTT connect timeout!\r\n");
    return 1;
}

/**
 * @brief  断开连接
 */
void MQTT_Disconnect(void)
{
    uint8_t disconnect_packet[2] = {MQTT_DISCONNECT << 4, 0};
    
    ESP8266_TCPSend(disconnect_packet, 2);
    ESP8266_TCPDisconnect();
    
    mqtt_client->connected = 0;
    
    if(mqtt_client->conn_callback)
        mqtt_client->conn_callback(0);
    
    printf("MQTT disconnected\r\n");
}

/**
 * @brief  构建PUBLISH报文
 */
static uint16_t BuildPublishPacket(uint8_t *buf, char *topic, 
                                   uint8_t *payload, uint16_t payload_len,
                                   uint8_t qos, uint8_t retain, uint16_t *packet_id)
{
    uint16_t i = 0;
    uint16_t variable_len;
    uint8_t fixed_header;
    static uint16_t next_packet_id = 1;
    
    // 固定头部
    fixed_header = MQTT_PUBLISH << 4;
    if(qos > 0)
        fixed_header |= (qos << 1);
    if(retain)
        fixed_header |= 0x01;
    
    buf[i++] = fixed_header;
    
    // 计算可变头部长度
    variable_len = 2 + strlen(topic);
    if(qos > 0)
    {
        variable_len += 2;  // Packet ID
        *packet_id = next_packet_id++;
    }
    
    // 编码剩余长度
    i += EncodeRemainingLength(&buf[i], variable_len + payload_len);
    
    // 可变头部
    i += WriteString(&buf[i], topic);
    
    if(qos > 0)
    {
        buf[i++] = (*packet_id >> 8) & 0xFF;
        buf[i++] = *packet_id & 0xFF;
    }
    
    // 载荷
    memcpy(&buf[i], payload, payload_len);
    i += payload_len;
    
    return i;
}

/**
 * @brief  发布消息
 */
uint8_t MQTT_Publish(char *topic, uint8_t *payload, uint16_t len, 
                     uint8_t qos, uint8_t retain)
{
    uint16_t packet_len;
    uint16_t packet_id;
    
    if(!mqtt_client->connected)
        return 1;
    
    packet_len = BuildPublishPacket(packet_buffer, topic, payload, len, 
                                    qos, retain, &packet_id);
    
    if(ESP8266_TCPSend(packet_buffer, packet_len) != 0)
        return 1;
    
    // QoS 1: 等待PUBACK
    if(qos == 1)
    {
        // 实现PUBACK等待逻辑
        // 简化处理,实际应用需要重传机制
    }
    
    return 0;
}

/**
 * @brief  构建SUBSCRIBE报文
 */
static uint16_t BuildSubscribePacket(uint8_t *buf, char *topic, uint8_t qos)
{
    uint16_t i = 0;
    uint16_t payload_len;
    static uint16_t packet_id = 1;
    
    // 固定头部
    buf[i++] = (MQTT_SUBSCRIBE << 4) | 0x02;  // QoS 1
    
    // 计算载荷长度
    payload_len = 2;  // Packet ID
    payload_len += 2 + strlen(topic) + 1;  // Topic + QoS
    
    // 编码剩余长度
    i += EncodeRemainingLength(&buf[i], payload_len);
    
    // 可变头部
    buf[i++] = (packet_id >> 8) & 0xFF;
    buf[i++] = packet_id & 0xFF;
    packet_id++;
    
    // 载荷
    i += WriteString(&buf[i], topic);
    buf[i++] = qos;
    
    return i;
}

/**
 * @brief  订阅主题
 */
uint8_t MQTT_Subscribe(char *topic, uint8_t qos)
{
    uint16_t packet_len;
    
    if(!mqtt_client->connected)
        return 1;
    
    printf("Subscribing to: %s (QoS %d)\r\n", topic, qos);
    
    packet_len = BuildSubscribePacket(packet_buffer, topic, qos);
    
    if(ESP8266_TCPSend(packet_buffer, packet_len) != 0)
        return 1;
    
    return 0;
}

/**
 * @brief  处理接收到的报文
 */
static void ProcessReceivedPacket(uint8_t *buf, uint16_t len)
{
    uint8_t packet_type = (buf[0] >> 4) & 0x0F;
    
    switch(packet_type)
    {
        case MQTT_PUBLISH:
        {
            // 解析PUBLISH报文
            uint16_t topic_len = (buf[2] << 8) | buf[3];
            char topic[64];
            uint16_t payload_offset = 4 + topic_len;
            uint8_t qos = (buf[0] >> 1) & 0x03;
            
            if(qos > 0)
                payload_offset += 2;  // Packet ID
            
            memcpy(topic, &buf[4], topic_len);
            topic[topic_len] = '\0';
            
            uint16_t payload_len = len - payload_offset - 2;  // 减去固定头部
            
            if(mqtt_client->msg_callback)
            {
                mqtt_client->msg_callback(topic, &buf[payload_offset], payload_len);
            }
            
            // QoS 1: 发送PUBACK
            if(qos == 1)
            {
                uint8_t puback[4];
                puback[0] = MQTT_PUBACK << 4;
                puback[1] = 2;
                puback[2] = buf[payload_offset - 2];
                puback[3] = buf[payload_offset - 1];
                ESP8266_TCPSend(puback, 4);
            }
            break;
        }
        
        case MQTT_PINGRESP:
            printf("PINGRESP received\r\n");
            break;
            
        case MQTT_SUBACK:
            printf("SUBACK received\r\n");
            break;
            
        default:
            printf("Received packet type: %d\r\n", packet_type);
            break;
    }
}

/**
 * @brief  MQTT主循环处理
 */
void MQTT_Process(void)
{
    uint16_t rx_len;
    
    if(!mqtt_client->connected)
        return;
    
    // 接收数据
    rx_len = ESP8266_TCPReceive(packet_buffer, MQTT_MAX_PACKET_SIZE, 0);
    if(rx_len > 0)
    {
        ProcessReceivedPacket(packet_buffer, rx_len);
    }
    
    // 发送PINGREQ(保活)
    if(HAL_GetTick() - mqtt_client->last_ping >= (mqtt_client->keepalive * 1000 / 2))
    {
        uint8_t ping_packet[2] = {MQTT_PINGREQ << 4, 0};
        ESP8266_TCPSend(ping_packet, 2);
        mqtt_client->last_ping = HAL_GetTick();
        printf("PINGREQ sent\r\n");
    }
}

/**
 * @brief  检查连接状态
 */
uint8_t MQTT_IsConnected(void)
{
    return mqtt_client ? mqtt_client->connected : 0;
}

3.2 ESP8266 TCP驱动

📄 创建文件:Src/esp8266_tcp.c

c 复制代码
#include "esp8266.h"

extern UART_HandleTypeDef huart2;

/**
 * @brief  建立TCP连接
 */
uint8_t ESP8266_TCPConnect(char *server, char *port)
{
    char cmd[128];
    
    // 设置单连接模式
    ESP8266_SendCommand("AT+CIPMUX=0", "OK", 1000);
    
    // 建立连接
    snprintf(cmd, sizeof(cmd), "AT+CIPSTART=\"TCP\",\"%s\",%s", server, port);
    
    if(ESP8266_SendCommand(cmd, "OK", 10000) != 0)
    {
        printf("TCP connect failed!\r\n");
        return 1;
    }
    
    printf("TCP connected to %s:%s\r\n", server, port);
    return 0;
}

/**
 * @brief  断开TCP连接
 */
void ESP8266_TCPDisconnect(void)
{
    ESP8266_SendCommand("AT+CIPCLOSE", "OK", 2000);
}

/**
 * @brief  发送TCP数据
 */
uint8_t ESP8266_TCPSend(uint8_t *data, uint16_t len)
{
    char cmd[32];
    char response[32];
    
    // 设置发送长度
    snprintf(cmd, sizeof(cmd), "AT+CIPSEND=%d", len);
    
    // 发送命令,等待">"提示
    HAL_UART_Transmit(&huart2, (uint8_t*)cmd, strlen(cmd), 1000);
    HAL_UART_Transmit(&huart2, (uint8_t*)"\r\n", 2, 100);
    
    // 等待">"
    uint32_t timeout = HAL_GetTick() + 5000;
    while(HAL_GetTick() < timeout)
    {
        uint16_t rx_len = ESP8266_GetRxData((uint8_t*)response, sizeof(response));
        if(rx_len > 0 && strstr(response, ">") != NULL)
        {
            break;
        }
        HAL_Delay(10);
    }
    
    // 发送数据
    HAL_UART_Transmit(&huart2, data, len, 5000);
    
    // 等待发送完成
    timeout = HAL_GetTick() + 5000;
    while(HAL_GetTick() < timeout)
    {
        uint16_t rx_len = ESP8266_GetRxData((uint8_t*)response, sizeof(response));
        if(rx_len > 0 && strstr(response, "SEND OK") != NULL)
        {
            return 0;
        }
        HAL_Delay(10);
    }
    
    return 1;
}

/**
 * @brief  接收TCP数据
 */
uint16_t ESP8266_TCPReceive(uint8_t *buf, uint16_t max_len, uint32_t timeout)
{
    uint16_t rx_len = ESP8266_GetRxData(buf, max_len);
    
    if(rx_len > 0)
    {
        // 检查是否是+IPD数据
        char *ipd = strstr((char*)buf, "+IPD,");
        if(ipd != NULL)
        {
            // 解析数据长度
            int data_len;
            sscanf(ipd, "+IPD,%d:", &data_len);
            
            // 找到数据开始位置
            char *data_start = strchr(ipd, ':') + 1;
            
            // 复制数据
            if(data_len > max_len)
                data_len = max_len;
            memcpy(buf, data_start, data_len);
            
            return data_len;
        }
    }
    
    return 0;
}

3.3 主程序实现

📄 创建文件:Src/main.c

c 复制代码
#include "main.h"
#include "MQTTClient.h"
#include "esp8266.h"

UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2;
TIM_HandleTypeDef htim2;

// MQTT客户端配置
MQTTClient_TypeDef mqtt_client;

// 消息回调
void MQTT_MessageCallback(char *topic, uint8_t *payload, uint16_t len)
{
    payload[len] = '\0';
    printf("Received message:\r\n");
    printf("  Topic: %s\r\n", topic);
    printf("  Payload: %s\r\n", payload);
    
    // LED控制
    if(strstr(topic, "led/control") != NULL)
    {
        if(strstr((char*)payload, "on") != NULL)
        {
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
            printf("LED ON\r\n");
        }
        else if(strstr((char*)payload, "off") != NULL)
        {
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
            printf("LED OFF\r\n");
        }
    }
}

// 连接回调
void MQTT_ConnectCallback(uint8_t connected)
{
    if(connected)
    {
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET);
        printf("MQTT Connected!\r\n");
    }
    else
    {
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET);
        printf("MQTT Disconnected!\r\n");
    }
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    MX_USART2_UART_Init();
    MX_TIM2_Init();
    
    printf("\r\n================================\r\n");
    printf("STM32 MQTT Client Demo\r\n");
    printf("================================\r\n\r\n");
    
    // 初始化ESP8266
    while(ESP8266_Init() != 0)
    {
        printf("ESP8266 init failed, retry...\r\n");
        HAL_Delay(2000);
    }
    
    // 连接WiFi
    while(ESP8266_ConnectWiFi() != 0)
    {
        printf("WiFi connect failed, retry...\r\n");
        HAL_Delay(5000);
    }
    
    // 配置MQTT客户端
    strcpy(mqtt_client.client_id, "stm32_client_001");
    strcpy(mqtt_client.username, "");
    strcpy(mqtt_client.password, "");
    strcpy(mqtt_client.will_topic, "stm32/status");
    strcpy(mqtt_client.will_msg, "offline");
    mqtt_client.will_qos = MQTT_QOS1;
    mqtt_client.will_retain = 1;
    mqtt_client.clean_session = 1;
    mqtt_client.keepalive = 60;
    mqtt_client.msg_callback = MQTT_MessageCallback;
    mqtt_client.conn_callback = MQTT_ConnectCallback;
    
    MQTT_Init(&mqtt_client);
    
    // 连接MQTT服务器
    while(MQTT_Connect("broker.emqx.io", 1883) != 0)
    {
        printf("MQTT connect failed, retry...\r\n");
        HAL_Delay(5000);
    }
    
    // 订阅主题
    MQTT_Subscribe("stm32/led/control", MQTT_QOS1);
    MQTT_Subscribe("stm32/command", MQTT_QOS0);
    
    // 发布上线消息(保留消息)
    MQTT_Publish("stm32/status", (uint8_t*)"online", 6, MQTT_QOS1, 1);
    
    printf("System ready!\r\n");
    
    uint32_t last_publish = 0;
    char payload[64];
    
    while(1)
    {
        // MQTT处理
        MQTT_Process();
        
        // 每10秒发布一次数据
        if(HAL_GetTick() - last_publish >= 10000)
        {
            last_publish = HAL_GetTick();
            
            // 生成模拟数据
            int temp = 20 + (HAL_GetTick() % 15);
            int humidity = 40 + (HAL_GetTick() % 30);
            
            snprintf(payload, sizeof(payload), 
                     "{\"temperature\":%d,\"humidity\":%d}", temp, humidity);
            
            MQTT_Publish("stm32/sensor", (uint8_t*)payload, strlen(payload), 
                        MQTT_QOS0, 0);
            
            printf("Published: %s\r\n", payload);
        }
        
        HAL_Delay(100);
    }
}

四、测试验证

4.1 EMQ X Dashboard测试

1. 查看连接:

  • 登录EMQ X Dashboard
  • 进入"连接"页面
  • 确认STM32客户端已连接

2. 发布测试消息:

复制代码
主题:stm32/led/control
消息:on
QoS:1

3. 订阅测试:

复制代码
主题:stm32/sensor
观察接收到的传感器数据

4.2 功能测试

测试项 操作 预期结果
连接测试 启动设备 LED2亮起,显示在线
订阅测试 Dashboard发布消息 STM32接收并处理
发布测试 等待10秒 传感器数据上传到Dashboard
遗嘱测试 断开设备电源 Dashboard收到offline消息

五、故障排查

5.1 连接问题

问题1:无法连接MQTT服务器

  • 检查服务器地址和端口
  • 确认防火墙允许1883端口
  • 验证网络连接

问题2:CONNACK返回错误码

  • 0x01:不支持协议版本
  • 0x02:无效的客户端标识符
  • 0x03:服务器不可用
  • 0x04:无效的用户名或密码
  • 0x05:未授权

5.2 通信问题

问题3:消息发布失败

  • 检查TCP连接状态
  • 确认主题格式正确
  • 验证消息大小不超过限制

问题4:无法接收消息

  • 确认已订阅对应主题
  • 检查QoS等级匹配
  • 验证消息格式正确

六、总结

6.1 核心知识点

  • MQTT协议:发布/订阅模式、QoS机制、遗嘱消息
  • 报文格式:CONNECT、PUBLISH、SUBSCRIBE等报文的构建和解析
  • EMQ X:开源MQTT服务器的部署和使用

6.2 扩展学习

  • TLS加密:使用MQTT over TLS保障通信安全
  • WebSocket:在浏览器中使用MQTT
  • 集群部署:EMQ X集群配置实现高可用
相关推荐
zmj3203242 小时前
51单片机
单片机·嵌入式硬件·51单片机
zmj3203242 小时前
MCS-51单片机
单片机·嵌入式硬件·51单片机
深念Y2 小时前
从CH341A编程器、SPI Flash到Linux+STM32理解
linux·stm32·flash·bios·固件·编程器·闪存
小柯博客2 小时前
从零开始打造 OpenSTLinux 6.6 Yocto 系统 - STM32MP2(基于STM32CubeMX)(八)
c语言·git·stm32·单片机·嵌入式硬件·嵌入式·yocto
421!11 小时前
GPIO工作原理以及核心
开发语言·单片机·嵌入式硬件·学习
cmpxr_15 小时前
【单片机】STM32的启动流程(Keil)
stm32·单片机·嵌入式硬件
广药门徒16 小时前
嵌入式常用通信协议速率对比及布线要点全解析
单片机·嵌入式硬件
cmpxr_17 小时前
【单片机】RAM和ROM
单片机·嵌入式硬件
信息安全专家19 小时前
sigmastar SSD222D编译问题总结2-dash问题
linux·嵌入式硬件·dash