STM32F103与ESP8266构建微信小程序温湿度监控系统实战指南在智能家居和工业物联网快速发展的今天实时环境监测已成为许多应用场景的基础需求。本文将手把手带您完成一个完整的物联网项目——基于STM32F103和ESP8266的温湿度监控系统并通过微信小程序实现远程数据可视化。不同于简单的教程罗列我们将重点关注项目架构设计、代码模块化以及实际开发中可能遇到的各种坑和解决方案。1. 项目整体架构与硬件选型一个完整的物联网系统通常分为感知层、传输层和应用层三个部分。在本项目中我们选择的硬件组合既考虑了性价比也确保了系统的稳定性和扩展性。核心硬件组件STM32F103C8T6作为主控制器这款Cortex-M3内核的MCU具有72MHz主频、64KB Flash和20KB RAM完全满足我们的需求且性价比极高ESP8266-01S负责Wi-Fi连接和MQTT通信相比更昂贵的ESP32在简单物联网应用中已经足够DHT11温湿度传感器虽然精度一般(湿度±5%温度±2℃)但对于大多数家庭和办公环境监测已经足够0.96寸OLED显示屏用于本地数据显示方便调试和现场查看USB转TTL模块用于程序烧录和调试硬件连接示意图STM32引脚连接组件备注PA0DHT11数据线需上拉电阻PB6/PB7I2C SCL/SDA连接OLEDPA2/PA3USART2_TX/RX连接ESP8266PC13LED指示灯系统状态指示注意ESP8266的CH_PD引脚需要接高电平GPIO0在烧录时需要拉低正常工作时需拉高2. 开发环境搭建与基础驱动编写2.1 Keil工程配置首先我们需要建立一个完整的Keil工程框架良好的工程结构能显著提高开发效率Project/ ├── CMSIS/ // 内核支持文件 ├── Drivers/ │ ├── STM32F1xx_HAL_Driver/ // HAL库 │ └── BSP/ // 板级支持包 ├── Middlewares/ │ ├── OLED/ // OLED驱动 │ └── DHT11/ // 传感器驱动 ├── Application/ │ ├── Inc/ // 头文件 │ └── Src/ // 源文件 └── MDK-ARM/ // Keil工程文件关键配置步骤在Keil中新建工程选择STM32F103C8器件配置时钟树确保系统时钟为72MHz开启USART2用于ESP8266通信配置I2C1用于OLED显示启用定时器TIM3用于DHT11时序控制2.2 传感器驱动开发DHT11虽然简单但时序要求严格。以下是经过优化的读取函数#define DHT11_PORT GPIOA #define DHT11_PIN GPIO_PIN_0 uint8_t DHT11_Read_Data(uint8_t *temperature, uint8_t *humidity) { uint8_t data[5] {0}; uint8_t i, j; // 主机拉低至少18ms HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); HAL_Delay(20); HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); // 等待从机响应 if(!DHT11_Wait_State(GPIO_PIN_RESET)) return 1; if(!DHT11_Wait_State(GPIO_PIN_SET)) return 1; // 读取40位数据 for(i0; i5; i) { for(j0; j8; j) { if(!DHT11_Wait_State(GPIO_PIN_RESET)) return 1; uint32_t start HAL_GetTick(); while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)) { if(HAL_GetTick()-start 50) return 1; } start HAL_GetTick(); uint8_t duration 0; while(!HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)) { if(HAL_GetTick()-start 50) return 1; } duration HAL_GetTick() - start; data[i] 1; if(duration 30) data[i] | 1; } } // 校验数据 if(data[4] ! (data[0]data[1]data[2]data[3])) return 1; *humidity data[0]; *temperature data[2]; return 0; }OLED显示驱动建议使用现成的SSD1306库但需要进行适当优化以减小内存占用void OLED_ShowString(uint8_t x, uint8_t y, const uint8_t *str, uint8_t size) { while(*str ! \0) { if(x 120) { x 0; y size; } OLED_ShowChar(x, y, *str, size); x size/2; str; } }3. ESP8266与MQTT协议实现3.1 ESP8266固件选择与AT指令配置建议使用安信可提供的AT固件支持MQTT协议且稳定性较好。以下是关键的初始化流程void ESP8266_Init(void) { uint8_t retry 3; while(retry--) { ESP8266_SendCmd(AT, OK, 1000); ESP8266_SendCmd(ATCWMODE1, OK, 1000); ESP8266_SendCmd(ATCWJAP\SSID\,\password\, OK, 5000); ESP8266_SendCmd(ATCIPMUX0, OK, 1000); ESP8266_SendCmd(ATCIPSTART\TCP\,\mqtt.server.com\,1883, OK, 3000); if(ESP8266_SendCmd(ATCIPSTATUS, CONNECTED, 1000)) break; } }实际项目中我们需要处理各种异常情况比如Wi-Fi断开重连void ESP8266_Check_Connection(void) { static uint32_t last_check 0; if(HAL_GetTick() - last_check 10000) return; last_check HAL_GetTick(); if(!ESP8266_SendCmd(ATCIPSTATUS, CONNECTED, 1000)) { ESP8266_SendCmd(ATCIPCLOSE, OK, 1000); ESP8266_Init(); } }3.2 MQTT协议实现要点虽然可以使用现成的MQTT库但理解协议原理对调试很有帮助。MQTT连接的基本流程发送CONNECT报文建立连接发送SUBSCRIBE报文订阅主题定期发送PINGREQ保持连接通过PUBLISH报文发布数据以下是CONNECT报文的构建示例void MQTT_Connect(void) { uint8_t buffer[128]; uint8_t *ptr buffer; // Fixed header *ptr 0x10; // CONNECT // Remaining length uint8_t rem_len 12 2 strlen(MQTT_CLIENT_ID) 2 strlen(MQTT_USER) 2 strlen(MQTT_PASS); *ptr rem_len; // Protocol name *ptr 0x00; *ptr 0x04; memcpy(ptr, MQTT, 4); ptr 4; // Protocol level *ptr 0x04; // MQTT 3.1.1 // Connect flags *ptr 0xC2; // CleanSession1, WillFlag0, Username1, Password1 // Keep alive *ptr 0x00; *ptr 0x3C; // 60 seconds // Payload *ptr 0x00; *ptr strlen(MQTT_CLIENT_ID); memcpy(ptr, MQTT_CLIENT_ID, strlen(MQTT_CLIENT_ID)); ptr strlen(MQTT_CLIENT_ID); *ptr 0x00; *ptr strlen(MQTT_USER); memcpy(ptr, MQTT_USER, strlen(MQTT_USER)); ptr strlen(MQTT_USER); *ptr 0x00; *ptr strlen(MQTT_PASS); memcpy(ptr, MQTT_PASS, strlen(MQTT_PASS)); ptr strlen(MQTT_PASS); ESP8266_SendData(buffer, ptr - buffer); }4. 微信小程序开发与数据可视化4.1 小程序基础框架搭建微信小程序开发需要先注册开发者账号并安装开发者工具。项目基本目录结构miniprogram/ ├── pages/ │ ├── index/ // 主页面 │ └── history/ // 历史数据页面 ├── components/ │ └── chart/ // 自定义图表组件 ├── utils/ │ └── mqtt.js // MQTT连接工具 └── app.json // 全局配置关键配置文件app.json{ pages: [ pages/index/index, pages/history/history ], window: { navigationBarTitleText: 温湿度监控, navigationBarBackgroundColor: #1E90FF }, tabBar: { list: [ { pagePath: pages/index/index, text: 实时数据, iconPath: images/home.png, selectedIconPath: images/home-active.png }, { pagePath: pages/history/history, text: 历史记录, iconPath: images/history.png, selectedIconPath: images/history-active.png } ] } }4.2 MQTT连接实现小程序端使用WebSocket连接MQTT服务器推荐使用Eclipse Paho的JavaScript客户端// utils/mqtt.js const MQTT require(./paho-mqtt.min.js); let client null; function connectMQTT(callback) { client new MQTT.Client(mqtt.server.com, 8083, client_ Math.random().toString(16).substr(2)); client.onConnectionLost function(response) { console.log(Connection lost: response.errorMessage); }; client.onMessageArrived function(message) { callback(JSON.parse(message.payloadString)); }; client.connect({ onSuccess: function() { console.log(MQTT Connected); client.subscribe(sensor/data); }, useSSL: true, userName: username, password: password }); } module.exports { connectMQTT };4.3 数据可视化实现使用ECharts for WeChat实现专业级图表展示// pages/history/history.js import * as echarts from ../../ec-canvas/echarts; Page({ data: { ec: { lazyLoad: true } }, onLoad: function() { this.ecComponent this.selectComponent(#mychart-dom-line); this.initChart(); }, initChart: function() { this.ecComponent.init((canvas, width, height) { const chart echarts.init(canvas, null, { width: width, height: height }); const option { tooltip: { trigger: axis }, legend: { data: [温度, 湿度] }, xAxis: { type: category, data: [00:00, 03:00, 06:00, 09:00, 12:00, 15:00, 18:00, 21:00] }, yAxis: { type: value, axisLabel: { formatter: {value} } }, series: [ { name: 温度, type: line, data: [22, 21, 23, 25, 26, 24, 22, 21], smooth: true }, { name: 湿度, type: line, data: [45, 50, 55, 60, 58, 52, 48, 45], smooth: true } ] }; chart.setOption(option); return chart; }); } });5. 系统优化与常见问题解决5.1 低功耗设计技巧对于电池供电的应用功耗优化至关重要将STM32设置为休眠模式定时唤醒采集数据调整ESP8266的休眠策略仅在发送数据时唤醒降低传感器采样频率关闭不必要的LED指示灯关键代码实现void Enter_Stop_Mode(uint32_t seconds) { // 配置唤醒引脚 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin WAKEUP_PIN; GPIO_InitStruct.Mode GPIO_MODE_IT_RISING; GPIO_InitStruct.Pull GPIO_NOPULL; HAL_GPIO_Init(WAKEUP_PORT, GPIO_InitStruct); // 配置RTC唤醒定时器 HAL_RTCEx_SetWakeUpTimer_IT(hrtc, seconds*8, RTC_WAKEUPCLOCK_RTCCLK_DIV16); // 进入停止模式 HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); // 唤醒后重新配置时钟 SystemClock_Config(); }5.2 常见问题排查指南问题1ESP8266无法连接Wi-Fi检查SSID和密码是否正确确保路由器不是5GHz频段(ESP8266只支持2.4GHz)尝试缩短Wi-Fi密码长度检查电源是否稳定(建议3.3V 500mA以上)问题2MQTT连接频繁断开增加Keep Alive时间(建议60秒以上)实现PINGREQ/PINGRESP机制检查网络信号强度服务器端可能需要调整超时设置问题3DHT11读取失败检查接线是否正确特别是上拉电阻确保供电电压稳定(3.3V-5V)调整时序延迟不同MCU可能需要微调避免在中断中读取DHT11问题4小程序无法显示实时数据检查MQTT服务器地址和端口是否正确确认订阅的主题与发布主题一致检查WebSocket是否启用(wss://)验证JSON数据格式是否正确5.3 项目扩展思路基础功能实现后可以考虑以下扩展方向添加多传感器支持(如CO2、PM2.5监测)实现历史数据存储与分析增加报警功能(微信消息推送)开发多设备管理功能加入OTA固件升级能力实现本地和远程双控制模式对于历史数据存储可以使用腾讯云的云开发数据库// 存储数据到云数据库 const db wx.cloud.database(); function saveData(temp, humi) { db.collection(sensor_data).add({ data: { temperature: temp, humidity: humi, timestamp: new Date() } }); } // 查询历史数据 function queryHistory(days) { const start new Date(); start.setDate(start.getDate() - days); return db.collection(sensor_data) .where({ timestamp: _.gte(start) }) .orderBy(timestamp, desc) .get(); }