Своими рукамипрактическая инструкция

Датчик без розетки: собираем солнечное питание для ESP32

Редакция Фермозавра·13 апреля 2026 г.·9 мин чтения

Сколько энергии тратит датчик, пока измеряет, передаёт и спит? Разберём питание ESP32, измерим расход и подберём аккумулятор с солнечной подзарядкой.

Датчик без розетки: собираем солнечное питание для ESP32

Хорошее место для датчика нашлось, а розетки рядом нет. Тянуть кабель или попробовать аккумулятор с солнечной панелью? Начнём со второго варианта и выясним, на что уходит энергия: пробуждение ESP32, измерение температуры и влажности, передача показаний, ожидание следующего цикла.

В этой лабораторной сборке соединим панель, зарядное устройство, защищённый аккумулятор и преобразователь питания. Проверим токи и зарядку, затем перенесём датчик на участок. Вся цепочка важна: энергия должна доходить от панели до работающего устройства в подходящем режиме.

Для опыта используем ESP32 с DHT22, передачу по Wi-Fi либо LoRa и глубокий сон. Интереснее всего сравнить расход во время связи и ожидания: эти измерения подскажут, что стоит изменить в своей сборке.

Проводим энергию от панели до платы

Для солнечного питания выбирайте зарядный модуль, рассчитанный на выбранную панель, одновременную нагрузку и температурный контроль аккумулятора. Пример документированного решения — плата Adafruit на BQ24074. У неё есть отдельные подключения батареи и нагрузки; выход нагрузки не является готовым питанием 3,3 В для ESP32. Подключение, полярность и допустимый аккумулятор следует сверить с документацией именно этой платы.

Солнечная панель → вход совместимого солнечного зарядного модуля
Защищённая сборка Li-ion 1S → вход BATT зарядного модуля
Термодатчик на аккумуляторе → вход температурного контроля зарядки
Выход LOAD → подходящий преобразователь → питание ESP32
Земли соединяются по схеме зарядного модуля и защиты аккумулятора

Например, для ESP32-DevKitC V4 производитель описывает три взаимоисключающих способа питания: USB, выводы 5V/GND или 3V3/GND. Используйте только один из них. Для другой DevKit сверяйтесь с её собственной схемой; батарея не заменяет стабилизированный источник 3,3 или 5 В. Выходной ток и переходные процессы проверяют при передаче, а не только во сне. В рекомендациях Espressif для питания ESP32 от единственного источника 3,3 В указан выходной ток не менее 500 мА; датчики и радио добавляют свою нагрузку.

Распространённый модуль TP4056 нельзя считать одновременно солнечным контроллером, стабилизатором питания и полной защитой аккумулятора. Его выходы OUT на платах с защитой обычно относятся к защищённой батарее, а не к стабилизированным 5 В. Подключение такого выхода к VIN DevKit может приводить к просадке питания; к 3V3 напрямую его также не подключают. Тип USB-разъёма и число микросхем не доказывают наличие всех необходимых защит — нужна схема конкретной платы.

В комплекте должны быть защита аккумулятора от недопустимых режимов, подходящий предохранитель, надёжные соединения и проверка полярности до включения. Переполюсовка может повредить зарядный модуль. Не заменяйте Li-ion на LiFePO4 без соответствующего зарядного режима: пример разных настроек приведён в документации Adafruit BQ25185. Не соединяйте отдельные элементы параллельно по упрощённой инструкции: для большей ёмкости используйте готовую совместимую защищённую сборку.

18650 обозначает формат корпуса, а не единые пределы зарядки. Температуру, напряжение и ток берут из паспорта выбранного элемента. Температурная блокировка должна действовать в зарядном устройстве, даже когда ESP32 спит. Например, у указанной платы Adafruit термодатчик подключается отдельно; штатная перемычка не означает, что температура батареи уже контролируется. Порядок подключения приведён в описании выводов и перемычек.

Смотрим, как меняется напряжение аккумулятора

Измерение берут с защищённых выводов одноэлементной батарейной сборки, до преобразователя питания ESP32. Не подключайте измерительную землю к выводу, который обходит ключи защиты. Ниже P+ и P− — доступные защищённые выводы сборки; применимость соединения P− с общей землёй нужно проверить по схеме зарядного узла.

P+ ── R1 100 кОм ──┬── R2 100 кОм ── P− / GND
                    │
                 GPIO34
                    │
                  C 100 нФ
                    │
                   GND

Два резистора образуют измерительную цепь между P+ и GND; прямой перемычки между ними нет. При равных сопротивлениях АЦП получает половину напряжения батареи. Например, из 4,2 В получится 2,1 В. Сам делитель тоже расходует энергию: 4,2 В / 200 кОм = 21 мкА. Добавим это число в общий расчёт.

Сравните показания скетча с мультиметром при нескольких напряжениях и подберите VBAT_CAL. Программа использует analogReadMilliVolts, усреднение и коэффициент делителя; конденсатор помогает фильтрации. Проверьте время установления показаний. GPIO34 подходит классическому ESP32, для другой модели выберите её вход АЦП. Полученный график показывает напряжение под нагрузкой. Для определения процента заряда понадобится отдельная оценка с учётом аккумулятора и режима работы.

Сколько дней получится по вашим измерениям

Измеряйте ток со стороны батареи — вместе со стабилизатором, датчиками, радио, индикаторами и делителем. Запишите, сколько длится каждый режим. Особенно любопытно сравнить удачное соединение и долгий поиск сети: готовая плата расходует энергию всеми своими узлами.

Iср = (Iактив × tактив + Iсон × tсон) / (tактив + tсон)
Qсутки = Iср × 24 часа
Дни без солнца ≈ доступная ёмкость батареи / Qсутки

Посчитаем на условных числах. Пусть десять секунд сборка потребляет 100 мА, а оставшиеся 890 секунд — 1 мА. Средний ток получится 2,1 мА, расход за сутки — 50,4 мА·ч. При доступной ёмкости 2000 мА·ч выходит около 40 суток без подзарядки. Подставьте свои измерения и учтите температуру, старение, саморазряд и периферию.

Для сравнения разных напряжений перейдите к Вт·ч и добавьте потери преобразования. Затем оцените суточную выработку панели на выбранном месте в худший сезон. Оставьте запас на пасмурные дни, снег и тень. Журнал напряжения и зарядного тока покажет, сходится ли расчёт: световой день включает часы с очень разной отдачей панели.

Учимся вовремя отправлять показания и засыпать

Пример рассчитан на классический ESP32 Dev Module. Он использует Arduino-ESP32 3.3.12, DHT sensor library 1.4.7, Adafruit Unified Sensor 1.1.15 и LoRa 0.8.0 от Sandeep Mistry. Параметр USE_LORA выбирает один из двух вариантов. DHT22 подключается к 3V3, GND и GPIO4; подтяжка DATA должна идти к 3V3 согласно схеме конкретного модуля.

Для Wi-Fi укажите сеть, адрес и токен собственного сервера. Сервер должен проверять токен, формат данных и возвращать 204 только после сохранения измерения. HTTP-пример предназначен для изолированной локальной сети: для передачи через интернет нужно HTTPS с проверкой сертификата. Очереди и повторной отправки старых измерений здесь нет; при ошибке текущая проба теряется, что явно выводится в журнал.

Для LoRa соединения SPI заданы в коде: SCK — GPIO18, MISO — GPIO19, MOSI — GPIO23, NSS — GPIO5, RESET — GPIO14, DIO0 — GPIO26. Питание и логика модуля должны быть совместимы с 3,3 В. Частота должна соответствовать модулю и антенне: SX1278 не рассчитан на 868 МГц. Перед включением передатчика проверьте допустимые частоту, мощность и режим использования в своей стране, затем измените настройки и RADIO_CONFIG_VERIFIED. Числа в коде не являются разрешением на радиоизлучение.

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <DHT.h>
#include <SPI.h>
#include <LoRa.h>
#include <esp_sleep.h>
#include <math.h>

// 0 — Wi-Fi; 1 — LoRa. Две сборки одного примера.
#ifndef USE_LORA
#define USE_LORA 0
#endif
constexpr int DHT_PIN = 4, VBAT_PIN = 34;
constexpr char DEVICE[] = "field_1";
constexpr char WIFI_SSID[] = "CHANGE_ME";
constexpr char WIFI_PASSWORD[] = "CHANGE_ME";
// Только изолированная локальная сеть; этот адрес — пример сервера.
constexpr char SERVER_URL[] = "http://192.168.1.100:1880/ferma/pole1";
constexpr char API_TOKEN[] = "CHANGE_ME";
constexpr float DIVIDER_RATIO = 2.0f;
constexpr float VBAT_CAL = 1.0f; // Уточнить сравнением с мультиметром.
// Пример порогов экономии, не защита аккумулятора и не проценты заряда.
constexpr float LOW_VBAT = 3.4f, ECONOMY_VBAT = 3.6f;
constexpr uint32_t NORMAL_MIN = 15, ECONOMY_MIN = 30, LOW_MIN = 360;
constexpr long LORA_FREQ = 433000000;
constexpr int LORA_DBM = 2;
constexpr bool RADIO_CONFIG_VERIFIED = false;
DHT dht(DHT_PIN, DHT22);
volatile bool txDone = false;
void IRAM_ATTR onRadioDone() { txDone = true; }

void sleepMinutes(uint32_t minutes) {
#if USE_LORA
  // LoRa.sleep() вызывается только после успешной инициализации радио.
#else
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);
#endif
  esp_sleep_enable_timer_wakeup(uint64_t(minutes) * 60ULL * 1000000ULL);
  Serial.flush();
  esp_deep_sleep_start();
}
float batteryVoltage() {
  analogReadResolution(12);
  analogSetPinAttenuation(VBAT_PIN, ADC_11db);
  uint32_t total = 0;
  for (int i = 0; i < 16; ++i) {
    total += analogReadMilliVolts(VBAT_PIN);
    delay(2);
  }
  return (total / 16.0f) * 0.001f * DIVIDER_RATIO * VBAT_CAL;
}
uint32_t sleepFor(float v) {
  if (!isfinite(v) || v < LOW_VBAT || v > 4.5f) return LOW_MIN;
  return v < ECONOMY_VBAT ? ECONOMY_MIN : NORMAL_MIN;
}
bool sendReading(float t, float h, float v) {
#if USE_LORA
  if (!RADIO_CONFIG_VERIFIED) {
    Serial.println("Radio disabled: verify module, antenna and local radio rules");
    return false;
  }
  SPI.begin(18, 19, 23, 5);
  LoRa.setPins(5, 14, 26);
  if (!LoRa.begin(LORA_FREQ)) return false;
  LoRa.setSpreadingFactor(7);
  LoRa.setSignalBandwidth(125000);
  LoRa.setCodingRate4(5);
  LoRa.setSyncWord(0x12);
  LoRa.enableCrc();
  LoRa.setTxPower(LORA_DBM);
  if (!LoRa.beginPacket()) { LoRa.sleep(); return false; }
  String packet = String(DEVICE) + "|T=" + String(t,1) + "|H=" + String(h,1) + "|B=" + String(v,2);
  LoRa.print(packet);
  txDone = false;
  LoRa.onTxDone(onRadioDone);
  LoRa.endPacket(true);
  const uint32_t started = millis();
  while (!txDone && uint32_t(millis() - started) < 5000) delay(5);
  const bool finished = txDone;
  LoRa.onTxDone(nullptr);
  LoRa.sleep();
  Serial.println(finished ? "Radio TX completed; gateway receipt NOT confirmed" : "Radio TX timeout");
  return finished;
#else
  if (String(WIFI_SSID) == "CHANGE_ME" || String(API_TOKEN) == "CHANGE_ME") return false;
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  const uint32_t started = millis();
  while (WiFi.status() != WL_CONNECTED && uint32_t(millis() - started) < 10000) delay(100);
  if (WiFi.status() != WL_CONNECTED) return false;
  HTTPClient http;
  http.setConnectTimeout(3000);
  http.setTimeout(5000);
  if (!http.begin(SERVER_URL)) return false;
  http.addHeader("Content-Type", "application/json");
  http.addHeader("Authorization", String("Bearer ") + API_TOKEN);
  String body = String("{\"device\":\"") + DEVICE + "\",\"temp\":" + String(t,1)
    + ",\"hum\":" + String(h,1) + ",\"vbat\":" + String(v,2) + "}";
  const int code = http.POST(body);
  http.end();
  Serial.printf("HTTP result: %d\n", code);
  // По контракту сервера 204 означает, что измерение принято и сохранено.
  return code == 204;
#endif
}
void setup() {
  Serial.begin(115200);
  const float v = batteryVoltage();
  const uint32_t nextSleep = sleepFor(v);
  if (!isfinite(v) || v < LOW_VBAT || v > 4.5f) {
    Serial.println("Battery measurement out of working range; skipping radio");
    sleepMinutes(nextSleep);
  }
  dht.begin();
  delay(2000); // Время старта датчика после включения питания.
  const float t = dht.readTemperature(), h = dht.readHumidity();
  if (!isfinite(t) || !isfinite(h) || h < 0 || h > 100) {
    Serial.println("Sensor invalid; measurement not sent");
    sleepMinutes(nextSleep);
  }
  const bool sent = sendReading(t,h,v);
  Serial.println(sent ? "Send step completed; see transport result" : "Send failed; sample not queued");
  sleepMinutes(nextSleep);
}
void loop() {}

Пакет LoRa имеет вид field_1|T=24.5|H=63.2|B=3.87. Шлюз должен использовать такой же формат, частоту, полосу, spreading factor, coding rate, sync word и CRC. Код ждёт завершения передачи с тайм-аутом; это подтверждает лишь окончание работы передатчика, а не приём или сохранение на шлюзе. Вместо фиксированной задержки код использует событие завершения передачи.

В примере пороги 3,4 и 3,6 В меняют интервал сна. Подберите их по измерениям своего узла, сохранив аппаратную защиту от глубокого разряда. После засыпания ESP32 ещё раз измерьте ток: внешние модули могут оставаться активными. Здесь сосредоточимся на измерении и передаче; обновление прошивки по сети потребует отдельного решения.

Первый день работы на участке

  1. Сверьте схему, полярность, допустимые напряжения и ток зарядки по документации всех компонентов. До установки батареи проверьте отсутствие короткого замыкания.
  2. На ограниченном по току источнике проверьте выход преобразователя без ESP32, затем с нагрузкой. Исключите просадки при передаче и непредусмотренное соединение USB с внешним питанием.
  3. Сравните показания АЦП с мультиметром. Проверьте низкое напряжение, отсутствие датчика, недоступную сеть и отказ сервера.
  4. Измерьте ток после засыпания со всей подключённой периферией. Отдельно проверьте отключение нагрузки защитой и температурный запрет зарядки.
  5. Поставьте батарею и электронику в подходящий корпус в тени; датчик воздуха вынесите в предназначенный для него защитный экран. Продумайте конденсат, вводы кабелей и обслуживание.
  6. Проверьте дневной и ночной режимы, пасмурную погоду и восстановление после разряда. Только после этого оценивайте автономность и размер панели.

Документация и связанные проекты

Питание: рекомендации Espressif. Измерения: API АЦП Arduino-ESP32. Передача: API библиотеки LoRa. Диапазон радио: характеристики SX1278.

Передатчик LoRa на ESP32 поможет отправить показания с удалённой точки; при объединении сверьте питание, интерфейсы и формат пакетов.

Вентиляция теплицы на ESP32 — повод отдельно измерить расход привода, если захочется питать от батареи ещё и автоматику.

💬 Комментарии

Чтобы оставить комментарий, войдите или зарегистрируйтесь

Загрузка комментариев...

Об этой теме также писали

Другие материалы об этом же событии — можно сравнить акценты источников.

Похожие статьи