前言:之前在阿里云做的智能家居,前段时间发现看不了数据后台了,才发现没有免费的服务器了,用公共的服务器又不太稳定,所以现在用OneNet重新做了一个智能家居,其实就是改了点esp8266上云的代码,其次这个在esp在连接OneNet平台上面好多坑啊,文章里面我会一一指出来的,感觉比阿里云难搞多了,闲话不多说了,我们现在开始吧。

首先是图片演示

第一章:搭建onenet平台

1.1为什么选择OneNet平台?

(其实最重要的是提供的服务器是免费的)

  1. ​设备管理能力​

    支持海量设备接入,提供设备生命周期管理(注册、激活、删除)

    • 可实时监控设备状态(在线/离线)

    • 支持固件远程升级(OTA)

  2. ​数据可视化​

    • 内置数据可视化工具(DataV)

    • 支持自定义仪表盘(Dashboard)

    • 提供历史数据存储与分析功能

  3. ​协议兼容性​

    • 原生支持MQTT/HTTP/CoAP等物联网协议

    • 提供AT指令集适配模块(如ESP8266)

    • 支持JSON/XML数据格式解析

1.OneNet平台配置​

 我们打开下面这个网站,第一次进去要注册什么的,注册登录完后点右上脚的开发者中心

open.iot.10086.cn/https://open.iot.10086.cn/这是进来后的页面,在点击创建产品,这里可以随便选。

这里是个坑,我之前用的不是这样的模式,代码下发控制的时候调试日志里面有脚本解析错误,属性设置设备,设备响应超时之类的报错,反正这里按照我的填就行了。

创建完后会更新你创建的产品出来,点击设备管理,在这里添加你的设备,这里的创建的设备就是我们设备端要连接的云平台,这里也是随便命名。

添加完设备后点击详情,在这里可以先用记事本,记录你的设备密钥,产品ID,还有你的设备名称test。

在产品开发里面创建你要的功能点,就是你设备端要上传的数据,设备端和云平台的数据类型不要搞错了。

1.2ESP8266连接OneNet的前提条件

1.首先你的esp8266模块要可以使用AT指令

要可以支持AT+mqtt的指令,一般买来的都是可以使用的,如果不知道自己的模块有没有这些指令的话,可以直接用数据线把esp8266和电脑连接起来,打开串口助手,输入以下指令:

AT+CMD?

这样的话,esp8266会跳出你所有支持的指令,可以看看有没有mqtt支持的指令出来,没有的话要去烧录mqtt固件,这里就不提供mqtt烧录了,可以直接去网上搜教程。

2.用mqttx软件模拟数据上传和下发控制

注意!这是是模拟设备端和onenet平台的上发信息和下发控制,不想模拟的可以直接用代码,完整源码我会放到文章底部

没有mqtt的可以在这里下载或者网页版MQTTX:全功能 MQTT 客户端工具,这里需要用到token生成工具文档中心

打开mqttx后,点击左上角这个+,新建连接,这里用到的配置是之前用笔记记下的信息,只有这个密码要用token计算工具生成。在token生成工具里面要把这里的设备ID和用户名还有设备密钥改成自己的,其他不用动,上面这个文档中心里面有解释。

下面点击连接后,可以去onenet平台看你的设备,这种就是连接成功了,如果mqttx连接不上的话注意你的这些ID什么的不要填错了,还有这个mqtt版本一定要选3.1.1

连接成功后,接下来就是添加订阅了,在这里可以看到这些订阅的解释文档中心

$sys/Prf2V6X5ql/test/thing/property/post/reply

$sys/Prf2V6X5ql/test/thing/property/set

添加这两个订阅

然后现在搞发布,这是发布的主题$sys/Prf2V6X5ql/test/thing/property/post,就是向我们之前的$sys/Prf2V6X5ql/test/thing/property/post/reply这个订阅发布信息,成功的话,相应的订阅会有信息发过来,然后onenet平台的数据也会相应的改变

这是发布数据的格式,注意我这里的数据都是可以读写的,这个是在功能点设置那里自己定义的

{
  "id":"1001",
  "version": "1.0",
  "params": {
    "temp":{
      "value":29
    },
    "humi":{
      "value":44
    },
    "LED":{
      "value":2
    },
    "servo":{
      "value":2
    },
    "motor":{
      "value":2
    }
  }
}

最后是平台下发控制命令,就是这个订阅$sys/Prf2V6X5ql/test/thing/property/set发送的信息,我们要用$sys/Prf2V6X5ql/test/thing/property/set_reply这个主题响应它的信息。

这里在onenet平台的设备详情里面找到设备调试,要调试哪个数据就勾选完后调节属性,但是对应的mqttx上面也要有响应信息发过去,一般要在5,6秒内发送你给平台的响应,ID要和它发过来的一样,不然的话就会提示响应超时。这是响应信息的格式

{
 "id":"2",
 "code":200,
 "msg":"success"
}

好,到这里我们的mqttx模拟设备端就完成了。

3.单独用esp8266使用AT指令连接onenet平台

1.首先要把esp8266用数据线单独连接电脑,我这里因为是esp8266WiFi模块,所以有直接连接电脑的接口,如果是esp8266-01s的话要搞一个转串口的USB连接到电脑。

下面是具体的指令:

(这里的 WiFi名称 zhe,WiFi密码 88888888

设备名称 test设备ID Prf2V6X5qltoken生成的密码version=2018-10-31&res=products%2FPrf2V6X5ql%2Fdevices%2Ftest&et=2345678910&method=md5&sign=Nx55tsoqWYiM86BW2BITxA%3D%3D,还有下面的主题和订阅里面的设备名称和ID

都要换成你自己创建的设备的属性)

  1. AT:测试通信

  2. AT+RST:复位模块

  3. AT+CWMODE=1:设置WiFi模式为STA

  4. AT+CWDHCP=1,1:启用DHCP

  5. AT+CWJAP="zhe","88888888":连接WiFi

  6. AT+MQTTUSERCFG=0,1,"test","Prf2V6X5ql","version=2018-10-31&res=products%2FPrf2V6X5ql%2Fdevices%2Ftest&et=2345678910&method=md5&sign=Nx55tsoqWYiM86BW2BITxA%3D%3D",0,0,""
    :配置MQTT用户参数

  7. AT+MQTTCONN=0,"mqtts.heclouds.com",1883,1
    :连接MQTT服务器

  8. AT+MQTTSUB=0,"$sys/Prf2V6X5ql/test/thing/property/post/reply",0:订阅命令回复主题

  9. AT+MQTTPUB=0,"$sys/Prf2V6X5ql/test/thing/property/post","{\"id\":\"123\"\,\"params\":{\"LED\":{\"value\":1\}\,\"motor\":{\"value\":1\}}}",0,0

    :发布设备状态数据

onenet平台对应属性改变:

第二章:硬件选择与系统搭建

2.1 硬件选型

​​STM32F103C8T6​​ 核心板​​、ESP8266​​ WiFi模块(01-s也可以)、DHT11温湿度传感器、​ SG90舵机、LED、直流电机​​:搭配L298N驱动模块、3个独立按键、​​0.96寸OLED显示屏​

2.2硬件接线

1.STM32与ESP8266通信连接方式:

  • STM32 TX(PA2)→ ESP8266 RX:STM32向ESP8266发送数据
  • STM32 RX(PA3)→ ESP8266 TX:ESP8266向STM32发送数据
  • VCC→正常接面包板正极
  • GND→正常接面包板负极

2.DHT11采用单总线(1-Wire)协议与STM32通信,接线简单,仅需三根线:

  • 数据线:DHT11 DATA → STM32 PA5(数据通信引脚)
  • 电源线:DHT11 VCC → 3.3V/5V电源(支持双电压)
  • 地线:DHT11 GND → STM32 GND(共地连接)

3.SG90舵机通过PWM信号控制角度(周期20ms,占空比0.5ms~2.5ms对应0°~180°):

  • ​​舵机信号线(黄色)​​ → ​​STM32 PB1​​(TIM2_CH2,PWM输出)

  • ​​舵机电源(红色)​​ → ​​5V电源​​(此处直接连接 ST-LINK的5v引脚)

  • ​​舵机GND(棕色)​​ → ​​STM32 GND​​(共地)

4.L298N 模块连接方式:

  • IN1 → PA11
  • IN2 → PA12
  • PWMA → PA8(TIM1_CH1,PWM输出)
  • VM → 外部电源正极(此处直接连接 ST-LINK的5v引脚)
  • GND → 外部电源负极

5.LED灯组(RGB共阳极)

正极连接→ P0(TIM2_CH1,PWM输出)

负极→接面包板负极

6.三个按键

  • 按键1 → PB0(控制LED)
  • 按键2 → PB1(控制电机)
  • 按键3 → PB10(控制舵机)

2.3定时器配置

  1. ​TIM1​​(电机PWM):

    • 通道1:PWM输出

    • 预分频:72-1

    • 周期:100-1 (100us周期)

  2. ​TIM2​​(LED/舵机):

    • 通道1:LED PWM

    • 通道2:舵机PWM

    • 预分频:72-1

    • 周期:20000-1 (20ms周期)

  3. ​TIM3​​(DHT11延时):

    • 基本定时器

    • 用于μs级精确延时

    • 默认配置即可

第三章 完成各个模块的配置​及代码编写

3.1模块配置与接线​

本项目的各个模块配置及均在 Cube 软件中完成。首先,根据硬件设备的型号和参数,在 Cube 中对 STM32 单片机进行引脚配置,包括 LED、电机、舵机、ESP8266 等模块的控制引脚和通信引脚。例如,将 LED 模块连接到 STM32 的定时器输出引脚,通过 PWM 信号控制 LED 的亮度;电机模块通过 GPIO 引脚控制其启停和转速;舵机模块同样连接到定时器输出引脚,利用 PWM 信号控制其转动角度;ESP8266 模块则通过 UART 接口与 STM32 进行通信,实现数据的上传和下达。​还有ch340转串口用来打印信息。以下是主要配置信息的截图

在这里,usart2是esp8266接接线口,要配置中断要接收信息,usart1是串口打印信息的跟usart2一致但是不用配置中断。

在接线过程中,需要注意各模块的电源电压和电流是否匹配,避免因电源问题导致模块损坏。同时,要确保接线牢固可靠,减少接触不良等问题对系统稳定性的影响。​

3.2项目结构

本项目采用 FreeRTOS 实时操作系统构建,在 Cube 中创建了多个任务和队列、信号量、互斥锁等同步机制。​

创建的任务包括:LED 任务、电机任务、舵机任务、按键任务、ESP8266 通信任务等。每个任务负责特定模块的功能实现,其中 LED 任务负责控制 LED 的亮灭、亮度调节和呼吸效果;电机任务负责控制电机的启停和转速调节;舵机任务负责控制舵机的转动角度;按键任务负责检测按键输入并实现对各模块的本地控制;ESP8266 通信任务负责与 OneNet 平台进行数据交互。​

队列的使用主要是为了实现任务之间的数据传递。其中,按键任务检测到按键输入后,将对应的控制指令通过队列发送给相应的模块任务;ESP8266 通信任务接收到 OneNet 平台下发的控制指令后,再通过队列发送给各模块任务。信号量和互斥锁则用于实现任务之间的同步和资源共享保护,避免多个任务同时访问共享资源而导致的数据混乱和冲突。​

在配置任务时,需要合理设置任务的优先级和栈空间大小。任务优先级的设置应根据任务的重要性和实时性要求来确定,例如 ESP8266 通信任务和按键任务需要较高的优先级,以确保能够及时响应外部事件;而一些周期性执行的任务可以设置较低的优先级。栈空间大小的设置要根据任务的复杂程度和所需的局部变量空间来确定,栈空间过小可能导致任务运行异常,过大则会浪费系统资源。​

这是优先级的配置:简单来说WiFi任务和LED,电机,舵机优先级一致,DHT11任务优先级要高于它们一级,防止读取不到温湿度数据,还有OLED任务高DHT11一级,按键任务为最高优先级。

/* Definitions for MotorTask */
osThreadId_t MotorTaskHandle;
const osThreadAttr_t MotorTask_attributes = {
  .name = "MotorTask",
  .stack_size = 128 * 4,
  .priority = (osPriority_t) osPriorityLow3,
};
/* Definitions for ServoTask */
osThreadId_t ServoTaskHandle;
const osThreadAttr_t ServoTask_attributes = {
  .name = "ServoTask",
  .stack_size = 128 * 4,
  .priority = (osPriority_t) osPriorityLow3,
};
/* Definitions for LEDTask */
osThreadId_t LEDTaskHandle;
const osThreadAttr_t LEDTask_attributes = {
  .name = "LEDTask",
  .stack_size = 128 * 4,
  .priority = (osPriority_t) osPriorityLow3,
};
/* Definitions for Key_TASK */
osThreadId_t Key_TASKHandle;
const osThreadAttr_t Key_TASK_attributes = {
  .name = "Key_TASK",
  .stack_size = 128 * 4,
  .priority = (osPriority_t) osPriorityLow5,
};
/* Definitions for OLEDtask */
osThreadId_t OLEDtaskHandle;
const osThreadAttr_t OLEDtask_attributes = {
  .name = "OLEDtask",
  .stack_size = 128 * 4,
  .priority = (osPriority_t) osPriorityBelowNormal,
};
/* Definitions for DHT11Task */
osThreadId_t DHT11TaskHandle;
const osThreadAttr_t DHT11Task_attributes = {
  .name = "DHT11Task",
  .stack_size = 256 * 4,
  .priority = (osPriority_t) osPriorityLow4,
};
/* Definitions for WiFiTask */
osThreadId_t WiFiTaskHandle;
const osThreadAttr_t WiFiTask_attributes = {
  .name = "WiFiTask",
  .stack_size = 512 * 4,
  .priority = (osPriority_t) osPriorityLow3,
};

这里是队列的配置:

// FreeRTOS消息队列句柄(用于任务间通信)
extern osMessageQueueId_t motorQueueHandle;     // 电机控制队列
extern osMessageQueueId_t servoQueueHandle;     // 舵机控制队列
extern osMessageQueueId_t ledQueueHandle;      // LED控制队列

// 云端相关队列(用于与云端通信)
extern osMessageQueueId_t cloudLedQueueHandle;    // 云端LED控制队列
extern osMessageQueueId_t cloudMotorQueueHandle;  // 云端电机控制队列
extern osMessageQueueId_t cloudServoQueueHandle;  // 云端舵机控制队列

信号量和互斥锁是为了能让其他任务安全的读取DHT11的温湿度数据,其他的像led的状态变量是直接用全局变量传递的。

3.3各模块任务之间的联系​

各模块任务之间通过队列、信号量等机制实现协同工作。当按键任务检测到按键输入时,会根据按键的不同生成相应的控制指令,并将指令通过对应的队列发送给 LED 任务、电机任务或舵机任务。各模块任务从队列中获取指令后,执行相应的操作,实现对硬件的本地控制。​

ESP8266 通信任务作为与 OneNet 平台交互的桥梁,起着至关重要的作用。它通过 UART 接口与 ESP8266 模块进行通信,将设备的状态信息按照规定的格式上传到 OneNet 平台;同时,接收 OneNet 平台下发的控制指令,解析后通过队列发送给各模块任务,实现对硬件的远程控制。​

ESP8266 上云和 OneNet 平台下发控制​

ESP8266 模块通过 MQTT 协议与 OneNet 平台建立连接。在代码中,首先需要配置 ESP8266 的网络参数,使其能够连接到指定的无线网络。然后,通过 MQTT 协议连接到 OneNet 平台,进行设备的注册和认证。​

当设备正常连接到 OneNet 平台后,ESP8266 通信任务会周期性地采集各模块的状态信息,按照 OneNet 平台要求的数据格式进行封装,然后通过 MQTT 协议上传到平台。​

当 OneNet 平台下发控制指令时,ESP8266 模块接收到指令后,将其传递给 STM32 的 ESP8266 通信任务。该任务对指令进行解析,提取出控制参数,然后通过队列发送给相应的模块任务,各模块任务根据控制参数执行相应的操作,并将执行结果反馈给 ESP8266 通信任务,由其上传到 OneNet 平台,实现控制指令的闭环。

3.4主要任务的代码

​1.esp8266:

// ESP8266初始化流程
void ESP8266_Init(void)
{
    char cmdBuf[512];  // 命令缓冲区
	
    ESP8266_Clear();  // 清空接收缓冲区
	
    // 1. 测试AT指令
    UsartPrintf(USART_DEBUG, "0. AT\r\n");
    while(ESP8266_SendCmd("AT\r\n", "OK"))
        HAL_Delay(500);
	
    // 2. 重启模块
    UsartPrintf(USART_DEBUG, "1. RST\r\n");
    ESP8266_SendCmd("AT+RST\r\n", "");
    HAL_Delay(500);	
    
    // 3. 设置WiFi模式为Station
    UsartPrintf(USART_DEBUG, "2. CWMODE\r\n");
    while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))  // 模式1(Station)
        HAL_Delay(500);
    
    // 4. 开启DHCP
    UsartPrintf(USART_DEBUG, "3. AT+CWDHCP\r\n");
    while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))  // 开启DHCP
        HAL_Delay(500);
    
    // 5. 连接WiFi
    UsartPrintf(USART_DEBUG, "4. CWJAP\r\n");
    while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "OK"))
        HAL_Delay(500);
    
    // 6. 配置MQTT用户信息
    UsartPrintf(USART_DEBUG, "5.MQTTUSERCFG\r\n");
    sprintf(cmdBuf, "AT+MQTTUSERCFG=0,1,\"%s\",\"%s\",\"%s\",0,0,\"\"\r\n", 
            ClintID, username, passwd);
    while(ESP8266_SendCmd(cmdBuf, "OK"))
        HAL_Delay(500);
    memset(cmdBuf, 0, sizeof(cmdBuf));
    
    // 7. 连接MQTT服务器
    UsartPrintf(USART_DEBUG, "6.MQTTCONN\r\n");
    sprintf(cmdBuf, "AT+MQTTCONN=0,\"%s\",1883,1\r\n", Url);
    while(ESP8266_SendCmd(cmdBuf, "OK"))
        HAL_Delay(500);
    memset(cmdBuf, 0, sizeof(cmdBuf));
    
    // 8. 订阅上报回复主题
    UsartPrintf(USART_DEBUG, "7.MQTTSUB\r\n");
    sprintf(cmdBuf, "AT+MQTTSUB=0,\"%s\",1\r\n", subtopic);
    while(ESP8266_SendCmd(cmdBuf, "OK"))
        HAL_Delay(500);
    memset(cmdBuf, 0, sizeof(cmdBuf));
    
    // 9. 订阅控制指令主题
    UsartPrintf(USART_DEBUG, "8. Subscribe to SET topic\r\n");
    sprintf(cmdBuf, "AT+MQTTSUB=0,\"%s\",1\r\n", set_topic);
    while(ESP8266_SendCmd(cmdBuf, "OK"))
        HAL_Delay(500);
    memset(cmdBuf, 0, sizeof(cmdBuf));
}

// 单属性上报函数
void Esp_PUB(const char* identifier, int value) {
    char cmdBuf[256];
    
    // 构造属性上报消息
    sprintf(cmdBuf, 
        "AT+MQTTPUB=0,\"%s\","
        "\"{\\\"id\\\":\\\"123\\\"\\,\\\"params\\\":{\\\"%s\\\":{\\\"value\\\":%d\\}}}\",0,0\r\n",
        pubtopic, identifier, value);
    
    // 发送并等待确认
    while(ESP8266_SendCmd(cmdBuf, "OK"))
        HAL_Delay(500);
    
    memset(cmdBuf, 0, sizeof(cmdBuf));
    HAL_Delay(100); // 短延时防止连续发送冲突
}

// 多属性上报函数
void ESP8266_ReportMulti(uint8_t led, uint8_t motor, uint8_t servo) {
    // 分别上报每个属性
    Esp_PUB("LED", led);     // LED状态
    HAL_Delay(200);
    Esp_PUB("motor", motor); // 电机状态
    HAL_Delay(200);
    Esp_PUB("servo", servo); // 舵机状态
}

// 串口接收中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance != USART2) return; // 仅处理USART2
    
    // 检查缓冲区空间
    if (ESP8266_cnt < ESP8266_BUF_SIZE - 1) {
        ESP8266_buf[ESP8266_cnt++] = aRxBuffer; // 存入数据
    } else {
        // 缓冲区溢出处理
        ESP8266_cnt = 0;
        memset(ESP8266_buf, 0, ESP8266_BUF_SIZE);
    }
    
    // 重启接收中断
    HAL_UART_Receive_IT(&huart2, (uint8_t *)&aRxBuffer, 1);
}

// 处理接收到的MQTT消息
void ESP8266_ProcessMessage(const char *message) {
    // 检查是否为控制消息
    if (strstr(message, "thing/property/set") == NULL) return;
    
    // 查找JSON起始位置
    const char *json_start = strchr(message, '{');
    if (!json_start) return;
    
    // 解析JSON
    cJSON *root = cJSON_Parse(json_start);
    if (!root) return;
    
    // 获取请求ID
    char req_id[32] = "default_id";
    cJSON *id_item = cJSON_GetObjectItem(root, "id");
    if (id_item && cJSON_IsString(id_item)) {
        strncpy(req_id, id_item->valuestring, sizeof(req_id)-1);
    }    
    
    // 发送响应
    SendCommandResponse(req_id);
    
    // 处理控制参数
    cJSON *params = cJSON_GetObjectItem(root, "params");
    if (params) {
        // LED控制
        cJSON *led_item = cJSON_GetObjectItem(params, "LED");
        if (led_item && cJSON_IsNumber(led_item)) {
            int value = led_item->valueint;
            if (value >= LED_STATE_OFF && value <= LED_STATE_BREATH) {
                ledState = (LedState_t)value;
                osMessageQueuePut(cloudLedQueueHandle, &ledState, 0, 0);
            }
        }
        
        // 电机控制
        cJSON *motor_item = cJSON_GetObjectItem(params, "motor");
        if (motor_item && cJSON_IsNumber(motor_item)) {
            int value = motor_item->valueint;
            if (value >= Motor_STATE_OFF && value <= Motor_STATE_100_SPEED) {
                motorState = (MotorState_t)value;
                osMessageQueuePut(cloudMotorQueueHandle, &motorState, 0, 0);
            }
        }
        
        // 舵机控制
        cJSON *servo_item = cJSON_GetObjectItem(params, "servo");
        if (servo_item && cJSON_IsNumber(servo_item)) {
            int value = servo_item->valueint;
            if (value >= Servo_STATE_OFF && value <= Servo_STATE_180_ANGLE) {
                servoState = (ServoState_t)value;
                osMessageQueuePut(cloudServoQueueHandle, &servoState, 0, 0);
            }
        }
    }
    
    cJSON_Delete(root); // 释放JSON对象
}

// 发送命令响应到OneNet平台
void SendCommandResponse(const char *req_id) {
    char cmd[256];
    char payload[128];
    
    // 构造响应消息
    snprintf(payload, sizeof(payload), 
             "{\"id\":\"%s\",\"code\":200,\"msg\":\"success\"}", 
             req_id);
    
    // 发送响应
    snprintf(cmd, sizeof(cmd), 
             "AT+MQTTPUB=0,\"$sys/teNI4Rcv8E/shangweiji/thing/property/set_reply\",\"%s\",0,0\r\n", 
             payload);
    
    ESP8266_SendCmd(cmd, "OK"); // 发送并等待OK响应
}

2.WiFi任务:

void StartWiFiTask(void *argument) {
    // 1. 初始化ESP8266模块(连接WiFi和MQTT)
    ESP8266_Init();
    printf("[WiFi] Started\n"); // 初始化完成提示
    
    uint32_t lastReportTime = HAL_GetTick(); // 记录上次上报时间
    
    // 2. 主任务循环
    for(;;) {
        uint32_t now = HAL_GetTick(); // 获取当前时间
        
        // 3. 处理接收到的消息
        if (ESP8266_WaitRecive() == REV_OK) { // 检查是否有完整数据包
            // 检测到控制消息时输出特殊标记(调试用)
            if (strstr((char*)ESP8266_buf, "thing/property/set")) {
                printf("---- CONTROL MESSAGE DETECTED ----\n");
            }
            
            // 处理接收到的MQTT消息
            ESP8266_ProcessMessage((const char *)ESP8266_buf);
            
            // 清空接收缓冲区
            ESP8266_Clear();
        }
        
        // 4. 定期上报数据(每5秒)
        if (now - lastReportTime > 5000) {
            // 4.1 上报设备状态(LED、电机、舵机)
            ESP8266_ReportMulti(ledState, motorState, servoState);
            
            // 4.2 读取并上报温湿度数据
            float temp, humi;
            if (GetDHT11Data(&temp, &humi)) { // 获取有效的温湿度数据
                Esp_PUB("temp", (int)temp);    // 上报温度值
                HAL_Delay(100);                // 短延时防止连续发送冲突
                Esp_PUB("humi", (int)humi);    // 上报湿度值
                HAL_Delay(100);
            }
            
            // 4.3 输出上报日志(调试信息)
//            printf("Reported: temp=%.1f, humi=%.1f, LED=%d\n", temp, humi, ledState);
            
            // 4.4 更新最后上报时间
            lastReportTime = now;
        }
        
        // 5. 任务延时(100ms)
        osDelay(1);
    }
}

3.DHT11任务:

void StartDHT11Task(void *argument)
{
  DHT11_Init();
  for(;;)
  {
    uint8_t temp = 0, humi = 0;
    uint32_t timeout = HAL_GetTick() + 100; // 100ms超时

    // 读取数据,超时退出
    while(DHT11_Read_Data(&temp, &humi) != 0)
    {
      if(HAL_GetTick() > timeout)
      {
//        printf("DHT11 read timeout\r\n");
        break;
      }
      osDelay(10);
    }

    if(temp && humi)
    {
      if(osMutexAcquire(dht11MutexHandleHandle, osWaitForever) == osOK)
      {
        dht11_data.temperature = (float)temp;
        dht11_data.humidity = (float)humi;
        dht11_data.valid = 1;
        osMutexRelease(dht11MutexHandleHandle);
        osSemaphoreRelease(data_ready_semHandle);
      }
    }
    else
    {
      if(osMutexAcquire(dht11MutexHandleHandle, osWaitForever) == osOK)
      {
        dht11_data.valid = 0;
        osMutexRelease(dht11MutexHandleHandle);
      }
    }
    osDelay(2000);
  }
}

/**
  * @brief  获取温湿度数据(线程安全)
  * @param  temp: 温度输出指针
  * @param  humi: 湿度输出指针
  * @retval 数据是否有效 (1: 有效, 0: 无效)
  */
uint8_t GetDHT11Data(float *temp, float *humi)
{
  uint8_t valid = 0;
  
  /* 获取互斥锁以安全读取全局数据 */
  if(osMutexAcquire(dht11MutexHandleHandle, osWaitForever) == osOK)
  {
    /* 检查数据是否有效 */
    if(dht11_data.valid)
    {
      *temp = dht11_data.temperature;
      *humi = dht11_data.humidity;
    valid = 1;
    }
    
    /* 释放互斥锁 */
    osMutexRelease(dht11MutexHandleHandle);
  }
  
  return valid;
}

4.DHT11的基础配置:

#include "DHT11.h"
#include "main.h"
#include "tim.h"  // CubeMX生成的定时器句柄头文件

void Delay_us(uint32_t us)
{
    __HAL_TIM_SET_COUNTER(&htim3, 0);  // 将计数器清零
    HAL_TIM_Base_Start(&htim3);        // 启动定时器
    while(__HAL_TIM_GET_COUNTER(&htim3) < us);  // 等待计数达到us
    HAL_TIM_Base_Stop(&htim3);         // 关闭定时器
}

/**
  * @brief  毫秒级延时
  * @param  xms 延时时长,范围:0~4294967295
  * @retval 无
  */
void Delay_ms(uint32_t xms)
{
    while(xms--)
    {
        Delay_us(1000);
    }
}

/**
  * @brief  秒级延时
  * @param  xs 延时时长,范围:0~4294967295
  * @retval 无
  */
void Delay_s(uint32_t xs)
{
    while(xs--)
    {
        Delay_ms(1000);
    }
}
void DHT11_OUT(void)
{
	GPIO_InitTypeDef  GPIO_InitStruct = {0};
 
	GPIO_InitStruct.Pin = DHT11_GPIO_PIN;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
/**
  * @brief  DATA引脚(PA7)设置为输入模式
  * @param  无
  * @retval	无 
  */
void DHT11_IN(void)
{
	GPIO_InitTypeDef  GPIO_InitStruct = {0};
 
	GPIO_InitStruct.Pin  = DHT11_GPIO_PIN;
	GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

}
/**
  * @brief  DHT11检测起始信号
  * @param  无
  * @retval 无
  */
void DHT11_Strat(void)
{
	DHT11_OUT();   //PA7设置为输出模式
	DHT11_Low;     //主机拉低总线
	HAL_Delay(20); //延迟必须大于18ms ; 
	DHT11_High;    //主机拉高总线等待DHT11响应
	Delay_us(30);   

}
/**
  * @brief  DHT11发送响应信号
  * @param  无
  * @retval 返回值0/1  0:响应成功 1:响应失败
  */
uint8_t DHT11_Check(void)
{
	uint8_t retry = 0 ;
	DHT11_IN();
	//采用while循环的方式检测响应信号
	while(DHT11_IO_IN && retry <100) // DHT11会拉低 40us ~80us
	{
		retry++;
		Delay_us(1);//1us
	}
	if(retry>=100) //判断当DHT11延迟超过80us时return 1 , 说明响应失败
	{return  1;}
	else retry =  0 ;
		
	while(!DHT11_IO_IN && retry<100)// // DHT11拉低之后会拉高 40us ~80us
	{
		retry++;
		Delay_us(1);//1us
	}
		
	if(retry>=100)
	{return 1;}
	return 0 ;
}
/*DHT11初始化*/
uint8_t DHT11_Init(void)
{	 
 	GPIO_InitTypeDef  GPIO_InitStructure;
    __HAL_RCC_GPIOA_CLK_ENABLE();  // 假设DHT11接在GPIOA,根据实际情况修改

    GPIO_InitStructure.Pin = DHT11_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStructure.Pull = GPIO_NOPULL;
    GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStructure);

    HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_SET);

    DHT11_Strat();  //复位DHT11
    return DHT11_Check();//等待DHT11的回应
} 

/**
  * @brief  DHT11读取一位数据
  * @param  无
  * @retval 返回值0/1  1:读取成功 0:读取失败
  */
uint8_t DHT11_Read_Bit(void)
{
	uint8_t retry = 0 ;
	while(DHT11_IO_IN && retry <100)//同上采用while循环的方式去采集数据
	{
		retry++;
		Delay_us(1);
	}
	retry = 0 ;
	while(!DHT11_IO_IN && retry<100)
	{
		retry++;
		Delay_us(1);
	}
 
	Delay_us(40);              //结束信号,延时40us 
	if(DHT11_IO_IN) return 1;  //结束信号后,总线会被拉高 则返回1表示读取成功
	else 
	return 0 ;
}
/**
  * @brief  DHT11读取一个字节数据
  * @param  无
  * @retval 返回值:dat 将采集到的一个字节的数据返回
  */
uint8_t DHT11_Read_Byte(void)
{
	uint8_t i , dat ;
	dat = 0 ;
	for(i=0; i<8; i++)
	{
		dat <<= 1; //通过左移存储数据
		dat |= DHT11_Read_Bit();
	}
	return dat ; 
}
/**
  * @brief  DHT11读取数据
  * @param  temp:温度值 humi :湿度值
  * @retval 返回值0/1 0:读取数据成功 1:读取数据失败
  */
uint8_t DHT11_Read_Data(uint8_t* temp , uint8_t* humi)
{
	uint8_t buf[5];        //储存五位数据
    uint8_t i;    
	DHT11_Strat();         //起始信号
	if(DHT11_Check() == 0) //响应信号
    {
		for(i=0; i<5; i++)
		{
			buf[i] = DHT11_Read_Byte();
		}
		if(buf[0]+buf[1]+buf[2]+buf[3] == buf[4]) //校验数据
		{
		    *humi = buf[0]; // 湿度
			*temp = buf[2]; // 温度
		}
	}else return 1;
	
   return 0 ;
}

/*配置主机输入输出*/

/**
  * @brief  DATA引脚(PA7)设置为输出模式
  * @param  无
  * @retval 无
  */

由于篇幅问题,这里只提供DHT11获取数据和esp8266加WiFi任务连接onenet平台的代码

第四章 常见问题与解决方案​

4.1mqttx上OneNet API 调用失败​

可能的原因是设备 ID 或 API Key 不正确。解决方案是仔细检查设备在 OneNet 平台上的注册信息,确保设备 ID 和 API Key 与代码中的配置一致。同时,检查网络连接是否正常,确保设备能够正常访问 OneNet 平台的 API 接口。​

4.2状态不同步​

设备的实际状态与 OneNet 平台显示的状态不一致,可能是由于单片机上传数据格式不正确导致的。需要检查单片机上传数据的格式是否符合 OneNet 平台的要求,包括数据的类型、长度、单位等。确保上传的数据准确无误,从而保证平台能够正确显示设备状态。​

4.3控制指令无响应​

当 OneNet 平台下发控制指令后,设备没有做出相应的反应,可能有以下原因:一是 OneNet 命令下发状态异常,需要检查平台上的命令下发记录,查看命令是否成功下发;二是单片机命令解析代码存在问题,需要检查代码中对控制指令的解析逻辑,确保能够正确提取控制参数并执行相应的操作。​这个问题和上面的都可以通过在串口的打印信息中看出来,如果实在排除不了问题,请在评论区问我。

第五章 项目前景和不足​

5.1项目前景​

本项目实现了对 LED、电机、舵机等硬件模块的本地和远程控制,具有一定的实用价值。该系统可以应用于智能家居、工业控制、智能农业等领域,例如在智能家居中,可以通过手机 APP 远程控制灯光、窗帘、电器等设备;在工业控制中,可以远程监控和控制生产设备的运行状态。随着物联网技术的不断发展,本项目具有广阔的拓展空间,可以进一步增加传感器模块,实现对环境温湿度、光照强度等参数的监测和控制,提升系统的智能化水平。​

5.2项目不足​

本项目在实际应用中也存在一些不足之处。首先,系统的稳定性和可靠性还有待提高,在网络环境不稳定的情况下,可能会出现数据传输延迟或丢失的问题。其次,系统的安全性有待加强,目前的通信过程中缺乏有效的加密机制,可能存在数据被窃取或篡改的风险。

5.3作者有话说

​其实,系统的功能还可以进一步完善,例如增加设备的故障诊断和报警功能,提高系统的易用性和可维护性,还可以在onenet平台的基础上增加微信小程序来实现,通过APP控制设备端,也可以通过移植u8g2来实现OLED界面的精美设计,外加语音模块来实现对设备端的语音控制。总之,我这个项目其实还有很多不足,但是由于时间的问题,只做出的这个半成品,希望大家在看完文章后,有不足的地方请多多指教,大家共同进步!

这里附上完整源码,已经打包放在了github上

https://github.com/tycrsg/freertos-esp8266-stm32-dht11-HAL.githttps://github.com/tycrsg/freertos-esp8266-stm32-dht11-HAL.git

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐