STM32之wifi模块与MQTT模块详解

1. 物联网组网

  • 设备组网是通过一定技术手段,将设备可以在一定的范围内进行数据传递和操作控制。常见的组网设备
  • WiFi 4G/5G NB-IoT LoRa BlueTooth LwIP (以太网)
  • 联网之后的设备可以将数据上传到云端,同时可以从云端得到相关的数据内容。
    • 远程监控
    • 远程操作
  • 批量多路处理

2. ESP 8266 WiFi 模块

2.1****原理图

  • 802.11 b/g/n
  • Wi-Fi Direct (P2P)、soft-AP
  • 内置 TCP/IP 协议栈
  • 内置 TR 开关、balun、LNA、功率放大器和匹配网络
  • 内置 PLL、稳压器和电源管理组件
  • 802.11b 模式下 + 19.5dBm 的输出功率
  • 支支持天线分集
  • 断电泄露电流小于 10uA
  • 内置低功率 32 位 CPU:可以兼作应用处理器
  • SDIO 2.0、SPI、UART
  • STBC、1x1 MIMO、2x1 MIMO
  • A-MPDU、A-MSDU 的聚合和 0.4μs 的保护间隔
  • 2ms 之内唤醒、连接并传递数据包
  • 待机状态消耗功率小于 1.0mW (DTIM3)

2.2 ESP8266模块和STM32****链接

ESP8266 模块对应的电路引脚情况

  • 重点关注 RXD 和 TXD
  • RXD 是用于接收 MCU 提供的 AT 指令
  • TXD 是用于反馈数据到 MCU,包括 AT 指令响应,网络数据响应内容,主要数据形式为【字符串形式】,除了基本字符串格式,包括方便后续解析使用的 JSON 格式数据

当前开发板中,对应的板载链接效果

  • ESP8266 模块的 RXD ==> MCU USART3 TX ==> PB10

  • ESP8266 模块的 TXD ==> MCU USART3 RX ==> PB11

  • 需要通过配置 USART3 数据发送和接收,来进行 ESP8266 WiFi 模块控制,利用 USART3 TX 发送 AT 指令到 WiFi 模块,利用 USART3 RX 接受 WiFi 模块的反馈数据。

  • PB11 ==> RX 浮空输入模式

  • PB10 ==> TX 复用推挽模式

2.3 AT指令操作WiFi模块连接AP****热点

cpp 复制代码
void ESP8266_Connect(const char *ssid, const char *psk)
{
    printf("ESP8266 WiFi Connect!\n");

    // 1. 发送 AT 指令,判断当前设备是否正常工作
    ESP8266_Send_AT("AT\r\n", "OK", 2000);
    systick_delay_ms(500);

    // 2. 设置当前 ESP8266 工作模式为 STA(Station) 模式
    ESP8266_Send_AT("AT+CWMODE=1\r\n", "OK", 2000);
    systick_delay_ms(500);

    // 3. 扫描当前可以连接的 AP 热点信息,包括 SSID,信号强度
    ESP8266_Send_AT("AT+CWLAP\r\n", "OK", 2000);
    systick_delay_ms(5000);

    // 4. 连接目标热点,提供 SSID 和 PSK,组装字符串时,必须将
    // AT 指令以 \r\n 结尾
    char connectCmd[128] = "";

    sprintf(connectCmd, "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, psk);
    ESP8266_Send_AT(connectCmd, "OK", 2000);
    systick_delay_ms(5000);

    // 5. 网络连接情况
    ESP8266_Send_AT("AT+CIFSR\r\n", "OK", 2000);
    systick_delay_ms(2000);
}

3. MQTT 协议

3.1 MQTT 概述

  • 广泛用于物联网设备联网通信的传输协议,基于 TCP/IP 实现
  • MQTT 区分客户端和服务器,本地 STM32 + ESP8266 + MQTT 支持,可以认为是 MQTT 的客户端,同时云平台 (ThingsCloud Onenet Aliyun) 物联网平台可以认为是 MQTT 服务器。
  • MQTT 协议中,重点内容是【服务器连接】【订阅主题】【发布消息】
    • 服务器连接:当前 MQTT 客户端链接对应的 MQTT 服务器
    • 订阅主题:MQTT 服务器一旦通过指定主题下发数据,MQTT 客户端可以收到服务器下发的数据内容,可以完成设备控制,例如 LED 亮灭,舵机控制。。。
    • 发布消息:MQTT 客户端按照 MQTT 服务器要求,和云平台自定义内容,将本地数据发送到云平台。可以将传感器数据进行上传。。。

3.2 MQTT 相关技术内容

MQTT 技术特征:

  • MQTT 版本有 5.0.0 和 3.1.1
    • 5.0.0 是最新版本,支持功能更多,使用更为方便
    • 3.1.1 是目前主流设备端使用的大面积版本,使用相对复杂。

核心特性:

  1. 轻量高效
    • 协议头最小仅 2 字节,适合资源受限的设备(如传感器、嵌入式设备)。
    • 低功耗设计,适合电池供电设备。
  2. 发布 / 订阅模型
    • 解耦消息生产者(Publisher)和消费者(Subscriber),通过主题(Topic)路由消息。
    • 支持一对多、多对多通信。
  3. 多种服务质量(QoS)
    • QoS 0(最多一次):消息可能丢失。
    • QoS 1(至少一次):消息确保送达,但可能重复。
    • QoS 2(恰好一次):消息确保送达且不重复。
  4. 支持持久会话
    • 客户端可订阅持久化消息,断线重连后接收离线期间的消息(需 Broker 支持)。
  5. 低协议开销
    • 基于 TCP/IP,但也可适配其他传输层(如 WebSocket、MQTT - SN 用于传感器网络)。

3.3 MQTT****控制报文结构

3.3.1 MQTT****报文组成部分

Fixed header: 确定当前 MQTT 发送的数据目标,例如 连接服务器,发布数据,订阅主题
Variable header: 根据当前 MQTT Fixed header 目标任务需求,提供不同的报文参数
Payload:根据当前 MQTT 报文类型和目标数据需求,提供的用户有效载荷内容

3.3.2 Fixed header****固定报头

格式内容

  • 主要有 MQTT 控制报文类型和报文类型指定标志
  • 剩余长度

在连接和发布报文中控制位置0即可,而订阅报文控制位必须是0x02.

**3.4 CONNECT --**连接服务端

3.4.1 CONNECT****主要作用

客户端到服务端的网络连接建立后,客户端发送给服务端的第一个报文必须是 CONNECT 报文 MQTT-3.1.0-1。

在一个网络连接上,客户端只能发送一次 CONNECT 报文。服务端必须将客户端发送的第二个 CONNECT 报文当作协议违规处理并断开客户端的连接 MQTT-3.1.0-2。有关错误处理的信息请查看 4.8 节。

有效载荷包含一个或多个编码的字段。包括客户端的唯一标识符,Will 主题,Will 消息,用户名和密码。除了客户端标识之外,其它的字段都是可选的,基于标志位来决定可变报头中是否需要包含这些字段。

3.4.2 CONNECT 数据包分析

一、整体结构

MQTT Connect 报文由 Fixed Header(固定头) 、Variable Header(可变头) 、Payload(有效载荷) 三部分组成,用数组 MQTT_Connect_Data[256] 存储报文数据。


二、Fixed Header(固定头,2 字节)

字节索引 作用 计算 / 取值逻辑 示例值
0 报文类型 + 标志位 固定为 0x10(二进制 0001 0000,表示 CONNECT 报文,无额外标志位) 0x10
1 剩余长度(可变头 + 有效载荷总长度) 可变头长度(10 字节) + 有效载荷长度(19 字节) = 29 → 二进制 0001 1101 → 0x1D 0x1D

核心逻辑 :固定头是报文 "身份标识",第 0 字节固定 0x10,第 1 字节需计算可变头 + 有效载荷的总长度。


三、Variable Header(可变头,10 字节)

1. 协议名称(2 字节 + 协议内容 "MQTT" 4 字节 → 共 6 字节)

字节索引 作用 计算 / 取值逻辑 示例值
2 协议长度高位(MSB) 协议内容 "MQTT" 长度是 4 → 4 / 256 = 0 → 二进制 0000 0000 → 0x00 0x00
3 协议长度低位(LSB) 4 % 256 = 4 → 二进制 0000 0100 → 0x04 0x04
4-7 协议内容 "MQTT" 字符转 16 进制:M(0x4D)、Q(0x51)、T(0x54)、T(0x54) 0x4D 0x51 0x54 0x54

核心逻辑 :用 2 + N 模式(2 字节存长度 + N 字节存内容),这里 "MQTT" 长度是 4,所以先存 0x00 0x04,再存字符对应的 16 进制。

2. 协议版本(1 字节)

字节索引 作用 计算 / 取值逻辑 示例值
8 MQTT 版本(3.1.1) 固定用 0x04 表示 MQTT 3.1.1 0x04

核心逻辑 :协议规定 MQTT 3.1.1 对应 0x04,直接填固定值。

3. 连接标志(Connect Flags,1 字节)

字节索引 作用 计算 / 取值逻辑 示例值
9 标志位组合 组成部分是告知当前 MQTT 协议后续的载荷内容,每一个标志位告示后续的数据中, 是否包含对应的数据内容 Username ( 占 1 位 ) Password ( 占 1 位 ) Will Retain ( 占 1 位 ) Will QoS ( 占 2 位 ) Will Flag ( 占 1 位 ) Clean Session ( 占 1 位 ) 保留 ( 占 1 位 ) 根据 ThingsCloud 分析,需要提供用户名和密码,同时按照常规内容,不需要返回, QoS = 0 flag 不需要, Clean Session 需要 MQTT_Connect_Data 9 ==> 1100 0010 ==> 0xC2 0xC2

核心逻辑:每一位代表不同功能(如是否用用户名密码、是否清除会话等),按需求组合后转 16 进制。

4. 保持连接时间(Keep Alive,2 字节)

字节索引 作用 计算 / 取值逻辑 示例值
10 时间高位(MSB) 假设 Keep Alive = 60秒 → 60 / 256 = 0 → 二进制 0000 0000 → 0x00 0x00
11 时间低位(LSB) 60 % 256 = 60 → 二进制 0011 1100 → 0x3C(注意:原示例计算可能有误,60 转 16 进制是 0x3C,非 0x3CD ,这里以正确逻辑为准 ) 0x3C

核心逻辑:用 2 字节存时长(秒),高位存商、低位存余数,超过范围会触发重连。


四、Payload(有效载荷,19 字节)

按 2 + N 模式存储 客户端标识、用户名、密码 ,总长度 = 2(客户端标识长度头) + 9(客户端标识内容) + 2(用户名长度头) + 2(用户名内容) + 2(密码长度头) + 2(密码内容) = 19 字节。

1. 客户端标识(示例:Client_GL ,9 字节内容 + 2 字节长度头 → 共 11 字节)

字节索引 作用 计算 / 取值逻辑 示例值
12 长度高位(MSB) 客户端标识长度 9 → 9 / 256 = 0 → 0x00 0x00
13 长度低位(LSB) 9 % 256 = 9 → 0x09 0x09
14-22 客户端标识 "Client_GL" 字符转 16 进制:C(0x43)、l(0x6C)、i(0x69)、e(0x65)、n(0x6E)、t(0x74)、_(0x5F)、G(0x47)、L(0x4C) 对应字节依次为 0x43 0x6C 0x69 0x65 0x6E 0x74 0x5F 0x47 0x4C

2. 用户名(示例:CG ,2 字节内容 + 2 字节长度头 → 共 4 字节)

字节索引 作用 计算 / 取值逻辑 示例值
23 长度高位(MSB) 用户名长度 2 → 2 / 256 = 0 → 0x00 0x00
24 长度低位(LSB) 2 % 256 = 2 → 0x02 0x02
25-26 用户名 "CG" 字符转 16 进制:C(0x43)、G(0x47) 0x43 0x47

3. 密码(示例:HL ,2 字节内容 + 2 字节长度头 → 共 4 字节)

字节索引 作用 计算 / 取值逻辑 示例值
27 长度高位(MSB) 密码长度 2 → 2 / 256 = 0 → 0x00 0x00
28 长度低位(LSB) 2 % 256 = 2 → 0x02 0x02
29-30 密码 "HL" 字符转 16 进制:H(0x48)、L(0x4C) 0x48 0x4C

核心逻辑 :所有字段都用 2 + N 模式(2 字节存长度 + N 字节存内容),按实际字符长度计算存储。


五、总结流程(快速梳理)

  1. 固定头 :填 0x10(报文类型) + 计算 "可变头 + 有效载荷长度" 填 0x1D。
  2. 可变头 :
    • 协议名称:存 0x00 0x04 + "MQTT"(0x4D 0x51 0x54 0x54)。
    • 协议版本:填 0x04。
    • 连接标志:按需求填 0xC2(含用户名密码、清除会话等)。
    • 保持连接:填 0x00(高位) + 0x3C(低位,Keep Alive=60 时)。
  3. 有效载荷 :
    • 客户端标识:存 0x00 0x09 + "Client_GL" 字符对应的 16 进制。
    • 用户名:存 0x00 0x02 + "CG" 字符对应的 16 进制。
    • 密码:存 0x00 0x02 + "HL" 字符对应的 16 进制。
3.4.3 STM32 利用 ESP8266 连接 MQTT 流程
  • ESP8266 联网
  • ESP8266 利用 TCP 协议和 MQTT 云平台建立连接
  • ESP8266 开启【透传】模式 + 【数据直发】方式
  • 将 Connect 操作数据包发送到 MQTT 云平台,建立和项目的连接。

3.5 PUBLISH -- 发布消息

3.5.1 发布消息 Topic

MQTT 客户端到服务器或者服务器到客户端,都需要通过发布主题进行明确当前提交的信息内容是什么内容。

ThingsCloud 支持的发布主题 Topic。针对于 ThingsCloud 云端设备,选择 public_topic 对应 attributes。提交数据格式为 JSON 格式。

【注意】发布消息是在 MQTT 服务器连接之后

3.5.2****发布数据包分析

一、整体结构

MQTT PUBLISH 报文用于 客户端向服务器发布消息 ,由 Fixed Header(固定头) 、Variable Header(可变头) 、Payload(有效载荷) 三部分组成,用数组 MQTT_Publish_Data[256] 存储。


二、Fixed Header(固定头,2 字节)

字节索引 作用 计算 / 取值逻辑 示例值
0 报文类型 + 标志位 固定为 0x30(二进制 0011 0000,表示 PUBLISH 报文,QoS 0 时标志位为 0000) 0x30
1 剩余长度(可变头 + 有效载荷总长度) 可变头长度(2 + strlen("attributes")) + 有效载荷长度(JSON 字符串长度) 需计算

核心逻辑:

  • 第 0 字节固定 0x30(PUBLISH 报文类型)。
  • 第 1 字节需动态计算:可变头长度 + 有效载荷长度,具体值取决于主题名和 JSON 内容长度。

三、Variable Header(可变头,2 + N 字节,N 是主题名长度)

作用:存储 发布主题(Topic) ,格式为 2 + N(2 字节存长度 + N 字节存主题名)。

以 ThingsCloud 要求的主题 attributes 为例:

字节索引 作用 计算 / 取值逻辑 示例值
2 主题名长度高位(MSB) strlen("attributes") / 256 → 主题名长度是 10 → 10 / 256 = 0 → 0x00 0x00
3 主题名长度低位(LSB) strlen("attributes") % 256 → 10 % 256 = 10 → 0x0A 0x0A
4-13 主题名字符串 "attributes" 字符转 16 进制:a(0x61)、t(0x74)、t(0x74)、r(0x72)、i(0x69)、b(0x62)、u(0x75)、t(0x74)、e(0x65)、s(0x73) 对应字节依次为 0x61 0x74 0x74 0x72 0x69 0x62 0x75 0x74 0x65 0x73

核心逻辑 :

用 2 + N 模式存储主题名,先存长度(高位 + 低位),再存主题名字符对应的 16 进制。


四、Payload(有效载荷,JSON 字符串长度字节)

作用:存储 发布的实际数据,格式为 JSON 字符串(如传感器数据)。

以示例 JSON {"Temp":25.3,"Hum":55} 为例:

字节索引 作用 计算 / 取值逻辑 示例值
14-... JSON 字符串内容 直接拼接在可变头之后,字符转 16 进制存储 如 0x7B 0x22 0x54 0x65 0x6D 0x70 0x22 0x3A 0x32 0x53 0x2E 0x33 0x2C 0x22 0x48 0x75 0x6D 0x22 0x3A 0x35 0x35 0x7D

核心逻辑 :

有效载荷是 JSON 格式的业务数据,直接拼接在可变头之后,按字符转 16 进制存储。


五、代码实现流程(快速梳理)

  1. 初始化数组 :
    u8 MQTT_Publish_Data[256] = "";(预留足够空间存报文)

  2. 固定头:

    • 第 0 字节:MQTT_Publish_Data[0] = 0x30;(PUBLISH 报文类型)
    • 第 1 字节:需计算 可变头长度 + 有效载荷长度,动态填充。
  3. 可变头(主题名):

    • 计算主题名长度:len = strlen("attributes")(结果为 10)
    • 存长度高位:MQTT_Publish_Data[2] = len / 256;(即 0x00)
    • 存长度低位:MQTT_Publish_Data[3] = len % 256;(即 0x0A)
    • 存主题名字符:memcpy(&MQTT_Publish_Data[4], "attributes", len);
  4. 有效载荷(JSON 数据):

    • 准备 JSON 字符串:{"Temp":25.3,"Hum":55}
    • 拼接数据:memcpy(&MQTT_Publish_Data[4 + len], json_str, strlen(json_str));
  5. 发送报文 :

    调用 ESP8266_SendBuffer(...) 发送完整数组。

**3.6 SUBSCRIBE -**订阅主题

3.6.1订阅主题Topic
3.5.2****订阅主题数据包分析

一、整体结构

MQTT SUBSCRIBE 报文用于 客户端向服务器订阅主题 ,由 Fixed Header(固定头) 、Variable Header(可变头) 、Payload(有效载荷) 三部分组成,用数组 MQTT_Subscribe_Data[256] 存储。


二、Fixed Header(固定头,2 字节)

字节索引 作用 计算 / 取值逻辑 示例值
0 报文类型 + 标志位 固定为 0x80(二进制 1000 0000,表示 SUBSCRIBE 报文,QoS 0 时标志位为 0000) 0x80
1 剩余长度(可变头 + 有效载荷总长度) 可变头长度(2 字节) + 有效载荷长度(2 + strlen("attributes/push") + 1) 需计算

核心逻辑:

  • 第 0 字节固定 0x80(SUBSCRIBE 报文类型)。
  • 第 1 字节需动态计算:可变头长度 + 有效载荷长度,具体值取决于主题名长度。

三、Variable Header(可变头,2 字节)

作用:存储 订阅数据包 ID(QoS 0 时可自定义,无实际校验逻辑)。

字节索引 作用 计算 / 取值逻辑 示例值
2 数据包 ID 高位 自定义为 0x00 0x00
3 数据包 ID 低位 自定义为 0x08 0x08

核心逻辑 :

QoS 0 时,ID 仅用于占位,可随意填(如 0x00 0x08),不影响功能。


四、Payload(有效载荷,2 + N + 1 字节,N 是主题名长度)

作用:存储 订阅主题(Topic) 和 QoS 等级 ,格式为 2(主题名长度) + N(主题名字符) + 1(QoS 等级)。

以订阅主题 attributes/push 为例:

字节索引 作用 计算 / 取值逻辑 示例值
4 主题名长度高位(MSB) strlen("attributes/push") / 256 → 主题名长度是 14 → 14 / 256 = 0 → 0x00 0x00
5 主题名长度低位(LSB) strlen("attributes/push") % 256 → 14 % 256 = 14 → 0x0E 0x0E
6-19 主题名字符串 "attributes/push" 字符转 16 进制:a(0x61)、t(0x74)、t(0x74)、r(0x72)、i(0x69)、b(0x62)、u(0x75)、t(0x74)、e(0x65)、s(0x73)、/(0x2F)、p(0x70)、u(0x75)、s(0x73)、h(0x68) 对应字节依次为 0x61 0x74 0x74 0x72 0x69 0x62 0x75 0x74 0x65 0x73 0x2F 0x70 0x75 0x73 0x68
20 QoS 等级 QoS 0 对应 0x00(二进制 0000 0000) 0x00

核心逻辑 :

用 2 + N + 1 模式存储订阅信息:

  • 先存主题名长度(高位 + 低位),
  • 再存主题名字符对应的 16 进制,
  • 最后存 QoS 等级(QoS 0 填 0x00)。

五、代码实现流程(快速梳理)

  1. 初始化数组 :
    u8 MQTT_Subscribe_Data[256] = "";(预留足够空间存报文)

  2. 固定头:

    • 第 0 字节:MQTT_Subscribe_Data[0] = 0x80;(SUBSCRIBE 报文类型)
    • 第 1 字节:需计算 可变头长度(2) + 有效载荷长度(2 + strlen("attributes/push") + 1),动态填充。
  3. 可变头(数据包 ID):

    • 第 2 字节:MQTT_Subscribe_Data[2] = 0x00;(ID 高位,自定义)
    • 第 3 字节:MQTT_Subscribe_Data[3] = 0x08;(ID 低位,自定义)
  4. 有效载荷(订阅主题 + QoS):

    • 计算主题名长度:len = strlen("attributes/push")(结果为 14)
    • 存长度高位:MQTT_Subscribe_Data[4] = len / 256;(即 0x00)
    • 存长度低位:MQTT_Subscribe_Data[5] = len % 256;(即 0x0E)
    • 存主题名字符:memcpy(&MQTT_Subscribe_Data[6], "attributes/push", len);
    • 存 QoS 等级:MQTT_Subscribe_Data[6 + len] = 0x00;(QoS 0)
  5. 发送报文 :

    调用 ESP8266_SendBuffer(...) 发送完整数组。

3.7 代码示例

mqtt.h:

cpp 复制代码
#ifndef _MQTT_H
#define _MQTT_H

#include "stdio.h"
#include "string.h"
#include "stdlib.h"

#include "stm32f10x.h"
#include "ESP8266.h"
#include "systick.h"

//替换自己的
#define CLIENT_ID ""
#define USARNAME ""
#define PASSWORD ""
#define KEEP_ALIVE (60)

#define MQTT_SERVER_ADDR "bj-2-mqtt.iot-api.com"
#define MQTT_SERVER_PORT (1883)

#define PUBLISH_TOPIC "attributes"
#define SUBSCRIBE_TOPIC "attributes/push"

void MQTT_Config(void);
u8 MQTT_Connect_ThingsCloud(const char *protocol, const char *mqtt_addr, u16 mqtt_port);

void MQTT_Send_Connect_Package(void);
void MQTT_Send_Publish_Package(const char *topic, const char *data, u32 len);
void MQTT_Send_Subscribe_Package(const char *topic);

void MQTT_Data_Handler(u8 *buffer, u16 length);


#endif

mqtt.c:

cpp 复制代码
#include "mqtt.h"

//固定头字节数
u32 fixed_header_len = 0;
//可变头子结束吧
u32 variable_header_len = 0;
//有效载荷字节数
u32 payload_len = 0;

void MQTT_Config(void){
	// TCP连接云平台
	MQTT_Connect_ThingsCloud("TCP",MQTT_SERVER_ADDR,MQTT_SERVER_PORT);
	SysTick_Delay_ms(1000);  // 延时1秒,等待连接稳定
	
	//开启ESP8266透传模式,数据不解析
	ESP8266_OpenTransparent();
	SysTick_Delay_ms(1000);
	
	//开启数据直发模式,起点是wifi模块,终点直发云服务器
	ESP8266_SendMode();
	SysTick_Delay_ms(1000);
	
	//告知MQTT平台,当前连接的用户项目是哪一个
	MQTT_Send_Connect_Package();
	SysTick_Delay_ms(1000);
	
	//发送订阅包,订阅指定的主题
	MQTT_Send_Subscribe_Package(SUBSCRIBE_TOPIC);
}

/**
 * @brief 通过TCP连接到MQTT云平台
 * @param protocol 协议类型(如"TCP")
 * @param mqtt_addr MQTT服务器地址
 * @param mqtt_port MQTT服务器端口
 * @return 连接结果(1表示成功,0表示失败)
 */
u8 MQTT_Connect_ThingsCloud(const char *protocol,const char *mqtt_addr,u16 mqtt_port){
	// 1.定义存储AT指令的缓冲区
	char cmd_buffer[128] = "";
	
	// 2.格式化AT指令,用于建立TCP连接
	// AT+CIPSTART指令用于ESP8266建立网络连接
    sprintf(cmd_buffer, "AT+CIPSTART=\"%s\",\"%s\",%d\r\n", protocol, mqtt_addr, mqtt_port);
	//发送AT指令
	ESP8266_SendString(cmd_buffer);

	 SysTick_Delay_ms(500);  // 延时500ms,等待指令响应
    
    // 3. 检查ESP8266的响应是否包含"OK",返回检查结果
    return ESP8266_AckCheck("OK");
}

/**
 * @brief 发送MQTT连接包(CONNECT报文)
 * @note 用于客户端与MQTT服务器建立连接,包含客户端标识、用户名、密码等信息
 */
void MQTT_Send_Connect_Package(void){
	/*
     1. 数据准备
     */
    u8 MQTT_Connect_Data[256] = "";  // 存储MQTT连接报文的缓冲区
	
		//计算客户端ID,用户名。密码的长度
		u32 client_id_len = strlen(CLIENT_ID);
		u32 username_len = strlen(USARNAME);    // 用户名长度(注:变量名可能应为USERNAME)
    u32 password_len = strlen(PASSWORD);    // 密码长度
	
		//计算各部分的长度
		fixed_header_len = 2;  //固定头部长度就是2
		variable_header_len = 10;
		//有效载荷长度
		payload_len = 2 + 2 + 2 + client_id_len + username_len + password_len;
	
		// 2. 【处理固定头】
		MQTT_Connect_Data[0] = 0x10;  // 报文类型:CONNECT(0x10),无标志位
		// 剩余长度 = 可变头长度 + 有效载荷长度
		MQTT_Connect_Data[1] = variable_header_len + payload_len;
	
		// 3. 【处理可变头】
    // 3.1 协议名(MQTT)及其长度
    // 协议名长度为2字节(高位在前,低位在后)
		MQTT_Connect_Data[2] = strlen("MQTT") / 256;   //高字节
		MQTT_Connect_Data[3] = strlen("MQTT") % 256;    //低字节
		
		// 拷贝协议名字符串("MQTT")到缓冲区,四个字节
    memcpy(&MQTT_Connect_Data[4], "MQTT", strlen("MQTT"));
		
		MQTT_Connect_Data[8] = 0x04;  // 协议版本:0x04表示MQTT 3.1.1
		
		// 3.2 连接标志(Connect Flags)
    // 0xC2 = 11000010,含义:
    // - 第7位:用户名标志(1,表示有效载荷包含用户名)
    // - 第6位:密码标志(1,表示有效载荷包含密码)
    // - 第1位:清除会话标志(1,表示清除之前的会话)
    MQTT_Connect_Data[9] = 0xC2;
		
		// 3.3 保持连接时间(Keep Alive),单位为秒
    // 这里设置为60秒,分为高低两个字节存储
    MQTT_Connect_Data[10] = KEEP_ALIVE / 256;  // 高位字节
    MQTT_Connect_Data[11] = KEEP_ALIVE % 256;  // 低位字节
		
		// 4. Payload 有效载荷内容
    /* Client_ID - 客户端标识 */
    // 客户端ID长度(2字节)
		MQTT_Connect_Data[12] = client_id_len / 256;  // 高位
    MQTT_Connect_Data[13] = client_id_len % 256;  // 低位
    // 拷贝客户端ID到缓冲区
    memcpy(&MQTT_Connect_Data[14], CLIENT_ID, client_id_len);
		
		 /* Username - 用户名 */
    // 计算用户名在缓冲区中的起始位置
    u16 username_index = 14 + client_id_len;
    // 用户名长度(2字节)
    MQTT_Connect_Data[username_index] = username_len / 256;    // 高位
    MQTT_Connect_Data[username_index + 1] = username_len % 256;  // 低位
    // 拷贝用户名到缓冲区
    memcpy(&MQTT_Connect_Data[username_index + 2], USARNAME, username_len);
		
		/* Password - 密码 */
    // 计算密码在缓冲区中的起始位置
    u16 password_index = username_index + 2 + username_len;
    // 密码长度(2字节)
    MQTT_Connect_Data[password_index] = password_len / 256;    // 高位
    MQTT_Connect_Data[password_index + 1] = password_len % 256;  // 低位
    // 拷贝密码到缓冲区
    memcpy(&MQTT_Connect_Data[password_index + 2], PASSWORD, password_len);
		
		// 发送完整的MQTT连接包(固定头+可变头+有效载荷)
    ESP8266_SendBuffer(MQTT_Connect_Data, fixed_header_len + variable_header_len + payload_len);
}

/**
 * @brief 发送MQTT订阅包(SUBSCRIBE报文)
 * @param topic 要订阅的主题
 * @note 用于客户端订阅指定的主题,以接收该主题的消息
 */
void MQTT_Send_Subscribe_Package(const char *topic){
	// 1.数据准备
	u8 MQTT_Subscribe_Data[128] = "";
	u32 topic_len = strlen(topic);
	
	//计算各部分的长度
	fixed_header_len = 2;
	variable_header_len = 2;
	//有效载荷长度,主题长度 + 主题 + qos0 1个字节  
	payload_len = 2 + topic_len + 1;   
	
	// 1.固定头内容
	MQTT_Subscribe_Data[0] = 0x82;  // 报文类型:SUBSCRIBE(0x80),标志位0x02
	//剩余长度 = 可变头长度 + 有效
	 MQTT_Subscribe_Data[1] = variable_header_len + payload_len;
	
		// 2. 可变头 - 消息ID(用于匹配订阅确认)由于是qos0,这两个字节随便填
    MQTT_Subscribe_Data[2] = 0x00;  // 消息ID高位
    MQTT_Subscribe_Data[3] = 0x08;  // 消息ID低位(此处固定为0x0008)
	
	// 3. 有效载荷
    // 主题长度(2字节)
    MQTT_Subscribe_Data[4] = topic_len / 256;  // 高位
    MQTT_Subscribe_Data[5] = topic_len % 256;  // 低位
    // 拷贝主题到缓冲区
    memcpy(&MQTT_Subscribe_Data[6], topic, topic_len);
    // QoS等级(0x00表示QoS 0,最多一次交付)
    MQTT_Subscribe_Data[6 + topic_len] = 0x00;
		
		
		// 发送完整的订阅包
    ESP8266_SendBuffer(MQTT_Subscribe_Data, fixed_header_len + variable_header_len + payload_len);
}

/**
 * @brief 发送MQTT发布包(PUBLISH报文)
 * @param topic 发布消息的主题
 * @param data 要发布的数据内容
 * @param len 数据内容的长度
 * @note 用于客户端向指定主题发布消息
 */
void MQTT_Send_Publish_Package(const char *topic, const char *data, u32 len)
{
    // 1. 数据准备
    u8 MQTT_Publish_Data[128] = "";  // 存储发布报文的缓冲区
    u32 topic_len = strlen(topic);   // 发布主题的长度
    
    // 计算各部分长度
    fixed_header_len = 2;  // 固定头长度为2字节
    variable_header_len = 2 + topic_len;  // 可变头长度 = 主题长度(2字节) + 主题内容长度
    payload_len = len;  // 有效载荷长度为数据长度
    
    // 2. 固定头内容
    MQTT_Publish_Data[0] = 0x30;  // 报文类型:PUBLISH(0x30),标志位0x00(QoS 0)
    // 剩余长度 = 可变头长度 + 有效载荷长度
    MQTT_Publish_Data[1] = variable_header_len + payload_len;
    
    // 3. 可变头 - 主题相关信息
    // 主题长度(2字节)
    MQTT_Publish_Data[2] = topic_len / 256;  // 高位
    MQTT_Publish_Data[3] = topic_len % 256;  // 低位
    // 拷贝主题到缓冲区
    memcpy(&MQTT_Publish_Data[4], topic, topic_len);
    
    // 4. 有效载荷 - 发布的数据内容
    memcpy(&MQTT_Publish_Data[4 + topic_len], data, len);
    
    // 发送完整的发布包
    ESP8266_SendBuffer(MQTT_Publish_Data, fixed_header_len + variable_header_len + payload_len);
}

0voice · GitHub

相关推荐
小程序设计2 分钟前
基于单片机的恒温水箱设计
单片机·嵌入式硬件
小程序设计33 分钟前
基于单片机的大棚温湿度自动监控系统
单片机·嵌入式硬件
XiHongShi20163 小时前
STM32F407 CRC-循环冗余校验例程,建议保存学习
stm32·单片机
lhbweilai4 小时前
结合手册调试各个功能的寄存器+最后通过寄存器编写SPI的初始化和读写函数
stm32·单片机
小程序设计7 小时前
本科毕设避坑|大数据 / 单片机物联网 / 网络工程 / PLC / 机械设计选题与落地心得
java·hive·hadoop·单片机
Zw-awa12 小时前
先让程序开口说话:为什么把人机交互放在最前面
stm32
Zwawa12 小时前
把按键和 LED 状态显示出来:第一次看见程序到底在干什么
单片机·嵌入式
Zw-awa13 小时前
把按键和 LED 状态显示出来:第一次看见程序到底在干什么
stm32
Zw-awa13 小时前
面包板接线为什么这样接:从模块需求到芯片资源分配
stm32
小程序设计13 小时前
基于STM32的办公室智能空气净化系统设计
stm32·单片机