MQTT+ESP8266实现STM32数据上传

目录

一、环境介绍

二、I2C配置

1.I2C总线协议

2.硬件I2C和模拟I2C

 三、传感器配置

1.温度传感器AHT20

2.光照传感器

3.传感器部分测试

 四、ESP8266配置

 五、MQTT

1.MQTT协议介绍

 2.MQTT主题订阅

2.1.CONNECT——连接报文

2.2.CONNACK——确认连接请求

2.3.SUBSCRIBE——订阅报文

2.4.SUBACK——订阅确认

2.5.UNSUBSCRIBE——取消订阅

2.6.UNSUBACK——取消订阅确认

 3.发布消息

3.1.PUBLISH——发布消息报文

3.2.PUBACK——发布确认

4.PINGREQ——心跳请求


一、环境介绍

项目完整代码:ESP8266+MQTT上传数据: ESP8266通过MQTT协议将温湿度数据传输至服务器https://gitee.com/zmfjoker/esp8266_mqtt

硬件部分:

        单片机:用的STM32F103C8T6

        温度传感器:AHT21

        光敏传感器:GY-302

        WIFI模块:ESP-01S

        画了个简单的电路图用来测试

MQTT+ESP8266实现STM32数据上传_第1张图片

MQTT+ESP8266实现STM32数据上传_第2张图片

二、I2C配置

1.I2C总线协议

温度和光照传感器都是通过I2C驱动的

MQTT+ESP8266实现STM32数据上传_第3张图片

I2C总线协议图

        I2C协议规定,总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。总线在空闲状态 时,SCL和SDA都保持着高电平,当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件;当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件。在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备 将释放总线,总线再次处于空闲状态。

2.硬件I2C和模拟I2C

        I2C有两种模式,直接使用硬件外设,还有就是通过GPIO模拟出I2C波形来控制。

硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。

下面是使用硬件I2C传送给的序列图,具体说明可以去看STM32中文参考手册

MQTT+ESP8266实现STM32数据上传_第4张图片

 主发送器传送序列图

MQTT+ESP8266实现STM32数据上传_第5张图片

 主接收器传送序列图

本次测试采用的是硬件I2C的方式来驱动。

myiic.c

#include "myiic.h"
#include "stm32f10x.h"



/*********************************************
	函数名:I2C1外设初始化                     *
	参  数:无                                *
	返回值:无                                *
**********************************************/
void I2C1_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	I2C_InitTypeDef  I2C_InitStructure;
	
	//开需要的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);       
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
	
	//设置IO口
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;	//选择端口号                      
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; 		//OD开漏输出      
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//设置IO接口速度(2/10/50MHz)    
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	//设置I2C参数
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;				//设置为I2C模式
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;		//I2C占空比
	I2C_InitStructure.I2C_OwnAddress1 = STM32_I2C_OWN_ADDR;	//主机地址(从机不得用此地址)
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;				//允许应答
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; 	//7位地址模式
	I2C_InitStructure.I2C_ClockSpeed = 100000;				//总线速度设置100kbps
	I2C_Init(I2C1,&I2C_InitStructure);
	I2C_Cmd(I2C1,ENABLE);									//开启I2C					
}


/**********************************************
	函数名:I2C1发送一连串数据                  *
	参  数:SlaveAddr从机地址  				  *
	       WriteAddr寄存器地址                *
	       pdata 数据指针                     *
	       t_len pdata的数据长度              *
	返回值:无                                 *
***********************************************/
void I2C1_SAND_BUFFER(u8 SlaveAddr,u8 WriteAddr, u8* pdata, u16 t_len)
{
	//发送起始信号与地址(写)
	I2C_GenerateSTART(I2C1,ENABLE);									//产生起始信号
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));		//EV5,等待主机模式起始位发送完
	I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Transmitter);	//发送器件地址,主机为写
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));	//EV6,等待主机发送模式地址发送完成	
	I2C_SendData(I2C1,WriteAddr);											//内部功能地址或命令
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));				//EV8,等待主机模式数据发送完成	
	//循环发送数据
	while(t_len--)
	{
		I2C_SendData(I2C1,*pdata);	//发送数据
		pdata++; //数据指针移位
		while (!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));		//EV8,等待主机模式数据发送完成
	}
	I2C_GenerateSTOP(I2C1,ENABLE);	//产生停止信号
}


/***********************************************
	函数名:I2C1读取一连串数据                   *
	参  数:SlaveAddr从机地址                   *
	        readAddrCmd 读取的地址或命令         *
            pdata 存放读取数据的指针             *
	        NumByteToRead 需要读取的数据的数量   *
	返回值:无                                  *
************************************************/
void I2C1_READ_BUFFER(u8 SlaveAddr, u8 readAddrCmd, u8* pdata, u16 NumByteToRead)
{
	//发送起始信号、地址(写)、从机寄存器地址或命令
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));	//等待总线空闲
	I2C_GenerateSTART(I2C1,ENABLE);			//产生起始信号
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));		//EV5,等待主机模式起始位发送完
	I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter); //发送器件地址,主机为写
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));	//EV6,等待主机发送模式地址发送完成
//	I2C_Cmd(I2C1,ENABLE);		//????
	I2C_SendData(I2C1,readAddrCmd);	//发送读的从机寄存器地址或命令
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));				//EV8,等待主机模式数据发送完成
	
	//发送起始信号、地址(读)
	I2C_GenerateSTART(I2C1,ENABLE); //开启信号
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));		//EV5,等待主机模式起始位发送完
	I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Receiver);		//发送器件地址,主机为读
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); 		//EV6,等待主机接收模式地址发送完成
	
	//有数据没接收完
	while(NumByteToRead)
	{
		if(NumByteToRead == 1)		//只剩下最后一个数据时进入 if 语句
		{
			I2C_AcknowledgeConfig(I2C1,DISABLE);	//最后有一个数据时关闭应答位
			I2C_GenerateSTOP(I2C1,ENABLE);			//最后一个数据时发送一个停止位
		}
		if(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED))	//EV7,主机接收到数据
		{
			*pdata = I2C_ReceiveData(I2C1);//调用库函数将数据取出到 pdata
			pdata++; //指针移位
			NumByteToRead--; //字节数减 1 
		}
	}
	I2C_AcknowledgeConfig(I2C1,ENABLE);		//接收完后重新开启应答位
}

        采用硬件IIC配置GPIO是固定的,需要设置为开漏输出,如果不理解配置过程可以去看看野火的I2C实验讲解的很详细,产生的EV事件可以对照STM32固件库来配置

myiic.h

#ifndef __MYIIC_H
#define __MYIIC_H
#include "sys.h"
#include "stm32f10x.h"


#define STM32_I2C_OWN_ADDR 			0X5F    			//单片机地址,自定义,但是不能和传感器地址一样

void I2C1_Init(void); //初始化IIC
void I2C1_SAND_BUFFER(u8 SlaveAddr,u8 WriteAddr, u8* pdata, u16 t_len); //发送一串数据
void I2C1_READ_BUFFER(u8 SlaveAddr, u8 readAddrCmd, u8* pdata, u16 NumByteToRead);//读取一串数据

#endif

 三、传感器配置

1.温度传感器AHT20

下面两张图片是AHT20产品规格书上截取的,讲述了AHT20读写的过程

MQTT+ESP8266实现STM32数据上传_第6张图片

 MQTT+ESP8266实现STM32数据上传_第7张图片

 aht20.c

#include "aht20.h"
#include "delay.h"

/************************************
									*
	功能:读取AHT20状态字 			*
									*
*************************************/

u8 AHT20_ReadStatus(void)
{
	u8 tmp[1];
	u8 a;
	I2C1_READ_BUFFER(ATH20_SLAVE_ADDRESS, AHT20_STATUS, tmp, 1);
	a = tmp[0];
	return a;
}


/*******************************************
   读AHT20 设备状态字 中的Bit3: 校准使能位 *
										   *
   校准使能位:1 - 已校准; 0 - 未校准	   *
********************************************/
uint8_t AHT20_ReadStatus_bit3(void)
{
	uint8_t tmp;
	tmp = AHT20_ReadStatus();
	return (tmp>>3)&0x01;
}



/************************
    芯片初始化命令		*
						*
						*
*************************/
void AHT20_IcInitCmd(void)
{
	uint8_t tmp[2];
	tmp[0] = 0x08;
	tmp[1] = 0x00;
	I2C1_SAND_BUFFER(ATH20_SLAVE_ADDRESS, 0xbe, tmp, 2);
}
/************************
    AHT20 软复位命令	*
						*
*************************/
void AHT20_SoftResetCmd(void)
{
	uint8_t tmp[1];
	I2C1_SAND_BUFFER(ATH20_SLAVE_ADDRESS,0xba, tmp, 0);
}




/************************************
									*
	功能:读取AHT20状态字bit7忙标志 *
	状态:1  设备忙;0   设备空闲   *
									*
*************************************/
u8 AHT20_ReadStatus_bit7(void)
{
	u8 tmp[1];
	
	I2C1_READ_BUFFER(ATH20_SLAVE_ADDRESS, AHT20_STATUS, tmp, 1);
	
	return (tmp[0]>>7)&0x01;
}

/**************************
						  *
	功能:发送AC触发命令  *
						  *
***************************/

void AHT20_AC_CMD(void)
{
	u8 tmp[2];
	tmp[0] = 0x33;
	tmp[1] = 0x00;
	
	I2C1_SAND_BUFFER(ATH20_SLAVE_ADDRESS,0xac, tmp, 2);
}


/***********************************
								   *
	功能:重新初始化寄存器		   *
	入口参数:addr	寄存器地址	   *
								   *
************************************/
void JH_Reset_REG(uint8_t addr)
{
	uint8_t Byte_first,Byte_second,Byte_third;
	u8 tmp1[2];
	u8 tmp2[3];
	u8 tmp3[2];
	tmp1[0] = 0x00;
	tmp1[1] = 0x00;
	
	I2C1_SAND_BUFFER(ATH20_SLAVE_ADDRESS,addr, tmp1, 3);
	
	delay_ms(5);
	
	I2C1_READ_BUFFER(ATH20_SLAVE_ADDRESS, addr, tmp2, 3);
	Byte_first = tmp2[0];
	Byte_second = tmp2[1];
	Byte_third = tmp2[2];
	
	delay_ms(10);
	
	
	tmp3[0] = Byte_second;
	tmp3[1] = Byte_third;
	I2C1_SAND_BUFFER(ATH20_SLAVE_ADDRESS,0xb0|addr, tmp3, 2);
	
	Byte_second=0x00;
	Byte_third =0x00;
	
}

void AHT20_Start_Init(void)
{
	JH_Reset_REG(0x1b);
	JH_Reset_REG(0x1c);
	JH_Reset_REG(0x1e);
}



/***********************************
								   *
	功能:没有CRC校验直接读取温度  *
								   *
************************************/


void AHT20_READ_CTdata(u32 *ct)
{
	volatile uint8_t  Byte_1th=0;
	volatile uint8_t  Byte_2th=0;
	volatile uint8_t  Byte_3th=0;
	volatile uint8_t  Byte_4th=0;
	volatile uint8_t  Byte_5th=0;
	volatile uint8_t  Byte_6th=0;
	uint32_t RetuData = 0;
	uint16_t cnt ;
	u8 tmp[6];
	
	AHT20_AC_CMD();      			//发送AC指令
	
	delay_ms(80);
	
	cnt = 5;
	//空闲状态等待
	while(AHT20_ReadStatus_bit7())
	{
		delay_ms(75);
		if(cnt--)
		{
		 break;
		 }
	}
	//读取六个字节
	I2C1_READ_BUFFER(ATH20_SLAVE_ADDRESS, AHT20_STATUS, tmp, 6);
	
	// 计算湿度
	RetuData = 0;   //第一个字节是状态字
	RetuData = (RetuData|tmp[1]) << 8;
	RetuData = (RetuData|tmp[2]) << 8;
	RetuData = (RetuData|tmp[3]);
	RetuData = RetuData >> 4;
	ct[0] = RetuData;
		
	// 计算温度
	RetuData = 0;
	RetuData = (RetuData|tmp[3]) << 8;
	RetuData = (RetuData|tmp[4]) << 8;
	RetuData = (RetuData|tmp[5]);
	RetuData = RetuData&0xfffff;
	ct[1] = RetuData;
}

这段代码是根据AHT20官方参考例程改的,官方的代码用的模拟I2C方式,这里改成了硬件方式

aht20.h

#ifndef __AHT20_H
#define __AHT20_H
#include "sys.h"
#include "myiic.h"
#include "delay.h"

#define ATH20_SLAVE_ADDRESS		0x70	//I2C从机地址
 
//****************************************
// 定义 AHT20 内部地址
//****************************************
#define	AHT20_STATUS			0x00	//状态字 寄存器地址
#define	AHT20_INIT_REG			0xBE	//初始化 寄存器地址
#define	AHT20_SoftReset			0xBA	//软复位 单指令
#define	AHT20_TrigMeasure_REG	0xAC	//触发测量 寄存器地址

u8 AHT20_ReadStatus(void);				//读取状态字
uint8_t AHT20_ReadStatus_bit3(void);	//读取状态字bit[3],返回1 或 0
void AHT20_IcInitCmd(void);				//芯片初始化0xbe
void AHT20_SoftResetCmd(void);			//软复位0xba
u8 AHT20_ReadStatus_bit7(void);			//读取状态子bit[7], 1-设备忙  0-设备空闲
void AHT20_AC_CMD(void);				//发送0xac  触发测量

void JH_Reset_REG(uint8_t addr); 		//初始化寄存器
void AHT20_Start_Init(void);			//初始化指令

void AHT20_READ_CTdata(u32 *ct);		//读取温度

#endif

2.光照传感器

  light.c

#include "light.h"
#include "stm32f10x.h"
#include "myiic.h"
#include "delay.h"


/**************************
						  *
	功能:启动函数	      *
						  *
***************************/
void Single_Write_BH1750(u8 add)
{
	u8 tmp[1];
	
	I2C1_SAND_BUFFER(SlaveAddress,add, tmp, 0);
	

}

/***********************************
								   *
	功能:读取2个数据    		   *
	参数:*data_light存储数据的数组* 
								   *
************************************/
void Multiple_read_BH1750(u8 *data_light)
{
	u8 tmp[2];
	u8 i;
	I2C1_READ_BUFFER(SlaveAddress,0x00, tmp, 2);
	for(i = 0;i<2;i++)
	{
		data_light[i] = tmp[i];
	}
	
	
}

/**************************
						  *
	功能:获取光照强度    *
						  *
***************************/
void read_light(u32* light)
{
	u8 buf[2];
	int dis_data;                       
	u32 temp1;
	
	Single_Write_BH1750(0x01);//发送上电命令
	Single_Write_BH1750(0x10);//发送高分辨率连续测量命令
	delay_ms(180);			  //根据手册要求这里要延迟180ms
	Multiple_read_BH1750(buf);//读出两个数据
	dis_data=buf[0];
	dis_data=(dis_data<<8)+buf[1]; //2个字节合成数据 
	temp1=dis_data/1.2;			   //计算光照度
	*light = temp1;

}

        这里只是简单写了下读取的代码,产品数据手册上还有一些测量分辨率的代码没写上,后期测试会加上去,这里获取光强度代码可以不用入口参数,直接给一个return返回值也是一样的。

light.h

#ifndef __LIGHT_H
#define __LIGHT_H
#include "stm32f10x.h"


#define	  SlaveAddress   0x46     //设备地址
void Single_Write_BH1750(u8 aa);
void Multiple_read_BH1750(u8 *data_light);
void conversion(u8 temp_data);
void read_light(u32 *light);    //获取光照强度

#endif

3.传感器部分测试

两个传感器代码大致部分已经写完,还有一些数据处理还没写

下面是传感器做测试的主函数代码

main.c

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "myiic.h"
#include "aht20.h"
#include "light.h"



int main()
{
	u32 light_num;
	uint32_t CT_data[2];
	
	volatile int  c1,t1;
	delay_init();      		//延时功能初始化
	LED_Init();				//LED初始化
	usart_Init(115200);			//串口1初始化,连电脑串口调试,PA9-TX,PA10-RX
	I2C1_Init();				//I2C1外设初始化,速度为100kbps
	GPIO_ResetBits(GPIOB,GPIO_Pin_0);
	while(1)
	{
	GPIO_ResetBits(GPIOB,GPIO_Pin_0);			//点亮LED
	delay_ms(500);
	GPIO_SetBits(GPIOB,GPIO_Pin_0);			//LED	
	read_light(&light_num);					//读取光照强度			
	AHT20_READ_CTdata(CT_data);				//读取温湿度
	c1 = CT_data[0]*100*10/1024/1024;  		//计算得到湿度值c1(放大了10倍)
	t1 = CT_data[1]*200*10/1024/1024-500;	//计算得到温度值t1(放大了10倍)
	printf("当前光照:%d\r\n", light_num);
	printf("当前湿度:%d%%\r\n",c1/10);
    printf("当前温度:%d.%d\r\n",t1/10,t1%10);
	}
}

调试串口输出的数据

MQTT+ESP8266实现STM32数据上传_第8张图片

 四、ESP8266配置

wifi.c

#include "wifi.h"
#include "stm32f10x.h"
#include "usart.h"
#include "usart2.h"
#include "delay.h"
#include "string.h"

char wifi_mode = 0;     //联网模式 0:SSID和密码写在程序里   
/************************************
									*
	功能:初始化wifi复位IO		    *
									*									
*************************************/

void WiFi_ResetIO_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;                    //定义一个设置IO端口参数的结构体
	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA , ENABLE); //使能PA端口时钟
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;               //准备设置PA4
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;       //速率50Mhz
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   	    //推挽输出方式
	GPIO_Init(GPIOA, &GPIO_InitStructure);            	    //设置PA4
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_SET);              //清除数据端口位

}

/************************************
									*
	功能:wifi发送指令			    *
	参数:cmd 要发的指令			*
		  waittime 等待时间			*
	返回值:0 正确 ;1 错误			*
									*									
*************************************/

char WiFi_SendCmd(char *cmd, int waittime)
{
	USART2_RX_STA=0;     							//接收状态标记清零
	memset(USART2_RX_BUF,0,USART2_RXBUFF_SIZE);		//清空串口2接收缓冲区
	usart2_printf("%s\r\n", cmd);					//发送指令
	
	while(waittime--)								//等待倒计时
	{
		delay_ms(10);
		if(strstr(USART2_RX_BUF,"OK"))				//找是否接收到 OK 表示指令发送成功
		{
			break;
		}
	}
	if(waittime == 0)								//超时返回1
	{
		return 1;
	}
	else
		return 0;									//未超时返回0
}



/************************************
									*
	功能:wifi复位			   	    *
	参数:waittime 等待时间			*
	返回值:0 正确;1 错误			*
									*									
*************************************/
char WiFi_Reset(int waittime)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_RESET);         //复位IO
	delay_ms(500);                                  	 //延时500ms
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_SET);           //复位IO拉高电平	
	while(waittime--)									 //等待超时时间到0 
	{                              		  
		delay_ms(100);                                 	 //延时100ms
		if(strstr(USART2_RX_BUF, "ready"))               //如果接收到ready表示复位成功
			break;       						   		 //主动跳出while循环		                     	  
	}
	printf("\r\n");                              	  	 //串口1输出信息
	if(waittime == 0)									 //超时返回1
	{
		return 1;
	}
	else
		return 0;										 //未超时返回0
}



/************************************
									*
	功能:wifi连网			   	    *
	参数:waittime 等待时间			*
	返回值:0 正确;1 错误			*
									*									
*************************************/

char WiFi_JoinAP(int waittime)
{
	USART2_RX_STA=0;     							//接收状态标记清零
	memset(USART2_RX_BUF,0,USART2_RXBUFF_SIZE);		//清空串口2接收缓冲区
	usart2_printf("AT+CWJAP=%s%s\r\n",WIFI,PASSWORLD);//发送指令
	
	while(waittime--)								//等待倒计时
	{
		delay_ms(1000);
		if(strstr(USART2_RX_BUF,"OK"))				//找是否接收到 OK 表示指令发送成功
		{
			break;
		}
	}
	printf("\r\n%s\r\n", USART2_RX_BUF);
	printf("\r\n");                             	       //串口1输出信息
	if(waittime == 0)								//超时返回1
	{
		return 1;
	}
	else
		return 0;									//未超时返回0
}


/************************************
									*
	功能:连接TCP服务器,透传模式	*
	参数:waittime 等待时间			*
	返回值:0 正确;其他 错误		*
									*									
*************************************/
char WiFi_Connect_Server(int waittime)
{	
	USART2_RX_STA=0;                              	//WiFi接收数据量变量清零                        
	memset(USART2_RX_BUF,0,USART2_RXBUFF_SIZE);         //清空WiFi接收缓冲区   
	usart2_printf("AT+CIPSTART=\"TCP\",\"%s\",%d\r\n", "192.168.0.127", 1883);//发送连接服务器指令
	while(waittime--)								//等待超时与否
	{                           
		delay_ms(100);                             	//延时100ms	
		if(strstr(USART2_RX_BUF, "CONNECT"))          //如果接受到CONNECT表示连接成功
			break;                                  //跳出while循环
		if(strstr(USART2_RX_BUF, "CLOSED"))           //如果接受到CLOSED表示服务器未开启
			return 1;                               //服务器未开启返回1
		if(strstr(USART2_RX_BUF, "ALREADY CONNECTED"))//如果接受到ALREADY CONNECTED已经建立连接
			return 2;                               //已经建立连接返回2
		printf("%d ", waittime);                   //串口1输出现在的超时时间  
	}
	printf("\r\n");                              //串口1输出信息
	if(waittime <= 0)return 3;                       //超时错误,返回3
	else                                            //连接成功,准备进入透传
	{
		printf("连接服务器成功,准备进入透传\r\n"); //串口1显示信息
		USART2_RX_STA = 0;                          //WiFi接收数据量变量清零                        
		memset(USART2_RX_BUF, 0, USART2_RXBUFF_SIZE);    //清空WiFi接收缓冲区     
		usart2_printf("AT+CIPSEND\r\n");               //发送进入透传指令
		while(waittime--)							 //等待超时与否
		{                            
			delay_ms(100);                            //延时100ms	
			if(strstr(USART2_RX_BUF, "\r\nOK\r\n\r\n>"))//如果成立表示进入透传成功
				break;                          	 //跳出while循环
			printf("%d ", waittime);                //串口1输出现在的超时时间  
		}
		if(waittime <= 0)return 4;                      //透传超时错误,返回4	
	}
	return 0;	                                     //成功返回0	
}
/*-------------------------------------------------*/
/*函数名:WiFi_Smartconfig                         */
/*参  数:timeout:超时时间(1s的倍数)            */
/*返回值:0:正确   其他:错误                     */
/*-------------------------------------------------*/
char WiFi_Smartconfig(int timeout)
{
	
	USART2_RX_STA=0;                           		//WiFi接收数据量变量清零                        
	memset(USART2_RX_BUF,0,USART2_RXBUFF_SIZE);     		//清空WiFi接收缓冲区     
	while(timeout--)									//等待超时时间到0
	{                           		
		delay_ms(1000);                         				//延时1s
		if(strstr(USART2_RX_BUF, "connected"))    	 		  //如果串口接受到connected表示成功
			break;                                  		//跳出while循环  
		printf("%d ", timeout);                 		//串口输出现在的超时时间  
	}	
	printf("\r\n");                          		//串口输出信息
	if(timeout <= 0)return 1;                     		//超时错误,返回1
	return 0;                                   		//正确返回0
}
/*-------------------------------------------------*/
/*函数名:等待加入路由器                           */
/*参  数:timeout:超时时间(1s的倍数)            */
/*返回值:0:正确   其他:错误                     */
/*-------------------------------------------------*/
char WiFi_WaitAP(int timeout)
{		
	while(timeout--){                               //等待超时时间到0
		delay_ms(1000);                             		//延时1s
		if(strstr(USART2_RX_BUF, "WIFI GOT IP"))         //如果接收到WIFI GOT IP表示成功
			break;       						   								  //主动跳出while循环
		printf("%d ", timeout);                     //串口输出现在的超时时间
	}
	printf("\r\n");                              //串口输出信息
	if(timeout <= 0)return 1;                         //如果timeout<=0,说明超时时间到了,也没能收到WIFI GOT IP,返回1
	return 0;                                       //正确,返回0
}

/***********************************
								   *
	函数名:WiFi连接服务器         *
	参  数:无                     *
	返回值:0:正确   其他:错误   *
								   *
************************************/
char WiFi_Connect_IoTServer(void)
{	
	printf("准备复位模块\r\n");                   //串口提示数据
	if(WiFi_Reset(50))								//复位,100ms超时单位,总计5s超时时间
	{                             
		printf("复位失败,准备重启\r\n");	      //返回非0值,进入if,串口提示数据
		return 1;                                   //返回1
	}else printf("复位成功\r\n");                 //串口提示数据
	
	printf("准备设置STA模式\r\n");                //串口提示数据
	if(WiFi_SendCmd("AT+CWMODE=1",50))//设置STA模式,100ms超时单位,总计5s超时时间
	{             
		printf("设置STA模式失败,准备重启\r\n");   //返回非0值,进入if,串口提示数据
		return 2;                                   //返回2
	}else printf("设置STA模式成功\r\n");          //串口提示数据
	
	if(wifi_mode==0) //如果联网模式=0:SSID和密码写在程序里 
	{                              
		printf("准备取消自动连接\r\n");            //串口提示数据
		if(WiFi_SendCmd("AT+CWAUTOCONN=0",50))		 //取消自动连接,100ms超时单位,总计5s超时时间
		{       
			printf("取消自动连接失败,准备重启\r\n"); //返回非0值,进入if,串口提示数据
			return 3;                                  //返回3
		}else printf("取消自动连接成功\r\n");        //串口提示数据
				
		printf("准备连接路由器\r\n");                //串口提示数据	
		if(WiFi_JoinAP(30))//连接路由器,1s超时单位,总计30s超时时间
		{                          
			printf("连接路由器失败,准备重启\r\n");  //返回非0值,进入if,串口提示数据
			return 4;                                 //返回4	
		}else printf("连接路由器成功\r\n");         //串口提示数据			
	}
	
	printf("准备设置透传\r\n");                    //串口提示数据
	if(WiFi_SendCmd("AT+CIPMODE=1",50)) 			 //设置透传,100ms超时单位,总计5s超时时间
	{           
		printf("设置透传失败,准备重启\r\n");       //返回非0值,进入if,串口提示数据
		return 8;                                    //返回8
	}else printf("设置透传成功\r\n");              //串口提示数据
	
	printf("准备关闭多路连接\r\n");                //串口提示数据
	if(WiFi_SendCmd("AT+CIPMUX=0",50)) 				 //关闭多路连接,100ms超时单位,总计5s超时时间
	{            
		printf("关闭多路连接失败,准备重启\r\n");   //返回非0值,进入if,串口提示数据
		return 9;                                    //返回9
	}else printf("关闭多路连接成功\r\n");          //串口提示数据
	 
	printf("准备连接服务器\r\n");                  //串口提示数据
	if(WiFi_Connect_Server(100))      				 //连接服务器,100ms超时单位,总计10s超时时间
	{            
		printf("连接服务器失败,准备重启\r\n");     //返回非0值,进入if,串口提示数据
		return 10;                                   //返回10
	}else printf("连接服务器成功\r\n");            //串口提示数据	
	return 0;                                        //正确返回0
}

 五、MQTT

1.MQTT协议介绍

        MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。

协议详情: MQTT Version 3.1.1(英文版)

                    (中文版)

B站上对MQTT协议讲的比较好的视频

迈向物联网第一步——MQTT理论知识详解_哔哩哔哩_bilibili

 2.MQTT主题订阅

2.1.CONNECT——连接报文

         固定报头 + 可变报头 + 有效载荷

固定包头 必须存在,用于描述报文信息,里面有指出什么类型的报文,报文等级
可变报头 不一定存在。主要是要看什么样子类型的报文
有效载荷 内容。信息存放的地方

固定报头

MQTT+ESP8266实现STM32数据上传_第9张图片

byte1转换成16进制 :0x10

byte2是剩余长度,指的是报文后面字符总数 = 可变报头字符数 + 有效载荷字符数

可变报头

MQTT+ESP8266实现STM32数据上传_第10张图片

协议名由六个字节组成,转出相应16进制:00 04 4D 51 54 54

协议级别:04

 MQTT+ESP8266实现STM32数据上传_第11张图片

 连接标志:

        这里比较复杂,暂且设置为11000010

        转换成16进制:C2

MQTT+ESP8266实现STM32数据上传_第12张图片

保持连接有两个字节,例如时间间隔为100s,表示为一个16为的字,转换成16进制为 00 64

可变报头由协议名、协议级别、连接标志、保持连接组成

将他们连起来:00 04 4D 51 54 54 05 C2 00 64

共10个字节,一般来说是固定的

有效载荷

客户端标识符(Client Identifier) + 用户名(User Name) + 密码(Password)

客户端ID:*|securemode=3,signmethod-hmacsha1|

用户名:*&#

密码:clientId*deviceName*productKey#

*:设备名称

#:ProductKey
 

 举例:

        客户端ID:XP001|securemode=3,signmethod-hmacsha1|

        转换成16进制 : 61 64 6D 69 6E 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E                                     6D 65 74 68 6F 64 2D 68 6D 61 63 73 68 61 31 7C 

        一共39个字节(0x27)

                               00 27  61 64 6D 69 6E 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67                                6E 6D 65 74 68 6F 64 2D 68 6D 61 63 73 68 61 31 7C 

        这里采用的UTF-8编码字符串,每一个字符串都有一个两字节的长度字段作为前缀,所有长度需要用  00 27 两个字节来表示

        用户名:XP001&alfSNCGR0G5

         转换成16进制 : 58 50 30 30 31 26 61 31 66 53 4E 43 47 52 30 47 35
         共17个字节(0x11)

                                    00 11 58 50 30 30 31 26 61 31 66 53 4E 43 47 52 30 47 35

        密码:2f7497cd33b0a9fceeOfeeeba1d2f1635d31ca3e

                转换成16进制:32 66 37 34 39 37 63 64 33 33 62 30 61 39 66 63 65 65 4F 66 65 65                                         65 62 61 31 64 32 66 31 36 33 35 64 33 31 63 61 33 65 

                共40个字节(0x28)

                                        00 28 32 66 37 34 39 37 63 64 33 33 62 30 61 39 66 63 65 65 4F 66 65                                         65 65 62 61 31 64 32 66 31 36 33 35 64 33 31 63 61 33 65 

       将三个整合起来

                                       00 27  61 64 6D 69 6E 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C                                        73 69 67 6E 6D 65 74 68 6F 64 2D 68 6D 61 63 73 68 61 31 7C  00                                              11 58 50 30 30 31 26 61 31 66 53 4E 43 47 52 30 47 35 00 28 32 66                                              37 34 39 37 63 64 33 33 62 30 61 39 66 63 65 65 4F 66 65  65 65 62                                            61 31 64 32 66 31 36 33 35 64 33 31 63 61 33 65 

CONNECT报文 = 固定报头 + 可变报头 + 有效载荷

        10 70 00 04 4D 51 54 54 05 C2 00 64 00 27  61 64 6D 69 6E 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C   73 69 67 6E 6D 65 74 68 6F 64 2D 68 6D 61 63 73 68 61 31 7C  00  11 58 50 30 30 31 26 61 31 66 53 4E 43 47 52 30 47 35 00 28 32 66 37 34 39 37 63 64 33 33 62 30 61 39 66 63 65 65 4F 66 65  65 65 62   61 31 64 32 66 31 36 33 35 64 33 31 63 61 33 65 

可变报头 + 有效载荷 字符数为112 转换成16进制为  0x70,所以固定报头的剩余长度为 70,但在实际使用时候剩余长度比较长就需要通过计算

剩余长度计算:

MQTT+ESP8266实现STM32数据上传_第13张图片

 (1)剩余长度字段使用一个变长度编码方案,对小于128的值它使用单字节
编码。
 (2)低7位有效位用于编码数据,最高有效位用于指示是否有更多的字节。剩余长度字段最大4 个字节。

这两句话的意思是剩余长度的第七位是不做计数的,是一个提示位,所以最大长度为 01111111 转换成十进制为127,即当长度小于128时可以使用单字节编码,第七位为0表示只有一个字节,第七位为1表示后面还有字节来表示剩余长度

当剩余长度超过127时

例如剩余长度为300,那么一个字节是不够用的就需要两个字节来表示,在协议中低字节在前,高字节在后,在高8位中数值1实际表示128,高8位中数组a实际表示128*a

300 = 128*a + b (其中a,b的值小于128)

可以得出  b=44   a =2   低字节在前,高字节在后

低8位:44——00101100      后面还有字节所以第7位为1       10101100

高8位:2———00000010    后面没有字节所以第7位为0        00000010

转换成16进制    AC 02 表示剩余长度

假设剩余长度为30000

根据上面表格可以知道应该用3个字节表示

30000 = 128*128*a + 128*b + c (a,b,c都小于128)

a = 1  b = 28   c = 32    低字节在前,高字节在后

32——00100000      后面有字节   10100000

28——00011100        后面有字节  10011100

1——00000001           后面没字节   00000001

转成16进制  A0 9C 01

2.2.CONNACK——确认连接请求

MQTT+ESP8266实现STM32数据上传_第14张图片

向服务器发送CONNECT报文后,会返回相应码,返回00说明连接成功

 CONNECT代码

/*********************************
    函数名:连接服务器报文         *
    参  数:无                    *
    返回值:无                    *
**********************************/
void MQTT_ConectPack(void)
{	
	int temp,Remaining_len;
	
	Fixed_len = 1;                                                        //连接报文中,固定报头长度暂时先=1
	Variable_len = 10;                                                    //连接报文中,可变报头长度=10
	Payload_len = 2 + ClientID_len + 2 + Username_len + 2 + Passward_len; //连接报文中,负载长度      
	Remaining_len = Variable_len + Payload_len;                           //剩余长度=可变报头长度+负载长度
	
	temp_buff[0]=0x10;                         //固定报头第1个字节 :固定0x01		
	do{                                        //循环处理固定报头中的剩余长度字节,字节量根据剩余字节的真实长度变化
		temp = Remaining_len%128;              //剩余长度取余128
		Remaining_len = Remaining_len/128;     //剩余长度取整128
		if(Remaining_len>0)               	
			temp |= 0x80;                      //按协议要求位7置位          
		temp_buff[Fixed_len] = temp;           //剩余长度字节记录一个数据
		Fixed_len++;	                       //固定报头总长度+1    
	}while(Remaining_len > 0);                 //如果Remaining_len>0的话,再次进入循环
	
	temp_buff[Fixed_len + 0] = 0x00;     //可变报头第1个字节 :固定0x00	            
	temp_buff[Fixed_len + 1] = 0x04;     //可变报头第2个字节 :固定0x04
	temp_buff[Fixed_len + 2] = 0x4D;	 //可变报头第3个字节 :固定0x4D
	temp_buff[Fixed_len + 3] = 0x51;	 //可变报头第4个字节 :固定0x51
	temp_buff[Fixed_len + 4] = 0x54;	 //可变报头第5个字节 :固定0x54
	temp_buff[Fixed_len + 5] = 0x54;     //可变报头第6个字节 :固定0x54
	temp_buff[Fixed_len + 6] = 0x04;	 //可变报头第7个字节 :固定0x04
	temp_buff[Fixed_len + 7] = 0xC2;	 //可变报头第8个字节 :使能用户名和密码校验,不使用遗嘱,不保留会话
	temp_buff[Fixed_len + 8] = 0x00; 	 //可变报头第9个字节 :保活时间高字节 0x00
	temp_buff[Fixed_len + 9] = 0x64;	 //可变报头第10个字节:保活时间高字节 0x64   100s
	
	/*     CLIENT_ID      */
	temp_buff[Fixed_len+10] = ClientID_len/256;                			  	//客户端ID长度高字节
	temp_buff[Fixed_len+11] = ClientID_len%256;               			  	//客户端ID长度低字节
	memcpy(&temp_buff[Fixed_len+12],ClientID,ClientID_len);                 //复制过来客户端ID字串	
	/*     用户名        */
	temp_buff[Fixed_len+12+ClientID_len] = Username_len/256; 				//用户名长度高字节
	temp_buff[Fixed_len+13+ClientID_len] = Username_len%256; 				//用户名长度低字节
	memcpy(&temp_buff[Fixed_len+14+ClientID_len],Username,Username_len);    //复制过来用户名字串	
	/*      密码        */
	temp_buff[Fixed_len+14+ClientID_len+Username_len] = Passward_len/256;	//密码长度高字节
	temp_buff[Fixed_len+15+ClientID_len+Username_len] = Passward_len%256;	//密码长度低字节
	memcpy(&temp_buff[Fixed_len+16+ClientID_len+Username_len],Passward,Passward_len); //复制过来密码字串

	TxDataBuf_Deal(temp_buff, Fixed_len + Variable_len + Payload_len);      //加入发送数据缓冲区
}

2.3.SUBSCRIBE——订阅报文

        客户端向服务端订阅,服务端发送PUBLISH报文给客户端,SUBSCRIBE报文指定了最大的QoS等级

固定报头

MQTT+ESP8266实现STM32数据上传_第15张图片

byte1:82 

byte2:剩余长度

可变报头

MQTT+ESP8266实现STM32数据上传_第16张图片

byte1:00

byte2:0A

有效载荷

        主题过滤器(主题) +   服务质量要求(QoS等级)

 举例:

 MQTT+ESP8266实现STM32数据上传_第17张图片

转换成16进制: 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31 2F 74 68                            69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74

共49个字节(0x31)

                00 31 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31 2F 74 68                    69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74

QoS等级,这里为 00

                 00 31 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31 2F 74 68                    69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 00

固定报头 + 可变报头 + 有效载荷

                82 36 00 0A 00 31 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30                 31 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65                 74 00

2.4.SUBACK——订阅确认

 固定报头

   MQTT+ESP8266实现STM32数据上传_第18张图片

byte1:90

byte2:剩余长度

可变报头

        可变报头包含等待确认的SUBSCRIBE报文的报文标识符,也就是和订阅报文时的可变报头一致

这里为 00 0A

有效载荷

        有效载荷包含一个返回码清单

        MQTT+ESP8266实现STM32数据上传_第19张图片

         允许返回码值:

        0x00        最大QoS

        0x01        成功-最大QoS1

        0x02        成功-最大QoS2

        0x80        Failure 失败

将三个连起来:90 03 00 0A 01

SUBSCRIBE代码

/*----------------------------------------------------------*/
/*函数名:SUBSCRIBE订阅topic报文                            */
/*参  数:QoS:订阅等级                                     */
/*参  数:topic_name:订阅topic报文名称                     */
/*返回值:无                                                */
/*----------------------------------------------------------*/
void MQTT_Subscribe(char *topic_name, int QoS)
{	
	Fixed_len = 2;                              		   //SUBSCRIBE报文中,固定报头长度=2
	Variable_len = 2;                          			   //SUBSCRIBE报文中,可变报头长度=2	
	Payload_len = 2 + strlen(topic_name) + 1;   		   //计算有效负荷长度 = 2字节(topic_name长度)+ topic_name字符串的长度 + 1字节服务等级
	
	temp_buff[0] = 0x82;                                   //第1个字节 :固定0x82                      
	temp_buff[1] = Variable_len + Payload_len;             //第2个字节 :可变报头+有效负荷的长度	
	temp_buff[2] = 0x00;                                   //第3个字节 :报文标识符高字节,固定使用0x00
	temp_buff[3] = 0x01;		                           //第4个字节 :报文标识符低字节,固定使用0x01
	temp_buff[4] = strlen(topic_name)/256;                 //第5个字节 :topic_name长度高字节
	temp_buff[5] = strlen(topic_name)%256;		           //第6个字节 :topic_name长度低字节
	memcpy(&temp_buff[6], topic_name, strlen(topic_name)); //第7个字节开始 :复制过来topic_name字串		
	temp_buff[6 + strlen(topic_name)] = QoS;               //最后1个字节:订阅等级
	
	TxDataBuf_Deal(temp_buff, Fixed_len + Variable_len + Payload_len);  //加入发送数据缓冲区
}

2.5.UNSUBSCRIBE——取消订阅

固定报头 + 可变报头 + 有效载荷

固定报头

MQTT+ESP8266实现STM32数据上传_第20张图片

byte1:A2

byte2:剩余长度

可变报头 

MQTT+ESP8266实现STM32数据上传_第21张图片

 包含一个报文标识符,与订阅主题报文的可以报头一致

00 0A

有效载荷

由主题过滤器,没有服务质量要求,与订阅主题报文一致,这里不重复说明

00 31 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74

将它们连起来

A2 35 00 0A 00 31 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74

2.6.UNSUBACK——取消订阅确认

固定报头 + 可变报头 + 有效载荷

固定报头

MQTT+ESP8266实现STM32数据上传_第22张图片

byte1:B0

byte2:剩余长度

可变报头

00 0A

有效载荷

连起来:B0 02 00 0A

 3.发布消息

3.1.PUBLISH——发布消息报文

        控制报文从客户端向服务端或者服务端向客户端传输一个应用消息

        这里QoS设置 00

组成:

        固定报头 + 可变报头 + 有效载荷 + 响应 + 动作

固定报头

MQTT+ESP8266实现STM32数据上传_第23张图片

 byte 1 的4-7位是确认的,0-3我们设为0

byte1:30

byte2:剩余长度

可变报头

由主题名(Topic Name)和报文标识符(Packet Identifier)组成

报文标识符只有当QoS等级为1或2时才有效,我们这里为0

举例:

        主题名:/sys/a1fSNCGROG5/XPO01/thing/event/property/post

        转换成16进制:2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31 2F 74                                 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74

        共48字节(0x30):

                               00 30 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31                                2F 74  68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F                                73 74

有效载荷

有效载荷包含将被发布的应用消息,数据的内容和格式是应用特定

特定的格式:Json

举例:

7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 65 76 65 6E 74 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 22 69 64 22 3A 22 30 30 30 30 30 30 30 3122 2C 22 70 61 72 61 6D 73 22 3A 7B 22 50 6F 77 65 72 53 77 69 74 63 68 22 3A 30 2C 22 43 75 72 72 65 6E 74 48 75 6D 69 64 69 74 79 22 3A 33 30 2C 43 75 72 72 65 6E 74 54 65 6D 70 65 72 61 74 75 72 65 3A 31 31 2E 31 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
 

这里需要注意,有效载荷前面不需要计算长度

响应

MQTT+ESP8266实现STM32数据上传_第24张图片

QoS等级为0无响应

动作

 固定报头 + 可变报头 + 有效载荷

00 30 2F 73 79 73 2F 61 31 66 53 4E 43 47 52 30 47 35 2F 58 50 30 30 31 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 65 76 65 6E 74 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 22 69 64 22 3A 22 30 30 30 30 30 30 30 3122 2C 22 70 61 72 61 6D 73 22 3A 7B 22 50 6F 77 65 72 53 77 69 74 63 68 22 3A 30 2C 22 43 75 72 72 65 6E 74 48 75 6D 69 64 69 74 79 22 3A 33 30 2C 43 75 72 72 65 6E 74 54 65 6D 70 65 72 61 74 75 72 65 3A 31 31 2E 31 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D

PUBLISH

/*----------------------------------------------------------*/
/*函数名:等级0 发布消息报文                                  */
/*参  数:topic_name:topic名称                              */
/*参  数:data:数据                                         */ 
/*参  数:data_len:数据长度                                 */
/*返回值:无                                                 */
/*----------------------------------------------------------*/
void MQTT_PublishQs0(char *topic, char *data, int data_len)
{	
	int temp,Remaining_len;
	
	Fixed_len = 1;                              //固定报头长度暂时先等于:1字节
	Variable_len = 2 + strlen(topic);           //可变报头长度:2字节(topic长度)+ topic字符串的长度
	Payload_len = data_len;                     //有效负荷长度:就是data_len
	Remaining_len = Variable_len + Payload_len; //剩余长度=可变报头长度+负载长度
	
	temp_buff[0] = 0x30;                      	//固定报头第1个字节 :固定0x30   	
	do{                                         //循环处理固定报头中的剩余长度字节,字节量根据剩余字节的真实长度变化
		temp = Remaining_len%128;           	//剩余长度取余128
		Remaining_len = Remaining_len/128;      //剩余长度取整128
		if(Remaining_len>0)               	
			temp |= 0x80;                    	//按协议要求位7置位          
		temp_buff[Fixed_len] = temp;            //剩余长度字节记录一个数据
		Fixed_len++;	                     	//固定报头总长度+1    
	}while(Remaining_len>0);                    //如果Remaining_len>0的话,再次进入循环
		             
	temp_buff[Fixed_len+0] = strlen(topic)/256;                       //可变报头第1个字节     :topic长度高字节
	temp_buff[Fixed_len+1] = strlen(topic)%256;		                  //可变报头第2个字节     :topic长度低字节
	memcpy(&temp_buff[Fixed_len+2], topic,strlen(topic));             //可变报头第3个字节开始 :拷贝topic字符串	
	memcpy(&temp_buff[Fixed_len + 2 + strlen(topic)], data, data_len);//有效负荷:拷贝data数据
	
	TxDataBuf_Deal(temp_buff, Fixed_len + Variable_len + Payload_len);//加入发送数据缓冲区	
}

3.2.PUBACK——发布确认

QoS等级0无响应

4.PINGREQ——心跳请求

客户端发送PINGREQ报文给服务端的。用于:

  1. 在没有任何其它控制报文从客户端发给服务的时,告知服务端客户端还活着。
  2. 请求服务端发送 响应确认它还活着。
  3. 使用网络以确认网络连接没有断开。

固定报头

MQTT+ESP8266实现STM32数据上传_第25张图片

0xC0  0x00

/*----------------------------------------------------------*/
/*函数名:PING报文,心跳包                                   */
/*参  数:无                                                */
/*返回值:无                                                */
/*----------------------------------------------------------*/
void MQTT_PingREQ(void)
{
	temp_buff[0] = 0xC0;              //第1个字节 :固定0xC0                      
	temp_buff[1] = 0x00;              //第2个字节 :固定0x00 

	TxDataBuf_Deal(temp_buff, 2);     //加入数据到缓冲区
}

 PINGRESP——心跳响应

服务端发送PINGRESP报文响应客户端的PINGREQ报文。表示服务端还活着。

固定报头

MQTT+ESP8266实现STM32数据上传_第26张图片

 0xD0 0x00

无可变报头,无有效载荷

你可能感兴趣的:(stm32,物联网,mqtt,嵌入式硬件)