前言:在工业控制和物联网场景中,温湿度数据的采集与传输是常见需求。本文详细介绍了一个基于STM32微控制器的分布式温湿度监测系统解决方案。该系统采用"从控+主控"的分布式架构设计,通过工业级CAN总线实现设备间通信,同时兼具本地显示和云端数据上传功能,具有高可靠性、灵活扩展的特点。另外,系统设计灵活,可根据需求简化为纯 CAN 通信模式。

一、项目概述

1.1 系统架构

系统采用模块化设计思想,分为两个功能明确的部分,可根据实际应用场景灵活配置:

  • 从控设备(CAN-DHT11):负责通过 DHT11 传感器采集温湿度数据,本地 OLED 实时显示,并通过 CAN 总线将数据发送至主控设备。
  • 主控设备(CAN-ESP8266-DHT11):通过 CAN 总线接收从控数据,本地 OLED 显示,并通过 ESP8266 模块上传至云端平台。

1.2 核心功能

  • 从控设备:DHT11 数据采集、OLED 本地显示、CAN 数据发送、环回测试(自测)。
  • 主控设备:CAN 数据接收、OLED 本地显示、ESP8266 云端上传(可选)。
  • 灵活性:支持删除 WiFi 功能,仅保留 CAN 总线数据传输与显示。

二、硬件设计

2.1 核心硬件清单

设备型号 / 规格作用
主控 MCUSTM32F103(或同系列)核心控制、CAN/USART 外设驱动
从控 MCUSTM32F103(或同系列)核心控制、传感器数据采集
温湿度传感器DHT11采集环境温湿度(精度 ±2℃/±5%)
显示屏0.96 寸 OLED(I2C 接口)本地数据显示
无线模块ESP8266(WiFi)云端数据上传
CAN 收发器TJA1050CAN 总线电平转换

2.2 硬件连接示意图

  • 从控设备:

    • DHT11 → 任意 GPIO(如 PA5,配置为输入 / 输出切换)
    • OLED(I2C) → SCL=PB8,SDA=PB9
    • CAN → PA12(CAN_TX)、PA11(CAN_RX)→ 连接 TJA1050
    • freertos任务配置→can发送任务,OLED显示任务,dht11读取数据任务(优先级为can>dht11>oled)
    • freertos队列配置→OLED读取数据队列,can发送任务读取队列(空间超出的话要增加栈大小)
  • 主控设备:

    • OLED(I2C) → 同从控(可复用引脚定义)
    • ESP8266 → USART2(TX=PA2,RX=PA3)
    • CAN → 同从控,通过 TJA1050 连接至 CAN 总线
    • freertos任务配置→can接收任务,OLED显示任务,WiFi上传数据任务(优先级can和OLED可以相同,但是都要大于WiFi任务,防止WiFi任务阻塞项目运行)
    • freertos队列配置→OLED读取数据队列,WiFi上传数据队列(空间超出的话要增加栈大小)

从控配置:

主控配置:

三、软件设计

3.1 从控设备软件流程(CAN-DHT11)

从控核心任务:数据采集→本地显示→CAN 发送,通过 FreeRTOS 任务与消息队列实现模块解耦。

3.1.1 核心任务设计
  1. DHT11 采集任务(DHT11StartTask):

    • 初始化 DHT11 传感器,周期性(2s)读取温湿度数据。
    • 将数据封装为DHT11_Data结构体(含温度、湿度、有效性标识)。
    • 通过消息队列(OLEDQueueHandle、CANQueueHandle)将数据分别发送给 OLED 显示任务和 CAN 发送任务。
    // 关键代码片段(DHT11_task.c)
    void DHT11StartTask(void *argument) {
        DHT11_Init(); // 初始化传感器
        DHT11_Data msg = {0};
        for(;;) {
            uint8_t temp = 0, humi = 0;
            // 读取DHT11数据(带超时重试)
            if(DHT11_Read_Data(&temp, &humi) == 0) {
                msg.temperature = (float)temp;
                msg.humidity = (float)humi;
                msg.valid = 1; // 标记数据有效
            } else {
                msg.valid = 0;
            }
            // 发送数据到OLED和CAN队列
            osMessageQueuePut(OLEDQueueHandle, &msg, 0, 0);
            osMessageQueuePut(CANQueueHandle, &msg, 0, 0);
            osDelay(2000); // 2s采集一次
        }
    }
    
  2. OLED 显示任务(OLEDStartTask):

    • 从OLEDQueueHandle队列获取数据,解析为整数 + 小数部分,在 OLED 第 3 行显示温度、第 4 行显示湿度。
    // 关键代码片段(OLED_task.c)
    void OLEDStartTask(void *argument) {
        OLED_Init();
        DHT11_Data displayData;
        for(;;) {
            if(osMessageQueueGet(OLEDQueueHandle, &displayData, NULL, 100) == osOK) {
                if(displayData.valid) {
                    // 显示温度(如"Temp:25.5C")
                    uint16_t temp_int = (uint16_t)displayData.temperature;
                    uint16_t temp_dec = (uint16_t)((displayData.temperature - temp_int) * 10);
                    OLED_ShowString(3, 1, "Temp:");
                    OLED_ShowNum(3, 6, temp_int, 2);
                    OLED_ShowString(3, 8, ".");
                    OLED_ShowNum(3, 9, temp_dec, 1);
                    OLED_ShowString(3, 10, "C");
                    // 湿度显示类似...
                }
            }
            osDelay(10);
        }
    }
    
  3. CAN 发送任务(CANStartTask):

    • 从CANQueueHandle队列获取数据,将温湿度打包为 16 位整数(保留 1 位小数),通过 CAN 总线发送(ID=0x123,数据长度 4 字节)。
    // 关键代码片段(CAN_task.c)
    void CANStartTask(void *argument) {
        CAN_Init(); // 初始化CAN外设
        DHT11_Data dhtData;
        for(;;) {
            if(osMessageQueueGet(CANQueueHandle, &dhtData, NULL, 100) == osOK) {
                if(dhtData.valid) {
                    uint16_t temp = (uint16_t)(dhtData.temperature * 10);
                    uint16_t humi = (uint16_t)(dhtData.humidity * 10);
                    uint8_t data[4] = {temp>>8, temp&0xFF, humi>>8, humi&0xFF};
                    CAN_SendData(0x123, data, 4); // 发送CAN消息
                }
            }
            osDelay(10);
        }
    }
    
3.1.2 环回测试功能

从控支持 CAN 环回测试(自测模式),无需主控即可验证功能:

  • 在CAN_Config.c中,将 CAN 模式配置为CAN_MODE_LOOPBACK:
    // CAN初始化时修改模式
    hcan.Init.Mode = CAN_MODE_LOOPBACK; // 环回模式
    
  • 环回模式下,从控发送的 CAN 消息会被自身接收,通过 OLED 显示验证数据是否正常。

3.2 主控设备软件流程(CAN-ESP8266-DHT11)

主控核心任务:CAN 接收→本地显示→云端上传,同样通过 FreeRTOS 任务与队列实现。

3.2.1 核心任务设计
  1. CAN 接收任务:

    • 初始化 CAN 外设,配置过滤器接收从控发送的 ID=0x123 消息。
    • 接收中断触发后,解析数据(温度 = 高 2 字节 / 10,湿度 = 低 2 字节 / 10),通过队列发送给 OLED 和 WiFi 任务。
    // 关键代码片段(CAN_Config.c)
    void CAN_ReceiveHandler(CAN_HandleTypeDef *hcan) {
        CAN_RxHeaderTypeDef header;
        uint8_t data[8];
        DHT11_Data receivedData;
        if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &header, data) == HAL_OK) {
            if(header.StdId == 0x123 && header.DLC == 4) {
                uint16_t temp = (data[0]<<8)|data[1];
                uint16_t humi = (data[2]<<8)|data[3];
                receivedData.temperature = temp / 10.0f;
                receivedData.humidity = humi / 10.0f;
                receivedData.valid = 1;
                // 发送到OLED和WiFi队列
                osMessageQueuePut(OLEDQueueHandle, &receivedData, 0, 0);
                osMessageQueuePut(WiFiQueueHandle, &receivedData, 0, 0);
            }
        }
    }
    
  2. OLED 显示任务:与从控逻辑一致,从队列获取数据并显示(可复用从控 OLED 代码)。

#include <stdio.h>

extern osMessageQueueId_t OLEDQueueHandle;

void OLEDStartTask(void *argument)
{
    OLED_Init();
    DHT11_Data_t displayData;
    
//    printf("OLED Task Started\r\n");
    
    OLED_ShowString(3,1,"Wait...");
    for(;;) {
        // 从CAN接收队列获取数据(等待最多100ms)
        if(osMessageQueueGet(OLEDQueueHandle, &displayData, NULL, 100) == osOK) {
//					printf("oled get ok \r\n");
            if(displayData.valid) {
                // 显示温度
                uint16_t temp_int = (uint16_t)displayData.temperature;
                uint16_t temp_dec = (uint16_t)((displayData.temperature - temp_int) * 10);
                
                OLED_ShowString(3, 1, "Temp:");
                OLED_ShowNum(3, 6, temp_int, 2);
                OLED_ShowString(3, 8, ".");
                OLED_ShowNum(3, 9, temp_dec, 1);
                OLED_ShowString(3, 10, "C");
                
                // 显示湿度
                uint16_t humi_int = (uint16_t)displayData.humidity;
                uint16_t humi_dec = (uint16_t)((displayData.humidity - humi_int) * 10);
                
                OLED_ShowString(4, 1, "Humi:  ");
                OLED_ShowNum(4, 6, humi_int, 2);
                OLED_ShowString(4, 8, ".");
                OLED_ShowNum(4, 9, humi_dec, 1);
                OLED_ShowString(4, 10, "%");
                
                printf("OLED Updated: %.1fC, %.1f%%\r\n", 
                      displayData.temperature, displayData.humidity);
            } else {
                OLED_ShowString(3, 1, "Invalid Data Received!");
                osDelay(2000);
                OLED_ShowString(3, 1, "                   "); // 清空行
            }
        }
        osDelay(10);
    }
}
  1. WiFi 上传任务(WiFiStartTask):

    • 初始化 ESP8266 模块(连接 WiFi、MQTT 服务器)。
    • 从WiFiQueueHandle队列获取数据,通过Esp_PUB函数将温湿度上报至云端。
    // 关键代码片段(WiFi_task.c)
    void WiFiStartTask(void *argument) {
        ESP8266_Init(); // 初始化WiFi和MQTT
        DHT11_Data displayData;
        uint32_t lastReportTime = HAL_GetTick();
        for(;;) {
            if(osMessageQueueGet(WiFiQueueHandle, &displayData, NULL, 100) == osOK) {
                if(displayData.valid && (HAL_GetTick() - lastReportTime > 5000)) {
                    // 每5秒上报一次
                    Esp_PUB("temp", (uint16_t)displayData.temperature);
                    Esp_PUB("humi", (uint16_t)displayData.humidity);
                    lastReportTime = HAL_GetTick();
                }
            }
            osDelay(10);
        }
    }
    
3.2.2 简化模式(无云端功能)

若无需云端上传,仅需删除 WiFi 相关代码:

  • 不创建WiFiStartTask任务。
  • 注释或删除ESP8266_Init、Esp_PUB等 WiFi 相关函数。
  • 主控仅保留 CAN 接收和 OLED 显示功能。

四、测试验证

4.1 从控环回测试

  1. 配置 CAN 为环回模式(CAN_MODE_LOOPBACK)。
  2. 上电后,从控 OLED 应显示 DHT11 采集的温湿度(如 "Temp:25.5C"、"Humi:60.0%")。
  3. 串口调试助手可观察到 CAN 发送与接收日志(如 "[TX] Temp:25.5C... [RX] ID:0x123...")。

4.2 主从通信测试

  1. 从控、主控通过 CAN 总线连接(TJA1050 的 CAN_H/CAN_L 互连,注意终端电阻)。
  2. 从控配置为正常模式(CAN_MODE_NORMAL),主控同样配置为正常模式。
  3. 上电后,主控 OLED 应同步显示从控采集的温湿度,与从控显示一致。

4.3 云端上传测试

  1. 配置 ESP8266 的 WiFi 信息(SSID、密码)和 MQTT 服务器地址(Url、username等)。
  2. 主控上电后,通过 MQTT 客户端工具(如 MQTTX)订阅上报主题,应能收到温湿度数据(如{"id":"123","params":{"temp":{"value":25}}})。

五、总结与扩展

本系统基于STM32F103C8T6微控制器,通过CAN总线(波特率125kbps)实现了1个主控设备与1个从控设备之间的温湿度数据传输。系统采用FreeRTOS实时操作系统,通过任务队列(如创建了CAN接收队列、OLED显示队列等)实现了模块化设计,各功能模块解耦程度高,代码可维护性强。从控设备特别设计了环回测试模式(通过拨码开关触发),在该模式下设备会自动生成测试数据包并通过CAN总线发送,同时接收自身发送的数据,极大简化了调试流程。主控设备可选配ESP8266 WiFi模块(通过AT指令控制),用户可根据实际需求决定是否启用物联网功能。

扩展方向:

  1. 多从控设备支持

    • 通过分配不同的CAN ID(如0x101、0x102等)区分各从控设备
    • 主控设备需增加设备发现与注册机制
    • 示例场景:可应用于温室大棚的多点温湿度监测
  2. 传感器升级方案

    • 当前使用DHT11(温度±2℃,湿度±5%精度)
    • 可替换为SHT30(温度±0.2℃,湿度±2%精度)
    • 需注意I2C接口改造及供电电压调整(SHT30工作电压2.4-5.5V)
  3. 异常报警功能

    • 在main.c中增加阈值检测逻辑
    • 触发条件示例:温度>35℃或湿度>80%
    • 报警方式:ESP8266发送邮件/短信报警,同时OLED显示警告图标
  4. 低功耗优化

    • 硬件层面:选用低功耗型号STM32L系列
    • 软件策略:
      • OLED息屏:30秒无操作后关闭显示
      • 唤醒方式:板载按键中断唤醒
      • 显示刷新:唤醒后立即读取最新传感器数据

代码适配说明:

  1. FreeRTOS配置需根据具体芯片型号调整堆栈大小
  2. 示例工程包含完整的Keil MDK项目文件,适合STM32初学者学习CAN总线通信与物联网应用开发,也可直接用于课程设计或毕业设计项目。

完整源码已上传github平台https://github.com/tycrsg/FREERTOS-CAN-DHT11.githttps://github.com/tycrsg/FREERTOS-CAN-DHT11.git

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐