智慧农业、智能家居类设计通用实现方法
在进行嵌入式设计学习时,针对物联网类设计内容,常需要使用物联网开放平台,物联网开放平台为嵌入式物联网设计提供了关键的基础支撑,它通过标准化协议与接口,将分散的嵌入式设备高效连接、统一管理,并处理海量数据。可借助其成熟的云服务、安全机制与数据分析工具,快速构建应用,从而专注于设备本身的功能创新,大幅降低开发复杂度与时间成本,加速产品落地与生态整合。
学习阶段选用的物联网开放平台,大致有以下几点需求:
- 优先选择免费试用,降低学习成本。
- 功能齐全,能够接驳多种附加功能或者云服务。
- 通信稳定。
一、平台特色对比
常见的物联网开放平台实现方式有如下几种。
1.云服务器+EMQX自主构建。

2.阿里云物联网平台。

3.腾讯云物联网通信。

4.华为云设备接入。

5.中国移动OneNET

对比:
- 云服务器+EMQX自主构建的方式灵活程度较高,但所使用的技术栈内容较多,比较适合有一定基础的开发者使用。
- 阿里云物联网接入便捷,能够实现基本的物联网通信,并且接入可以接入大量的阿里云云服务,例如存储服务、数据分析、云服务器等。但阿里云的物联网服务已经停止新增用户了,目前只支持现有用户的维护,无法用于新用户的学习使用,考虑使用阿里云物联网的可以选择其他平台。
- 腾讯云物联网同样支持基础的物联网通信与多种云服务,但是其免费单元较少,仅支持5-10个设备使用,其支持使用腾讯连连APP实现快速的上位机开发,这是其一大特点。
- 华为云是十分成熟的云服务提供商,华为云设备接入服务,可以实现基础的物联网通信,并直接接驳华为云中大量的云服务功能,实现便捷的物联网消息存储、流转等。并支持通过邮箱以及短信等多种方式实现平台告警推送。其唯一的不足是平台使用的鉴权流程较为复杂,对于初学者而言,需要花费较多时间去完成基础代码的开发。
- 中国移动OneNET是使用最为便利的物联网开放平台,支持基础物联网通信,可以通过邮箱发送告警信息,但是中国移动云的云服务较少,如果需要拓展其他功能,如转发、存储物联网消息,则需要接驳其他平台的云服务,例如华为云、阿里云存储。
综合:
以上五种物联网平台的解决方案在我的学开发设计中都有使用过,对于各个平台的使用体验总结如下。
1.对于初学者而言,基础入门使用体验最佳的是中国移动OneNET平台,注册、产品/设备创建等流程都十分简单,能够快速入门,通过中国移动OneNET平台,可以为设计接入云平台,实现远程数据显示,指令下发、告警推送等基础功能。
2.想要推展更多功能与服务的,可以选择华为云IOTDA服务,其可用的免费单元数量达到1000之多。可以直接对接华为云中的大量云服务,只需要通过控制台设置流转即可,不需要再自行配置云服务以及进行linux操作和服务部署等多中复杂流程,可以在平时的学习设计中快速实现所需功能。
二、基础代码
华为云、腾讯云、阿里云已经封装好了基于STM32的基础代码文件,以及微信小程序的基础模板代码,通过这两个工程代码,可以构建所有物联网类设计,例如智能家居、智慧农业、仓储物流等设计。
该工程实现了基础的设备消息发布、云平台下发指令解析等基础功能。通过修改代码中的鉴权信息,并在云平台新建实例,即可实现STM32单片机与云平台的通信。
代码所使用的是WIFI的联网模式,使用ESP-01S模块。支持AirKiss模式配网,更为灵活。
ESP-01S模块代码如下:
/* - 功能:ESP8266硬件驱动函数。 - 硬件:使用了串口二 - 修改:WiFi账号/密码 - 使用: #include "ESP8266.h" Uart2_Init(115200); // 包括了配网引脚初始化 Esp_Waiting_Wifi(); // 连接历史WIFI操作 //Esp_Connect_Wifi(); // 连接写入的 SSID //Esp_AirKiss_Setting(); // 配网模式 - 注意:要避开已经被使用的配网引脚,防止卡死。 此文件中配置了联网函数以及AirKiss配网方式,需要手动配置按键并指向AirKiss函数,实现配网。 */ #include <string.h> #include <stdio.h> #include "stm32f10x.h" #include "delay.h" #include "usart.h" #include "ESP8266.h" #include "HWIOTDA.h" // 此处修改固定连接的WiFi账号密码 #define WiFi_SSID "xiaomi" #define WiFi_PSD "00000000" // 固定连接WIFI的AT指令 #define ESP8266_WIFI_INFO "AT+CWJAP=\"" WiFi_SSID "\",\"" WiFi_PSD "\"\r\n" #define ESP8266_AirKiss "AT+CWSTARTSMART\r\n" // 全局内存 unsigned char esp8266_buf[esp8266_buf_size]; //创建接收缓冲区 unsigned short esp8266_cnt = 0, esp8266_cntPre = 0; // 接收计数于结尾检测 // 等待默认WIFI连接 void Esp_Waiting_Wifi(void){ printf("\r\n"); printf("1/5: 进入等待连接模式\r\n"); printf("2/5: AT 确认连接\r\n"); while(ESP8266_SendCmd("AT\r\n", "OK")) delay_ms(500); printf("3/5: AT+RST 重启模块\r\n"); while(ESP8266_SendCmd("AT+RST\r\n", "")) delay_ms(500); // 不需要校验返回指令 printf("4/5: 等待Wi-Fi连接\r\n"); // ESP上电后大约2s连接到WIFI。 while(ESP8266_SendCmd("", "GOT IP")) delay_ms(500); printf("5/5: 默认Wi-Fi已经连接\r\n"); } // 固定联网 void Esp_Connect_Wifi(void){ printf("\r\n"); printf("1/6: 开始固定联网,AT确认连接\r\n"); while(ESP8266_SendCmd("AT\r\n", "OK")) delay_ms(500); printf("2/6: AT+RST 重启\r\n"); while(ESP8266_SendCmd("AT+RST\r\n", "OK")) delay_ms(500); printf("3/6: AT+CWMODE=1 设置为客户端\r\n"); while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK")) delay_ms(500); printf("4/6: AT+CWQAP 关闭当前连接\r\n"); while(ESP8266_SendCmd("AT+CWQAP\r\n", "OK")) delay_ms(500); printf("5/6: AT+CWJAP 连接到WIFI\r\n"); while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "WIFI GOT IP")) delay_ms(500); printf("6/6: 固定联网结束\r\n"); } // AirKiss 网络配置方式 void Esp_AirKiss_Setting(void){ printf("\r\n"); printf("1/5: 开始AirKIss配网,AT确认连接\r\n"); while(ESP8266_SendCmd("AT\r\n", "OK")) delay_ms(500); printf("2/5: AT+RST 重启\r\n"); while(ESP8266_SendCmd("AT+RST\r\n", "OK")) delay_ms(500); printf("3/5: AT+CWMODE=1 设置为客户端\r\n"); while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK")) delay_ms(500); printf("4/5: AT+CWSTARTSMART 进入AirKiss配网模式\r\n"); while(ESP8266_SendCmd(ESP8266_AirKiss, "OK")) delay_ms(500); while(ESP8266_SendCmd("", "smartconfig connected wifi")) delay_ms(500); // 不发送指令,检索配网成功标志。 printf("5/5: 配网成功\r\n"); } // 串口初始化 void Uart2_Init(unsigned int baud){ GPIO_InitTypeDef gpio_initstruct; USART_InitTypeDef usart_initstruct; NVIC_InitTypeDef nvic_initstruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //PA2 TXD gpio_initstruct.GPIO_Mode = GPIO_Mode_AF_PP; gpio_initstruct.GPIO_Pin = GPIO_Pin_2; gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio_initstruct); //PA3 RXD gpio_initstruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; gpio_initstruct.GPIO_Pin = GPIO_Pin_3; gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio_initstruct); usart_initstruct.USART_BaudRate = baud; usart_initstruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件流控 usart_initstruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //接收和发送 usart_initstruct.USART_Parity = USART_Parity_No; //无校验 usart_initstruct.USART_StopBits = USART_StopBits_1; //1位停止位 usart_initstruct.USART_WordLength = USART_WordLength_8b; //8位数据位 USART_Init(USART2, &usart_initstruct); USART_Cmd(USART2, ENABLE); //使能串口 USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); //使能接收中断 nvic_initstruct.NVIC_IRQChannel = USART2_IRQn; nvic_initstruct.NVIC_IRQChannelCmd = ENABLE; nvic_initstruct.NVIC_IRQChannelPreemptionPriority = 0; nvic_initstruct.NVIC_IRQChannelSubPriority = 0; NVIC_Init(&nvic_initstruct); } // 串口二 发送函数 void Usart_SendString(USART_TypeDef *USARTx, unsigned char *str, unsigned short len){ unsigned short count = 0; for(; count < len; count++){ USART_SendData(USARTx, *str++); //发送数据 while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET); //等待发送完成 } } // 串口二 接收中断 void USART2_IRQHandler(void){ if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET){ if(esp8266_cnt >= sizeof(esp8266_buf)) esp8266_cnt = 0; //防止串口被刷爆 esp8266_buf[esp8266_cnt++] = USART2->DR; USART_ClearFlag(USART2, USART_FLAG_RXNE); } } // ESP 清空缓存 void ESP8266_Clear(void){ memset(esp8266_buf, 0, sizeof(esp8266_buf)); esp8266_cnt = 0; } // ESP 等待接收完成 _Bool ESP8266_WaitRecive(void){ if(esp8266_cnt == 0) //如果接收计数为0 则说明没有处于接收数据中,所以直接跳出,结束函数 return REV_WAIT; if(esp8266_cnt == esp8266_cntPre){ //如果上一次的值和这次相同,则说明接收完毕 esp8266_cnt = 0; //清0接收计数 return REV_OK; //返回接收完成标志 } esp8266_cntPre = esp8266_cnt; //置为相同 return REV_WAIT; //返回接收未完成标志 } // ESP发送命令 (返回:0-成功 1-失败) _Bool ESP8266_SendCmd(char *cmd, char *res){ unsigned char timeOut = 200; //设置时间 Usart_SendString(USART2, (unsigned char *)cmd, strlen((const char *)cmd)); while(timeOut--){ if(ESP8266_WaitRecive() == REV_OK){ //如果收到数据 if(strstr((const char *)esp8266_buf, res) != NULL){ //如果检索到关键词 ESP8266_Clear(); //清空缓存 return 0; } } delay_ms(10); //10ms检测一次 } return 1; } // Esp发送数据 void ESP8266_SendData(unsigned char *data, unsigned short len){ char cmdBuf[32]; ESP8266_Clear(); //清空接收缓存 sprintf(cmdBuf, "AT+CIPSEND=%d\r\n", len); //发送命令 if(!ESP8266_SendCmd(cmdBuf, ">")){ //收到‘>’时可以发送数据 Usart_SendString(USART2, data, len); //发送设备连接请求数据 } } // 查询是否有 IOTDA 平台的下发指令,下发标识为 +MQTTSUBRECV:0 unsigned char *ESP8266_GetRECV(unsigned short timeOut){ char *ptrRECV = NULL; do{ if(ESP8266_WaitRecive() == REV_OK){ //如果接收完成 ptrRECV = strstr((char *)esp8266_buf, "MQTTSUBRECV"); //搜索“MQTTSUBRECV”头 if(ptrRECV == NULL){ //如果没找到,可能是IPD头的延迟,还是需要等待一会,但不会超过设定的时间 printf("\"MQTTSUBRECV\" not found\r\n"); }else{ ptrRECV = strchr(ptrRECV, ':'); //找到':' if(ptrRECV != NULL){ ptrRECV++; return (unsigned char *)(ptrRECV); }else return NULL; } } delay_ms(5); //延时等待 }while(timeOut--); return NULL; //超时还未找到,返回空指针 } 华为云IOTDA基础代码如下:
// AT指令整理(无需修改) // 设置MQTT的用户名与密码 #define HW_MQTT_SetUserPsd "AT+MQTTUSERCFG=0,1,\"NULL\",\"" HW_Username "\",\"" HW_Password "\",0,0,\"\"\r\n" // 客户端ID #define HW_MQTT_SetClientID "AT+MQTTCLIENTID=0,\"" HW_ClientID "\"\r\n" // 所属地域接入接口 #define HW_MQTT_Address_Port "AT+MQTTCONN=0,\"" HW_AddressPort "\",1883,1\r\n" // 主题订阅 #define HW_MQTT_ThemeSub "AT+MQTTSUB=0,\"$oc/devices/" HW_ClientID "/sys/properties/report\",1\r\n" // 设备信息上报 #define HW_MQTT_Report "AT+MQTTPUB=0,\"$oc/devices/" HW_Username "/sys/properties/report\",\"{\\\"services\\\":[{\\\"service_id\\\":\\\"" HW_ServiceID "\\\"\\,\\\"properties\\\":{" // 下发指令回复包 #define HW_MQTT_Response "AT+MQTTPUB=0,\"$oc/devices/" HW_ProjectID "/sys/commands/response/request_id=" char PubMemory[250]; // 设备上行消息缓冲区,可用于 属性上报 + 回复包发送 char EditMemory[50]; // 拼接一个键值对的内存。 char ParasMemory[150]; // 截取出消息中的paras部分内容。 // 连接到华为云IOT平台 void HWIOTDA_ConnectCloud(){ // 设置MQTT的用户名与密码 printf("\r\n"); printf("1/4: 设置MQTT用户名与密码\r\n"); while(ESP8266_SendCmd(HW_MQTT_SetUserPsd, "OK")) delay_ms(500); // 设置MQTT的客户端ID printf("2/4: 设置MQTT客户端ID\r\n"); while(ESP8266_SendCmd(HW_MQTT_SetClientID, "OK")) delay_ms(500); // 连接到IOTDA服务器 printf("3/4: 连接到 IOTDA 服务器\r\n"); while(ESP8266_SendCmd(HW_MQTT_Address_Port, "OK")) delay_ms(500); // 订阅命令下行主题,用于接收App下发指令 printf("4/4: 订阅命令下行主题\r\n"); while(ESP8266_SendCmd(HW_MQTT_ThemeSub, "OK")) delay_ms(500); } 中国移动OneNET代码如下:
// ---------- AT指令整理(无需修改) ---------- // 设备接入信息的请求接口 #define OneNET_AddressPort "mqtts.heclouds.com" // 设置MQTT的用户名与密码(产品ID作为username,Token作为password) #define OneNET_MQTT_SetUserPsd "AT+MQTTUSERCFG=0,1,\"NULL\",\"" OneNET_ProjectID "\",\"" OneNET_Password "\",0,0,\"\"\r\n" // 客户端ID(设备名称作为ClientID) #define OneNET_MQTT_SetClientID "AT+MQTTCLIENTID=0,\"" OneNET_DeviceName "\"\r\n" // MQTT连接指令 #define OneNET_MQTT_Address_Port "AT+MQTTCONN=0,\"" OneNET_AddressPort "\",1883,1\r\n" // 设备上行数据结果响应主题 #define OneNET_MQTT_ReplyThemeSub "AT+MQTTSUB=0,\"$sys/" OneNET_ProjectID "/" OneNET_DeviceName "/thing/property/post/reply\",1\r\n" // App下发指令接收主题 #define OneNET_MQTT_AppThemeSub "AT+MQTTSUB=0,\"$sys/" OneNET_ProjectID "/" OneNET_DeviceName "/thing/property/set\",1\r\n" // 设备信息上报 包头 #define OneNET_MQTT_Report "AT+MQTTPUB=0,\"$sys/" OneNET_ProjectID "/" OneNET_DeviceName "/thing/property/post\",\"{\\\"id\\\":\\\"001\\\"\\,\\\"version\\\":\\\"1.0\\\"\\,\\\"params\\\":{" // 下发指令回复包 包头 #define OneNET_MQTT_Response "AT+MQTTPUB=0,\"$sys/" OneNET_ProjectID "/" OneNET_DeviceName "/thing/property/set_reply\"" // 全局内存 char PubMemory[250]; // 设备上行消息缓冲区,可用于 属性上报 + 回复包发送 char EditMemory[50]; // 拼接一个键值对的临时内存。 char ParamsMemory[150]; // 解析JSON时用于截取出消息中的params内容的暂存空间。 // 连接到 OneNET 平台 void OneNET_ConnectCloud(){ printf("\r\n"); // 设置MQTT的用户名与密码 printf("1/5: 设置MQTT用户名与密码\r\n"); while(ESP8266_SendCmd(OneNET_MQTT_SetUserPsd, "OK")) delay_ms(500); // 设置MQTT的客户端ID printf("2/5: 设置MQTT客户端ID\r\n"); while(ESP8266_SendCmd(OneNET_MQTT_SetClientID, "OK")) delay_ms(500); // 连接到IOTDA服务器,指令与监听分开,防止频繁反复连接 printf("3/5: 连接到 OneNET 服务器\r\n"); while(ESP8266_SendCmd(OneNET_MQTT_Address_Port, "")) delay_ms(500); // 发送连接指令 while(ESP8266_SendCmd("", "OK")) delay_ms(500); // 监听连接结果 // 订阅上行响应结果主题,用于接收上行消息的响应结果 printf("4/5: 订阅上行响应主题\r\n"); while(ESP8266_SendCmd(OneNET_MQTT_ReplyThemeSub, "OK")) delay_ms(500); // 订阅命令下行主题,用于接收App下发指令 printf("5/5: 订阅命令下行主题\r\n"); while(ESP8266_SendCmd(OneNET_MQTT_AppThemeSub, "OK")) delay_ms(500); } 三、完整工程包
完整STM32工程包如下,包括可以直接烧录实现。

微信小程序:
