基于 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秒发送一次
    }
}

效果展示


相关推荐
LCG元2 小时前
USB设备开发:STM32F105实现USB HID设备,虚拟串口通信实战
stm32·单片机·嵌入式硬件
香水5只用六神2 小时前
【TIM】基本定时器定时实验(2)
c语言·开发语言·stm32·单片机·嵌入式硬件·mcu·学习
A-刘晨阳3 小时前
工业物联网时代时序数据库选型指南:从大数据架构视角深度解析Apache IoTDB
大数据·物联网·时序数据库·iotdb
DolphinDB智臾科技3 小时前
2026 工业时序数据库选型指南:抽象复用能力如何降低 80% 开发成本——DolphinDB vs InfluxDB/TimescaleDB 深度对比与实践
数据库·物联网·时序数据库·dolphindb
xcLeigh3 小时前
KWDB 跨界实战:当“时序数据库”遇上“草莓大棚”,数据如何指导种地?
数据库·物联网·智慧农业·时序数据库·农业·自动控制·kwdb
小龙报3 小时前
【数据结构与算法】环与相遇:链表带环问题的底层逻辑与工程实现
c语言·数据结构·c++·物联网·算法·链表·visualstudio
WangUnionpub4 小时前
别只盯着MDPI,又贵还卡单位,平替SCI/EI,免收版面费,这本15天录用!
大数据·人工智能·深度学习·物联网·计算机视觉
江湖有缘5 小时前
基于华为openEuler系统部署MicroBin粘贴板工具
华为·docker·华为云·openeuler
foundbug9995 小时前
基于CAN总线的STM32F103 BootLoader实现方案
stm32·单片机·嵌入式硬件