Solartracker-Projekt 2017 / 2018
                von Michael Schulte, Rönkhausen

 

 

 

 

 

 

 

 

image001.jpg

 

Abb. 1: Solartracker-Modell, gebaut mit Fischertechnik; unter dem roten
Dach befindet sich ein Arduino-Uno-Mikrocontroller mit Motor-  und datalogger-shield zur Modellsteuerung (C++-Programmcode s.u.).
Das Programm wird von einem PC per USB auf den Controller geladen und wird danach ohne PC selbständig ausgeführt. Die Aktualisierung der Azimut- und Elevation-Positionen erfolgt etwa alle 5 sec.

 

 

 

 

 

image002.jpg

 

Abb. 2: Modellansicht von vorne; unter dem waagerecht
liegenden grossen Zahnrad befindet sich ein 20-kOhm-
10-Gang-Drehpotentiometer, das sich 1:1 mit dem
Solarpanel bewegt. Somit kann jederzeit die aktuelle
Azimut-Position (AzI) bestimmt und mit dem astronomisch
berechneten Azimutwert (AzS) verglichen werden.

 

 

 

 

 

 

 

 

  

image003.jpg

 

Abb. 3: Longruner-Servo für die Höhenwinkel-Verstellung (0-180 Grad) mit
aufmontiertem 3-Volt-
ETM500-Solarpanel. Der astronomisch berechnete
Elevation-Wert wird 1:1 direkt auf den Servo übertragen und bewirkt eine ständig aktualisierte, exakte Schrägausrichtung des Solarpanels zur Sonne.

image004.jpg

Abb. 4: Pololu-Steppermotor mit Schneckentrieb für die Azimut-Verstellung. Falls der berechnete Azimutwert (AzS, s. Abb. 5) grösser als der vom Drehpotentiometer gelieferte Azimutwert (AzI), so dreht der Steppermotor 10 Schritte vorwärts; ist AzS kleiner als AzI, so dreht der Steppermotor 10 Schritte rückwärts. Auf diese Weise ist (mit  geringen Abweichungen von ca. 1-2 Grad) im Idealfall immer AzI = AzS, d.h. die Azimutposition des Solarpanels stimmt mit dem berechneten Azimutwert überein.

 

image005.JPG

 

Abb. 5: Display zur Anzeige der wichtigsten Daten: Datum, Uhrzeit (MEZ), AzS = Azimut
(Sollwert, berechneter Wert, in [°]), WZ = Wahre Ortszeit (12:00 h -> Sonne steht am
Standort genau im Süden), AzI = aktueller Azimut des Solarpanels (Ist-Wert. in [°]),
S = aktuelle Himmelsrichtung (hier: Süd), TL = Tageslänge in [h], Elv = Elevation
( = Sonnenhöhe, berechneter Wert in [°]), PV = aktuelle Spannung am Solarpanel [Volt].
 

image006

 

Abb. 6:  Ein Zusatzdisplay (Kuman UNO R3 3,5" TFT Touchscreen)
mit SD Karten Slot zeigt die wichtigsten Werte im 10-Sek-Takt an.
Das Display wird von einem Arduino-Mega-2560-Mikrocontroller angesteuert.

 

 

 

Arduino-Programmcode

 

 

/*

  Sonnenstandberechnung und Solartracking mit DS3231 RTC Zeit

  Sonnenstandsberechnung geht konform mit:

  http://lexikon.astronomie.info/zeitgleichung/sunscript.html

  - Mit serieller und LCD -Ausgabe -

  - mit Spannungswert PV-Modul -

  Positionsangaben für Ort: Rönkhausen

  Version 6.5, Michael Schulte, 12. März 2018

*/

 

#include <Servo.h>

#include <DS3231.h>

#include <Wire.h>

#include <LiquidCrystal_I2C.h>

#include <Adafruit_MotorShield.h>

#include "utility/Adafruit_MS_PWMServoDriver.h"

 

Adafruit_MotorShield AFMS = Adafruit_MotorShield();

Adafruit_StepperMotor *stepper = AFMS.getStepper(200, 1);                                   // Steppermotor für Azimut

LiquidCrystal_I2C lcd(0x27, 20, 4);

RTCDateTime dt;

DS3231 clock;

Servo servo;                                                                                                          // Servomotor für Elevation

 

double pi = 3.1415926535897932384626433832795;

double kwert = pi / 180;

double latitude = 51.222241;

double longitude = 7.953978;

double Elevation;

double Aufgang;

double Untergang;

 

void setup() {

 

  Serial.begin(1000000);

  pinMode(11, OUTPUT);                                                                                      // für grüne LED

  pinMode(12, OUTPUT);                                                                                      // für rote LED

  AFMS.begin();

  stepper->setSpeed(10);

  lcd.init();                                                                                                            // initialisiere 20x4 LCD

  lcd.backlight();

  servo.attach(9);                                                                                                  // Servo Signal von Pin 9

  clock.begin();

  servo.write(3);

 

  //clock.setDateTime(2018, 03, 12, 17, 55, 50);                                                      // Hier Datum und aktuelle Uhrzeit (MEZ) setzen

}

 

 

// Beginn der Schleife

 

void loop() {

 

  dt = clock.getDateTime();                                                                                   // Zeitwert holen von RTC Time

 

  String hour = clock.dateFormat("H", dt);

  String minute = clock.dateFormat("i", dt);

  String year = clock.dateFormat("z", dt);

  String second = clock.dateFormat("s", dt);

 

  int yearday = year.toInt() + 1;

  int Jahr = (dt.year);

  int Monat = (dt.month);

  int Tag = (dt.day);

  double Stunde = hour.toInt();

  double Minute = minute.toInt();

  double Sekunde = second.toInt();

 

  double Deklination;

  double Azimut;

  double Zeitgleichung;

  double Zeitdifferenz;

  double AufgangOrtszeit;

  double UntergangOrtszeit;

  double Tageslaenge;

  double Refraktion;

  double MEZ;                                                                                                           // Mitteleuropäische Zeit

  double MOZ;                                                                                                           // Mittlere Ortszeit

  double WOZ;                                                                                                           // Wahre Ortszeit

  double ZeitSeitMittag;

  double B = latitude * kwert;                                                                                      // geogr. Breite in Radians

  double R = 0.00;

  double h = -(50.0 / 60.0) * kwert;                                                                               // Höhe des Sonnenmittelpunkts bei Aufgang: Radius + Refraktion

  int sensorValue;                                                                                                       // Potentiometerwert

  int azimutPoti;                                                                                                          // Ist-Wert Azimut

  int Zeitzone = 1;

  float P = 1013.25;                                                                                                    // Luftdruck der Standard-Atmosphäre in hPa

  float T = 15.0;                                                                                                          // Temperatur der Standard-Atmosphäre in °C

  int zeitschritt = 5000;

 

  MEZ = Stunde + Minute / 60 + Sekunde / 3600;

 

  Zeitgleichung = BerechneZeitgleichung (yearday);                                                     // Aufruf der Funktion: BerechneZeitgleichung

  Deklination = BerechneDeklination (yearday);                                                           // Aufruf der Funktion: BerechneDeklination

  Zeitdifferenz = BerechneZeitdifferenz (h, latitude, kwert, Deklination, pi);                   // Aufruf der Funktion: BerechneZeitdifferenz

  ZeitSeitMittag = MEZ + longitude / 15.0 - Zeitzone - 12 + Zeitgleichung;

  Azimut = SunAngles(Deklination, B, ZeitSeitMittag, Elevation);                                 // Aufruf der Funktion: SunAngles

  Refraktion = BerechneRwert(Elevation, kwert, P, T);                                                 // Aufruf der Funktion: BerechneRwert

  Elevation  = (Elevation + Refraktion) / kwert;                                                           // Höhe mit Refraktionskorrektur in Grad

 

  Tageslaenge = BerechneTageslaenge (Zeitdifferenz, Zeitgleichung, longitude, Zeitzone, Aufgang, Untergang);   // Aufruf der Funktion: BerechneTageslaenge

 

  MOZ = MEZ - (-longitude / 15) - 1;

  WOZ = MOZ + Zeitgleichung;

 

  int WOZdez = (int)WOZ;

  int WOZmin = (WOZ - WOZdez) * 60;

  int WOZsec = (WOZ - WOZdez) * 60;

 

  int MOZdez = (int)MOZ;

  int MOZmin = (MOZ - MOZdez) * 60;

  int MOZsec = (MOZ - MOZdez) * 60;

 

  int StdL = (int)Tageslaenge;

  int MinL = (int)((Tageslaenge - StdL) * 60);

 

  int StdA = (int)Aufgang;

  int MinA = (int)((Aufgang - StdA) * 60);

 

  int StdU = (int)Untergang;

  int MinU = (int)((Untergang - StdU) * 60);

 

 

  // Servomotor für Elevation

 

  double elevation;

 

  if (Elevation <= 0) {

    elevation = 0;

  }

  else {

    elevation = constrain(Elevation, 0, 63);                                                                     // Begrenzung der Servoauslenkung

    servo.write(elevation + 3);                                                                                        // Kalibrierung der Servoposition (Elevation)

  }

 

 

  // Steppermotor für Azimutposition

 

  sensorValue = analogRead(A0);                                                                                // Analogwert Poti an Pin A0

 

  if (sensorValue <= 471 && sensorValue > 433)  {                                      // 23.02.18: Skalierung der analogen Potentiometerwerte in Gradwerte (Azimut, NO-NW)

    azimutPoti = map(sensorValue, 471, 433, 45, 180);

  }

  if (sensorValue <= 433 && sensorValue >= 394)  {

    azimutPoti = map(sensorValue, 433, 394, 180, 315);

  }

 

  int azimut = (int)Azimut;

 

  if (Elevation > 0) {

 

    if (azimutPoti < azimut) {

      digitalWrite(11, HIGH);                                                                             // LED grün an: Stepper vorwärts

      digitalWrite(12, LOW);                                                                             // LED rot aus

      stepper->step(10, FORWARD, MICROSTEP);                                           // Nachführung: Wenn Poti-Wert kleiner als Azimut, dann Stepper 10 steps vorwärts

    }

    if (azimutPoti > azimut) {

      digitalWrite(12, HIGH);                                                                             // LED rot an: Stepper rückwärts

      digitalWrite(11, LOW);                                                                              // LED grün aus

      stepper->step(10, BACKWARD, MICROSTEP);                                          // Nachführung: Wenn Poti-Wert grösser als Azimut, dann Stepper 10 steps rückwärts

    }

  }

  else  {

    if (Elevation < 0 && azimutPoti > 90)  {

      zeitschritt = 0;

      digitalWrite(12, HIGH);                                                                              // LED rot an: Stepper rückwärts

      digitalWrite(11, LOW);                                                                              // LED grün aus

      stepper->step(100, BACKWARD, DOUBLE);                                           // Bei Erreichen der NW-Position schneller Rücklauf solange Azimut (Istwert) > 90 Grad

    }

  }

 

 

  // Deklarationen und Typumwandlungen für LCD-Anzeige

 

  const char* windrichtung[16] = {"N  ", "NNO", "NO ", "ONO", "O  ", "OSO", "SO ", "SSO", "S  ", "SSW", "SW ", "WSW", "W  ", "WNW", "NW ", "NNW"};

 

  int i = Windrichtung(azimutPoti);                                                                  // Aufruf Funktion: Windrichtung

 

  String wr = windrichtung[i];

  int analogPin = 2;

  float pvmod;

  float volt;                                                                                                  // Abfrage Spannung Solar Panel

  byte stellen = 2;

  pvmod = analogRead(analogPin);

  volt = pvmod / 1023 * 4.75;

 

  int Std = (int)Tageslaenge;

  int Min = (int)((Tageslaenge - Std) * 60);

 

 

  // Ausgabe der Werte auf LCD-Display

 

  lcd.setCursor(0, 0);

  lcd.print(clock.dateFormat("d.M.Y H:i:s", dt));

 

  lcd.setCursor(0, 1);

  lcd.print("AzS");

  lcd.setCursor(4, 1);

  lcd.print(Azimut);

  lcd.write(0xDF);

  lcd.print(" ");

 

  lcd.setCursor(12, 1);

  lcd.print("WZ ");

  if (WOZdez < 10) lcd.print("0");

  lcd.print(WOZdez);

  lcd.print(":");

  if (WOZmin < 10) lcd.print("0");

  lcd.print(WOZmin);

 

  lcd.setCursor(0, 2);

  lcd.print("AzI ");

  lcd.print(azimutPoti);

  lcd.write(0xDF);

  lcd.print(wr);

  lcd.print(" ");

 

  lcd.setCursor(12, 2);

  lcd.print("TL ");

  if (Std < 10) lcd.print("0");

  lcd.print(Std);

  lcd.print(":");

  if (Min < 10) lcd.print("0");

  lcd.print(Min);

 

  lcd.setCursor(0, 3);

  lcd.print("Elv ");

  lcd.print(Elevation, 2);

  lcd.write(0xDF);

  lcd.print("  ");

 

  lcd.setCursor(12, 3);

  lcd.print("PV ");

  lcd.print(volt, 1);

  lcd.print(" V");

 

 

  // Serielle Ausgabe

 

  Serial.print(" Datum                          ");

  Serial.println(clock.dateFormat("d.M.Y ", dt));

  Serial.print(" Mitteleurop. Zeit (MEZ)        ");

  Serial.println(clock.dateFormat("H:i:s", dt));

 

  Serial.print(" Wahre Ortszeit                 ");

  if (WOZdez < 10) Serial.print("0");

  Serial.print(WOZdez);

  Serial.print(":");

  if (WOZmin < 10) Serial.print("0");

  Serial.print(WOZmin);

  Serial.println(" h");

 

  Serial.print(" Mittlere Ortszeit              ");

  if (MOZdez < 10) Serial.print("0");

  Serial.print(MOZdez);

  Serial.print(":");

  if (MOZmin < 10) Serial.print("0");

  Serial.print(MOZmin);

  Serial.println(" h");

 

  Serial.print(" Tageslänge                     ");

  if (StdL < 10) Serial.print("0");

  Serial.print(StdL);

  Serial.print(":");

  if (MinL < 10) Serial.print("0");

  Serial.print(MinL);

  Serial.println(" h");

 

  Serial.print(" Sonnenaufgang                   ");

  if (StdA < 10) Serial.print("0");

  Serial.print(StdA);

  Serial.print(":");

  if (MinA < 10) Serial.print("0");

  Serial.print(MinA);

  Serial.println(" h");

 

  Serial.print(" Sonnenuntergang                ");

  Serial.print(StdU);

  Serial.print(":");

  if (MinU < 10) Serial.print("0");

  Serial.print(MinU);

  Serial.println(" h");

 

  Serial.print(" Tag im Jahr                    ");

  Serial.println(yearday);

 

  Serial.print(" Zeitgleichung                  ");

  Serial.print(Zeitgleichung * 60, 3);

  Serial.println(" min");

 

  Serial.print(" Deklination                    ");

  Serial.print(Deklination / kwert, 3);

  Serial.println("°");

 

  Serial.print(" Refraktion                     ");

  Serial.print(Refraktion / kwert, 3);

  Serial.println("°");

 

  Serial.print(" Elevation                      ");

  Serial.print(Elevation, 3);

  Serial.println("°");

  Serial.print(" Azimut                         ");

  Serial.print(Azimut, 3);

  Serial.println("°");

  Serial.println(" ");

 

  delay(zeitschritt);  // Programmpause

}

 

 

// Funktion: Windrichtung

 

int Windrichtung (float azimutPoti) {

 

  int i;

 

  if (azimutPoti >= 0 && azimutPoti < 11.25)  {

    i = 0;

  }

  if (azimutPoti >= 11.25 && azimutPoti < 33.75)  {

    i = 1;

  }

  if (azimutPoti >= 33.75 && azimutPoti < 56.25)  {

    i = 2;

  }

  if (azimutPoti >= 56.25 && azimutPoti < 78.75)  {

    i = 3;

  }

  if (azimutPoti >= 78.75 && azimutPoti < 101.25)  {

    i = 4;

  }

  if (azimutPoti >= 101.25 && azimutPoti < 123.75)  {

    i = 5;

  }

  if (azimutPoti >= 123.75 && azimutPoti < 146.25)  {

    i = 6;

  }

  if (azimutPoti >= 146.25 && azimutPoti < 168.75)  {

    i = 7;

  }

  if (azimutPoti >= 168.75 && azimutPoti < 191.25)  {

    i = 8;

  }

  if (azimutPoti >= 191.25 && azimutPoti < 213.75)  {

    i = 9;

  }

  if (azimutPoti >= 213.75 && azimutPoti < 236.25)  {

    i = 10;

  }

  if (azimutPoti >= 236.25 && azimutPoti < 258.75)  {

    i = 11;

  }

  if (azimutPoti >= 258.75 && azimutPoti < 281.25)  {

    i = 12;

  }

  if (azimutPoti >= 281.25 && azimutPoti < 303.75)  {

    i = 13;

  }

  if (azimutPoti >= 303.75 && azimutPoti < 326.25)  {

    i = 14;

  }

  if (azimutPoti >= 326.25 && azimutPoti < 348.75)  {

    i = 15;

  }

  return i;

}

 

 

// Funktion: SunAngles für die Berechnung von Azimut und Elevation

 

double SunAngles (double Deklination, double B, double ZeitSeitMittag, double &Elevation) {

  double DK = Deklination;

  double cosdec = cos(DK);

  double sindec = sin(DK);

  double lha = ZeitSeitMittag * (1.0027379 - 1 / 365.25) * 15 * kwert;

  double coslha = cos(lha);

  double sinlha = sin(lha);

  double coslat = cos(B);

  double sinlat = sin(B);

  double N = -cosdec * sinlha;

  double D = sindec * coslat - cosdec * coslha * sinlat;

  Elevation = asin(sindec * sinlat + cosdec * coslha * coslat);                     // Höhe des Sonnenmittelpunkts

  double Azimut = atan2(N, D);

  if (Azimut < 0) {

    Azimut += 2 * pi;

  }

  Azimut = Azimut / kwert;

  return Azimut;

}

 

 

// Funktion: BerechneZeitgleichung

 

double BerechneZeitgleichung (int yearday) {

  double Zeitgleichung = -0.170869921174742 * sin(0.0336997028793971 * yearday + 0.465419984181394) - 0.129890681040717 * sin(0.0178674832556871 * yearday - 0.167936777524864);

  return Zeitgleichung;

}

 

 

// Funktion: BerechneDeklination

 

double BerechneDeklination(int yearday) {

  double Deklination = 0.409526325277017 * sin(0.0169060504029192 * (yearday - 80.0856919827619));

  return Deklination;

}

 

 

// Funktion: BerechneZeitdifferenz

 

double BerechneZeitdifferenz(double h, double latitude, double kwert, double Deklination, double pi) {

  double Zeitdifferenz;

  Zeitdifferenz = 12 * acos((sin(h) - sin(latitude * kwert) * sin(Deklination)) / (cos(latitude * kwert) * cos(Deklination))) / pi;

  return Zeitdifferenz;

}

 

 

// Funktion: BerechneRwert

// Näherungslösung für die Refraktion für ein Objekt bei Höhe hoehe über mathematischem Horizont

// Refraktion beträgt bei Sonnenaufgang 34 Bogenminuten = 0.56667°

// Falls die Höhe der Sonne nicht genauer als auf 0.5° gewünscht ist, kann diese Funktion ignoriert werden

 

double BerechneRwert (double Elevation, double kwert, float P, float T)  {

  double R;

  if (Elevation >= 15 * kwert) {

    R = 0.00452 * kwert * P / tan(Elevation) / (273 + T);                                    // über 15° - einfachere Formel

  }

  else if (Elevation > -1 * kwert) {

    R = kwert * P * (0.1594 + 0.0196 * Elevation + 0.00002 * (Elevation * Elevation)) / ((273 + T) * (1 + 0.505 * Elevation + 0.0845 * (Elevation * Elevation)));

  }

  return R;

}

 

 

// Funktion: BerechneTageslaenge

 

double BerechneTageslaenge (double Zeitdifferenz, double Zeitgleichung, double longitude, int Zeitzone, double &Aufgang, double &Untergang)  {

  double AufgangOrtszeit = 12 - Zeitdifferenz - Zeitgleichung;

  double UntergangOrtszeit = 12 + Zeitdifferenz - Zeitgleichung;

  Aufgang = AufgangOrtszeit - longitude / 15 + Zeitzone;

  Untergang = UntergangOrtszeit - longitude / 15 + Zeitzone;

  double Tageslaenge = Untergang - Aufgang;

  return Tageslaenge;

}

 

Hardware-Liste

 

·         Arduino-Uno-Mikrocontroller REV

·         Arduino-Mega-2560-Mikrocontroller (für Zusatzdisplay)

·         DS3231 AT24C32 IIC Real Time Clock Module
   (für Zusatzdisplay)

·         AZDelivery-DataLogger-Modul mit DS3231-RTC-Echtzeituhr

·         Adafruit-Motor-Shield v2.3

·         POLOLU-1207-Stepper-Motor 7.4 VDC

·         Longruner-17kg-Digital High-Torque Robot-Servo-Motor 7.5 VDC

·         SainSmart IIC/I2C/TWI Serial 2004 Character 20x4 LCD
   Display Modul 5 VDC

·         10-Gang-Drehpotentiometer 20 kOhm

·         diverse Fischertechnik-Bauteile

·         Kuman UNO R3 3,5" TFT Touchscreen (Zusatzdisplay)

·         Solarpanel ETM500-3V (14 x 13 cm, 3.0 VDC, aus einem
   Kosmos-Solarbaukasten)