↩️ Назад

Категории

DIY: Солнечная станция + Ветряк + ESP32 Ballast Controller

07.10.2026 | из категории: IOT умный дом

DIY: Солнечная станция + Ветряк + ESP32 Ballast Controller

DIY: Солнечная станция + Ветряк + Теплоаккумулятор

Полный фарш: MPPT, балластный нагрев воды и собственный контроллер на ESP32 с PID-регулированием

1. Архитектура системы

Система делится на два независимых контура управления, которые не должны мешать друг другу:

Ветряк — это третий, отдельный контур, у него свой контроллер (КЭВ) со своим балластом. Не смешивайте его с ESP32-контроллером, иначе получите кашу.

☀️ Солнце 🌬️ Ветер │ │ ▼ ▼ ┌───────────────┐ ┌─────────────────┐ │ КЭС DOMINATOR │ │ КЭВ DOMINATOR │ │ (MPPT) │ │ + свой ТЭН │ └───────┬───────┘ └────────┬────────┘ │ │ └──────────┬────────────────┘ ▼ ┌─────────────┐ │ АКБ 48V │ └──────┬──────┘ ▼ ┌─────────────┐ │ Инвертор │ └──────┬──────┘ │ ┌───────────────────┼───────────────────┐ ▼ ▼ ▼ Дом (потребители) PZEM-004T ТЭН теплоаккумулятора (датчик мощности) ▲ │ │ └────► ESP32 ────────┘ PID + ШИМ

2. Зачем нужен балласт

☠️ Ветряк без балласта = катастрофа

Если разомкнуть цепь ветрогенератора, он останется без нагрузки и раскрутится до оборотов, при которых лопасти отрываются или генератор горит. Поэтому балласт для ветряка обязателен, и управляет им штатный контроллер КЭВ, а не ESP32.

✅ Солнце без балласта = просто потери

Панель можно отключить без последствий. Но если вы хотите использовать избыток — например, чтобы греть воду в теплоаккумуляторе — нужен отдельный дивертер. Это и есть наша ESP32-система.

Два принципиально разных балласта

ТипГде стоитКто управляетЗадача
Защитный балласт ветрякаНа выходе ветрякаКЭВ DOMINATORНе дать ветряку разогнаться
Утилизирующий балласт (дивертер)На AC-стороне инвертораESP32Слить избыток солнца в тепло

3. Железо

КомпонентМодельЗачем
MCUESP32 DevKit v1 / S3PID, Wi-Fi, веб-морда
Датчик мощностиPZEM-004T v3.0Меряет U, I, P, энергию по UART
Силовой ключSSR 40A или симистор BTA41 + MOC3063Коммутация ТЭНа 2–3 кВт
Диммер (опция)RobotDyn AC Dimmer 2 кВтПлавное ШИМ-регулирование мощности
Дисплей (опция)OLED SSD1306 I2CПоказ P, duty, режима
ТЭН2–3 кВт, 220 ВГреет воду в теплоаккумуляторе
Питание ESP32Hi-Link HLK-PM01 (5V 3W)Питание от 220 В
⚠️ Мощность SSR подбирайте с запасом ×2

Для ТЭНа 2 кВт при 220 В ток ≈ 9 А. SSR на 40 А — нормально, но обязательно на радиаторе. Для 3 кВт берите 50–60 А.

4. Логика PID

Цель: держать мощность обмена с сетью около нуля. Если P_grid < 0 — есть избыток, греем сильнее. Если P_grid > 0 — берём из сети, греем слабее.

// Уставка: 0 Вт обмена (всё, что даёт солнце, уходит в дом + ТЭН)
float setpoint = 0.0;
float error    = setpoint - P_grid;
float output   = Kp*error + Ki*integral + Kd*derivative;
// output → duty 0..100% для SSR/диммера

Настройка коэффициентов (отправная точка)

Начинайте с Ki=0, Kd=0, поднимайте Kp до лёгкого колебания, затем добавьте Ki для устранения смещения.

5. Полный скетч ESP32

/* ============================================================
   ESP32 Solar Diverter — PID ballast controller
   Читает PZEM-004T, управляет SSR/диммером ТЭНа
   ============================================================ */

#include <WiFi.h>
#include <WebServer.h>
#include <PZEM004Tv30.h>
#include <ArduinoOTA.h>

// ---------- Пины ----------
#define SSR_PIN      25   // через оптопару к SSR
#define ZC_PIN       26   // zero-cross (если диммер)
#define PZEM_RX      16
#define PZEM_TX      17

// ---------- PZEM ----------
HardwareSerial pzemSerial(2);
PZEM004Tv30 pzem(pzemSerial, PZEM_RX, PZEM_TX);

// ---------- PID ----------
float Kp = 0.8, Ki = 0.05, Kd = 0.1;
float setpoint = 0.0;
float integral = 0, prevError = 0;
float duty = 0;              // 0..100 %
const float DUTY_MAX = 100.0;
const float DUTY_MIN = 0.0;

// ---------- Фильтр ----------
float P_filtered = 0;
const float ALPHA = 0.2;   // EMA

// ---------- Веб ----------
WebServer server(80);
unsigned long lastPid = 0;
const unsigned long PID_MS = 1000;

float P_grid = 0, V_grid = 0, I_grid = 0;
float P_heat = 0;

// ============================================================
void setup() {
  Serial.begin(115200);
  pzemSerial.begin(9600, SERIAL_8N1, PZEM_RX, PZEM_TX);
  pinMode(SSR_PIN, OUTPUT);
  digitalWrite(SSR_PIN, LOW);

  WiFi.begin("YOUR_SSID", "YOUR_PASS");
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  Serial.println("\nWiFi OK: " + WiFi.localIP().toString());

  server.on("/", [](){
    String html = "<html><head><meta charset=utf-8><meta http-equiv=refresh content=3></head><body>";
    html += "<h2>Solar Diverter</h2>";
    html += "<p>P_grid: " + String(P_grid,1) + " W</p>";
    html += "<p>U: " + String(V_grid,1) + " V, I: " + String(I_grid,2) + " A</p>";
    html += "<p>Duty: " + String(duty,1) + " %</p>";
    html += "<p>P_heat ~ " + String(P_heat,0) + " W</p>";
    html += "</body></html>";
    server.send(200, "text/html", html);
  });
  server.begin();
}

// ============================================================
void loop() {
  server.handleClient();

  if (millis() - lastPid >= PID_MS) {
    lastPid = millis();
    readPzem();
    computePid();
    applyDuty();
  }
}

// ============================================================
void readPzem() {
  V_grid = pzem.voltage();
  I_grid = pzem.current();
  P_grid = pzem.power();
  if (isnan(V_grid)) V_grid = 0;
  if (isnan(I_grid)) I_grid = 0;
  if (isnan(P_grid)) P_grid = 0;

  // EMA-фильтр, чтобы облака не дёргали PID
  P_filtered = ALPHA * P_grid + (1.0 - ALPHA) * P_filtered;
}

// ============================================================
void computePid() {
  float error = setpoint - P_filtered;

  integral += error * (PID_MS / 1000.0);
  // анти-виндап
  if (integral > 50) integral = 50;
  if (integral < -50) integral = -50;

  float derivative = (error - prevError) / (PID_MS / 1000.0);
  prevError = error;

  float out = Kp*error + Ki*integral + Kd*derivative;

  // P_grid > 0 — берём из сети, значит уменьшаем нагрев
  // P_grid < 0 — избыток, увеличиваем нагрев
  duty -= out * 0.1;   // знак и шаг подбираются под вашу систему

  if (duty > DUTY_MAX) duty = DUTY_MAX;
  if (duty < DUTY_MIN) duty = DUTY_MIN;
}

// ============================================================
void applyDuty() {
  // Простой вариант: SSR вкл/выкл с окном 1 сек
  // (для ТЭНа инерция воды огромная, ШИМ 1 Гц — ок)
  static unsigned long winStart = 0;
  static bool on = false;
  const unsigned long WINDOW = 1000;

  if (millis() - winStart >= WINDOW) {
    winStart = millis();
    on = false;
  }
  if (!on && (millis() - winStart) < (WINDOW * duty / 100.0)) {
    digitalWrite(SSR_PIN, HIGH);
  } else {
    digitalWrite(SSR_PIN, LOW);
  }

  // оценка мощности нагрева (если ТЭН 2000 Вт при 100%)
  P_heat = 2000.0 * duty / 100.0;
}
⚠️ Что нужно доработать под себя
  • Знак и масштаб duty -= out * 0.1 — зависит от того, как PZEM включён в цепь (прямой/обратный ток).
  • Если используете настоящий AC-диммер (RobotDyn), замените applyDuty() на фазовое управление с прерыванием по ZC_PIN.
  • Для SSR окно 1 сек — норма. Для диммера — 10 мс.

6. Схема подключения и безопасность

☠️ 220 В — это смертельно
  • Все работы при обесточенной линии.
  • ESP32 и AC-часть — только через оптопару (MOC3063) или SSR с изоляцией.
  • Корпус — в DIN-рейку в щитке, не в картонной коробке на полу.
  • Автомат + УЗО на линии ТЭНа обязательно.
  • Если нет опыта — наймите электрика для силовой части.

Порядок подключения PZEM

  1. PZEM ставится между инвертором и домом (на вводной фазе).
  2. Направление тока: если инвертор отдаёт в дом — P > 0; если馈电 — P < 0. Проверьте знаком.
  3. UART PZEM → ESP32 (RX/TX перекрёстно).

7. Теплоаккумулятор: как встроить

Теплоаккумулятор (ТА) — это бак 500–2000 л с водой и ТЭНом. Логика:

ПараметрЗначение
Объём ТА1000 л
ТЭН2 кВт (9 А)
ΔT за 4 ч солнца≈ 17 °C (при 100% duty)
Термостатмеханический на 85 °C — обязателен
💡 Совет

Поставьте два ТЭНа: один на 1 кВт (низкий порог, для пасмурных дней) и один на 2 кВт (высокий порог, для яркого солнца). ESP32 переключает между ними SSR-ами. Это даёт более широкий диапазон утилизации.

8. Типичные ошибки

  1. Путать балласт ветряка и дивертер солнца. Это разные контуры, разные контроллеры.
  2. Ставить SSR без радиатора. 9 А на 40 А SSR — норм, но греется. Радиатор обязателен.
  3. PID без фильтра. Облако пролетело — P_grid дёрнулся — duty скачет. EMA-фильтр спасает.
  4. Забыть про анти-виндап. Интеграл уходит в бесконечность, потом система «залипает».
  5. Нет термостата на ТЭНе. ESP32 завис — вода кипит — бак рвёт. Механический термостат — последняя линия обороны.
  6. Питать ESP32 от того же инвертора без гальваноразвязки. Помехи от ШИМ лезут в Wi-Fi. Ставьте HLK-PM01.
✅ Чек-лист запуска
  1. PZEM показывает правильный знак P при потреблении.
  2. SSR щёлкает при duty > 0.
  3. При выключенном солнце duty = 0, ТЭН холодный.
  4. При ярком солнце и полной АКБ duty растёт, вода греется.
  5. P_grid держится в пределах ±50 Вт.
  6. Термостат ТЭНа срабатывает при 85 °C.



Категории:

Категории

← Назад к списку

Посетителей сегодня: 0
о блоге | карта блога | 📡 Подписаться на RSS

© Digital Specialist | Не являемся сотрудниками Google, Яндекса и NASA