文章目录
- [基于 STM32F103 + ESP8266 实现 DHT11 温湿度数据上传华为云 IoT 平台](#基于 STM32F103 + ESP8266 实现 DHT11 温湿度数据上传华为云 IoT 平台)
基于 STM32F103 + ESP8266 实现 DHT11 温湿度数据上传华为云 IoT 平台
在物联网项目中,将本地传感器数据上传至云端是核心需求之一。本文将手把手教你如何使用 STM32F103C8T6驱动 ESP8266 模块 ,通过 AT 指令连接华为云 IoT 平台 ,并将 DHT11 采集的温湿度数据以 JSON 格式上报 。代码仓库
项目架构概览
cpp
[ DHT11 ] → [ STM32F103 ] ←→ [ ESP8266 (AT 指令) ] → 华为云 IoT
↑ ↑
(单总线通信) (USART2通信)
- 主控:STM32F103C8T6
- WiFi 模块:ESP8266(固件支持 MQTT AT 指令)
- 传感器:DHT11(单总线温湿度)
- 云平台:华为云 IoTDA(设备接入服务)
准备工作
硬件连接
| STM32 | ESP8266 |
|---|---|
| PA2 (USART2_TX) | RX |
| PA3 (USART2_RX) | TX |
| 3.3V | VCC、CH_PD |
| GND | GND |
注:STM32的USART1与电脑的串口模块连接用来打印调试信息(PA9 <--> RXD)
软件依赖
- STM32 标准外设库
- 自定义
Delay.h(基于 SysTick) - 已实现 DHT11 驱动
- 华为云 IoT 设备信息(需提前在控制台创建)
华为云 IoT 设备配置
在 华为云 IoT 控制台 创建设备后,获取以下信息:
c
/* 连接您的WIFI SSID和密码 */
#define WIFI_SSID your_wifi_ssid
#define WIFI_PASSWD your_wifi_password
#define DEVICEID your_decice_id
#define DECIVESECRET your_decice_secert
/* 线上环境域名和端口号,不需要改 */
#define MQTT_SERVER "c80c8bf1c5.st1.iotda-device.cn-north-4.myhuaweicloud.com"
#define MQTT_PORT 1883
#define MQTT_USRNAME your_mqtt_username
// 硬件的ID和密码
#define MQTT_CLIENT_ID your_mqtt_client_id
#define MQTT_PASSWD your_mqtt_password
工作流程
由于STM32向ESP8266发送AT指令这一步骤会被多次使用,因此我们将该功能封装为一个函数
cpp
/**
* @brief 向 ESP8266 发送 AT 指令并等待期望响应
* @param cmd: 要发送的 AT 指令字符串(必须以 "\r\n" 结尾)
* @param response: 期望在 ESP8266 返回数据中出现的关键字(如 "OK", "WIFI CONNECTED")
* @param timeout: 超时时间(单位:10ms),总等待时间 ≈ timeout × 10ms
* @retval 0: 在超时前收到包含 response 的响应,操作成功
* @retval 1: 超时未收到期望响应,操作失败
*
* @note 该函数依赖全局变量:
* - WiFi_RxBuffer:ESP8266 串口接收缓冲区(由 USART1 中断或轮询填充)
* - flag:特殊延时标志(用于某些耗时较长的指令,如 MQTT 连接)
*/
u8 ESP8266_SendAT(char *cmd, char *response, u16 timeout)
{
// 1. 清空上一次的接收缓冲区,避免残留数据干扰判断
USART1_ClearBuffer();
// 2. 发送 AT 指令到 ESP8266(通过 USART1)
USART1_SendString(cmd);
// 3. 等待 1 秒,确保 ESP8266 有足够时间开始处理指令并返回初步响应
// (某些指令如 AT+RST、AT+CWJAP 响应较慢)
Delay_ms(1000);
// 4. 【特殊处理】检查全局标志位 flag
// 若 flag == 1,说明当前指令可能需要额外延时(如首次 MQTT 连接)
// 此处清零 flag 并追加 3 秒延时,防止因响应延迟导致误判超时
if (flag) {
flag = 0; // 清除标志,避免重复触发
Delay_ms(3000); // 额外等待 3 秒(适用于 MQTTCONN 等慢响应指令)
}
// 5. 循环等待期望响应,最多等待 timeout × 10ms
while (timeout--) {
// 检查接收缓冲区是否包含期望的关键字(如 "OK")
if (strstr(WiFi_RxBuffer, response)) {
return 0; // 找到响应,立即返回成功
}
Delay_ms(10); // 每次循环间隔 10ms,降低 CPU 占用
}
// 6. 超时仍未收到期望响应,返回失败
return 1;
}
STM32向ESP8266发送AT指令连接WIFI
STM32向ESP8266发送AT指令连接WIFI的具体步骤如下:
- 格式化ESP8266
- 进行基本响应测试
- 配置Station模式
- 取消自动连接
- 连接WiFi连接
前两步是为了保证ESP8266能正常工作,确保出bug后不是ESP8266的问题
ESP8266 支持以下三种 WiFi 工作模式(通过 AT+CWMODE 设置):
| 模式 | 值 | 功能 | 类比 |
|---|---|---|---|
| Station (STA) | 1 |
作为客户端,连接到外部 WiFi 路由器 | ≈ 手机、笔记本连 WiFi |
| SoftAP | 2 |
作为热点,其他设备可连接它 | ≈ 手机开热点 |
| Station + SoftAP | 3 |
同时具备以上两种功能 | ≈ 路由器(既连上级网络,又发热点) |
因此只有 Station 模式下的 ESP8266 才能作为"客户端"去连接外部的 WiFi 路由器(AP)
cpp
/**
* @brief 初始化 ESP8266 并连接到指定的 WiFi 路由器
* @note 执行流程:
* 1. 恢复出厂设置(可选,确保干净状态)
* 2. 测试基础 AT 通信
* 3. 设置为 Station 模式(必须!)
* 4. 关闭自动连接(避免干扰)
* 5. 连接目标 WiFi 网络
* @retval 0: 初始化并连接成功
* @retval 1: 基础通信或恢复失败
* @retval 2: 模式配置失败
* @retval 3: WiFi 连接失败
*/
u8 ESP8266_InitWiFi(void)
{
// 1. 【可选】恢复出厂设置
// 清除所有保存的配置(如 SSID、密码、MQTT 设置等),确保从干净状态开始
// 避免因历史配置冲突导致连接异常
if (ESP8266_SendAT("AT+RESTORE\r\n", "OK", 1000)) {
printf("%s\r\n", WiFi_RxBuffer); // 打印实际返回内容,便于调试
printf("格式化异常\r\n");
return 1;
}
printf("格式化成功\r\n");
// 2. 发送最简单的 AT 指令,测试 STM32 与 ESP8266 串口通信是否正常
// 正常响应应为:\r\nOK\r\n
if (ESP8266_SendAT("AT\r\n", "OK", 1000)) {
printf("基本响应测试异常\r\n");
return 1;
}
printf("基本响应测试成功\r\n");
// 3. 【关键步骤】配置 ESP8266 为 Station 模式(模式 1)
// 只有在此模式下,ESP8266 才能作为客户端连接外部 WiFi 路由器
// 若跳过此步,后续 AT+CWJAP 将失败!
if (ESP8266_SendAT("AT+CWMODE=1\r\n", "OK", 1000)) {
printf("配置Station模式失败\r\n");
return 2;
}
printf("配置Station模式成功\r\n");
// 4. 关闭上电自动连接功能
// AT+CWAUTOCONN=0:禁止 ESP8266 启动时自动连接上次保存的 WiFi
// 原因:我们希望由主控(STM32)完全控制连接逻辑,避免自动连接旧网络
if (ESP8266_SendAT("AT+CWAUTOCONN=0\r\n", "OK", 1000)) {
printf("取消自动连接失败\r\n");
return 2;
}
printf("取消自动连接成功\r\n");
// 5. 构造连接 WiFi 的 AT 指令
// 格式:AT+CWJAP="SSID","PASSWORD"
// 注意:SSID 和密码中的特殊字符需转义(本例假设无特殊字符)
char connectWiFiCmd[64];
sprintf(connectWiFiCmd, "AT+CWJAP=\"%s\",\"%s\"\r\n", WIFI_SSID, WIFI_PASSWD);
// 6. 发送连接指令,并等待关键响应 "WIFI CONNECTED"
// ⚠️ 注意:此处的成功标志是 "WIFI CONNECTED",不是 "OK"!
// 因为 "OK" 可能在连接完成前就返回,而 "WIFI CONNECTED" 表示真正连上路由器
if (ESP8266_SendAT(connectWiFiCmd, "WIFI CONNECTED", 5000)) {
printf("WiFi连接失败\r\n");
return 3;
}
printf("WiFi连接成功\r\n");
// 7. 返回成功
return 0;
}
STM32向ESP8266发送AT指令连接华为云平台
cpp
/**
* @brief 配置并连接 ESP8266 到华为云 IoT 平台(MQTT 协议)
* @note 华为云设备接入要求:
* - 使用平台分配的 ClientID、Username、Password
* - 连接地址:c80c8bf1c5.st1.iotda-device.cn-north-4.myhuaweicloud.com
* - 端口:1883(非加密,因普通 ESP8266 不支持 TLS)
* @retval 0: 连接成功
* @retval 1: MQTT 参数配置失败
* @retval 2: MQTT 连接失败
*/
u8 ESP8266_ConnectHuaweiCloud(void)
{
// 1. 构造 MQTT 用户配置指令(AT+MQTTUSERCFG)
// 格式:AT+MQTTUSERCFG=<linkID>,<scheme>,<client_id>,<username>,<password>,<cert_flag>,<ca_flag>[,<path>]
// 参数说明:
// linkID=0 : 连接 ID(单连接模式固定为 0)
// scheme=1 : 认证方式(1=用户名密码认证)
// client_id : 华为云分配的完整 ClientID(含时间戳等)
// username : 设备标识(通常等于 DEVICEID)
// password : 动态生成的 token(由设备密钥 + 时间戳等计算得出)
// cert_flag=0 : 不使用证书(因不支持 TLS)
// ca_flag=0 : 不校验 CA
char cmd[256] = "AT+MQTTUSERCFG=0,1,\"690028b2e41f2979315c5cd2_esp8266-01_0_0_2025103102\",\"690028b2e41f2979315c5cd2_esp8266-01\",\"32ba9c69ce954354f912d54578bb3790a860ba89a7a011d7b3782aeb1a95b43c\",0,0,\"\"\r\n";
// 2. 构造 MQTT 连接指令(AT+MQTTCONN)
// 格式:AT+MQTTCONN=<linkID>,"<host>",<port>,<keepalive>
// 注意:端口必须为 1883(非 8883),因为 ESP8266 AT 固件不支持 TLS 加密
char cmd2[256] = "AT+MQTTCONN=0,\"c80c8bf1c5.st1.iotda-device.cn-north-4.myhuaweicloud.com\",1883,0\r\n";
/*
* 注:以下为动态拼接版本(更灵活,但当前硬编码已通过测试)
* sprintf(cmd, "AT+MQTTUSERCFG=0,1,\"%s\",\"%s\",\"%s\",0,0,\"\"\r\n",
* MQTT_CLIENT_ID, MQTT_USRNAME, MQTT_PASSWD);
*/
// 3. 短暂延时,确保上一步 WiFi 连接完全稳定
Delay_ms(1000);
// 4. 发送 MQTT 用户参数配置
// 成功响应:"OK"
if (ESP8266_SendAT(cmd, "OK", 2000)) {
printf("MQTT CFG FAIL\r\n");
return 1;
}
printf("MQTT CFG SUCCESS\r\n");
// 5. 【可选但推荐】显式设置 ClientID(部分固件要求)
// 虽然 AT+MQTTUSERCFG 已包含 ClientID,但某些 ESP8266 AT 版本需单独设置
if (ESP8266_SendAT("AT+MQTTCLIENTID=0,\"690028b2e41f2979315c5cd2_esp8266-01_0_0_2025103102\"\r\n", "OK", 2000)) {
printf("%s\r\n", WiFi_RxBuffer);
printf("MQTT ClientID FAIL\r\n");
return 1;
}
printf("MQTT ClientID SUCCESS\r\n");
// 6. 设置全局标志位 flag=1
// 作用:在 ESP8266_SendAT() 中触发额外 3 秒延时(应对 MQTT 连接慢的问题)
flag = 1;
// 7. 发起 MQTT 连接
// ⚠️ 关键:成功响应是 "MQTTCONNECTED",不是 "OK"!
// "OK" 仅表示指令被接受,"MQTTCONNECTED" 才表示真正连上服务器
if (ESP8266_SendAT(cmd2, "MQTTCONNECTED", 2000)) {
printf("%s\r\n", WiFi_RxBuffer); // 打印实际返回,便于排查错误(如 DNS 失败、认证失败)
printf("MQTT CONNECT FAIL\r\n");
return 2;
}
printf("MQTT CONNECT SUCCESS\r\n");
// 8. 【调试用】查询当前 MQTT 连接状态
// 响应示例:+MQTTCONN:0,1 表示连接 ID=0 已连接
ESP8266_SendAT("AT+MQTTCONN?\r\n", "OK", 2000);
printf("%s\r\n", WiFi_RxBuffer); // 打印状态,确认连接正常
return 0; // 全部成功
}
STM32控制ESP8266发送AT指令向云平台发送数据
cpp
/**
* @brief 将温湿度数据以 JSON 格式通过 MQTT 上报至华为云 IoT 平台
* @note 华为云要求:
* - 主题(Topic)固定为:$oc/devices/{device_id}/sys/properties/report
* - 消息体为严格格式的 JSON,包含 service_id 和 properties
* - 属性名(Temperature/Humidity)必须与产品模型定义完全一致
* @param temp: 温度值(单位:°C)
* @param humi: 湿度值(单位:%RH)
*/
void ESP8266_SendDataToHuawei(float temp, float humi)
{
// 1. 构造符合华为云规范的 JSON 数据体
// 示例输出:
// {"services":[{"service_id":"Tem_Hum_Data","properties":{"Temperature":25.0,"Humidity":60.0}}]}
//
// ⚠️ C 字符串中双引号需转义为 \"
// ⚠️ 而 JSON 本身又要求 "key",因此最终需写成 \\\" (即:\ + \")
// \\ → 在 C 中表示一个反斜杠 \
// \" → 在 C 中表示一个双引号 "
// 合起来 \\\" → 最终字符串中是 \"
// 发送给 ESP8266 后,MQTT 消息体才是合法 JSON:"key"
char data[128];
sprintf((char*)data,
"{\\\"services\\\":[{\\\"service_id\\\":\\\"Tem_Hum_Data\\\"\\,\\\"properties\\\":{\\\"Temperature\\\":%.1f\\,\\\"Humidity\\\":%.1f}}]}",
temp, humi);
// 2. 构造完整的 AT+MQTTPUB 指令
// 格式:AT+MQTTPUB=<linkID>,"<topic>","<payload>",<qos>,<retain>
// 参数说明:
// linkID = 0 : 连接 ID(单连接模式)
// topic = $oc/.../report : 华为云设备属性上报专用主题
// payload= data : 上述 JSON 字符串
// qos = 0 : QoS 等级(华为云通常用 0 或 1)
// retain = 0 : 不保留消息
char cmd[512];
sprintf((char*)cmd,
"AT+MQTTPUB=0,\"$oc/devices/%s/sys/properties/report\",\"%s\",0,0\r\n",
DEVICEID, data);
/*
* 注:下方被注释的写法是错误的!原因:
* sprintf(cmd, "...\"{\"services...}\",...", temp, humi);
* → C 编译器会认为 \"{\" 是字符串结束 + {变量},导致语法错误或非法 JSON
*/
// 3. 发送 AT 指令并等待响应
// 成功标志:"OK"(表示指令被 ESP8266 接受并成功发布)
if (ESP8266_SendAT(cmd, "OK", 2000)) {
// 若失败,打印实际返回内容便于调试(如 "ERROR"、"FAIL" 等)
printf("%s\r\n", WiFi_RxBuffer);
printf("向华为云发送数据失败\r\n");
} else {
printf("向华为云发送数据成功\n");
printf("Tem:%.1f,Hum:%.1f\r\n", temp, humi);
}
}
主函数
cpp
#include "stm32f10x.h"
#include "../System/Delay.h"
#include "../Hardware/Key.h"
#include "../Hardware/LED.h"
#include "../Hardware/OLED.h"
#include "../Hardware/USART1.h"
#include "../Hardware/USART2.h"
#include "../Hardware/WiFi.h"
#include "../Hardware/DHT11.h"
int main()
{
DHT11_Init();
OLED_Init();
USART1_Init();
USART2_Init();
// 初始化ESP8266并连接WiFi
while (ESP8266_InitWiFi() != 0) {
Delay_ms(1000); // 失败则重试
}
// 连接华为云MQTT
while (ESP8266_ConnectHuaweiCloud() != 0) {
Delay_ms(1000); // 失败则重试
}
// 循环发送数据
float temp,humi;
while (1) {
DHT11_Read_Data(&temp,&humi);
ESP8266_SendDataToHuawei(temp, humi);
//temp += 0.5f; // 模拟数据变化
//humi += 0.3f;
//if (temp > 35.0f) temp = 25.0f;
//if (humi > 70.0f) humi = 60.0f;
Delay_ms(5000); // 每5秒发送一次
}
}
效果展示

