嵌入式物联网设计通用实现方法
做嵌入式物联网设计时,平台选型往往决定了开发效率。物联网开放平台通过标准化协议与接口,将分散的设备高效连接、统一管理,并提供成熟的云服务与安全机制。借助这些工具,我们可以快速构建应用,专注于设备本身的功能创新,大幅降低开发复杂度。
学习阶段选用的物联网开放平台,建议遵循以下原则:
- 成本可控:优先选择免费试用额度充足的平台。
- 功能完备:能够接驳多种附加功能或云服务(如存储、数据分析)。
- 通信稳定:保证消息传输的可靠性。
一、主流平台方案对比
常见的物联网开放平台实现方式主要有以下几种,各有优劣:
1. 云服务器 + EMQX 自主构建
这种方式灵活程度较高,但技术栈内容较多,涉及 MQTT Broker 部署与维护,比较适合有一定基础的开发者使用。

2. 阿里云物联网平台
接入便捷,能对接大量阿里云云服务(存储、数据分析等)。不过目前该服务已停止新增用户,仅支持现有用户维护,不适合新用户用于学习。

3. 腾讯云物联网通信
支持基础通信与多种云服务,但免费单元较少(约 5-10 个设备)。其特点是支持腾讯连连 APP 实现快速的上位机开发。

4. 华为云设备接入 (IOTDA)
成熟度高,支持基础通信并直接接驳华为云大量服务,实现便捷的 IoT 消息存储与流转。支持邮箱及短信告警推送。不足在于鉴权流程相对复杂,初学者需花费时间配置。

5. 中国移动 OneNET
使用最为便利,支持基础通信与邮件告警。缺点是原生云服务较少,若需拓展转发、存储等功能,可能需要接驳其他平台(如华为云、阿里云存储)。

综合建议
基于实际开发体验,总结如下:
- 初学者入门:首选 中国移动 OneNET。注册、产品/设备创建流程简单,能快速实现远程数据显示、指令下发及告警推送。
- 进阶功能扩展:推荐 华为云 IOTDA。免费单元数量充足(可达 1000+),可直接对接华为云生态中的大量云服务,通过控制台设置流转即可,无需自行部署 Linux 服务,适合快速验证功能。
二、基础代码实现
华为云、腾讯云、阿里云均提供了封装好的 STM32 基础代码及微信小程序模板。利用这些工程,可以构建智能家居、智慧农业等各类物联网设计。
核心逻辑是修改鉴权信息并在云平台新建实例,即可实现 STM32 单片机与云平台的通信。本例采用 WIFI 联网模式,搭配 ESP-01S 模块,支持 AirKiss 配网模式,更加灵活。
ESP-01S 驱动层代码
这部分负责处理串口通信、AT 指令交互以及 Wi-Fi 连接状态管理。注意避开被占用的配网引脚,防止卡死。
/* - 功能:ESP8266 硬件驱动函数。
* - 硬件:使用了串口二
* - 修改:WiFi 账号/密码
* - 使用:#include "ESP8266.h"
*/
#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");
while(ESP8266_SendCmd("AT\r\n", "OK")) delay_ms(500);
printf("2/5: AT 确认连接\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_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;
usart_initstruct.USART_WordLength = USART_WordLength_8b;
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) return REV_WAIT;
if(esp8266_cnt == esp8266_cntPre){
esp8266_cnt = 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);
}
return 1;
}
// 查询是否有 IOTDA 平台的下发指令
unsigned char *ESP8266_GetRECV(unsigned short timeOut){
char *ptrRECV = NULL;
do{
if(ESP8266_WaitRecive() == REV_OK){
ptrRECV = strstr((char *)esp8266_buf, "MQTTSUBRECV");
if(ptrRECV == NULL){
printf("\"MQTTSUBRECV\" not found\r\n");
}else{
ptrRECV = strchr(ptrRECV, ':');
if(ptrRECV != NULL){
ptrRECV++;
return (unsigned char *)(ptrRECV);
}
}
}
delay_ms(5);
}while(timeOut--);
return NULL;
}
华为云 IOTDA 接入代码
这里整理了关键的 AT 指令宏定义,包括 MQTT 用户名密码设置、客户端 ID 配置以及主题订阅。注意根据实际项目 ID 和密钥替换宏定义中的变量。
// 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];
// 连接到华为云 IOT 平台
void HWIOTDA_ConnectCloud(){
printf("\r\n");
printf("1/4: 设置 MQTT 用户名与密码\r\n");
while(ESP8266_SendCmd(HW_MQTT_SetUserPsd, "OK")) delay_ms(500);
printf("2/4: 设置 MQTT 客户端 ID\r\n");
while(ESP8266_SendCmd(HW_MQTT_SetClientID, "OK")) delay_ms(500);
printf("3/4: 连接到 IOTDA 服务器\r\n");
while(ESP8266_SendCmd(HW_MQTT_Address_Port, "OK")) delay_ms(500);
printf("4/4: 订阅命令下行主题\r\n");
while(ESP8266_SendCmd(HW_MQTT_ThemeSub, "OK")) delay_ms(500);
}
中国移动 OneNET 接入代码
OneNET 的鉴权方式略有不同,通常使用产品 ID 作为用户名,Token 作为密码。连接逻辑与华为云类似,主要区别在于 Topic 的格式。
// ---------- 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];
// 连接到 OneNET 平台
void OneNET_ConnectCloud(){
printf("\r\n");
printf("1/5: 设置 MQTT 用户名与密码\r\n");
while(ESP8266_SendCmd(OneNET_MQTT_SetUserPsd, "OK")) delay_ms(500);
printf("2/5: 设置 MQTT 客户端 ID\r\n");
while(ESP8266_SendCmd(OneNET_MQTT_SetClientID, "OK")) delay_ms(500);
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);
printf("5/5: 订阅命令下行主题\r\n");
while(ESP8266_SendCmd(OneNET_MQTT_AppThemeSub, "OK")) delay_ms(500);
}
三、完整工程参考
完整的 STM32 工程包包含上述代码逻辑,可以直接烧录测试。同时配套了微信小程序的基础模板,方便快速搭建上位机界面。




