【第一章】:物联网简介(什么是物联网)
【第二章】:物联网十大应用场景
【第三章】:什么是MCU?
【第四章】:MCU的应用范围
【第五章】:咱们要怎么入门MCU开发?
【第六章】:如何使用STM32Cube MX进行STM32的快速开发
【第七章】:ESP8266+MQTT上阿里云物联网平台实践(附源码)git
STM32CubeMX AT指令实现MQTT协议并接入阿里云IOT平台 源码解析
CSDN源代码下载
Github源代码下载github
关于阿里云IOT平台如何配置请参考这篇博客,本篇只讲ESP8266 驱动和实现MQTT协议的核心代码:
ESP8266+MQTT上阿里云物联网平台实践(附源码)web
//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); } }
订阅成功后,平台下发消息会经过串口透传出来,对消息进行处便可数组
//处理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); } }
调用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); }
#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; }
#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
#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; }
#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实践【第三章】:什么是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