STM32CubeMX ESP8266 AT指令实现MQTT协议并接入阿里云IOT平台源码解析【附Github源码地址】

课程大纲

【第一章】:物联网简介(什么是物联网)
【第二章】:物联网十大应用场景
【第三章】:什么是MCU?
【第四章】:MCU的应用范围
【第五章】:咱们要怎么入门MCU开发?
【第六章】:如何使用STM32Cube MX进行STM32的快速开发
【第七章】:ESP8266+MQTT上阿里云物联网平台实践(附源码)git

STM32CubeMX AT指令实现MQTT协议并接入阿里云IOT平台 源码解析
CSDN源代码下载
Github源代码下载github

效果展现

在这里插入图片描述
关于阿里云IOT平台如何配置请参考这篇博客,本篇只讲ESP8266 驱动和实现MQTT协议的核心代码:
ESP8266+MQTT上阿里云物联网平台实践(附源码)web

1.单片机接入阿里云平台步骤解析

1.1 接入阿里云物联网平台步骤

//MQTT初始化函数
void ES8266_MQTT_Init(void)
{
	uint8_t status=0;

	//1.ESP8266初始化
	if(ESP8266_Init())
	{
		user_main_info("ESP8266初始化成功!\r\n");
		status++;
	}
	else Enter_ErrorMode(0);

	//2.链接热点
	if(status==1)
	{
		if(ESP8266_ConnectAP(WIFI_NAME,WIFI_PASSWD))
		{
			user_main_info("ESP8266链接热点成功!\r\n");
			status++;
		}
		else Enter_ErrorMode(1);
	}
	
	//3.链接阿里云IOT服务器
	if(status==2)
	{
		if(ESP8266_ConnectServer("TCP",MQTT_BROKERADDRESS,1883)!=0)
		{
			user_main_info("ESP8266链接阿里云服务器成功!\r\n");
			status++;
		}
		else Enter_ErrorMode(2);
	}
	
	//4.登录MQTT
	if(status==3)
	{
		if(MQTT_Connect(MQTT_CLIENTID, MQTT_USARNAME, MQTT_PASSWD) != 0)
		{
			user_main_info("ESP8266阿里云MQTT登录成功!\r\n");
			status++;
		}
		else Enter_ErrorMode(3);
	}

	//5.订阅主题
	if(status==4)
	{
		if(MQTT_SubscribeTopic(MQTT_SUBSCRIBE_TOPIC,0,1) != 0)
		{
			user_main_info("ESP8266阿里云MQTT订阅主题成功!\r\n");
		}
		else Enter_ErrorMode(4);
	}
}

1.2 阿里云IOT平台订阅消息处理

订阅成功后,平台下发消息会经过串口透传出来,对消息进行处便可数组

//处理MQTT下发的消息
void deal_MQTT_message(uint8_t* buf,uint16_t len)
{
	uint8_t data[512];
	uint16_t data_len = len;
	for(int i=0;i<data_len;i++)
	{
		data[i] = buf[i];
		HAL_UART_Transmit(&huart2,&data[i],1,100);
	}
	memset(usart1_rxbuf,0,sizeof(usart1_rxbuf)); //清空接收缓冲 
	usart1_rxcounter=0;
	//user_main_info("MQTT收到消息,数据长度=%d \n",data_len);
	
	//查找是不是设置红灯
	int i = GetSubStrPos((char*)data,"LEDR");
	if( i>0 )
	{
		uint8_t ledr_status = data[i+6]-'0';
		HAL_GPIO_WritePin(LED_R_GPIO_Port,LED_R_Pin,GPIO_PIN_SET);
		HAL_GPIO_WritePin(LED_G_GPIO_Port,LED_G_Pin,GPIO_PIN_SET);
		HAL_GPIO_WritePin(LED_B_GPIO_Port,LED_B_Pin,GPIO_PIN_SET);
		if(ledr_status)
			HAL_GPIO_WritePin(LED_R_GPIO_Port,LED_R_Pin,GPIO_PIN_RESET);
		else
			HAL_GPIO_WritePin(LED_R_GPIO_Port,LED_R_Pin,GPIO_PIN_SET);
	}
	
	//查找是不是设置绿灯
	i = GetSubStrPos((char*)data,"LEDG");
	if( i>0 )
	{
		uint8_t ledr_status = data[i+6]-'0';
		HAL_GPIO_WritePin(LED_R_GPIO_Port,LED_R_Pin,GPIO_PIN_SET);
		HAL_GPIO_WritePin(LED_G_GPIO_Port,LED_G_Pin,GPIO_PIN_SET);
		HAL_GPIO_WritePin(LED_B_GPIO_Port,LED_B_Pin,GPIO_PIN_SET);
		if(ledr_status)
			HAL_GPIO_WritePin(LED_G_GPIO_Port,LED_G_Pin,GPIO_PIN_RESET);
		else
			HAL_GPIO_WritePin(LED_G_GPIO_Port,LED_G_Pin,GPIO_PIN_SET);
	}
	
	//查找是不是设置蓝灯
	i = GetSubStrPos((char*)data,"LEDB");
	if( i>0 )
	{
		uint8_t ledr_status = data[i+6]-'0';
		HAL_GPIO_WritePin(LED_R_GPIO_Port,LED_R_Pin,GPIO_PIN_SET);
		HAL_GPIO_WritePin(LED_G_GPIO_Port,LED_G_Pin,GPIO_PIN_SET);
		HAL_GPIO_WritePin(LED_B_GPIO_Port,LED_B_Pin,GPIO_PIN_SET);
		if(ledr_status)
			HAL_GPIO_WritePin(LED_B_GPIO_Port,LED_B_Pin,GPIO_PIN_RESET);
		else
			HAL_GPIO_WritePin(LED_B_GPIO_Port,LED_B_Pin,GPIO_PIN_SET);
	}

}

1.3 本地消息发布

调用MQTT发布消息驱动接口便可服务器

//单片机状态上报
void STM32DHT11_StatusReport(void)
{
	//获取温湿度信息
	uint8_t temperature;
	uint8_t humidity;
	uint8_t get_times;
	
	// 获取温湿度信息并用串口打印,获取十次,直到成功跳出
	for(get_times=0;get_times<10;get_times++)
	{
		if(!dht11Read(&temperature, &humidity))//Read DHT11 Value
		{
			user_main_info("temperature=%d,humidity=%d \n",temperature,humidity);
			break;
		}
	}
	
	//上报一次数据
	uint8_t led_r_status = HAL_GPIO_ReadPin(LED_R_GPIO_Port,LED_R_Pin) ? 0:1;
	uint8_t led_g_status = HAL_GPIO_ReadPin(LED_G_GPIO_Port,LED_G_Pin) ? 0:1;
	uint8_t led_b_status = HAL_GPIO_ReadPin(LED_B_GPIO_Port,LED_B_Pin) ? 0:1;
	sprintf(mqtt_message,
	"{\"method\":\"thing.service.property.set\",\"id\":\"181454577\",\"params\":{\ \"DHT11_Temperature\":%.1f,\ \"DHT11_Humidity\":%.1f,\ \"Switch_LEDR\":%d,\ \"Switch_LEDG\":%d,\ \"Switch_LEDB\":%d\ },\"version\":\"1.0.0\"}",
	(float)temperature,
	(float)humidity,
	led_r_status,
	led_g_status,
	led_b_status
	);

	MQTT_PublishData(MQTT_PUBLISH_TOPIC,mqtt_message,0);
}

2.ESP8266 AT指令驱动

2.1 ESP8266_AT.C

#include "esp8266_at.h"

//usart1发送和接收数组
uint8_t usart1_txbuf[256];
uint8_t usart1_rxbuf[512];
uint8_t usart1_rxone[1];
uint8_t usart1_rxcounter;


//串口1发送一个字节
static void USART1_SendOneByte(uint8_t val)
{
	((UART_HandleTypeDef *)&huart1)->Instance->DR = ((uint16_t)val & (uint16_t)0x01FF);
	while((((UART_HandleTypeDef *)&huart1)->Instance->SR&0X40)==0);//等待发送完成
}


//向ESP8266发送定长数据
void ESP8266_ATSendBuf(uint8_t* buf,uint16_t len)
{
	memset(usart1_rxbuf,0, 256);
	
	//每次发送前将接收串口接收总数置0,为了接收
	usart1_rxcounter = 0;	
	
	//定长发送
	HAL_UART_Transmit(&huart1,(uint8_t *)buf,len,0xFFFF);
}

//向ESP8266发送字符串
void ESP8266_ATSendString(char* str)
{
  memset(usart1_rxbuf,0, 256);
	
	//每次发送前将接收串口接收总数置0,为了接收
	usart1_rxcounter = 0;	
	
	//发送方法1
	while(*str)		USART1_SendOneByte(*str++);
	
	//发送法法2
// HAL_UART_Transmit(&huart1,(uint8_t *)str,strlen(str),0xFFFF);
}

//退出透传
void ESP8266_ExitUnvarnishedTrans(void)
{
	ESP8266_ATSendString("+++");HAL_Delay(50);
	ESP8266_ATSendString("+++");HAL_Delay(50);	
}

//查找字符串中是否包含另外一个字符串
uint8_t FindStr(char* dest,char* src,uint16_t retry_nms)
{
	retry_nms/=10;                   //超时时间

	while(strstr(dest,src)==0 && retry_nms--)//等待串口接收完毕或超时退出
	{		
		HAL_Delay(10);
	}

	if(retry_nms) return 1;                       

	return 0; 
}

/** * 功能:检查ESP8266是否正常 * 参数:None * 返回值:ESP8266返回状态 * 非0 ESP8266正常 * 0 ESP8266有问题 */
uint8_t ESP8266_Check(void)
{
	uint8_t check_cnt=5;
	while(check_cnt--)
	{
		memset(usart1_rxbuf,0,sizeof(usart1_rxbuf)); 	 //清空接收缓冲
		ESP8266_ATSendString("AT\r\n");     		 			//发送AT握手指令 
		if(FindStr((char*)usart1_rxbuf,"OK",200) != 0)
		{
			return 1;
		}
	}
	return 0;
}

/** * 功能:初始化ESP8266 * 参数:None * 返回值:初始化结果,非0为初始化成功,0为失败 */
uint8_t ESP8266_Init(void)
{
	//清空发送和接收数组
	memset(usart1_txbuf,0,sizeof(usart1_txbuf));
	memset(usart1_rxbuf,0,sizeof(usart1_rxbuf));
	
	ESP8266_ExitUnvarnishedTrans();		//退出透传
	HAL_Delay(500);
	ESP8266_ATSendString("AT+RST\r\n");
	HAL_Delay(800);
	if(ESP8266_Check()==0)              //使用AT指令检查ESP8266是否存在
	{
		return 0;
	}
	
	memset(usart1_rxbuf,0,sizeof(usart1_rxbuf));    //清空接收缓冲
	ESP8266_ATSendString("ATE0\r\n");     	//关闭回显 
	if(FindStr((char*)usart1_rxbuf,"OK",500)==0)  //设置不成功
	{
			return 0;      
	}
	return 1;                         //设置成功
}

/** * 功能:恢复出厂设置 * 参数:None * 返回值:None * 说明:此时ESP8266中的用户设置将所有丢失回复成出厂状态 */
void ESP8266_Restore(void)
{
	ESP8266_ExitUnvarnishedTrans();          	//退出透传
  HAL_Delay(500);
	ESP8266_ATSendString("AT+RESTORE\r\n");		//恢复出厂 
}

/** * 功能:链接热点 * 参数: * ssid:热点名 * pwd:热点密码 * 返回值: * 链接结果,非0链接成功,0链接失败 * 说明: * 失败的缘由有如下几种(UART通讯和ESP8266正常状况下) * 1. WIFI名和密码不正确 * 2. 路由器链接设备太多,未能给ESP8266分配IP */
uint8_t ESP8266_ConnectAP(char* ssid,char* pswd)
{
	uint8_t cnt=5;
	while(cnt--)
	{
		memset(usart1_rxbuf,0,sizeof(usart1_rxbuf));     
		ESP8266_ATSendString("AT+CWMODE_CUR=1\r\n");              //设置为STATION模式 
		if(FindStr((char*)usart1_rxbuf,"OK",200) != 0)
		{
			break;
		}             		
	}
	if(cnt == 0)
		return 0;

	cnt=2;
	while(cnt--)
	{                    
		memset(usart1_txbuf,0,sizeof(usart1_txbuf));//清空发送缓冲
		memset(usart1_rxbuf,0,sizeof(usart1_rxbuf));//清空接收缓冲
		sprintf((char*)usart1_txbuf,"AT+CWJAP_CUR=\"%s\",\"%s\"\r\n",ssid,pswd);//链接目标AP
		ESP8266_ATSendString((char*)usart1_txbuf);	
		if(FindStr((char*)usart1_rxbuf,"OK",8000)!=0)                      //链接成功且分配到IP
		{
			return 1;
		}
	}
	return 0;
}

//开启透传模式
static uint8_t ESP8266_OpenTransmission(void)
{
	//设置透传模式
	uint8_t cnt=2;
	while(cnt--)
	{
		memset(usart1_rxbuf,0,sizeof(usart1_rxbuf));    
		ESP8266_ATSendString("AT+CIPMODE=1\r\n");  
		if(FindStr((char*)usart1_rxbuf,"OK",200)!=0)
		{	
			return 1;
		}
	}
	return 0;
}

/** * 功能:使用指定协议(TCP/UDP)链接到服务器 * 参数: * mode:协议类型 "TCP","UDP" * ip:目标服务器IP * port:目标是服务器端口号 * 返回值: * 链接结果,非0链接成功,0链接失败 * 说明: * 失败的缘由有如下几种(UART通讯和ESP8266正常状况下) * 1. 远程服务器IP和端口号有误 * 2. 未链接AP * 3. 服务器端禁止添加(通常不会发生) */
uint8_t ESP8266_ConnectServer(char* mode,char* ip,uint16_t port)
{
	uint8_t cnt;
   
	ESP8266_ExitUnvarnishedTrans();                   //屡次链接需退出透传
	HAL_Delay(500);

	//链接服务器
	cnt=2;
	while(cnt--)
	{
		memset(usart1_txbuf,0,sizeof(usart1_txbuf));//清空发送缓冲
		memset(usart1_rxbuf,0,sizeof(usart1_rxbuf));//清空接收缓冲 
		sprintf((char*)usart1_txbuf,"AT+CIPSTART=\"%s\",\"%s\",%d\r\n",mode,ip,port);
		ESP8266_ATSendString((char*)usart1_txbuf);
		if(FindStr((char*)usart1_rxbuf,"CONNECT",8000) !=0 )
		{
			break;
		}
	}
	if(cnt == 0) 
		return 0;
	
	//设置透传模式
	if(ESP8266_OpenTransmission()==0) return 0;
	
	//开启发送状态
	cnt=2;
	while(cnt--)
	{
		memset(usart1_rxbuf,0,sizeof(usart1_rxbuf)); //清空接收缓冲 
		ESP8266_ATSendString("AT+CIPSEND\r\n");//开始处于透传发送状态
		if(FindStr((char*)usart1_rxbuf,">",200)!=0)
		{
			return 1;
		}
	}
	return 0;
}

/** * 功能:主动和服务器断开链接 * 参数:None * 返回值: * 链接结果,非0断开成功,0断开失败 */
uint8_t DisconnectServer(void)
{
	uint8_t cnt;
	
	ESP8266_ExitUnvarnishedTrans();	//退出透传
	HAL_Delay(500);
	
	while(cnt--)
	{
		memset(usart1_rxbuf,0,sizeof(usart1_rxbuf)); //清空接收缓冲 
		ESP8266_ATSendString("AT+CIPCLOSE\r\n");//关闭连接

		if(FindStr((char*)usart1_rxbuf,"CLOSED",200)!=0)//操做成功,和服务器成功断开
		{
			break;
		}
	}
	if(cnt) return 1;
	return 0;
}

2.2 ESP8266_AT.H

#ifndef __ESP8266_AT_H
#define __ESP8266_AT_H

#include "stm32f4xx_hal.h"
#include "usart.h"
#include <string.h>
#include <stdio.h>

extern uint8_t usart1_txbuf[256];
extern uint8_t usart1_rxbuf[512];
extern uint8_t usart1_rxone[1];
extern uint8_t usart1_rxcounter;

extern uint8_t ESP8266_Init(void);
extern void ESP8266_Restore(void);

extern void ESP8266_ATSendBuf(uint8_t* buf,uint16_t len);		//向ESP8266发送指定长度数据
extern void ESP8266_ATSendString(char* str);								//向ESP8266模块发送字符串
extern void ESP8266_ExitUnvarnishedTrans(void);							//ESP8266退出透传模式
extern uint8_t ESP8266_ConnectAP(char* ssid,char* pswd);		//ESP8266链接热点
extern uint8_t ESP8266_ConnectServer(char* mode,char* ip,uint16_t port);	//使用指定协议(TCP/UDP)链接到服务器

#endif

3.MQTT协议实现

3.1 ESP8266_MQTT.C

#include "esp8266_mqtt.h"
#include "esp8266_at.h"

//链接成功服务器回应 20 02 00 00
//客户端主动断开链接 e0 00
const uint8_t parket_connetAck[] = {0x20,0x02,0x00,0x00};
const uint8_t parket_disconnet[] = {0xe0,0x00};
const uint8_t parket_heart[] = {0xc0,0x00};
const uint8_t parket_heart_reply[] = {0xc0,0x00};
const uint8_t parket_subAck[] = {0x90,0x03};

volatile uint16_t MQTT_TxLen;

//MQTT发送数据
void MQTT_SendBuf(uint8_t *buf,uint16_t len)
{
	ESP8266_ATSendBuf(buf,len);
}	

//发送心跳包
void MQTT_SentHeart(void)
{
	MQTT_SendBuf((uint8_t *)parket_heart,sizeof(parket_heart));
}

//MQTT无条件断开
void MQTT_Disconnect()
{
	MQTT_SendBuf((uint8_t *)parket_disconnet,sizeof(parket_disconnet));
}

//MQTT初始化
void MQTT_Init(uint8_t *prx,uint16_t rxlen,uint8_t *ptx,uint16_t txlen)
{
	memset(usart1_txbuf,0,sizeof(usart1_txbuf)); //清空发送缓冲
	memset(usart1_rxbuf,0,sizeof(usart1_rxbuf)); //清空接收缓冲
	
	//无条件先主动断开
	MQTT_Disconnect();HAL_Delay(100);
	MQTT_Disconnect();HAL_Delay(100);
}

//MQTT链接服务器的打包函数
uint8_t MQTT_Connect(char *ClientID,char *Username,char *Password)
{
	int ClientIDLen = strlen(ClientID);
	int UsernameLen = strlen(Username);
	int PasswordLen = strlen(Password);
	int DataLen;
	MQTT_TxLen=0;
	//可变报头+Payload 每一个字段包含两个字节的长度标识
  DataLen = 10 + (ClientIDLen+2) + (UsernameLen+2) + (PasswordLen+2);
	
	//固定报头
	//控制报文类型
  usart1_txbuf[MQTT_TxLen++] = 0x10;		//MQTT Message Type CONNECT
	//剩余长度(不包括固定头部)
	do
	{
		uint8_t encodedByte = DataLen % 128;
		DataLen = DataLen / 128;
		// if there are more data to encode, set the top bit of this byte
		if ( DataLen > 0 )
			encodedByte = encodedByte | 128;
		usart1_txbuf[MQTT_TxLen++] = encodedByte;
	}while ( DataLen > 0 );
    	
	//可变报头
	//协议名
	usart1_txbuf[MQTT_TxLen++] = 0;        		// Protocol Name Length MSB 
	usart1_txbuf[MQTT_TxLen++] = 4;        		// Protocol Name Length LSB 
	usart1_txbuf[MQTT_TxLen++] = 'M';        	// ASCII Code for M 
	usart1_txbuf[MQTT_TxLen++] = 'Q';        	// ASCII Code for Q 
	usart1_txbuf[MQTT_TxLen++] = 'T';        	// ASCII Code for T 
	usart1_txbuf[MQTT_TxLen++] = 'T';        	// ASCII Code for T 
	//协议级别
	usart1_txbuf[MQTT_TxLen++] = 4;        		// MQTT Protocol version = 4 
	//链接标志
	usart1_txbuf[MQTT_TxLen++] = 0xc2;        	// conn flags 
	usart1_txbuf[MQTT_TxLen++] = 0;        		// Keep-alive Time Length MSB 
	usart1_txbuf[MQTT_TxLen++] = 60;        	// Keep-alive Time Length LSB 60S心跳包 

	usart1_txbuf[MQTT_TxLen++] = BYTE1(ClientIDLen);// Client ID length MSB 
	usart1_txbuf[MQTT_TxLen++] = BYTE0(ClientIDLen);// Client ID length LSB 
	memcpy(&usart1_txbuf[MQTT_TxLen],ClientID,ClientIDLen);
	MQTT_TxLen += ClientIDLen;
	
	if(UsernameLen > 0)
	{   
		usart1_txbuf[MQTT_TxLen++] = BYTE1(UsernameLen);		//username length MSB 
		usart1_txbuf[MQTT_TxLen++] = BYTE0(UsernameLen);    	//username length LSB 
		memcpy(&usart1_txbuf[MQTT_TxLen],Username,UsernameLen);
		MQTT_TxLen += UsernameLen;
	}
	
	if(PasswordLen > 0)
	{    
		usart1_txbuf[MQTT_TxLen++] = BYTE1(PasswordLen);		//password length MSB 
		usart1_txbuf[MQTT_TxLen++] = BYTE0(PasswordLen);    	//password length LSB 
		memcpy(&usart1_txbuf[MQTT_TxLen],Password,PasswordLen);
		MQTT_TxLen += PasswordLen; 
	}    
	
	uint8_t cnt=2;
	uint8_t wait;
	while(cnt--)
	{
		memset(usart1_rxbuf,0,sizeof(usart1_rxbuf));
		MQTT_SendBuf(usart1_txbuf,MQTT_TxLen);
		wait=30;//等待3s时间
		while(wait--)
		{
			//CONNECT
			if(usart1_rxbuf[0]==parket_connetAck[0] && usart1_rxbuf[1]==parket_connetAck[1]) //链接成功 
			{
				return 1;//链接成功
			}
			HAL_Delay(100);			
		}
	}
	return 0;
}

//MQTT订阅/取消订阅数据打包函数
//topic 主题 
//qos 消息等级 
//whether 订阅/取消订阅请求包
uint8_t MQTT_SubscribeTopic(char *topic,uint8_t qos,uint8_t whether)
{    
	MQTT_TxLen=0;
	int topiclen = strlen(topic);
	
	int DataLen = 2 + (topiclen+2) + (whether?1:0);//可变报头的长度(2字节)加上有效载荷的长度
	//固定报头
	//控制报文类型
	if(whether) usart1_txbuf[MQTT_TxLen++] = 0x82; //消息类型和标志订阅
	else	usart1_txbuf[MQTT_TxLen++] = 0xA2;    //取消订阅

	//剩余长度
	do
	{
		uint8_t encodedByte = DataLen % 128;
		DataLen = DataLen / 128;
		// if there are more data to encode, set the top bit of this byte
		if ( DataLen > 0 )
			encodedByte = encodedByte | 128;
		usart1_txbuf[MQTT_TxLen++] = encodedByte;
	}while ( DataLen > 0 );	
	
	//可变报头
	usart1_txbuf[MQTT_TxLen++] = 0;				//消息标识符 MSB
	usart1_txbuf[MQTT_TxLen++] = 0x01;           //消息标识符 LSB
	//有效载荷
	usart1_txbuf[MQTT_TxLen++] = BYTE1(topiclen);//主题长度 MSB
	usart1_txbuf[MQTT_TxLen++] = BYTE0(topiclen);//主题长度 LSB 
	memcpy(&usart1_txbuf[MQTT_TxLen],topic,topiclen);
	MQTT_TxLen += topiclen;

	if(whether)
	{
		usart1_txbuf[MQTT_TxLen++] = qos;//QoS级别
	}
	
	uint8_t cnt=2;
	uint8_t wait;
	while(cnt--)
	{
		memset(usart1_rxbuf,0,sizeof(usart1_rxbuf));
		MQTT_SendBuf(usart1_txbuf,MQTT_TxLen);
		wait=30;//等待3s时间
		while(wait--)
		{
			if(usart1_rxbuf[0]==parket_subAck[0] && usart1_rxbuf[1]==parket_subAck[1]) //订阅成功 
			{
				return 1;//订阅成功
			}
			HAL_Delay(100);			
		}
	}
	if(cnt) return 1;	//订阅成功
	return 0;
}

//MQTT发布数据打包函数
//topic 主题 
//message 消息
//qos 消息等级 
uint8_t MQTT_PublishData(char *topic, char *message, uint8_t qos)
{  
	int topicLength = strlen(topic);    
	int messageLength = strlen(message);     
	static uint16_t id=0;
	int DataLen;
	MQTT_TxLen=0;
	//有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度
	//QOS为0时没有标识符
	//数据长度 主题名 报文标识符 有效载荷
	if(qos)	DataLen = (2+topicLength) + 2 + messageLength;       
	else	DataLen = (2+topicLength) + messageLength;   

    //固定报头
	//控制报文类型
	usart1_txbuf[MQTT_TxLen++] = 0x30;    // MQTT Message Type PUBLISH 

	//剩余长度
	do
	{
		uint8_t encodedByte = DataLen % 128;
		DataLen = DataLen / 128;
		// if there are more data to encode, set the top bit of this byte
		if ( DataLen > 0 )
			encodedByte = encodedByte | 128;
		usart1_txbuf[MQTT_TxLen++] = encodedByte;
	}while ( DataLen > 0 );	
	
	usart1_txbuf[MQTT_TxLen++] = BYTE1(topicLength);//主题长度MSB
	usart1_txbuf[MQTT_TxLen++] = BYTE0(topicLength);//主题长度LSB 
	memcpy(&usart1_txbuf[MQTT_TxLen],topic,topicLength);//拷贝主题
	MQTT_TxLen += topicLength;
        
	//报文标识符
	if(qos)
	{
			usart1_txbuf[MQTT_TxLen++] = BYTE1(id);
			usart1_txbuf[MQTT_TxLen++] = BYTE0(id);
			id++;
	}
	memcpy(&usart1_txbuf[MQTT_TxLen],message,messageLength);
  MQTT_TxLen += messageLength;
        
	MQTT_SendBuf(usart1_txbuf,MQTT_TxLen);
  return MQTT_TxLen;
}

3.2 ESP8266_MQTT.H

#ifndef __ES8266_MQTT_H
#define __ES8266_MQTT_H

#include "stm32f4xx_hal.h"

#define BYTE0(dwTemp) (*( char *)(&dwTemp))
#define BYTE1(dwTemp) (*((char *)(&dwTemp) + 1))
#define BYTE2(dwTemp) (*((char *)(&dwTemp) + 2))
#define BYTE3(dwTemp) (*((char *)(&dwTemp) + 3))
	
//MQTT链接服务器
extern uint8_t MQTT_Connect(char *ClientID,char *Username,char *Password);
//MQTT消息订阅
extern uint8_t MQTT_SubscribeTopic(char *topic,uint8_t qos,uint8_t whether);
//MQTT消息发布
extern uint8_t MQTT_PublishData(char *topic, char *message, uint8_t qos);
//MQTT发送心跳包
extern void MQTT_SentHeart(void);

#endif

【STM32 ES8266上阿里云IOT MQTT实践课程大纲】

STM32 ES8266上阿里云IOT MQTT实践【第一章】:物联网简介(什么是物联网)
STM32 ES8266上阿里云IOT MQTT实践【第二章】:物联网十大应用场景
STM32 ES8266上阿里云IOT MQTT实践【第三章】:什么是MCU?
STM32 ES8266上阿里云IOT MQTT实践【第四章】:MCU的应用范围
STM32 ES8266上阿里云IOT MQTT实践【第五章】:咱们要怎么入门MCU开发?
STM32 ES8266上阿里云IOT MQTT实践【第六章】:如何使用STM32Cube MX进行STM32的快速开发
STM32 ES8266上阿里云IOT MQTT实践【第七章】:ESP8266+MQTT上阿里云物联网平台实践(附源码)
STM32CubeMX经过ESP8266 AT指令MQTT上阿里云物联网平台实践源代码svg

【Github源码地址】

https://github.com/SuperBigOnion/ESP8266_MQTT_aliyunIOT函数