基于 ESP32-C3 的便携式温湿度、气压与电量监测终端设计
从太阳能供电到手机曲线基于 ESP32-C3 的低功耗环境监测节点1. 项目简介本项目实现了一个低功耗环境监测节点可采集温度、湿度、气压、电池电压和剩余电量并通过 Wi-Fi MQTT 上传到手机端以图表形式查看历史变化趋势。设备采用太阳能板和锂电池供电主控周期性唤醒、采集、联网、上报然后进入深度睡眠适合用于室内环境记录、阳台植物监测、户外半开放环境数据采集等场景。2. 实现效果手机端可以查看- 温度曲线- 湿度曲线- 气压曲线- 电池剩余电量- 电池电压- 当前充放电状态从实际曲线可以观察到温度、湿度和气压均能随环境变化连续记录同时电量计可以显示当前电池电压和剩余容量。3. 系统架构4. 硬件设计4.1 主控与传感器主控使用 ESP32-C3-WROOM负责传感器采集、Wi-Fi 连接、MQTT 上报和低功耗控制。传感器部分采用共用 I2C 总线器件功能I2C 地址AHT20温度、湿度默认地址BMP280气压0x76兼容尝试 0x77MAX17048电池电压、电量、充放电速率0x36I2C 引脚配置如下GPIO4 - I2C_SDA GPIO5 - I2C_SCL其中温湿度以 AHT20 的数据为主BMP280 主要用于气压检测MAX17048 用于读取电池电压、SOC 电量百分比和充放电速率。4.2 电源与充电部分设备采用单节锂电池供电电池为双节并联 18650 电池组标称电压 3.7V、容量 5400mAh并带保护板。供电与充电部分包括12V 4.5W 太阳能板输入CN3801 太阳能充电管理电路USB-C 输入与 TP4056 锂电池充电电路MAX17048 电量计量芯片TPS63802 升降压 DC-DC输出稳定 3.3VUSB-C 烧录接口与下载按键注意太阳能输入和 USB 充电输入不建议长期同时使用。若后续做成正式产品建议增加输入互锁或电源路径管理避免两套充电芯片同时对电池充电。4.3 PCB 与结构设计主板尺寸约为60.2 mm × 51.2 mm四角预留安装孔。外壳整体尺寸约为87.4 mm × 97.3 mm通过螺丝柱固定 PCB并为太阳能板、电池、USB-C 接口和传感器预留结构空间。5. 软件设计程序采用“唤醒一次、采集一次、上传一次、深睡眠一次”的低功耗策略。具体流程如下ESP32-C3 从深度睡眠唤醒初始化 I2C 总线读取 AHT20、BMP280 和 MAX17048连接 Wi-Fi连接 MQTT Broker组装 JSON 数据并发布进入深度睡眠等待下一次唤醒当前深度睡眠时间设置为 240 秒即每 4 分钟上传一次数据。static constexpr uint32_t DEEP_SLEEP_SEC 240;为提高稳定性程序对 I2C 通信加入了重试与总线复位机制BMP280 使用单次测量模式减少唤醒期间的功耗。6. MQTT 数据格式上传数据采用 JSON 格式例如{ humi: 58.2, temp: 25.6, pa: 1008.4, voltage: 4.146, soc: 94.8, rate: -0.52, state: Discharging, aht_ok: true, bmp_ok: true, bat_ok: true }字段说明字段含义humi湿度单位 %RHtemp温度单位 ℃pa气压单位 hPavoltage电池电压单位 Vsoc剩余电量单位 %rate充放电速率单位 %/hstateCharging / Discharging / Idleaht_okAHT20 是否正常bmp_okBMP280 是否正常bat_okMAX17048 是否正常MQTT 消息使用 retained 模式保存最新状态因此手机端重新连接后可以立即获取最近一次上报数据。7. 低功耗设计本项目没有让 ESP32-C3 持续联网而是在每次上报完成后关闭 Wi-Fi、释放 I2C 总线并进入深度睡眠。esp_sleep_enable_timer_wakeup( (uint64_t)DEEP_SLEEP_SEC * 1000000ULL ); esp_deep_sleep_start();这种方案适合太阳能与电池供电场景可以显著降低平均功耗。8. 项目难点与优化方向已解决的问题多个 I2C 设备共用总线BMP280 地址兼容处理I2C 通信失败后的自动复位与重试多设备 MQTT Client ID 冲突问题电池电压、电量和充放电状态读取深度睡眠后的周期性自动唤醒后续优化方向将 MQTT 测试服务器替换为自建或带鉴权的服务器使用 MQTT over TLS避免明文传输增加 OTA 远程升级增加温湿度阈值告警增加本地数据缓存避免断网丢失数据优化太阳能与 USB 双输入的电源路径为传感器设计通风、防水和防冷凝结构9. 总的代码/* * ESP32-C3 户外气象 / 电池监测节点 * * 硬件对应本项目原理图 * GPIO4 - I2C_SDAAHT20、BMP280、MAX17048 共用 * GPIO5 - I2C_SCLAHT20、BMP280、MAX17048 共用 * BMP280 I2C 地址0x76SDO 接地如板子不同会自动尝试 0x77 * MAX17048 I2C 地址0x36 * * Arduino IDE 开发板选择ESP32C3 Dev Module。 * 需要安装PubSubClient、Adafruit AHTX0、Adafruit BMP280、 * Adafruit Unified Sensor由上面两个 Adafruit 库依赖。 */ #include WiFi.h #include PubSubClient.h #include Wire.h #include Adafruit_AHTX0.h #include Adafruit_BMP280.h #include esp_sleep.h #include math.h // 网络和 MQTT 服务配置 static const char *WIFI_SSID wifi账号; static const char *WIFI_PASSWORD wifi密码; // 公共测试 Broker仅用于调试主题中的数据对其他人可见。 static const char *MQTT_SERVER ; static const uint16_t MQTT_PORT ; static const char *MQTT_USER ; static const char *MQTT_PASSWORD ; static const char *MQTT_TOPIC ; // 上传间隔单位为秒。 static constexpr uint32_t DEEP_SLEEP_SEC 240; // 原理图中唯一的 I2C 总线不能再沿用 ESP8266 版本的两条 I2C 总线。 static constexpr uint8_t I2C_SDA_PIN 4; static constexpr uint8_t I2C_SCL_PIN 5; static constexpr uint8_t MAX17048_ADDR 0x36; static constexpr uint8_t MAX_I2C_RETRY 3; // MAX17048 寄存器。 static constexpr uint8_t REG_VCELL 0x02; static constexpr uint8_t REG_SOC 0x04; static constexpr uint8_t REG_VERSION 0x08; static constexpr uint8_t REG_STATUS 0x1A; static constexpr uint8_t REG_CRATE 0x16; WiFiClient wifiClient; PubSubClient mqtt(wifiClient); Adafruit_AHTX0 aht; Adafruit_BMP280 bmp; bool ahtAvailable false; bool bmpAvailable false; struct BatteryData { bool available false; float voltage NAN; float soc NAN; float rate NAN; const char *state Unknown; }; struct EnvironmentData { bool ahtAvailable false; bool bmpAvailable false; float temperature NAN; float humidity NAN; float pressure NAN; }; void resetI2cBus() { // 传感器总线异常时释放 GPIO 后重新初始化。所有传感器共用这条总线。 Wire.end(); pinMode(I2C_SCL_PIN, INPUT); pinMode(I2C_SDA_PIN, INPUT); delay(20); Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN); Wire.setClock(100000); delay(20); } bool i2cPing(uint8_t address) { Wire.beginTransmission(address); return Wire.endTransmission() 0; } bool maxRead(uint8_t reg, uint16_t value) { for (uint8_t attempt 0; attempt MAX_I2C_RETRY; attempt) { Wire.beginTransmission(MAX17048_ADDR); Wire.write(reg); const uint8_t error Wire.endTransmission(false); if (error 0 Wire.requestFrom(MAX17048_ADDR, (uint8_t)2) 2) { value ((uint16_t)Wire.read() 8) | Wire.read(); return true; } Serial.printf(MAX17048 read error%u, retry%u/%u\n, error, attempt 1, MAX_I2C_RETRY); resetI2cBus(); } return false; } bool maxWrite(uint8_t reg, uint16_t value) { for (uint8_t attempt 0; attempt MAX_I2C_RETRY; attempt) { Wire.beginTransmission(MAX17048_ADDR); Wire.write(reg); Wire.write((uint8_t)(value 8)); Wire.write((uint8_t)value); if (Wire.endTransmission() 0) { return true; } resetI2cBus(); } return false; } bool initMax17048() { for (uint8_t attempt 0; attempt MAX_I2C_RETRY; attempt) { if (i2cPing(MAX17048_ADDR)) { uint16_t version 0; if (maxRead(REG_VERSION, version) version ! 0x0000 version ! 0xFFFF) { Serial.printf(MAX17048 OK, version0x%04X\n, version); return true; } } resetI2cBus(); } Serial.println(MAX17048 not found); return false; } const char *chargeState(float rate) { if (!isfinite(rate)) return Unknown; if (rate 5.0f) return Charging; if (rate -0.5f) return Discharging; return Idle; } BatteryData readBattery() { BatteryData data; if (!initMax17048()) return data; uint16_t rawVoltage 0; uint16_t rawSoc 0; uint16_t rawRate 0; if (!maxRead(REG_VCELL, rawVoltage) || !maxRead(REG_SOC, rawSoc) || !maxRead(REG_CRATE, rawRate)) { Serial.println(MAX17048 read failed); return data; } data.available true; data.voltage rawVoltage * 0.000078125f; data.soc constrain(rawSoc / 256.0f, 0.0f, 100.0f); data.rate ((int16_t)rawRate) * 0.208f; data.state chargeState(data.rate); // 清除 MAX17048 的 SOC/电压告警标志失败不影响本次上传。 maxWrite(REG_STATUS, 0x00FF); return data; } void initEnvironmentSensors() { // AHT20 与 BMP280 挂在同一条已初始化的 I2C 总线上。 ahtAvailable aht.begin(Wire); Serial.println(ahtAvailable ? AHT20 OK : AHT20 not found); // 此库版本使用默认 Wire上面已通过 Wire.begin(GPIO4, GPIO5) 配置好它。 bmpAvailable bmp.begin(0x76); if (!bmpAvailable) bmpAvailable bmp.begin(0x77); if (bmpAvailable) { // 单次测量可以缩短唤醒时间降低太阳能节点的耗电。 bmp.setSampling(Adafruit_BMP280::MODE_FORCED, Adafruit_BMP280::SAMPLING_X1, Adafruit_BMP280::SAMPLING_X1, Adafruit_BMP280::FILTER_OFF, Adafruit_BMP280::STANDBY_MS_1); } Serial.println(bmpAvailable ? BMP280 OK : BMP280 not found); } EnvironmentData readEnvironment() { EnvironmentData data; data.ahtAvailable ahtAvailable; data.bmpAvailable bmpAvailable; if (ahtAvailable) { sensors_event_t humidityEvent; sensors_event_t temperatureEvent; aht.getEvent(humidityEvent, temperatureEvent); data.temperature temperatureEvent.temperature; data.humidity humidityEvent.relative_humidity; } if (bmpAvailable bmp.takeForcedMeasurement()) { data.pressure bmp.readPressure() / 100.0f; // Pa - hPa } return data; } bool connectWifi() { WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print(Connecting to Wi-Fi); for (uint8_t retry 0; WiFi.status() ! WL_CONNECTED retry 20; retry) { delay(500); Serial.print(.); } Serial.println(); if (WiFi.status() ! WL_CONNECTED) { Serial.println(Wi-Fi connection failed); return false; } Serial.print(Wi-Fi connected, IP: ); Serial.println(WiFi.localIP()); return true; } bool connectMqtt() { // 用芯片 MAC 的后 24 位区分节点避免多个设备使用相同 Client ID 而互相踢下线。 char clientId[32]; const uint64_t mac ESP.getEfuseMac(); snprintf(clientId, sizeof(clientId), esp32c3_outdoor_%06llX, (unsigned long long)(mac 0xFFFFFFULL)); mqtt.setServer(MQTT_SERVER, MQTT_PORT); mqtt.setBufferSize(256); for (uint8_t retry 0; retry 5; retry) { // MQTT_USER 留空时使用匿名连接便于连接公开测试 Broker。 const bool connected MQTT_USER[0] \0 ? mqtt.connect(clientId) : mqtt.connect(clientId, MQTT_USER, MQTT_PASSWORD); if (connected) { Serial.println(MQTT connected); return true; } Serial.printf(MQTT connection failed, state%d\n, mqtt.state()); delay(500); } return false; } // 将 NAN 编码为 JSON null避免传感器暂时离线时向服务器提交伪造的 0。 void appendJsonNumber(char *buffer, size_t bufferSize, const char *key, float value, uint8_t precision, bool first) { const size_t used strlen(buffer); if (used bufferSize) return; snprintf(buffer used, bufferSize - used, %s\%s\:, first ? : ,, key); if (isfinite(value)) { const size_t updated strlen(buffer); snprintf(buffer updated, bufferSize - updated, precision 1 ? %.1f : precision 2 ? %.2f : %.3f, value); } else { const size_t updated strlen(buffer); snprintf(buffer updated, bufferSize - updated, null); } first false; } bool publishData(const BatteryData battery, const EnvironmentData environment) { char payload[256] {; bool first true; appendJsonNumber(payload, sizeof(payload), humi, environment.humidity, 1, first); appendJsonNumber(payload, sizeof(payload), temp, environment.temperature, 1, first); appendJsonNumber(payload, sizeof(payload), pa, environment.pressure, 1, first); appendJsonNumber(payload, sizeof(payload), voltage, battery.voltage, 3, first); appendJsonNumber(payload, sizeof(payload), soc, battery.soc, 1, first); appendJsonNumber(payload, sizeof(payload), rate, battery.rate, 2, first); const size_t used strlen(payload); snprintf(payload used, sizeof(payload) - used, ,\state\:\%s\,\aht_ok\:%s,\bmp_ok\:%s,\bat_ok\:%s}, battery.state, environment.ahtAvailable ? true : false, environment.bmpAvailable ? true : false, battery.available ? true : false); Serial.print(MQTT payload: ); Serial.println(payload); // 保留最新一条状态手机 MQTT 客户端重新连接后会立即收到 // 无需等待 ESP32-C3 下一次从深睡唤醒并上报。 return mqtt.publish(MQTT_TOPIC, payload, true); } void enterDeepSleep() { mqtt.disconnect(); WiFi.disconnect(); WiFi.mode(WIFI_OFF); Wire.end(); pinMode(I2C_SDA_PIN, INPUT); pinMode(I2C_SCL_PIN, INPUT); Serial.flush(); // ESP32-C3 通过 RTC 定时器自动唤醒不需要 ESP8266 的 GPIO16(D0)-RST 连线。 esp_sleep_enable_timer_wakeup((uint64_t)DEEP_SLEEP_SEC * 1000000ULL); esp_deep_sleep_start(); } void setup() { Serial.begin(115200); delay(200); Serial.println(\n ESP32-C3 Outdoor Monitor ); Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN); Wire.setClock(100000); delay(100); initEnvironmentSensors(); const BatteryData battery readBattery(); const EnvironmentData environment readEnvironment(); if (battery.available) { Serial.printf(Battery: %.3f V, %.1f %%, %.2f %%/h, %s\n, battery.voltage, battery.soc, battery.rate, battery.state); } if (environment.ahtAvailable) { Serial.printf(AHT20: %.1f C, %.1f %%RH\n, environment.temperature, environment.humidity); } if (environment.bmpAvailable) { Serial.printf(BMP280: %.1f hPa\n, environment.pressure); } if (connectWifi() connectMqtt()) { Serial.println(publishData(battery, environment) ? MQTT publish OK : MQTT publish failed); } enterDeepSleep(); } void loop() { // 所有工作都在 setup() 中完成然后进入深睡眠。 }10. 总结本项目完成了从太阳能供电、锂电池管理、环境数据采集到 Wi-Fi MQTT 上传和手机端曲线展示的完整闭环。ESP32-C3 配合 AHT20、BMP280、MAX17048可以较低成本地实现一个低功耗、可扩展的环境监测节点。后续继续完善网络安全、结构防护和远程管理后就可以用于更长期的实际部署。

相关新闻

论文被AIGC检测“冤枉“了怎么办?2026毕业季申诉全流程指南

论文被AIGC检测“冤枉“了怎么办?2026毕业季申诉全流程指南

2026年毕业季最荒诞的一幕:西南财经大学一位同学,独立查阅文献、逐字手写的论文综述,被某平台判定为AI生成疑似度97%。 这个数字意味着什么?意味着检测系统认为这篇论文有97%的概率不是人写的。但当事人知道——它就是人写的。 这…

2026/7/30 2:20:37 阅读更多 →
开源社区五大 AI Agent 记忆系统设计范式深度解析

开源社区五大 AI Agent 记忆系统设计范式深度解析

开源社区五大 AI Agent 记忆系统设计范式深度解析关键词:AI Agent、记忆系统、Text2Mem、Mem0、Letta、ReMe、memU、Long-term Memory、RAG 摘要:本文系统盘点当前开源社区最具代表性的五大 AI Agent 记忆架构——Text2Mem、Mem0、Letta、ReMe、memU。它…

2026/7/30 2:20:37 阅读更多 →
AI芯片设计转向:从算力竞争到数据流优化,片上SRAM如何重塑推理芯片架构

AI芯片设计转向:从算力竞争到数据流优化,片上SRAM如何重塑推理芯片架构

上周和一位做芯片验证的朋友聊天,他提到一个细节:现在很多AI芯片项目在早期设计阶段,最纠结的不是算力峰值,而是怎么把数据高效地喂给计算单元。“就像你建了个超级工厂,但原材料进不来、成品出不去,再强的…

2026/7/30 2:20:37 阅读更多 →

最新新闻

核弹级漏洞Log4j2(CVE-2021-44228)全解析:从原理到绕过,手把手复现与防御(附靶场+POC)

核弹级漏洞Log4j2(CVE-2021-44228)全解析:从原理到绕过,手把手复现与防御(附靶场+POC)

哈喽,大家好,我是专注实战攻防的网安博主小北。2021年底,Apache Log4j2 远程代码执行漏洞(CVE-2021-44228)如同核弹一般席卷了整个安全圈,无数企业、服务商连夜应急。即便到了今天,它依然是渗透…

2026/7/30 2:29:39 阅读更多 →
SpringBoot电商平台开题答辩全流程与核心技术解析

SpringBoot电商平台开题答辩全流程与核心技术解析

1. 开题答辩全流程解析:以SpringBoot电商平台为例刚完成硕士开题答辩的学弟跑来问我:"学长,电商平台这种烂大街的题目怎么才能讲出新意?"看着他电脑里那个用SpringBootMyBatis拼凑的简陋Demo,我仿佛看到三年…

2026/7/30 2:29:39 阅读更多 →
HarmonyOS NEXT 文件管理器:项目规划与架构设计

HarmonyOS NEXT 文件管理器:项目规划与架构设计

HarmonyOS NEXT 文件管理器:项目规划与架构设计 前言 随着 HarmonyOS NEXT 的正式发布,纯血鸿蒙生态迎来了爆发式增长。文件管理类应用作为操作系统的基础设施,在企业级场景中具有不可替代的价值。本文将详细介绍 HarmonyExplorer 项目的整…

2026/7/30 2:29:39 阅读更多 →
GPU封装技术解析:从基础原理到AI应用优化实践

GPU封装技术解析:从基础原理到AI应用优化实践

最近在AI和GPU领域有个重要动态值得关注:台积电在美国亚利桑那州工厂生产了首批英伟达GB300 GPU,但芯片封装环节仍需返回台湾完成。这一事件不仅反映了全球半导体产业链的复杂布局,也让我们思考GPU从设计到封装的全流程技术细节。作为开发者&…

2026/7/30 2:29:39 阅读更多 →
如何快速解决Windows游戏乱码问题:Locale Remulator系统区域语言模拟器终极指南

如何快速解决Windows游戏乱码问题:Locale Remulator系统区域语言模拟器终极指南

如何快速解决Windows游戏乱码问题:Locale Remulator系统区域语言模拟器终极指南 【免费下载链接】Locale_Remulator System Region and Language Simulator. 项目地址: https://gitcode.com/gh_mirrors/lo/Locale_Remulator 你是否曾经遇到过这样的困扰&…

2026/7/30 2:29:39 阅读更多 →
深入解析PMSM有效磁链:从核心概念到FOC控制优化实践

深入解析PMSM有效磁链:从核心概念到FOC控制优化实践

1. 项目概述:从“有效磁链”切入,重新理解PMSM的控制核心在永磁同步电机(PMSM)的驱动与控制领域,我们经常听到“磁场定向控制”、“最大转矩电流比”、“弱磁控制”这些术语。这些高级控制策略的基石,都离不…

2026/7/30 2:28:39 阅读更多 →

日新闻

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南 【免费下载链接】DriverStoreExplorer Driver Store Explorer 项目地址: https://gitcode.com/gh_mirrors/dr/DriverStoreExplorer 您是否曾因Windows系统盘空间不足而烦恼?是否遇到过设…

2026/7/30 0:00:13 阅读更多 →
如何3步掌握Video Download Helper:网页视频下载的完整实战指南

如何3步掌握Video Download Helper:网页视频下载的完整实战指南

如何3步掌握Video Download Helper:网页视频下载的完整实战指南 【免费下载链接】VideoDownloadHelper Chrome Extension to Help Download Video for Some Video Sites. 项目地址: https://gitcode.com/gh_mirrors/vi/VideoDownloadHelper 你是否曾经在浏览…

2026/7/30 0:00:13 阅读更多 →
“双减”后首个AI备课压力测试报告:覆盖32所中小学的176节AI辅助课,暴露4大隐性增负节点

“双减”后首个AI备课压力测试报告:覆盖32所中小学的176节AI辅助课,暴露4大隐性增负节点

更多请点击: https://intelliparadigm.com 第一章:AI 教师备课辅助 AI 教师备课辅助系统正逐步成为教育数字化转型的核心支撑工具,它并非替代教师,而是通过语义理解、知识图谱与多模态生成能力,将教师从重复性劳动中解…

2026/7/30 0:00:13 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/29 22:18:20 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/29 14:34:28 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/29 15:00:03 阅读更多 →

月新闻