STM32+FreeRTOS+OneNet物联网温湿度监测:从硬件选型到微信小程序对接全流程

最近几年,身边不少创客朋友和嵌入式爱好者都开始尝试物联网项目,尤其是环境监测这类实用又有趣的方向。我自己也陆陆续续做了好几个,从最开始的裸机轮询,到后来引入实时操作系统,踩过的坑、焊坏的板子,加起来都能写本小册子了。今天想和大家深入聊聊的,是一个相对成熟且极具学习价值的方案:基于 STM32FreeRTOSOneNet 云平台的温湿度监测系统。这个项目麻雀虽小,五脏俱全,它不仅仅是将传感器数据传到网上那么简单,更涉及了硬件选型、实时任务调度、网络协议栈对接、云端数据流处理以及移动端应用展示这一整套现代物联网开发的完整链路。

对于初学者而言,最大的挑战往往不是写代码,而是如何把一堆分散的模块(单片机、Wi-Fi、传感器、云平台)有机地整合起来,并让它们稳定、可靠地协同工作。网上教程很多,但要么过于零散,只讲某一部分;要么版本老旧,无法对接现在云平台的新接口。这篇文章,我将结合自己最近一次项目迭代的经验,为你梳理一条从零到一的清晰路径。我们会重点关注那些容易让人“卡壳”的细节,比如ESP8266固件的选择与烧录、OneNet新版MQTT协议的配置、以及在FreeRTOS环境下如何优雅地处理多任务与网络通信。无论你是想完成毕业设计,还是为自己的工作室搭建一个环境监控节点,相信这些实践中的心得都能给你带来实实在在的帮助。

1. 硬件选型与核心电路设计

硬件是项目的骨架,选型不当会让后续的软件开发举步维艰。我们的目标是:在满足功能、保证稳定性的前提下,尽可能选择性价比高、资料丰富、易于采购的元件。

1.1 主控制器:STM32F103C8T6为何仍是首选

在众多STM32型号中,STM32F103C8T6(俗称“蓝色药丸”)历经市场考验,至今仍是入门级物联网项目的性价比之王。它基于ARM Cortex-M3内核,主频72MHz,拥有64KB Flash和20KB RAM,对于运行FreeRTOS和处理MQTT协议来说,资源是足够充裕的。

注意:市场上存在大量国产仿制型号(如GD32、APM32),其性能与原厂ST产品基本兼容,但在某些极端时序要求的外设(如高级定时器、USB)上可能存在细微差异。对于温湿度采集和串口通信这类应用,国产芯片是完全可用的,且成本更低。

除了核心性能,它的外设资源也正好契合我们的需求:

  • USART1 & USART2/3:至少需要两个串口,一个用于连接ESP8266进行Wi-Fi通信,另一个可以用于调试信息打印或连接其他模块(如OLED屏)。
  • 足够的GPIO:用于连接DHT11传感器、控制LED状态灯等。
  • SWD调试接口:这是高效开发和调试的必备,相比古老的JTAG,它只需要两根线(SWDIO, SWCLK)。

下面是一个简化的核心引脚分配表示例,你可以在绘制原理图时参考:

外设模块引脚功能STM32F103C8T6 引脚备注
DHT11数据线PA11单总线协议,需接4.7K-10K上拉电阻
ESP8266-01STXPA9 (USART1_RX)接ESP8266的TX脚
RXPA10 (USART1_TX)接ESP8266的RX脚
CH_PD (使能)3.3V接高电平使能模块
VCC3.3V务必使用3.3V供电
OLED (I2C)SCLPB6 (I2C1_SCL)可选,用于本地显示
SDAPB7 (I2C1_SDA)可选,用于本地显示
用户LED状态指示PC13板载LED,可用于指示网络连接状态
调试串口TXPA2 (USART2_TX)连接USB转TTL,用于打印日志
RXPA3 (USART2_RX)连接USB转TTL

1.2 传感与通信模块:DHT11与ESP8266的实战细节

DHT11 是一款经典的温湿度复合传感器,输出为单总线数字信号。它的优点是价格低廉、接口简单,但需要注意其响应速度较慢(每次读取间隔需大于1秒),且湿度精度一般(±5%RH)。对于要求不高的环境监测完全够用。接线时,数据引脚需要通过一个4.7KΩ的上拉电阻接到3.3V,以确保信号稳定。

ESP8266 系列模块是连接物联网的关键。推荐使用 ESP-01S 型号,它体积小巧,自带板载PCB天线。这里有几个极易出错的点:

  1. 供电:ESP8266在发射数据时瞬时电流可能超过200mA,因此绝对不能直接用STM32开发板上的3.3V引脚供电,否则会导致STM32复位或Wi-Fi连接不稳定。必须使用独立的LDO稳压芯片(如AMS1117-3.3)或外部3.3V电源为其供电。
  2. 固件:模块出厂固件可能不支持MQTT协议。我们需要为其烧录 AT固件,并最好选择支持MQTT功能的版本。烧录过程需要用到USB转TTL工具和几个GPIO的控制,具体步骤我会在下一章详细说明。
  3. 电平转换:ESP8266的串口是3.3V电平,与STM32直接连接即可,无需电平转换。

1.3 电源与PCB布局考量

一个稳定的电源是整个系统可靠运行的基石。建议的电源方案是:外部输入5V(可以是USB或适配器),通过一个DC-DC或LDO芯片转换为3.3V,同时为STM32和ESP8266供电。在ESP8266的电源引脚附近,务必并联一个100μF的电解电容和一个0.1μF的陶瓷电容,以应对其工作时的大电流波动。

如果打算自己设计PCB,布局时请遵循以下原则:

  • MCU的退耦电容(通常为0.1μF)应尽可能靠近其电源引脚放置。
  • ESP8266模块尽量远离模拟电路(虽然本项目模拟部分不多)和晶振电路,减少干扰。
  • DHT11的数据走线不宜过长,并远离高频信号线。

2. 软件环境搭建与FreeRTOS基础任务设计

硬件准备就绪后,我们进入软件世界。首先需要一个高效的开发环境和对FreeRTOS的基本理解。

2.1 开发环境与工程模板创建

我习惯使用 Keil MDK (ARMCC)STM32CubeIDE (GCC)。对于初学者,STM32CubeMX + STM32CubeIDE的组合更为友好,因为它能图形化配置引脚和时钟,并一键生成包含FreeRTOS的初始化代码。

首先,用STM32CubeMX创建一个新工程,选择MCU型号为STM32F103C8Tx。关键配置步骤如下:

  1. 时钟树:将HCLK设置为72MHz,这是F103的极限性能。
  2. 引脚分配:根据上一章的表格,配置USART1、USART2、I2C1等外设引脚。
  3. 中间件:在“Middleware”选项卡中,激活 FREERTOS,并使用“CMSIS_V2”接口模式,这是当前推荐的方式。
  4. 任务创建:在FreeRTOS配置页,我们可以预先创建几个任务。例如,创建一个名为StartDefaultTask的默认任务,用于硬件初始化和创建其他任务。

生成代码后,你会得到一个已经包含FreeRTOS内核、HAL库和基础引脚配置的完整工程。接下来,我们需要在main.cStartDefaultTask函数中,初始化各个硬件模块。

void StartDefaultTask(void *argument)
{
  /* 硬件初始化 */
  MX_GPIO_Init();
  MX_USART1_UART_Init(); // 用于ESP8266
  MX_USART2_UART_Init(); // 用于调试打印
  MX_I2C1_Init(); // 用于OLED

  /* 初始化传感器和Wi-Fi模块 */
  DHT11_Init();
  ESP8266_Init(&huart1); // 传入USART1的句柄

  /* 创建应用任务 */
  xTaskCreate(Task_SensorRead, "Sensor", 128, NULL, 3, NULL);
  xTaskCreate(Task_WiFiComm, "WiFi", 256, NULL, 4, NULL);
  xTaskCreate(Task_Display, "Display", 128, NULL, 2, NULL);

  /* 删除自身,因为初始化已完成 */
  vTaskDelete(NULL);
}

2.2 FreeRTOS任务划分与优先级设计

在物联网设备中,不同的功能对实时性的要求不同。合理的任务划分和优先级设置是系统流畅运行的关键。我们的系统可以抽象为以下几个核心任务:

  • Task_SensorRead (优先级: 3):负责周期性读取DHT11的温湿度数据。由于DHT11响应慢,这个任务需要阻塞等待其读取完成。读取成功后,将数据放入一个队列(Queue)流缓冲区(Stream Buffer) 中,供其他任务消费。周期可设为2秒。
  • Task_WiFiComm (优先级: 4):这是系统的核心通信任务,负责与ESP8266交互,并通过MQTT协议与OneNet云端通信。它需要从传感器任务获取数据,并处理来自云端的命令(如下发阈值)。优先级设为最高,因为网络通信的及时性直接影响用户体验。
  • Task_Display (优先级: 2):负责在OLED屏幕上刷新显示当前数据和系统状态。优先级可以设低一些,因为显示更新延迟几百毫秒用户通常感知不到。

任务间通信推荐使用FreeRTOS的 Queue。例如,创建一个包含温湿度数据的结构体队列:

typedef struct {
    float temperature;
    float humidity;
} SensorData_t;

QueueHandle_t xSensorDataQueue;
xSensorDataQueue = xQueueCreate(5, sizeof(SensorData_t)); // 创建能容纳5个数据项的队列

传感器任务在读取数据后,调用xQueueSend()将数据发送到队列;Wi-Fi任务则调用xQueueReceive()阻塞等待新数据到来。这种生产者-消费者模型能有效解耦任务,提高系统可靠性。

3. ESP8266固件烧录与OneNet MQTT协议对接

这是连接物理世界与数字云端的桥梁,也是项目中最容易遇到问题的环节。

3.1 ESP8266 AT固件烧录指南

你需要准备一个 USB转TTL模块(如CH340、CP2102)和几根杜邦线。ESP-01S的烧录接线方式如下:

ESP-01S 引脚连接至说明
VCC3.3V必须确保是稳定的3.3V
GNDGND共地
TXUSB转TTL的RX
RXUSB转TTL的TX
GPIO0GND拉低进入烧录模式
CH_PD (EN)3.3V使能引脚接高电平
RST可接按钮到GND用于手动复位

连接好后,使用 乐鑫官方的Flash下载工具 (ESPFlashDownloadTool)Arduino IDE 进行烧录。我推荐使用安信可科技整理发布的 AT固件合并包,它通常集成了MQTT、SSL等常用功能。在烧录工具中,选择正确的串口号、波特率(通常115200),并加载固件文件(.bin)。关键步骤是:在开始烧录前,先将GPIO0接地,然后给模块上电,最后点击工具的“START”按钮。 烧录成功后,将GPIO0断开与地的连接,重新上电,模块便进入正常运行模式。

3.2 OneNet平台新版MQTT设备创建

OneNet平台近年来进行了升级,旧版的多协议接入逐渐被功能更强大的新版物联网平台取代。登录OneNet官网,在控制台创建新产品和设备:

  1. 创建产品:选择“设备接入”->“产品”,创建一个新产品。关键参数:联网方式选“Wi-Fi”,数据协议选“MQTT(旧版)”(目前更稳定易用),其他根据情况填写。
  2. 创建设备:在产品下添加设备。成功后,平台会生成三个至关重要的信息:设备ID (device_id)产品ID (product_id)鉴权信息 (auth_info)。请妥善保存。
  3. 数据流与APIKey:在设备详情页,你可以创建数据流(如temperature, humidity)。同时,在产品详情页的“API管理”中,可以添加一个Master-APIkey,用于小程序端调用数据查询API。

3.3 STM32端MQTT客户端实现要点

我们不需要在资源有限的STM32上实现完整的MQTT协议栈,而是通过ESP8266的AT指令,让其作为MQTT客户端代理。核心流程如下:

  1. 连接Wi-Fi:发送AT+CWJAP="你的Wi-Fi名","密码"
  2. 连接OneNet MQTT服务器:新版OneNet的MQTT服务器地址为mqtts.heclouds.com,端口1883。由于我们使用AT指令,需要先建立TCP连接,再发送MQTT CONNECT报文。这个过程需要按照OneNet的规则拼接用户名、密码等参数。一个常见的痛点是如何生成正确的password。它是由鉴权信息 (auth_info) 经过MD5或SHA1计算得到的token,具体算法OneNet文档有说明,但我们可以先用一个在线工具生成,并在代码中写死,以简化调试。
  3. 订阅与发布:连接成功后,订阅设备命令主题(如$sys/{pid}/{dev}/cmd/request/#),以便接收云端下发的指令。然后,周期性(或在传感器数据更新时)向数据点主题(如$sys/{pid}/{dev}/dp/post/json)发布JSON格式的温湿度数据。

下面是一段简化的代码逻辑,展示如何在FreeRTOS任务中处理:

void Task_WiFiComm(void *pvParameters)
{
    SensorData_t data;
    char mqttMsg[128];

    ESP8266_ConnectWiFi("SSID", "PASSWORD");
    vTaskDelay(pdMS_TO_TICKS(2000));

    ESP8266_ConnectMQTTServer();
    vTaskDelay(pdMS_TO_TICKS(2000));

    ESP8266_MQTTSubscribe();
    
    for(;;)
    {
        // 等待传感器数据
        if(xQueueReceive(xSensorDataQueue, &data, portMAX_DELAY) == pdTRUE)
        {
            // 构造JSON字符串
            sprintf(mqttMsg, "{\"id\":123,\"dp\":{\"temperature\":[{\"v\":%.1f}],\"humidity\":[{\"v\":%.1f}]}}",
                    data.temperature, data.humidity);
            
            // 通过ESP8266发布MQTT消息
            ESP8266_MQTTPublish(mqttMsg);
        }
        // 处理可能来自云端的命令(非阻塞检查)
        ESP8266_ProcessCommand();
        
        vTaskDelay(pdMS_TO_TICKS(100)); // 让出CPU时间片
    }
}

在实际项目中,你需要将ESP8266_ConnectMQTTServer等函数实现为基于AT指令的可靠通信流程,并加入超时重试和错误处理机制。

4. 微信小程序开发与数据可视化

设备数据成功上传至OneNet后,我们需要一个终端来查看和控制。微信小程序因其无需安装、跨平台的特性,成为非常理想的载体。

4.1 小程序端获取OneNet数据

小程序端通过调用OneNet提供的 HTTP API 来获取设备的最新数据。你需要在app.js或页面js中封装一个网络请求函数。核心是使用小程序的wx.request API,并正确设置header,其中包含在OneNet产品详情页获取的 Master-APIkey

// 获取设备数据流
function getDeviceDatastream(deviceId, datastreamId, apiKey, callback) {
  wx.request({
    url: `https://api.heclouds.com/devices/${deviceId}/datastreams/${datastreamId}`,
    header: {
      'api-key': apiKey // 这里填入你的Master-APIkey
    },
    success(res) {
      if (res.data && res.data.data && res.data.data.current_value !== undefined) {
        callback(null, res.data.data.current_value);
      } else {
        callback('数据解析失败', null);
      }
    },
    fail(err) {
      callback(err, null);
    }
  })
}

4.2 构建直观的监控界面

小程序的UI设计可以简洁明了。一个典型的监控页面可能包含:

  1. 实时数据卡片:用大字体清晰展示当前的温度和湿度数值,并配以直观的图标(如太阳、水滴)。
  2. 历史图表:使用 ec-canvas 引入ECharts图表库,绘制温湿度随时间变化的折线图,可以直观反映环境变化趋势。
  3. 状态指示:根据设定的阈值(如温度高于30℃),改变卡片颜色或显示警告图标。
  4. 手动控制区域(可选):如果设备端接了继电器控制风扇或加湿器,可以在这里添加按钮,通过OneNet的命令下发API向设备发送控制指令。

page.jsonLoadonShow生命周期函数中,定时调用数据获取函数(例如每5秒一次),并更新页面数据。为了提升体验,可以加入下拉刷新功能。

4.3 安全与体验优化

  • APIKey管理绝对不要将APIKey硬编码在小程序前端代码中!这会导致密钥泄露。更安全的做法是搭建一个自己的后端代理服务器(可以用云函数如微信云开发、或自己的轻量服务器)。小程序请求你自己的服务器,再由服务器携带APIKey去请求OneNet API。这样可以将APIKey保护在后端。
  • 数据缓存:为了避免频繁请求API,可以将获取到的数据缓存在小程序的本地存储(wx.setStorage)中,并在网络不可用时显示缓存数据。
  • 连接状态:在界面上显示设备的在线/离线状态。OneNet的设备详情API会返回设备最后的活跃时间,可以通过计算时间差来判断是否离线。

5. 系统联调与常见问题排查

所有部分开发完成后,进入最关键的联调阶段。问题往往出现在模块间的交互上。

问题一:ESP8266连接Wi-Fi或OneNet频繁失败。

  • 检查供电:这是头号嫌疑犯。用万用表测量ESP8266 VCC引脚在发射数据时的电压,如果跌落到3.0V以下,说明电源功率不足。
  • 检查AT指令响应:在调试串口打印所有与ESP8266的交互数据,确认每一条AT指令都收到了预期的“OK”回复。注意指令的格式和结尾的回车换行符(\r\n)。
  • 检查网络信息:确保Wi-Fi密码正确,并且路由器没有设置MAC地址过滤。OneNet的设备三元组信息(产品ID、设备ID、鉴权信息)务必核对无误。

问题二:FreeRTOS运行一段时间后死机或重启。

  • 堆栈溢出:这是多任务系统最常见的问题。在FreeRTOSConfig.h中增加configCHECK_FOR_STACK_OVERFLOW的配置,并在调试时查看任务栈使用情况。适当增加Task_WiFiComm等任务的栈大小。
  • 队列阻塞:检查队列操作。确保发送和接收队列时没有在中断服务程序(ISR)中错误地调用阻塞版本的函数(应使用xQueueSendFromISR)。
  • 看门狗:如果开启了独立看门狗(IWDG),确保在所有任务中定期“喂狗”,防止因某个任务卡死导致系统复位。

问题三:小程序无法获取到数据。

  • 跨域问题:OneNet的API支持CORS,一般不会有此问题。如果使用了自己的代理服务器,需确保服务器响应头中包含正确的CORS设置。
  • APIKey权限:确认使用的Master-APIkey具有该设备的数据查询权限。
  • 设备未上线:在小程序请求数据前,务必确认STM32设备已经成功连接OneNet并在线。可以在OneNet控制台查看设备状态。

问题四:数据上传延迟大或不稳定。

  • 网络信号:检查ESP8266所在位置的Wi-Fi信号强度。
  • MQTT Keep Alive:在连接MQTT服务器时,设置一个合理的“保活”时间(如60秒),让连接在空闲时也能维持。
  • 任务优先级:确保Task_WiFiComm有足够高的优先级,避免因其他低优先级任务长时间占用CPU而延误网络数据发送。

调试是一个需要耐心和逻辑分析的过程。我自己的习惯是,在代码的关键节点(如任务开始、收到数据、发送指令前后)通过调试串口打印状态信息,并给不同任务的信息加上前缀,例如[WiFi][Sensor],这样在复杂的日志中也能快速定位问题来源。当所有模块开始协同工作,小程序上第一次成功显示出从自己亲手搭建的硬件传回的实时数据时,那种成就感是无可替代的。这个项目所涵盖的知识点——从MCU编程、实时操作系统、网络协议到移动端开发——几乎构成了一个现代嵌入式物联网工程师的核心技能树。希望你在实现它的过程中,不仅收获了一个能用的作品,更构建起一套解决问题的系统化思维。

Logo

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

更多推荐