
最近在做一个智能农业大棚的项目客户提了一个很实际的需求能不能实时看到棚里的温度和湿度数据最好能传到手机上设备要稳定成本还不能太高。这让我想起了几年前做类似项目时用一堆分立元件、自己写协议、调试串口的痛苦经历。现在基于STM32和成熟物联网模块的方案已经能让这件事变得像搭积木一样简单。今天要分享的就是一个完全开源的“基于STM32的温湿度在线监测系统”。它绝不仅仅是一个简单的传感器读数Demo。这个项目的核心价值在于它完整地演示了如何将一个嵌入式设备的数据稳定、可靠、低成本地“在线化”并最终呈现在一个现代Web界面上。对于想从单片机裸机开发迈向物联网应用的开发者来说这是一个绝佳的“全栈”练手项目。如果你正在学习STM32想了解如何连接传感器、如何处理数据、如何通过4G/NB-IoT/Wi-Fi上传数据以及后端和前端如何接收并展示这些数据那么这篇文章就是为你准备的。我会从硬件选型、软件架构、代码实现到云端部署一步步拆解并提供完整的开源代码。你会发现构建一个可用的物联网监测系统门槛并没有想象中那么高。1. 为什么你需要一个“在线”监测系统在开始技术细节之前我们先明确一个问题为什么是“在线监测”传统的本地LCD显示、SD卡存储方案不行吗当然可以但它们解决的是不同维度的问题。本地方案的核心是实时查看与离线记录而在线方案的核心是远程感知与集中管理。想象一下这些场景设备部署在野外或难以触及的角落比如高山气象站、分布式水塔、大型仓库的角落。你不可能每次都跑过去抄表。需要同时监控成百上千个点比如连锁店内的冷链柜、智慧农业的多个大棚。人工巡检的成本是天文数字。数据需要用于进一步分析和预警当温湿度超过阈值时立即给管理员手机发送告警短信或推送而不是等下次巡检才发现问题。历史数据回溯与报表生成需要分析过去一周、一月的温湿度变化趋势为决策提供依据。“在线化”就是将单片机的数据通过无线网络注入到互联网的数据流中。STM32负责精准采集和初步处理无线模块负责可靠传输云平台负责海量存储、实时计算和可视化展示。三者各司其职构成了现代物联网监测的典型架构。我们这个项目就是要打通从“传感器”到“网页图表”的完整链路。2. 系统架构与核心组件选型一个完整的在线监测系统通常包含以下四层感知与控制层以STM32为核心连接温湿度传感器负责数据采集、滤波和本地逻辑控制。网络传输层负责将STM32处理后的数据打包通过无线网络发送到远端的云服务器。这是实现“在线”的关键。云平台层接收并存储海量设备数据提供设备管理、数据解析、规则引擎如超限报警和API接口。应用层面向最终用户的Web页面或手机APP用于数据可视化、设备状态监控和远程设置。对于个人开发者或中小型项目自建完整的云平台和后端成本高昂。因此本项目采用了一种高性价比的混合架构硬件端STM32 模块完全自主开发掌握核心。云端与服务端选用成熟的第三方物联网云平台如阿里云物联网平台、OneNET、ThingsBoard开源版来承接设备连接和数据存储极大降低了后端开发复杂度。前端使用简单的Web技术如Vue.js ECharts调用云平台API来展示数据。2.1 硬件选型清单与考量以下是本项目的核心硬件组件选择它们是基于稳定性、通用性和成本的综合考虑组件推荐型号说明替代方案主控MCUSTM32F103C8T6 (蓝桥杯/正点原子开发板常用)ARM Cortex-M3内核72MHz主频64KB Flash20KB RAM。性能足够资料极多性价比之王。STM32G系列性能更强STM32F4系列带浮点单元温湿度传感器DHT11数字输出单总线通信成本极低。精度湿度±5%RH温度±2℃。适用于要求不高的场合。DHT22AM2302精度更高湿度±2%RH温度±0.5℃。SHT30I2C接口工业级精度和稳定性。无线传输模块ESP8266 (Wi-Fi)最流行的Wi-Fi SOC可直接运行AT指令或编程。适合有Wi-Fi覆盖的环境。ESP32功能更强支持蓝牙。4G Cat.1模块如移远EC200S无Wi-Fi时用蜂窝网络。NB-IoT模块如移远BC95低功耗适合电池供电。显示模块可选0.96寸 OLED (SSD1306驱动)I2C接口用于本地实时显示方便调试。LCD1602 TFT屏幕其他USB转TTL串口模块用于程序下载和调试打印。ST-Link/V2调试器选型核心逻辑精度要求如果只是看个大概趋势DHT11足够如果需要精确控制如实验室、精密仓储建议选择SHT30或更专业的传感器。网络环境有稳定Wi-Fi首选ESP8266最简单设备在移动或无Wi-Fi场景必须选择4G或NB-IoT模块。功耗考量常年插电的设备无需考虑电池供电且需长期工作如一年NB-IoT是首选但需注意其数据传输的延迟和成本。本项目将以STM32F103C8T6 DHT11 ESP8266 (AT指令模式)这一最经典的组合进行详解其他组合的原理相通。3. 开发环境与工程准备在开始写代码前请确保你的开发环境已就绪。3.1 软件环境准备集成开发环境 (IDE)Keil uVision 5 (MDK-ARM)STM32开发最主流的IDE之一。需要安装对应的STM32F1 Device Family Pack。STM32CubeIDEST官方推出的免费IDE基于Eclipse集成STM32CubeMX配置工具生成代码更方便。本项目示例将基于此环境串口调试助手用于查看STM32和ESP8266的打印信息。如XCOM、SSCOM、Putty等。网络调试助手用于模拟TCP服务器测试ESP8266的网络连接。如NetAssist。前端开发环境可选Node.js、Vue CLI用于运行和构建前端项目。3.2 创建STM32CubeMX工程STM32CubeMX能图形化配置引脚、时钟、外设生成初始化代码极大提高效率。新建项目打开STM32CubeMX选择STM32F103C8Tx。配置时钟在RCC中将HSE设置为Crystal/Ceramic Resonator。在Clock Configuration标签页将系统时钟HCLK配置为72MHz。配置串口这是与ESP8266通信的关键。启用USART1或USART2根据你的板子连接。模式为Asynchronous异步。配置波特率为115200与ESP8266默认波特率一致。开启串口中断NVIC Settings中使能USARTx global interrupt便于接收ESP8266的响应。配置GPIO找到连接DHT11数据线的引脚例如PA0将其设置为GPIO_Output初始状态为高电平。实际上DHT11的单总线协议需要动态切换引脚的输入输出模式我们将在代码中实现。配置一个LED引脚如PC13蓝桥杯板上的用户LED用于指示系统状态。生成代码在Project Manager标签页设置项目名称和路径选择Toolchain / IDE为STM32CubeIDE。点击GENERATE CODE生成工程。4. 核心驱动与逻辑代码实现生成了基础工程后我们需要在Core/Src/main.c和自行添加的文件中编写业务逻辑。4.1 DHT11温湿度传感器驱动DHT11使用单总线协议时序要求严格。我们需要编写一个可靠的驱动。首先在Core/Inc文件夹下创建dht11.h头文件// dht11.h #ifndef __DHT11_H #define __DHT11_H #include main.h // 根据你的硬件连接修改 #define DHT11_GPIO_PORT GPIOA #define DHT11_GPIO_PIN GPIO_PIN_0 // 函数声明 void DHT11_Init(void); uint8_t DHT11_Read_Data(uint8_t *temperature, uint8_t *humidity); uint8_t DHT11_Check_Response(void); void DHT11_Delay_us(uint16_t us); // 微秒级延时函数需要根据系统时钟实现 #endif接着在Core/Src文件夹下创建dht11.c源文件。这里给出核心的数据读取函数// dht11.c (部分核心代码) #include dht11.h // 微秒延时函数需根据你的系统时钟频率实现此处为示例 void DHT11_Delay_us(uint16_t us) { uint32_t ticks us * (SystemCoreClock / 1000000) / 5; // 粗略计算 while(ticks--) { __NOP(); } } uint8_t DHT11_Read_Data(uint8_t *temp, uint8_t *humi) { uint8_t buf[5] {0}; uint8_t i, j; // 1. 主机发出起始信号 HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_RESET); DHT11_Delay_us(18000); // 拉低至少18ms HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_SET); DHT11_Delay_us(30); // 拉高20-40us // 2. 设置引脚为输入等待DHT11响应 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT11_GPIO_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(DHT11_GPIO_PORT, GPIO_InitStruct); // 3. 检测DHT11响应 if(DHT11_Check_Response() 0) { return 0; // 响应失败 } // 4. 读取40位数据 (8位湿度整数8位湿度小数8位温度整数8位温度小数8位校验和) for(i0; i5; i) { for(j0; j8; j) { // 等待低电平结束表示数据位开始 while(HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN) GPIO_PIN_RESET); DHT11_Delay_us(40); // 延时40us后检测电平 if(HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN) GPIO_PIN_SET) { buf[i] | (1 (7-j)); // 高电平表示‘1’ // 等待高电平结束 while(HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN) GPIO_PIN_SET); } // 低电平表示‘0’无需操作 } } // 5. 校验数据 if(buf[4] (buf[0] buf[1] buf[2] buf[3])) { *humi buf[0]; *temp buf[2]; return 1; // 读取成功 } return 0; // 校验失败 }4.2 ESP8266 WiFi模块驱动AT指令我们将ESP8266连接到STM32的USART1使用AT指令控制。首先封装一个发送指令并等待响应的函数。在Core/Src/main.c中或者单独创建esp8266.c/h文件// 在 main.c 中或 esp8266.c 中的示例函数 #include string.h UART_HandleTypeDef huart1; // CubeMX生成的句柄 uint8_t uart_rx_buf[256]; // 接收缓冲区 uint8_t rx_len 0; // 发送AT指令并等待指定响应简化版 uint8_t ESP8266_SendCmd(char *cmd, char *expect_resp, uint32_t timeout) { char resp_buf[128] {0}; uint32_t start_tick HAL_GetTick(); // 清空接收缓冲区 rx_len 0; memset(uart_rx_buf, 0, sizeof(uart_rx_buf)); // 发送指令 HAL_UART_Transmit(huart1, (uint8_t*)cmd, strlen(cmd), 1000); HAL_UART_Transmit(huart1, (uint8_t*)\r\n, 2, 100); // 发送回车换行 // 等待响应 while((HAL_GetTick() - start_tick) timeout) { // 这里需要实现UART中断接收将数据存入uart_rx_buf并更新rx_len // 假设数据已接收在 uart_rx_buf 中 if(strstr((char*)uart_rx_buf, expect_resp) ! NULL) { return 1; // 成功 } HAL_Delay(10); } return 0; // 超时失败 } // 在main函数初始化部分配置ESP8266 void ESP8266_Init(void) { HAL_Delay(2000); // 等待模块启动 // 1. 测试AT指令 while(ESP8266_SendCmd(AT, OK, 2000) 0) { HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // LED闪烁指示未就绪 HAL_Delay(500); } // 2. 设置为Station模式设备模式 ESP8266_SendCmd(ATCWMODE1, OK, 5000); // 3. 连接Wi-Fi (替换你的SSID和密码) char wifi_cmd[64]; sprintf(wifi_cmd, ATCWJAP\Your_SSID\,\Your_Password\); while(ESP8266_SendCmd(wifi_cmd, OK, 10000) 0) { // 连接失败重试或处理 HAL_Delay(1000); } // 4. 启用多连接 ESP8266_SendCmd(ATCIPMUX0, OK, 2000); // 单连接模式 // 连接成功指示 HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); }4.3 主程序逻辑与数据上传主循环中我们周期性地读取传感器数据并通过ESP8266以TCP协议发送到服务器。// main.c 中的主循环部分 (while(1)) int main(void) { // ... HAL初始化系统时钟配置等由CubeMX生成 MX_GPIO_Init(); MX_USART1_UART_Init(); // ... DHT11_Init(); ESP8266_Init(); // 初始化Wi-Fi并连接 uint8_t temperature, humidity; char tcp_send_buf[128]; uint32_t last_send_time 0; while (1) { // 每5秒读取并发送一次数据 if(HAL_GetTick() - last_send_time 5000) { last_send_time HAL_GetTick(); if(DHT11_Read_Data(temperature, humidity)) { // 1. 建立TCP连接到服务器例如IP为192.168.1.100端口为8080 char tcp_conn_cmd[64]; sprintf(tcp_conn_cmd, ATCIPSTART\TCP\,\192.168.1.100\,8080); if(ESP8266_SendCmd(tcp_conn_cmd, CONNECT, 5000)) { // 2. 准备要发送的数据例如JSON格式 sprintf(tcp_send_buf, {\temp\:%d,\humi\:%d}, temperature, humidity); // 3. 发送数据 char send_len_cmd[32]; sprintf(send_len_cmd, ATCIPSEND%d, strlen(tcp_send_buf)); if(ESP8266_SendCmd(send_len_cmd, , 2000)) { HAL_UART_Transmit(huart1, (uint8_t*)tcp_send_buf, strlen(tcp_send_buf), 1000); // 等待发送完成提示 // 这里可以简单延时或等待SEND OK响应 HAL_Delay(100); } // 4. 关闭连接根据服务器协议也可保持长连接 ESP8266_SendCmd(ATCIPCLOSE, CLOSED, 2000); } } else { // 读取传感器失败 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } } HAL_Delay(100); // 主循环延时 } }5. 服务器端与数据可视化简易方案设备端的数据需要有一个服务器来接收。这里提供一个最简单的Python Flask服务器示例运行在你的电脑或云服务器上用于接收并存储数据。# server.py from flask import Flask, request, jsonify import json import time from datetime import datetime app Flask(__name__) # 用于存储最新数据生产环境请用数据库 latest_data {} app.route(/upload, methods[POST]) def upload_data(): try: # 假设设备发送的是JSON格式的POST请求体 # 实际中ESP8266可能以TCP原始数据发送需要根据协议解析 data request.get_json() if data and temp in data and humi in data: latest_data.update(data) latest_data[timestamp] datetime.now().strftime(%Y-%m-%d %H:%M:%S) print(f[{latest_data[timestamp]}] 收到数据: {data}) return jsonify({status: success}), 200 else: return jsonify({status: invalid data}), 400 except Exception as e: print(f处理数据时出错: {e}) return jsonify({status: error}), 500 app.route(/data, methods[GET]) def get_data(): 提供一个API接口供前端页面获取最新数据 return jsonify(latest_data) if __name__ __main__: # 注意在生产环境中不要使用debug模式并考虑使用WSGI服务器 app.run(host0.0.0.0, port8080, debugTrue)运行这个服务器python server.py。确保你的电脑和STM32设备在同一个局域网并将STM32代码中的服务器IP改为你电脑的IP地址。前端页面可以使用任何你熟悉的技术。这里是一个极简的HTMLJavaScript示例使用Chart.js绘制实时曲线!-- monitor.html -- !DOCTYPE html html head title温湿度实时监测/title script srchttps://cdn.jsdelivr.net/npm/chart.js/script /head body h2STM32温湿度在线监测/h2 div canvas idsensorChart width800 height400/canvas /div div idcurrentData p温度: span idcurrentTemp--/span °C/p p湿度: span idcurrentHumi--/span %RH/p p更新时间: span idupdateTime--/span/p /div script const ctx document.getElementById(sensorChart).getContext(2d); const chart new Chart(ctx, { type: line, data: { labels: [], // 时间标签 datasets: [{ label: 温度 (°C), data: [], borderColor: rgb(255, 99, 132), tension: 0.1 }, { label: 湿度 (%RH), data: [], borderColor: rgb(54, 162, 235), tension: 0.1 }] }, options: { responsive: false } }); function fetchData() { // 调用后端 /data 接口 fetch(http://你的服务器IP:8080/data) .then(response response.json()) .then(data { if(data.temp data.humi) { document.getElementById(currentTemp).textContent data.temp; document.getElementById(currentHumi).textContent data.humi; document.getElementById(updateTime).textContent data.timestamp || 刚刚; // 更新图表仅保留最近20个点 const now new Date().toLocaleTimeString(); chart.data.labels.push(now); chart.data.datasets[0].data.push(data.temp); chart.data.datasets[1].data.push(data.humi); if(chart.data.labels.length 20) { chart.data.labels.shift(); chart.data.datasets[0].data.shift(); chart.data.datasets[1].data.shift(); } chart.update(); } }) .catch(err console.error(获取数据失败:, err)); } // 每3秒获取一次数据 setInterval(fetchData, 3000); fetchData(); // 页面加载立即获取一次 /script /body /html将monitor.html放在与server.py同目录下用浏览器打开http://你的服务器IP:8080/static/monitor.html需要配置Flask静态文件路由或直接用文件打开即可看到动态更新的温湿度图表。6. 进阶接入物联网云平台自建服务器对于学习和演示没问题但对于真实项目稳定性、可扩展性和安全性是巨大挑战。强烈建议将设备接入成熟的物联网云平台。这里以阿里云物联网平台为例简述步骤平台注册与产品创建在阿里云物联网平台创建产品例如“温湿度监测器”定义物模型属性温度、湿度。创建设备在产品下创建设备获取ProductKey,DeviceName,DeviceSecret三元组。设备端SDK移植阿里云提供了C语言的Link Kit SDK。你需要将SDK移植到STM32工程中这涉及网络层AT Socket适配、HAL层实现等。这是项目中最有挑战但也最锻炼人的部分。修改设备代码不再直接连接你的TCP服务器而是调用阿里云SDK的API将温湿度数据作为属性上报。平台查看与前端开发数据上报后可以在阿里云平台控制台实时查看。你还可以使用平台提供的“Web可视化开发”工具拖拽生成监控页面或者调用其API来自行开发更复杂的前端应用。使用云平台的好处是巨大的免去了服务器运维、内置设备管理、数据存储、实时计算、规则引擎报警、安全认证等全套服务。你只需要专注于设备端的嵌入式逻辑。7. 常见问题与排查思路在开发过程中你几乎一定会遇到下面这些问题。问题现象可能原因排查方式解决方案DHT11读取始终失败1. 时序不准确。2. 上拉电阻未接DHT11需接4.7K-10K上拉。3. 电源电压不稳。4. 传感器损坏。1. 用逻辑分析仪或示波器抓取总线波形对比数据手册时序图。2. 检查硬件电路。3. 更换传感器测试。1. 精细调整延时函数确保微秒级延时准确。2. 确保数据线有上拉电阻到VCC。3. 使用稳定的3.3V供电。ESP8266无响应1. 串口波特率不匹配默认115200。2. 模块未正确供电需3.3V电流峰值可达300mA。3. CH_PD/EN引脚未拉高。4. 模块损坏。1. 用USB-TTL单独连接ESP8266用串口助手发“AT”测试。2. 检查电源电压和电流能力最好用独立LDO供电。3. 确认启动引脚电平。1. 统一使用115200波特率。2. 使用AMS1117等稳压芯片单独供电并并联大电容如220uF。3. 确保CH_PD/EN引脚通过10K电阻上拉到3.3V。ESP8266连不上Wi-Fi1. SSID或密码错误。2. Wi-Fi路由器设置了MAC过滤或隐藏SSID。3. 信号太弱。1. 用手机确认Wi-Fi可连接。2. 发送ATCWLAP扫描附近网络看目标SSID是否存在。1. 仔细核对SSID和密码注意大小写和特殊字符。2. 关闭路由器的MAC过滤或添加ESP8266的MAC地址到白名单。3. 靠近路由器或使用信号放大器。TCP连接服务器失败1. 服务器IP或端口错误。2. 服务器程序未运行或防火墙阻止。3. 网络不通设备与服务器不在同一网络。1. 在电脑上用netstat -an查看端口是否监听。2. 用电脑上的网络调试助手创建TCP服务器测试。3. 尝试Ping服务器IP。1. 确认服务器IP和端口关闭电脑防火墙或添加入站规则。2. 如果是云服务器确保安全组开放了对应端口。3. 对于跨网络需要使用公网IP或内网穿透工具。数据上传成功但前端不显示1. 前端JS代码中服务器地址错误。2. 浏览器跨域问题CORS。3. 后端API返回的数据格式不正确。1. 打开浏览器开发者工具F12查看Console和Network标签页报错。2. 直接用浏览器访问http://服务器IP:端口/data看能否返回JSON。1. 修正前端代码中的请求地址。2. 在Flask后端添加CORS支持from flask_cors import CORS。3. 确保后端返回标准的JSON数据。8. 项目优化与最佳实践当你成功跑通基础功能后可以考虑以下优化让项目更接近产品级应用数据滤波传感器数据可能存在抖动。在STM32端实现软件滤波如滑动平均滤波、中值滤波再上报平滑后的数据。低功耗设计如果使用电池供电需要让STM32和ESP8266大部分时间处于休眠模式定时唤醒采集发送。这涉及GPIO配置、串口唤醒、看门狗等复杂操作。掉电存储与断网续传将历史数据暂存到STM32的Flash或外置EEPROM中待网络恢复后补传。更稳定的通信协议心跳包定期发送心跳包到服务器便于服务器判断设备在线状态。协议封装定义简单的应用层协议包含帧头、设备ID、数据长度、命令字、数据内容、校验和等提高通信可靠性。使用MQTT协议这是物联网领域的事实标准协议轻量、支持订阅发布模式。阿里云、腾讯云等平台都原生支持MQTT。可以移植开源的MQTT客户端库如Eclipse Paho MQTT C Client到STM32。OTA远程升级通过云平台下发新固件让设备自动升级无需人工干预。这是量产设备的必备功能。前端工程化使用Vue.js、React等框架构建更美观、交互更丰富的前端界面并部署到Nginx等Web服务器。9. 总结从项目到产品还有多远通过这个开源项目我们完成了一个物联网监测系统的核心闭环感知 - 传输 - 云端 - 展示。它麻雀虽小五脏俱全涵盖了嵌入式开发、网络通信、后端接口和前端展示等多个技术栈。对于学习者而言这个项目的价值在于提供了一个可运行、可修改、可扩展的范本。你可以更换更精确的传感器如SHT30。尝试不同的网络模块4G、NB-IoT。移植到更强大的MCU如STM32H7。接入不同的云平台腾讯云、华为云、ThingsBoard。开发更复杂的前端大屏。从“项目”到“产品”关键的差距在于稳定性、可靠性和可维护性。这需要你在异常处理、日志记录、功耗管理、安全认证、批量生产测试等方面投入更多的工程化思考。所有的代码和更详细的文档我都已整理并开源在Gitee上。建议你亲手敲一遍代码而不是直接复制在解决问题的过程中你会对每个细节有更深刻的理解。物联网开发的世界很大这个温湿度监测系统只是一个起点。