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

news2025/6/3 20:27:06

知识点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_revbuf
		data_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);
	}
}

结束

代码重在练习!

代码重在练习!

代码重在练习!

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2395562.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML5 Canvas 星空战机游戏开发全解析

HTML5 Canvas 星空战机游戏开发全解析 一、游戏介绍 这是一款基于HTML5 Canvas开发的2D射击游戏,具有以下特色功能: 🚀 纯代码绘制的星空动态背景✈️ 三种不同特性的敌人类型🎮 键盘控制的玩家战机📊 完整的分数统…

箱式不确定集

“箱式不确定集(Box Uncertainty Set)”可以被认为是一种 相对简单但实用的不确定集建模方式。 ✅ 一、什么是“简单的不确定集”? 在鲁棒优化领域,“简单不确定集”通常指的是: 特点描述形式直观数学表达简洁&#…

内存管理 : 04段页结合的实际内存管理

一、课程核心主题引入 这一讲,我要给大家讲的是真正的内存管理,也就是段和页结合在一起的内存管理方式。之前提到过,我们先学习了分段管理内存的工作原理,知道操作系统采用分段的方式,让用户程序能以分段的结构进行编…

vue3: baidusubway using typescript

项目结构&#xff1a; <!--npm install -D tailwindcss-3d BaiduSubwayMap.vue npm install -D tailwindcss postcss autoprefixer--> <template><div class"relative w-full h-screen"><!-- 地图容器 --><div id"subway-container…

Redis最佳实践——性能优化技巧之集群与分片

Redis集群与分片在电商应用中的性能优化技巧 一、Redis集群架构模式解析 1. 主流集群方案对比 方案核心原理适用场景电商应用案例主从复制读写分离数据冗余中小规模读多写少商品详情缓存Redis Sentinel自动故障转移监控高可用需求场景订单状态缓存Redis Cluster原生分布式分片…

常见相机的ISP算法

常见的ISP算法 3A算法 去雾算法 图像增强算法 图像宽动态算法 图像的电子缩放算法&#xff0c;无极电子缩放 图像降噪算法 相机常见问题 1.相机启动速度问题&#xff0c;启动速度较慢 2.相机扛不住高低温问题 3.相机散热问题问题 4.相机高低温芯片保护掉电 5.相机的成像效果或者…

2024 CKA模拟系统制作 | Step-By-Step | 8、题目搭建-创建 Ingress

目录 ​​​​​​免费获取题库配套 CKA_v1.31_模拟系统 一、题目 二、核心考点 Ingress 资源定义 Ingress Controller 依赖 服务暴露验证 网络层次关系 三、搭建模拟环境 1.创建命名空间 2.安装ingress ingress-nginx-controller 3.创建hello.yaml并部署 四、总结 …

OldRoll复古胶片相机:穿越时光,定格经典

在数字摄影盛行的今天&#xff0c;复古胶片相机的独特魅力依然吸引着无数摄影爱好者。OldRoll复古胶片相机这款软件&#xff0c;以其独特的复古风格和丰富的胶片滤镜效果&#xff0c;让用户仿佛穿越回了那个胶片摄影的黄金时代。它不仅模拟了胶片相机的操作界面&#xff0c;还提…

通俗易懂的 JS DOM 操作指南:从创建到挂载

目录 &#x1f9e9; 1. 创建元素&#xff1a;document.createElement / createElementNS &#x1f4dd; 2. 创建文本&#xff1a;document.createTextNode ✏️ 3. 修改文本&#xff1a;node.nodeValue &#x1f5d1;️ 4. 移除元素&#xff1a;el.removeChild() &#x1…

CSS Day07

1.搭建项目目录 2.网页头部SEO三大标签 3.Favicon图标与版心 &#xff08;1&#xff09;Favicon图标 &#xff08;2&#xff09;版心 4.快捷导航 5.头部-布局 6.头部-logo 7.头部-导航 8.头部-搜索 9头部-购物车 10.底部-布局 11.底部-服务区域 12.底部-帮助中心 13.底部-版权…

RV1126-OPENCV 交叉编译

一.下载opencv-3.4.16.zip到自己想装的目录下 二.解压并且打开 opencv 目录 先用 unzip opencv-3.4.16.zip 来解压 opencv 的压缩包&#xff0c;并且进入 opencv 目录(cd opencv-3.4.16) 三. 修改 opencv 的 cmake 脚本的内容 先 cd platforms/linux 然后修改 arm-gnueabi.to…

【深度学习】 19. 生成模型:Diffusion Models

Diffusion Models Diffusion Models 简介 Diffusion 模型是一类通过逐步添加噪声并再逆向还原的方式进行图像生成的深度生成模型。其基本流程包括&#xff1a; 前向过程&#xff08;Forward Process&#xff09;&#xff1a;将真实图像逐步加噪&#xff0c;最终变为高斯噪声…

JMeter 直连数据库

1.直连数据库的使用场景 1.1 参数化&#xff0c;例如登录使用的账户名密码都可以从数据库中取得 1.2 断言&#xff0c;查看实际结果和数据库中的预期结果是否一致 1.3 清理垃圾数据&#xff0c;例如插入一个用户&#xff0c;它的ID不能相同&#xff0c;在测试插入功能后将数据删…

易路 iBuilder:解构企业 AI 落地困境,重构智能体时代生产力范式

一、从大模型到智能体的产业跃迁 2024 年堪称中国人工智能产业的 "战略拐点" 之年。当 DeepSeek R1 模型以 "技术 价格" 双重普惠模式掀起行业震荡时&#xff0c;各企业纷纷意识到&#xff0c;大模型的真正价值不在于技术炫技&#xff0c;而在于成为企业…

计算机网络之路由表更新

1.解题思路 对新接收到的路由表进行更新&#xff0c;全部"距离"1&#xff0c;且"下一跳路由器"都写成发送方路由器的名称。 开始对比新表和原来的路由表 1.看目的网络 如果是新的目的网络&#xff0c;则直接把对应的各项信息填入表中&#xff1b;如果是相同…

万兴PDF手机版

万兴PDF手机版(万兴PDF编辑器)是一款国产PDF编辑工具.万兴PDF安卓版提供PDF文档编辑,AI撰写摘要,文档签名,设置密码保护等功能,万兴PDF专家APP以简约风格及文档编辑功能为核心,支持多设备终端同步保存.全免 万兴 PDF 编辑器是一款功能强大的 PDF 编辑软件&#xff0c;它支持多种…

Qt -使用OpenCV得到SDF

博客主页&#xff1a;【夜泉_ly】 本文专栏&#xff1a;【暂无】 欢迎点赞&#x1f44d;收藏⭐关注❤️ 目录 cv::MatdistanceTransform获得SDF 本文的目标&#xff0c; 是简单学习并使用OpenCV的相关函数&#xff0c; 并获得QImage的SDF(Signed Distance Field 有向距离场) 至…

DDR5 ECC详细原理介绍与基于协议讲解

本文篇幅较长,涉及背景原理介绍方便大家理解其运作方式 以及 基于DDR5协议具体展开介绍。 背景原理介绍 上图参考:DDR 内存中的 ECC 写入操作时,On-die ECC的工作过程如下: SoC将需要写入到Memory中的数据发送给控制器控制器将需要写入的数据直接发送给DRAM芯片在DDR5 DR…

EC800X QuecDuino开发板介绍

支持的模组列表 EG800KEC800MEC800GEC800E 功能列表 基本概述 EC800X QuecDuino EVB 搭载移远 EC800 系列模组。支持模组型号为&#xff1a; EC800M 系列、EC800K 系列、EG800K 系列、EC800E 系列等。 渲染图 开发板的主要组件、接口布局见下图 资料下载 EC800X-QuecDui…

PHP轻量级聊天室源码(源码下载)

最新版本&#xff1a;v2.1.2 (2024.08更新) 运行环境&#xff1a;PHP5.6&#xff08;无需MySQL&#xff09; 核心特性&#xff1a;手机电脑自适应、TXT数据存储、50条历史消息 适用场景&#xff1a;小型社区/企业内网/教育培训即时通讯 一、核心功能亮点&#xff08;SEO关键词布…