↩️ Назад

Категории

Солнечный трекер своими руками

22.07.2026 | Статья из категории: 2040

Трекер для панелей: DIY для конца света
— или как мы победили лень, сварили каркас и заставили ESP32 охотиться за фотонами
ESP32 + ZigBee EROI: 4.2 (и это нормально)

Реалии 2040 таковы: центральное электричество — это роскошь, а за стабильность в розетке теперь отвечают EROI, климатические налоги и токсичные батареи. Но, как говорится, если не можешь подключиться к сети — сделай так, чтобы солнце само бегало за твоей панелью.

Мы (пара инженеров с дивана) придумали механический трекер из подручных материалов, двух супер-медленных моторов с Алиэкспресс и датчика света на ZigBee. Всё управляется через ESP32, а код умещается на пару экранов.

Почему трекер — это must-have

Статичная панель — это прошлый век. Если ты рассчитываешь на автономность, каждый градус поворота даёт до 30% прироста энергии в утренние/вечерние часы. Но давайте по-честному: перед тем, как браться за сварку, мы оценили риски.

-12%
Падение EROI из-за климата
+40%
Токсичность при утилизации
28%
Средний выигрыш энергии
72 ч
Автономия (средняя полоса)
Важно: Производство панелей и аккумуляторов уже не окупается по "зелёным" критериям. Трекер продлевает жизнь системе, позволяя заряжать батареи при низком солнце — и это наш единственный шанс на автономку без дизеля.

Как это собрано (максимально просто)

  • Каркас: квадратная труба 20x20, сварена на коленке. Всё держится на двух шпильках с приваренной гайкой.
  • Привод: два мотора-редуктора с Алиэкспресс (120 RPM, но с редуктором 1:500). Крутят супер-медленно, но супер-сильно. Торопиться нам некуда, солнце за день смещается на 15° в час — наши моторы отрабатывают это за пару минут.
  • Управление: классический H-мост (L298N или аналоги). Два канала: вверх/вниз и влево/вправо.
  • Глаз трекера: датчик света на ZigBee (выдаёт 0..1024). Просто приклеен на панель термоклеем — так он видит реальное освещение панели.
  • Мозг: ESP32. Wi-Fi нам не нужен, выключаем его для экономии. Работает в цикле: сканирует свет, крутит моторы, ищет максимум.
Логика кода: Сначала крутим по азимуту (влево-вправо) пока показания не перестанут расти. Потом фиксируем угол и ищем максимум по высоте. И так каждые 15 минут.
(Потому что редукторы медленные, а солнце не убежит).

ESP32: минимальный код

// Трекер для выживальщиков v1.0 #define PIN_MOTOR1_A 13 #define PIN_MOTOR1_B 14 #define PIN_MOTOR2_A 27 #define PIN_MOTOR2_B 26 #define ZIGBEE_ADC 34 // датчик света int readLight() { return analogRead(ZIGBEE_ADC); } void sweep() { // Ищем максимум по горизонтали int maxVal = readLight(); for(int i=0; i<50; i++) { digitalWrite(PIN_MOTOR1_A, HIGH); delay(100); digitalWrite(PIN_MOTOR1_A, LOW); int current = readLight(); if(current > maxVal) maxVal = current; else break; // нашли пик } // Аналогично по вертикали... } // И вуаля — панель смотрит в солнце!

В реальности мы добавили защиту от дурака (концевые выключатели) и сглаживание АЦП, но суть та же. Моторы жрут 300 мА на пике, но включаются редко — ESP32 спит в глубоком сне между циклами.

Тест-драйв и выводы

Первый блин — комом: мы перекрутили шпильки и сорвали резьбу. Но со второй попытки система отработала 3 дня в пасмурную погоду и показала +27% заряда по сравнению со статикой.

Главный вывод: в условиях дефицита ресурсов (падение EROI, токсичные отходы и климатические "сюрпризы") трекер — это не хайп, а необходимость. Он позволяет выжать максимум из каждой панели и продлить жизнь аккумуляторам, снижая глубину разряда.

Совет на будущее: бери моторы с железными шестернями (пластик рассыпается на морозе) и залей плату эпоксидкой — влага убьёт ESP32 быстрее, чем зомби-апокалипсис.

Эпилог: готовь сани летом

Сейчас 2026 год, но через 10 лет «просто купить панели» уже не будет работать — из-за дефицита редкоземельных металлов и налогов на углерод. Умный трекер — это инвестиция в автономию. Мы выложили все чертежи и BOM-лист в открытый доступ. Если у тебя есть руки и паяльник — повтори, не пожалеешь.

А я пошёл за кофе. Панель пусть сама крутится, она теперь умнее меня. Вот тестовый код от нейросети, сорян, не проверял ))

// ============================================================
//  Трекер для солнечной панели v3.0 (с защитой от облаков)
//  ESP32 + 2 редуктора + ZigBee датчик (0-1024)
//  Алгоритм: поиск максимума ТОЛЬКО при стабильном свете
//  Или если разница между сторонами > 10% (чтобы не дёргаться на шум)
// ============================================================

#include 

// ========== ПИНЫ ДЛЯ МОТОРОВ (H-Bridge) ==========
#define MOTOR1_IN1  13   
#define MOTOR1_IN2  14
#define MOTOR2_IN1  27   
#define MOTOR2_IN2  26

// ========== КОНЦЕВЫЕ ВЫКЛЮЧАТЕЛИ ==========
#define LIMIT_LEFT   15   
#define LIMIT_RIGHT  4    
#define LIMIT_UP     16   
#define LIMIT_DOWN   17   

// ========== ДАТЧИК СВЕТА ==========
#define LIGHT_SENSOR 34   

// ========== НАСТРОЙКИ ==========
#define STEP_DELAY   120   
#define SLEEP_SEC    600   // 10 минут сна
#define MAX_STEPS    80    
#define CLOUD_THRESHOLD 0.45  // Если свет < 45% от максимума — считаем, что облако

// ========== ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ==========
int globalMaxLight = 0;      // Запоминаем абсолютный максимум (солнечный день)
int lastGoodPosition = 0;    // Последняя удачная позиция (условный угол)
int currentLight = 0;

// ============================================================
//  УПРАВЛЕНИЕ МОТОРАМИ
// ============================================================
void motorStop() {
  digitalWrite(MOTOR1_IN1, LOW);
  digitalWrite(MOTOR1_IN2, LOW);
  digitalWrite(MOTOR2_IN1, LOW);
  digitalWrite(MOTOR2_IN2, LOW);
}

void motorLeft() {
  digitalWrite(MOTOR1_IN1, HIGH);
  digitalWrite(MOTOR1_IN2, LOW);
}

void motorRight() {
  digitalWrite(MOTOR1_IN1, LOW);
  digitalWrite(MOTOR1_IN2, HIGH);
}

void motorUp() {
  digitalWrite(MOTOR2_IN1, HIGH);
  digitalWrite(MOTOR2_IN2, LOW);
}

void motorDown() {
  digitalWrite(MOTOR2_IN1, LOW);
  digitalWrite(MOTOR2_IN2, HIGH);
}

// ============================================================
//  ЧТЕНИЕ СВЕТА (усреднение)
// ============================================================
int readLight() {
  long sum = 0;
  for (int i = 0; i < 15; i++) {
    sum += analogRead(LIGHT_SENSOR);
    delay(2);
  }
  return sum / 15;
}

// ============================================================
//  ОБНОВЛЕНИЕ ГЛОБАЛЬНОГО МАКСИМУМА (калибровка)
// ============================================================
void updateGlobalMax() {
  int val = readLight();
  if (val > globalMaxLight) {
    globalMaxLight = val;
    Serial.printf("☀️ Новый глобальный максимум: %d\n", globalMaxLight);
  }
}

// ============================================================
//  УМНЫЙ ПОИСК МАКСИМУМА (с защитой от облаков)
// ============================================================
int sweepAxis(void (*stepFunc)(), int limitPin1, int limitPin2, int direction) {
  /*
    Возвращает:
    - >0 если найден локальный максимум
    - -1 если облачно и дёргаться не надо
  */
  
  // 1. Проверяем, есть ли солнце вообще
  int baseLight = readLight();
  if (globalMaxLight > 0 && baseLight < globalMaxLight * CLOUD_THRESHOLD) {
    Serial.println("☁️ Облачно! Пропускаем трекинг, остаёмся на месте.");
    return -1;  // Не дёргаемся
  }

  int bestVal = baseLight;
  int bestSteps = 0;
  int stepsDone = 0;
  
  // 2. Ищем рост сигнала (крутим в одну сторону)
  while (stepsDone < MAX_STEPS) {
    if (digitalRead(limitPin1) == LOW || digitalRead(limitPin2) == LOW) {
      break;
    }
    
    stepFunc();
    delay(STEP_DELAY);
    motorStop();
    delay(20);
    
    int val = readLight();
    stepsDone++;
    
    // Если свет начал падать — значит, прошли пик
    if (val < bestVal * 0.98) {  // 2% гистерезис, чтобы не дёргаться
      break;
    }
    
    if (val > bestVal) {
      bestVal = val;
      bestSteps = stepsDone;
    }
  }
  
  // 3. Возвращаемся в точку максимума
  int backSteps = stepsDone - bestSteps;
  if (backSteps > 0) {
    void (*reverseFunc)() = NULL;
    if (stepFunc == motorLeft) reverseFunc = motorRight;
    else if (stepFunc == motorRight) reverseFunc = motorLeft;
    else if (stepFunc == motorUp) reverseFunc = motorDown;
    else if (stepFunc == motorDown) reverseFunc = motorUp;
    
    if (reverseFunc) {
      for (int i = 0; i < backSteps; i++) {
        reverseFunc();
        delay(STEP_DELAY);
        motorStop();
        delay(10);
      }
    }
  }
  
  // 4. Проверяем, есть ли смысл в этом максимуме
  if (bestVal < globalMaxLight * 0.5) {
    // Если нашли только 50% от солнечного дня — это шум
    Serial.println("🌥️ Свет слабый, позиция не обновляется.");
    return -1;
  }
  
  return bestVal;
}

// ============================================================
//  ОСНОВНОЙ ТРЕКИНГ
// ============================================================
void doTracking() {
  Serial.println("\n=== СТАРТ ТРЕКИНГА ===");
  
  // Обновляем глобальный максимум (если сейчас солнечно)
  updateGlobalMax();
  
  // --- 1. Горизонталь ---
  int hBest1 = sweepAxis(motorLeft, LIMIT_LEFT, LIMIT_RIGHT, 1);
  delay(300);
  int hBest2 = sweepAxis(motorRight, LIMIT_RIGHT, LIMIT_LEFT, -1);
  
  int finalH = -1;
  if (hBest1 > 0 && hBest2 > 0) {
    finalH = max(hBest1, hBest2);
  } else if (hBest1 > 0) {
    finalH = hBest1;
  } else if (hBest2 > 0) {
    finalH = hBest2;
  }
  
  // --- 2. Вертикаль ---
  int vBest1 = sweepAxis(motorUp, LIMIT_UP, LIMIT_DOWN, 1);
  delay(300);
  int vBest2 = sweepAxis(motorDown, LIMIT_DOWN, LIMIT_UP, -1);
  
  int finalV = -1;
  if (vBest1 > 0 && vBest2 > 0) {
    finalV = max(vBest1, vBest2);
  } else if (vBest1 > 0) {
    finalV = vBest1;
  } else if (vBest2 > 0) {
    finalV = vBest2;
  }
  
  // --- 3. Логируем результат ---
  motorStop();
  if (finalH > 0 || finalV > 0) {
    Serial.printf("✅ Трекинг успешен: H=%d, V=%d\n", finalH, finalV);
    lastGoodPosition = max(finalH, finalV);
  } else {
    Serial.println("⏸️ Облачно или шум — остаёмся в прошлой позиции.");
  }
  
  Serial.println("=== ТРЕКИНГ ЗАВЕРШЁН ===");
}

// ============================================================
//  SETUP
// ============================================================
void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("\n\n[BOOT] Солнечный трекер v3.0 (анти-облако)");
  
  pinMode(LIGHT_SENSOR, INPUT);
  
  pinMode(MOTOR1_IN1, OUTPUT);
  pinMode(MOTOR1_IN2, OUTPUT);
  pinMode(MOTOR2_IN1, OUTPUT);
  pinMode(MOTOR2_IN2, OUTPUT);
  motorStop();
  
  pinMode(LIMIT_LEFT, INPUT_PULLUP);
  pinMode(LIMIT_RIGHT, INPUT_PULLUP);
  pinMode(LIMIT_UP, INPUT_PULLUP);
  pinMode(LIMIT_DOWN, INPUT_PULLUP);
  
  // Калибруем начальный максимум
  globalMaxLight = readLight();
  Serial.printf("Начальный максимум: %d\n", globalMaxLight);
  
  doTracking();
}

// ============================================================
//  LOOP (глубокий сон)
// ============================================================
void loop() {
  Serial.printf("💤 Сон %d секунд...\n", SLEEP_SEC);
  
  WiFi.mode(WIFI_OFF);
  btStop();
  
  esp_sleep_enable_timer_wakeup(SLEEP_SEC * 1000000LL);
  esp_deep_sleep_start();
  
  delay(1000);
  doTracking();
}
👍 Оцените статью
Всего голосов: 0



Категории:

Категории

Комментарии

Пока нет комментариев. Будьте первым!

Оставить комментарий

← Назад к списку статей

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

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