当前位置: 首页 > news >正文

STM32:ESP8266 + MQTT 云端与报文全解析

知识点1【MQTT的概述】

1、概述

MQTT是一种基于发布/订阅模式的轻量级应用层协议,运行在TCP/IP协议之上,专用物联网(IoT)和机器对机器(M2M)设计,其核心目标是低带宽高延迟不稳定网络环境下实现可靠的消息传输,尤其适用于资源受限的设备。

  • 关键点

    1、基于 发布/订阅:对实时性要求不高

    2、应用层协议

    3、低带宽:轻量级传输

    4、高延迟 可靠

    5、专用 物联网 和 嵌入式 设备间通信

    6、基于TCP/IP协议基础之上

2、透传模式

透传模式:一种数据通信方式

特点:不对传输的数据进行任何解析,封装或修改。仅是将数据从一段传输到另一端。

3、回显模式

回显模式:串口通信 和 AT指令交互中 的一种基础功能。将 设备 接收到的指令原样返回给发送端。

4、心跳包

心跳包:用于 维持长连接,检测连接状态 的一种机制。功能:定期发送小型数据包确保通信双方能够感知到对方的存活状态

知识点2【WIFI和MQTT的关系】

我接下来要介绍的是 ESP8266 与 MQTT 一起实现上云(Thingscloud)操作

1、层次不同

WIFI:物理层和数据链路层——负责设备间的无线连接,提供数据传输的通道

MQTT:应用层——定义设备间传输消息的格式

2、功能分工

WIFI:为设备提供互联网接入,确保数据能够在互联网中传输

MQTT:在已经建立的网络连接上,通过 订阅/发布 模式管理消息,实现低宽带,高延迟环境下的可靠通信。

知识点3【QoS介绍】

QoS级别传递保证重复风险传输流程适用场景
QoS 0最多一次(At most once)可能丢失消息发送后不等待确认,无重试机制。非关键数据(如周期性传感器读数)
QoS 1至少一次(At least once)可能重复发送方存储消息直到收到确认(PUBACK),否则重发。需要可靠传输但允许重复(如状态更新)
QoS 2恰好一次(Exactly once)无重复四次握手(PUBREC/PUBREL/PUBCOMP),确保消息唯一性。关键指令(如支付、设备控制)

知识点4【MQTT报文分析】

MQTT的报文类型有很多,这里我仅介绍一下比较重要的 连接报文,订阅报文,发布报文

以上报文均是由三部分组成:固定报头,可变报头,有效载荷

一、三者定义与作用

操作定义核心作用
连接(Connection)客户端(如设备)与MQTT代理(Broker)建立通信链路的过程。建立通信通道,为订阅和发布提供基础。
订阅(Subscribe)客户端向代理注册对某个**主题(Topic)**的兴趣,声明希望接收该主题的消息。接收特定主题的消息,实现“监听”功能。
发布(Publish)客户端或代理向某个**主题(Topic)**发送消息,消息会被路由给所有订阅者。传递数据,驱动系统行为(如控制指令)。

1、连接报文:CONNECT

(1)固定报头

可变报头功能介绍数值
byte1高4bits是报文类型,后4bits是保留位0x10
byte2剩余长度:可变报头 + 有效载荷 的字节数

(2)可变报头

我们这一用的协议都是 “MQTT”

byte1协议长度的高4位0x00
byte2协议长度的第四位0x04
byte3‘M’
byte4‘Q’
byte5‘T’
byte6‘T’
byte7协议的版本号,我们是3.1.1版本对应的是40x04

byte8配置的是连接标志,我们单独介绍

连接标志

bit7:用户名标志

**作用:**声明 CONNECT 报文 中 是否包含用户名(在有效载荷中)

取值功能
1有效载荷包含用户名
0无用户名

bit6:密码标志

**作用:**声明 CONNECT 报文 中 是否包含密码(在有效载荷中)

取值功能
1有效载荷中包含密码
0无密码

bit5:遗嘱保留

**作用:**控制服务器是否将 遗嘱消息 作为保留消息存储

取值功能
1遗嘱消息保留在服务器,新订阅者会立即受到该消息
0不保留

注意:仅当 bit2 = 1 (启动遗嘱)时有效

bit4 - 3:遗嘱服务质量

**作用:**定义遗嘱消息的 服务质量等级(QoS)

取值功能
00QoS 0(最多一次)
01QoS 1(至少一次)
10QoS 2(恰好一次)
11保留值(禁止使用)

注意:仅当 bit2 = 1 (启动遗嘱)时有效

bit2:遗嘱标志

作用:声明客户端是否设置了遗嘱消息(设备 异常断线 时 触发的消息)

取值功能
1遗嘱消息有效
0遗嘱消息无效

bit1:清理会话

**作用:**声明

取值功能
1清理会话:
    连接断开后,服务器丢弃所有订阅和未确认消息 |

| 0 | 持久会话: 服务器保留订阅和未确认消息(QOS1/2),重连恢复 |

bit0:保留位

**作用:**协议保留位,必须是0

(3)有效载荷

CONNECT的有效载荷(PAYLOAD) 包含一个或多个长度为前缀的字段,可变报头中的标志位决定是否包含这些字段

字段需要按照顺序一下出现:客户端标识符,遗嘱主题,遗嘱消息,用户名,密码

2、订阅报文:SUBSCRIBE

(1)固定报文

可变报头功能介绍数值
byte1高4bits是报文类型,SUBSCRIBE的类型值是 8
后4bits是标志位,必须是 20x82
byte2剩余长度:可变报头 + 有效载荷 的字节数可变字节编程

(2)可变报文

byte1报文标识符的高4位
byte2报文标识符的第四位

报文标识符(Packet Identifier):用来区别客户端对多个订阅请求的应答(SUBACK)

Packet Identifier 从1开始递增(客户端自行管理),0不可用

一般第一个订阅包使用1,切不能重复使用正在等待的ID,但是对应的 Packet Identifier 收到SUBACK后就可以再次使用该值了。

(3)有效载荷

有效载荷功能介绍
byte1主题长度的高8位
byte2主题长度的低8位
byte3~N主题名
byteN+1服务质量等级(QoS),仅 低两bits 有效

3、发布报文:PUBLISH

(1)固定报文

可变报头功能介绍数值
byte1 7-4高4bits是报文类型0011→3
bit 3DUP标志:0表示首次发送,1表示重发
bits 2–1QoS等级
bit 0保留

DUP介绍:

目的:告诉接收端:“这条消息可能是重发的副本,请不要当作全新消息去处理多次。”

一般用于QoS1模式下

在QoS2模式下,PUBLISH→PUBREC→PUBREL→PUBCOMP 任何一个阶段超时,也需要重发,也需要DUP = 1

(2)可变报文

以上是以主题名位“a/b”举例的

可变报头功能介绍
byte1主题名长度的高8位
byte2主题名长度的8位
byte3-N主题名

补充:

这里补充一下PUBACK/PUBREC/PUBCOMP

这三个都是针对于 PUBLISH 的,在不同的QoS在会有不同的流程

  • QoS 0:直接 PUBLISH,不要任何 ACK
  • QoS 1:PUBLISH → PUBACK
  • QoS 2:PUBLISH → PUBREC → PUBREL → PUBCOMP

(3)有效载荷

要发送的应用消息内容存储在有效载荷当中

知识点4【代码演示】

代码实现是利用AT指令将 WIFI 与 服务器(安信可透传云)建立连接,MQTT层面的还没有写,每天将补充AT指令

安信可透传云的连接:

安信可透传云 V1.0

main.c

#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "rs485.h"
#include "esp8266.h"
#include "delay.h"int main(void)
{Systick_Init(72000);//优先级组的配置NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);USART1_Config(115200);USART3_Config(115200);printf("你好\\n");ESP8266_CMD_Init();while(1){	if(data_esp8266.over_flag){data_esp8266.over_flag = 0;data_esp8266.recv_size = 0;memset(data_esp8266.recv_data, 0, sizeof(data_esp8266.recv_data));}}
}

esp.h

#ifndef _ESP8266_H_
#define _ESP8266_H_
#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "string.h"
#include "delay.h"
//GPIO 与 PIN口的宏定义
//使用的是USART3  TX:PB10  RX:PB11
#define GPIO_USART3_TXRX GPIOB
#define PIN_USART3_TX GPIO_Pin_10
#define PIN_USART3_RX GPIO_Pin_11
#define WIFI_ID "11223344"
#define WIFI_PASSWORD "12345678"
#define SER_ADDR "36.137.226.30"typedef struct
{u8 recv_data[256];u16 recv_size;u8 over_flag;
}DATA_ESP8266;extern DATA_ESP8266 data_esp8266;
void ESP8266_CMD_Init(void);
void USART3_Config(u32 baud);
void USART3_SendByte(u8 data);
void USART3_SendStr(u8* data);
void USART1_IRQHandler(void);
void USART3_IRQHandler(void);
uint8_t ESP8266_SetMode(uint8_t *cmd,uint8_t *ack1,uint8_t *ack2,uint8_t count);#endif

esp.c

#include "esp8266.h"//存储 接收数据的数组DATA_ESP8266 data_esp8266 = {0};
void ESP8266_CMD_Init(void)
{char send_cmd[256]={0};printf("AT+RST......\\r\\n");
//	while(!ESP8266_SetMode("AT+RST\\r\\n","OK",NULL,10));ESP8266_SetMode("AT+RST\\r\\n","OK",NULL,10);printf("\\r\\n");Delay_ms(2000);printf("ATE0......\\r\\n");ESP8266_SetMode("AT+CWMODE=1\\r\\n","OK",NULL,10);memset(send_cmd,0,sizeof(send_cmd));printf("正在设置热点连接......\\r\\n");sprintf(send_cmd,"AT+CWJAP=\\"%s\\",\\"%s\\"\\r\\n",WIFI_ID,WIFI_PASSWORD);ESP8266_SetMode(send_cmd,"OK",NULL,500);Delay_ms(2000);memset(send_cmd,0,sizeof(send_cmd));printf("正在设置单链接......\\r\\n");ESP8266_SetMode("AT+CIPMUX=0\\r\\n","OK",NULL,10);Delay_ms(2000);memset(send_cmd,0,sizeof(send_cmd));printf("正在设置服务端连接信息......\\r\\n");sprintf(send_cmd,"AT+CIPSTART=\\"TCP\\",\\"%s\\",%d\\r\\n",SER_ADDR,35270);ESP8266_SetMode(send_cmd,"OK",NULL,50);Delay_ms(2000);memset(send_cmd,0,sizeof(send_cmd));printf("正在设置透传......\\r\\n");ESP8266_SetMode("AT+CIPMODE=1\\r\\n","OK",NULL,10);Delay_ms(2000);memset(send_cmd,0,sizeof(send_cmd));printf("连接服务器准备发送数据......\\r\\n");ESP8266_SetMode("AT+CIPSEND\\r\\n",">",NULL,10);Delay_ms(2000);
}uint8_t ESP8266_SetMode(uint8_t *cmd,uint8_t *ack1,uint8_t *ack2,uint8_t count)
{//1.发送字符串--AT指令集USART3_SendStr(cmd);//接收返回值,判断返回值是否正确if(data_esp8266.over_flag==1){//esp_revbufdata_esp8266.over_flag=0;while(count--)  //count--10{if((strstr((char *)data_esp8266.recv_data,(char *)ack1)!=NULL)||(strstr((char *)data_esp8266.recv_data,(char *)ack2)!=NULL)){Delay_ms(100);//本次AT指令发送成功printf("CMD SEND OK!!\\r\\n");return 1;}}}memset(data_esp8266.recv_data,0,sizeof(data_esp8266.recv_data));return 0;}void USART3_Config(u32 baud)
{GPIO_InitTypeDef GPIO_InitStruct;USART_InitTypeDef USART_InitStruct;NVIC_InitTypeDef NVIC_InitStruct;//时钟配置RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);//模式配置GPIO_StructInit(&GPIO_InitStruct);GPIO_InitStruct.GPIO_Pin = PIN_USART3_TX;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIO_USART3_TXRX,&GPIO_InitStruct);GPIO_InitStruct.GPIO_Pin = PIN_USART3_RX;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIO_USART3_TXRX,&GPIO_InitStruct);//串口初始化USART_StructInit(&USART_InitStruct);USART_InitStruct.USART_BaudRate = baud;USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;USART_InitStruct.USART_Parity = USART_Parity_No;USART_InitStruct.USART_StopBits = USART_StopBits_1;USART_InitStruct.USART_WordLength = USART_WordLength_8b;USART_Init(USART3,&USART_InitStruct);//中断使能USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);USART_ITConfig(USART3,USART_IT_IDLE,ENABLE);//中断配置NVIC_InitStruct.NVIC_IRQChannel = USART3_IRQn;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x01;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x01;NVIC_Init(&NVIC_InitStruct);//串口使能USART_Cmd(USART3,ENABLE);}void USART3_SendByte(u8 data)
{USART3->DR = data;while(!USART_GetFlagStatus(USART3,USART_FLAG_TXE));
}void USART3_SendStr(u8* data)
{while(*data){USART3_SendByte(*data);data++;}while(!USART_GetFlagStatus(USART3,USART_FLAG_TC));
}void USART1_IRQHandler(void)
{u8 data;//接收中断:存储后,通过USART1发送(调试助手)if(USART_GetITStatus(USART1,USART_IT_RXNE)){data = USART1->DR;USART3_SendByte(data);USART_ClearITPendingBit(USART1,USART_IT_RXNE);}
}void USART3_IRQHandler(void)
{//接收中断:存储后,通过USART1发送(调试助手)if(USART_GetITStatus(USART3,USART_IT_RXNE)){data_esp8266.recv_data[data_esp8266.recv_size] = USART3->DR;USART1->DR = data_esp8266.recv_data[data_esp8266.recv_size++];while(!USART_GetFlagStatus(USART1,USART_FLAG_TXE));USART_ClearITPendingBit(USART3,USART_IT_RXNE);}//空闲中断if(USART_GetITStatus(USART3,USART_IT_IDLE)){USART3->SR;USART3->DR;data_esp8266.over_flag = 1;//memset(data_esp8266.recv_data,0,sizeof(data_esp8266.recv_data));data_esp8266.recv_size = 0;//防止影响下一次接收USART_ClearITPendingBit(USART3, USART_IT_IDLE);}
}

结束

代码重在练习!

代码重在练习!

代码重在练习!

今天的分享就到此结束了,希望对你有所帮助,如果你喜欢我的分享,请点赞收藏加关注,谢谢大家!!!

相关文章:

  • MCP Server的五种主流架构:从原理到实践的深度解析
  • Vue Router 钩子函数与组件生命周期执行顺序详解
  • 【赵渝强老师】OceanBase的部署架构
  • GIT命令行的一些常规操作
  • HIS换代升级辅助脚手架:数据清洗、人员信息标准化、电子病历接口标准化、多模态影像接口标准化
  • 佰力博科技与您探讨DEAI介电阻抗分析仪的特点
  • 960g轻薄本,把科技塞进巧克力盒子
  • 数据分析学习笔记——A/B测试
  • PostgreSQL 数据完整性检查工具对比:amcheck 与 pg_checksums
  • : influxdb + grafana+JMeter
  • WHAT - 学习 WebSocket 实时 Web 开发
  • 【Redis】热点key问题,的原因和处理,一致性哈希,删除大key的方法
  • 单点登陆(SSO)简介-笔记
  • 用 Python 和 Rust 构建可微分的分子势能模型:深入解析 MOLPIPx 库
  • Redis 的内存回收机制
  • 资质评审周期压缩方案 实验室引入质检LIMS系统的关键步骤
  • 毫秒级数据采集的极致优化:如何用C#实现高性能、无冗余的实时文件写入?
  • 深度解析 Nginx 配置:从性能优化到 HTTPS 安全实践
  • 板凳-------Mysql cookbook学习 (八)
  • Ubuntu 24.04 LTS 和 ROS 2 Jazzy 环境中使用 Livox MID360 雷达
  • 万户网络学校网站建设/营销型网站的类型有哪些
  • 家装公司名称/vue seo 优化方案
  • 广州珠吉网站建设/海外seo是什么
  • 网站推广的渠道/新手seo入门教程
  • 佛山网站建设公司排名/seo销售好做吗
  • asp网站建设软件/南京百度竞价推广公司排名