STM32+ESP8266连接阿里云物联网平台

一、准备

硬件:

PC电脑、无线节点模块、ST-LINK仿真器、MiniUSB线

软件:

阿里云物联网平台、KEIL、串口工具

二、原理

2.1、建立连接的流程

与阿里云物联网平台的方式主要是MQTT协议,首先通过无线模块的AT指令与物联网平台建立TCP连接,随后向物联网平台发送MQTT连接请求报文,在物联网回复连接确认报文后,则成功建立MQTT连接,随后向平台发送订阅用于属性设置的主题的请求报文,成功订阅后进入下一步。需要注意的是,MQTT连接需要定时发送心跳报文,不然会超时断开连接。

2.2、串口

在本次演示中使用了两个串口分别是 USART2 和 USART3 串口,其中串口2用于打印运行信息,串口3用于向阿里云物联网平台发送MQTT协议报文和接收物联网平台的回复报文。

三、代码分析

3.1、首先实现对ESP8266模块的操作,主要利用模块底板上STM32的串口3与ESP8266 串口进行通信,发送AT指令的方式控制Wifi模块,包括Wifi模块配置、Wifi连接热点、Wifi使用指定协议连接到服务器等功能的实现。

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

/**
 * 功能:初始化ESP8266
 * 参数:None
 * 返回值:初始化结果,非0为初始化成功,0为失败
 */
uint8_t ESP8266_Init(void)
{

	
	//清空发送和接收数组
	memset(ESP8266_txbuf,0,sizeof(ESP8266_txbuf));
	memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));
	
	ESP8266_ExitUnvarnishedTrans();		//退出透传
	delay_ms(500);
	ESP8266_ATSendString("AT+RST\r\n");
	delay_ms(800);
	if(ESP8266_Check()==0)              //使用AT指令检查ESP8266是否存在
	{
		return 0;
	}
	
	memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));    //清空接收缓冲
	ESP8266_ATSendString("ATE0\r\n");     	//关闭回显 
	if(FindStr((char*)ESP8266_rxbuf,"OK",500)==0)  //设置不成功
	{
			return 0;      
	}
	return 1;                         //设置成功
}

/**
 * 功能:恢复出厂设置
 * 参数:None
 * 返回值:None
 * 说明:此时ESP8266中的用户设置将全部丢失回复成出厂状态
 */
void ESP8266_Restore(void)
{
	ESP8266_ExitUnvarnishedTrans();          	//退出透传
  delay_ms(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(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));     
		ESP8266_ATSendString("AT+CWMODE_CUR=1\r\n");              //设置为STATION模式	
		if(FindStr((char*)ESP8266_rxbuf,"OK",200) != 0)
		{
			break;
		}             		
	}
	if(cnt == 0)
		return 0;

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

//开启透传模式
static uint8_t ESP8266_OpenTransmission(void)
{
	//设置透传模式
	uint8_t cnt=2;
	while(cnt--)
	{
		memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));    
		ESP8266_ATSendString("AT+CIPMODE=1\r\n");  
		if(FindStr((char*)ESP8266_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();                   //多次连接需退出透传
	delay_ms(500);

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

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

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

3.2、根据MQTT协议工作原理,编写MQTT协议驱动文件,包括MQTT协议的连接服务器的打包、订阅/取消订阅数据打包、发布数据打包等功能。

//连接成功服务器回应 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);
}

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

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

//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);
	
	//固定报头
	//控制报文类型
	ESP8266_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;
		ESP8266_txbuf[MQTT_TxLen++] = encodedByte;
	}while ( DataLen > 0 );
    	
	//可变报头
	//协议名
	ESP8266_txbuf[MQTT_TxLen++] = 0;        		// Protocol Name Length MSB    
	ESP8266_txbuf[MQTT_TxLen++] = 4;        		// Protocol Name Length LSB    
	ESP8266_txbuf[MQTT_TxLen++] = 'M';        	// ASCII Code for M    
	ESP8266_txbuf[MQTT_TxLen++] = 'Q';        	// ASCII Code for Q    
	ESP8266_txbuf[MQTT_TxLen++] = 'T';        	// ASCII Code for T    
	ESP8266_txbuf[MQTT_TxLen++] = 'T';        	// ASCII Code for T    
	//协议级别
	ESP8266_txbuf[MQTT_TxLen++] = 4;        		// MQTT Protocol version = 4    
	//连接标志
	ESP8266_txbuf[MQTT_TxLen++] = 0xc2;        	// conn flags 
	ESP8266_txbuf[MQTT_TxLen++] = 0;        		// Keep-alive Time Length MSB    
	ESP8266_txbuf[MQTT_TxLen++] = 60;        	// Keep-alive Time Length LSB  60S心跳包  

	ESP8266_txbuf[MQTT_TxLen++] = BYTE1(ClientIDLen);// Client ID length MSB    
	ESP8266_txbuf[MQTT_TxLen++] = BYTE0(ClientIDLen);// Client ID length LSB  	
	memcpy(&ESP8266_txbuf[MQTT_TxLen],ClientID,ClientIDLen);
	MQTT_TxLen += ClientIDLen;
	
	if(UsernameLen > 0)
	{   
		ESP8266_txbuf[MQTT_TxLen++] = BYTE1(UsernameLen);		//username length MSB    
		ESP8266_txbuf[MQTT_TxLen++] = BYTE0(UsernameLen);    	//username length LSB    
		memcpy(&ESP8266_txbuf[MQTT_TxLen],Username,UsernameLen);
		MQTT_TxLen += UsernameLen;
	}
	
	if(PasswordLen > 0)
	{    
		ESP8266_txbuf[MQTT_TxLen++] = BYTE1(PasswordLen);		//password length MSB    
		ESP8266_txbuf[MQTT_TxLen++] = BYTE0(PasswordLen);    	//password length LSB  
		memcpy(&ESP8266_txbuf[MQTT_TxLen],Password,PasswordLen);
		MQTT_TxLen += PasswordLen; 
	}    
	
	//uint8_t cnt=2;
	uint8_t wait;
	//while(cnt--)
	{
		memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));
		MQTT_SendBuf(ESP8266_txbuf,MQTT_TxLen);
		wait=30;//等待3s时间
		while(wait--)
		{
			//CONNECT
			if(ESP8266_rxbuf[0]==parket_connetAck[0] && ESP8266_rxbuf[1]==parket_connetAck[1]) //连接成功			   
			{
				return 1;//连接成功
			}
			delay_ms(100);			
		}
	}
	return 0;
}
/*
* MQTT订阅/取消订阅数据打包函数
* topic       主题 
* qos         消息等级 
* whether     订阅/取消订阅请求包
*/
uint8_t MQTT_SubscribeTopic(char *topic,uint8_t qos,uint8_t whether)
{    
	int topiclen;
	int DataLen;
	uint8_t cnt;
	uint8_t wait;
	MQTT_TxLen=0;
	cnt=2;
	topiclen = strlen(topic);
	
	DataLen = 2 + (topiclen+2) + (whether?1:0);//可变报头的长度(2字节)加上有效载荷的长度
	//固定报头
	//控制报文类型
	if(whether) ESP8266_txbuf[MQTT_TxLen++] = 0x82; //消息类型和标志订阅
	else	ESP8266_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;
		ESP8266_txbuf[MQTT_TxLen++] = encodedByte;
	}while ( DataLen > 0 );	
	
	//可变报头
	ESP8266_txbuf[MQTT_TxLen++] = 0;				//消息标识符 MSB
	ESP8266_txbuf[MQTT_TxLen++] = 0x01;           //消息标识符 LSB
	//有效载荷
	ESP8266_txbuf[MQTT_TxLen++] = BYTE1(topiclen);//主题长度 MSB
	ESP8266_txbuf[MQTT_TxLen++] = BYTE0(topiclen);//主题长度 LSB   
	memcpy(&ESP8266_txbuf[MQTT_TxLen],topic,topiclen);
	MQTT_TxLen += topiclen;

	if(whether)
	{
		ESP8266_txbuf[MQTT_TxLen++] = qos;//QoS级别
	}
	
	while(cnt--)
	{
		memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));
		MQTT_SendBuf(ESP8266_txbuf,MQTT_TxLen);
		wait=30;//等待3s时间
		while(wait--)
		{
			if(ESP8266_rxbuf[0]==parket_subAck[0] && ESP8266_rxbuf[1]==parket_subAck[1]) //订阅成功			   
			{
				return 1;//订阅成功
			}
			delay_ms(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;   

    //固定报头
	//控制报文类型
	ESP8266_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;
		ESP8266_txbuf[MQTT_TxLen++] = encodedByte;
	}while ( DataLen > 0 );	
	
	ESP8266_txbuf[MQTT_TxLen++] = BYTE1(topicLength);		//主题长度MSB
	ESP8266_txbuf[MQTT_TxLen++] = BYTE0(topicLength);		//主题长度LSB 
	memcpy(&ESP8266_txbuf[MQTT_TxLen],topic,topicLength);	//拷贝主题
	MQTT_TxLen += topicLength;
        
	//报文标识符
	if(qos)
	{
			ESP8266_txbuf[MQTT_TxLen++] = BYTE1(id);
			ESP8266_txbuf[MQTT_TxLen++] = BYTE0(id);
			id++;
	}
	memcpy(&ESP8266_txbuf[MQTT_TxLen],message,messageLength);
	MQTT_TxLen += messageLength;
        
	MQTT_SendBuf(ESP8266_txbuf,MQTT_TxLen);
	return MQTT_TxLen;
}

3.3、最后在main.c中编写函数调用这两个文件里的函数以实现与阿里云物联网平台的MQTT协议连接。

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

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

	//连接热点
	if(status==1)
	{
		if(ESP8266_ConnectAP(WIFI_NAME,WIFI_PASSWD))
		{
			user_main_info("ESP8266连接热点成功!\r\n");
			status++;
		}
		else Enter_ErrorMode(1);
	}

	//连接阿里云IOT服务器
	if(status==2)
	{
		if(ESP8266_ConnectServer("TCP",MQTT_BROKERADDRESS,1883)!=0)
		{
			user_main_info("ESP8266连接阿里云服务器成功!\r\n");
			status++;
		}
		else Enter_ErrorMode(2);
	}
	
	//登陆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);
	}

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

四、写在最后

为了大家能够更好的理解代码,本文只写出了用于建立连接的关键函数以供大家参考,其余细节就不在此赘述了,祝你们在2024年前途似锦,事业高飞~

你可能感兴趣的:(嵌入式,stm32,阿里云,物联网)