/*
Sonnenstandberechnung und Solartracking,
jetzt mit Arduino-Motor-Shield Rev3.
und Nextion 5-Zoll
Display.
Arduino Mega
erforderlich.
Positionsangaben für Ort: Rönkhausen
Version 2.1, M. Schulte, 17. März 2022
Achtung: Vin max. 7.5 Volt
*/
#include <TimeLib.h>
#include <DTutils.h>
#include <Time.h>
#include <Servo.h>
#include <Stepper.h> // Include the Stepper library
#include <Wire.h>
#include <DS3231.h>
#define RTC_I2C_ADDRESS 0x68 // I2C Adresse des RTC
DS3231
#define pwmA 3 // Give the motor
control pins names:
#define pwmB 11
#define dirA 12
#define dirB 13
const int stepsPerRevolution =
200; // Define number of
steps per revolution
RTCDateTime dt;
DS3231 clock;
Servo
servo2;
// Servomotor für Elevation
Stepper myStepper = Stepper(stepsPerRevolution,
dirA, dirB); // Initialize the
stepper library on the motor shield
void setup() {
Serial2.begin(9600);
pinMode(pwmA, OUTPUT); // Set the PWM and brake
pins so that the direction pins
can be used
to control the motor:
pinMode(pwmB, OUTPUT);
digitalWrite(pwmA, HIGH);
digitalWrite(pwmB, HIGH);
myStepper.setSpeed(80); // Set the motor speed
(RPMs)
pinMode(A2,
INPUT); //
Hall-Sensor
pinMode(A15,
INPUT); //
PV-Panel
pinMode(4,
OUTPUT); //
grüne LED, Anschluss an D4
pinMode(6,
OUTPUT); //
blaue LED, Anschluss an D6
pinMode(7,
OUTPUT); //
rote LED, Anschluss an D7
servo2.attach(5);
servo2.write(3); // Servo
senkrechte Grundstellung
clock.begin(); // Starte
RTC Uhr
Serial2.print("ref=page0.t0.txt"); // refresh Seite 0
Textfeld 0
endNextionCommand();
Serial2.print("ref=page0.t1.txt"); // refresh Seite 0 Textfeld
1
endNextionCommand();
Serial2.print("ref=page0.t2.txt"); // refresh Seite 0
Textfeld 2
endNextionCommand();
Serial2.print("ref=page0.t3.txt"); // refresh Seite 0
Textfeld 3
endNextionCommand();
Serial2.print("ref=page0.t4.txt"); // refresh Seite 0
Textfeld 4
endNextionCommand();
Serial2.print("ref=page1.t0.txt"); // refresh Seite 1
Textfeld 0
endNextionCommand();
Serial2.print("ref=page1.t1.txt"); // refresh Seite 1
Textfeld 1
endNextionCommand();
Serial2.print("ref=page1.t2.txt"); // refresh Seite 1
Textfeld 2
endNextionCommand();
Serial2.print("ref=page1.t3.txt"); // refresh Seite 1
Textfeld 3
endNextionCommand();
Serial2.print("ref=page1.t4.txt"); // refresh Seite 1
Textfeld 4
endNextionCommand();
Serial2.print("ref=page1.t5.txt"); // refresh Seite 1
Textfeld 5
endNextionCommand();
Serial2.print("ref=page1.t6.txt"); // refresh Seite 1
Textfeld 6
endNextionCommand();
Serial2.print("ref=page2.t0.txt"); // refresh Seite 2
Textfeld 0
endNextionCommand();
Serial2.print("ref=page2.t1.txt"); // refresh Seite 2
Textfeld 1
endNextionCommand();
Serial2.print("ref=page2.t2.txt"); // refresh Seite 2
Textfeld 2
endNextionCommand();
Serial2.print("ref=page2.t3.txt"); // refresh Seite 2
Textfeld 3
endNextionCommand();
Serial2.print("ref=page2.t4.txt"); // refresh Seite 2
Textfeld 4
endNextionCommand();
Serial2.print("ref=page2.t5.txt"); // refresh Seite 2
Textfeld 5
endNextionCommand();
Serial2.print("ref=page2.t6.txt"); // refresh Seite 2
Textfeld 6
endNextionCommand();
Serial2.print("ref=page3.t0.txt"); // refresh Seite 3
Textfeld 0
endNextionCommand();
Serial2.print("ref=page3.t1.txt"); // refresh Seite 3
Textfeld 1
endNextionCommand();
Serial2.print("ref=page3.t2.txt"); // refresh Seite 3
Textfeld 2
endNextionCommand();
//clock.setDateTime(2022,
03, 18, 9, 32, 30); // Hier Datum und
aktuelle Uhrzeit (MEZ) setzen, danach auskommentieren und neu laden
}
// Beginn
der Schleife
void loop() {
const int stepsV = 16; // Anzahl steps Vorwärtslauf
const int stepsR = -16; // Anzahl steps Rückwärtslauf
const int stepsSR = -100; // Anzahl steps schneller Rücklauf
dt = clock.getDateTime(); // Zeitwert holen von
RTC Echtzeituhr
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 Jahr = (dt.year);
int Monat = (dt.month);
int Tag = (dt.day);
float Stunde = hour.toInt();
float Minute = minute.toInt();
float Sekunde = second.toInt();
int yearday = DayOfYear(Jahr, Monat, Tag);
float Elevation;
float elevation;
float Aufgang;
float Untergang;
float Deklination;
float Azimut;
float
Zeitgleichung;
float
Zeitdifferenz;
float AufgangOrtszeit;
float
UntergangOrtszeit;
float Tageslaenge;
float Refraktion;
float MEZ;
// Mitteleuropäische Zeit
float MOZ;
// Mittlere Ortszeit
float WOZ; //
Wahre Ortszeit
float
ZeitSeitMittag;
const float pi = 3.14159265;
const float kwert = pi
/ 180;
const float latitude = 51.222191;
const float longitude = 7.954169;
const float B = latitude * kwert;
// geogr. Breite in Radians
const float h = -(50.0 / 60.0) * kwert;
// Höhe des Sonnenmittelpunkts bei Aufgang
const int Zeitzone = 1;
// Zeitzone Greenwich + 1 (z.B. Berlin)
int Grenzwert;
int sensorValue;
// Analoger Ist-Wert (Hall-Sensor)
int azimutHall;
// Gradwert Ist-Azimutwert
(Hall-Sensor)
MEZ = Stunde + Minute / 60 + Sekunde /
3600; // Mitteleuropäische Zeit
Zeitgleichung = BerechneZeitgleichung
(yearday); //
Aufruf der Funktion: BerechneZeitgleichung
ZeitSeitMittag = MEZ + longitude
/ 15.0 - Zeitzone - 12 + Zeitgleichung; // Berechne ZeitSeitMittag
Deklination = BerechneDeklination
(yearday); //
Aufruf der Funktion: BerechneDeklination
Zeitdifferenz = BerechneZeitdifferenz
(h, pi, latitude, kwert, Deklination); // Aufruf der Funktion: BerechneZeitdifferenz
Azimut = SunAngles(Deklination,
B, kwert, pi,
ZeitSeitMittag, Elevation);
// Aufruf der Funktion: SunAngles
Tageslaenge = BerechneTageslaenge (Zeitdifferenz, Zeitgleichung, longitude, Zeitzone, Aufgang, Untergang); // Aufruf der Funktion: BerechneTageslaenge
Elevation
= Elevation / kwert;
Deklination = Deklination / kwert;
int AziH = (int)azimutHall;
int AziB = (int)Azimut;
MOZ = MEZ - (-longitude
/ 15) - 1; // Berechne
Mittlere Ortszeit aus MEZ
WOZ = MOZ + Zeitgleichung;
// Berechne Wahre Ortszeit
int WOZdez = (int)WOZ;
int WOZmin = (WOZ - WOZdez) * 60;
int MOZdez = (int)MOZ;
int MOZmin = (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);
if (StdL >= 7 && StdL <
9) {
Grenzwert = 135;
};
// Grenzwerte für Rücklaufposition
if (StdL >= 9 && StdL <
10) {
Grenzwert = 120;
}; //
abhängig von der Tageslänge (nur Stunde)
if (StdL >= 10 && StdL
< 11) {
Grenzwert = 104;
};
if (StdL >= 11 && StdL
< 13) {
Grenzwert = 90;
};
if (StdL >= 13 && StdL
< 14) {
Grenzwert = 75;
};
if (StdL >= 14 && StdL
< 15) {
Grenzwert = 60;
};
if (StdL >= 15 && StdL
< 17) {
Grenzwert = 45;
};
sensorValue = analogRead(A2); // Auslesen
Hall-Sensor an Pin A2
azimutHall =
MotorenSteuerung (Azimut, Elevation, sensorValue, AziH, AziB, Grenzwert, stepsV, stepsR, stepsSR); // Aufruf
der Funktion: MotorenSteuerung
// Für Anzeige Windrichtung, abhängig von der
Position des Drehtellers
const char* windrichtung[16] =
{"N", "NNO", "NO", "ONO",
"O", "OSO", "SO", "SSO", "S",
"SSW", "SW", "WSW", "W",
"WNW", "NW", "NNW"};
int i =
Windrichtung(azimutHall); // Aufruf Funktion:
Windrichtung
String wr = windrichtung[i];
// 5-Volt-Solarpanel
const int analogPin = A15;
float pvmod = analogRead(analogPin);
float Volt =
Solarspannung (pvmod); // Aufruf Funktion:
Solarspannung
float Prozent =
Volt / 4.75 * 100;
// für Anzeige: Stunden, Minuten, Deklination
int Std = (int)Tageslaenge;
int Min = (int)((Tageslaenge - Std) * 60);
// Ausgabe auf Nextion-Display
5.0""
// Seite 0: Home
Serial2.print("page0.t0.txt=\"");
Serial2.print("Nextion-Solartracker-Projekt");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page0.t0.txt");
endNextionCommand();
Serial2.print("page0.t1.txt=\"");
Serial2.print("Version 2.1 vom
17.03.2022");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page0.t1.txt");
endNextionCommand();
Serial2.print("page0.t2.txt=\"");
Serial2.print("Dr. Michael
Schulte");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page0.t2.txt");
endNextionCommand();
Serial2.print("page0.t3.txt=\"");
Serial2.print("http://wetterstation.mi-schu.de");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page0.t5.txt");
endNextionCommand();
Serial2.print("page0.t4.txt=\"");
Serial2.print("E-Mail:
wetter.roenkhausen@gmx.de");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page0.t4.txt");
endNextionCommand();
// Seite 1: Time
Serial2.print("page1.t0.txt=\"");
Serial2.print(clock.dateFormat(" d. M. Y H:i:s", dt));
Serial2.print(" MEZ");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page1.t0.txt");
endNextionCommand();
Serial2.print("page1.t1.txt=\"");
if (WOZmin >= 0 && WOZmin
< 10) {
Serial2.print(" Wahre Ortszeit: " + String (WOZdez)
+ ":" + "0" + String (WOZmin) +
" h");
}
else
{ Serial2.print(" Wahre Ortszeit: " + String (WOZdez)
+ ":" + String (WOZmin) + " h");
}
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page1.t1.txt");
endNextionCommand();
Serial2.print("page1.t2.txt=\"");
if (MOZmin >= 0 && MOZmin
< 10) {
Serial2.print(" Mittlere Ortszeit: " + String (MOZdez)
+ ":" + "0" + String (MOZmin) +
" h");
}
else
{ Serial2.print(" Mittlere Ortszeit: " + String (MOZdez)
+ ":" + String (MOZmin) + " h");
}
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page1.t2.txt");
endNextionCommand();
Serial2.print("page1.t3.txt=\"");
Serial2.print(" Tag im Jahr: " + String(yearday));
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page0.t3.txt");
endNextionCommand();
Serial2.print("page1.t4.txt=\"");
if (MinA >= 0 && MinA <
10) {
Serial2.print(" Sonnenaufgang: " + String (StdA)
+ ":" + "0" + String (MinA) +
" h");
}
else
{ Serial2.print(" Sonnenaufgang: " + String (StdA)
+ ":" + String (MinA) + " h");
}
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page1.t4.txt");
endNextionCommand();
Serial2.print("page1.t5.txt=\"");
if (MinU >= 0 && MinU <
10) {
Serial2.println(" Sonnenuntergang: " + String (StdU)
+ ":" + "0" + String (MinU) +
" h");
}
else
{ Serial2.println(" Sonnenuntergang: " + String (StdU)
+ ":" + String (MinU) + " h");
}
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page1.t5.txt");
endNextionCommand();
Serial2.print("page1.t6.txt=\"");
if (MinL >= 0 && MinL <
10) {
Serial2.print(" Tageslaenge: " + String (StdL) + ":" + "0" + String (MinL) + " h");
}
else
{ Serial2.print(" Tageslaenge: " + String (StdL) + ":" + String (MinL)
+ " h");
}
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page1.t6.txt");
endNextionCommand();
// Seite 2: Data
Serial2.print("page2.t0.txt=\"");
Serial2.print(clock.dateFormat(" d. M. Y H:i:s", dt));
Serial2.print(" MEZ");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page2.t0.txt");
endNextionCommand();
Serial2.print("page2.t1.txt=\"");
Serial2.print(" Azimut-Soll: " + String(Azimut) +
" Grd");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page2.t1.txt");
endNextionCommand();
Serial2.print("page2.t2.txt=\"");
Serial2.print(" Azimut-Ist: " + String(azimutHall) + ".00 Grd
" + "(" + String(wr) + ")");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page2.t2.txt");
endNextionCommand();
Serial2.print("page2.t3.txt=\"");
Serial2.print(" Elevation: " +
String(Elevation) + " Grd");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref
= page2.t3.txt");
endNextionCommand();
Serial2.print("page2.t4.txt=\"");
Serial2.print(" Deklination: " + String(Deklination)
+ " Grd");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page2.t4.txt");
endNextionCommand();
Serial2.print("page2.t5.txt=\"");
Serial2.print(" Zeitgleichung: " + String(Zeitgleichung *
60) + " min");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page2.t5.txt");
endNextionCommand();
Serial2.print("page2.t6.txt=\"");
Serial2.print(" Solarspannung: " + String(Volt) + " V " +
"(" + String (int (Prozent)) + "% Vmax" + ")");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page2.t6.txt");
endNextionCommand();
// Seite 3: Sun
Serial2.print("page3.t0.txt=\"");
Serial2.print(" Azimut-Soll: " +
String(Azimut) + " Grd");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref
= page3.t5.txt");
endNextionCommand();
Serial2.print("page3.t1.txt=\"");
Serial2.print(" Azimut-Ist: " + String(azimutHall)
+ ".00 Grd");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page2.t2.txt");
endNextionCommand();
Serial2.print("page3.t2.txt=\"");
Serial2.print(" Elevation: " + String(Elevation) + " Grd");
Serial2.write('"');
endNextionCommand();
Serial2.print("ref=page2.t5.txt");
endNextionCommand();
}
void endNextionCommand()
{
Serial2.write(0xff);
Serial2.write(0xff);
Serial2.write(0xff);
}
//
Funktion: Windrichtung
int
Windrichtung (float azimutHall)
{
int i;
if (azimutHall >= 0 && azimutHall <
11) {
i = 0;
}
if (azimutHall >= 11 && azimutHall <
34) {
i = 1;
}
if (azimutHall >= 34 && azimutHall <
56) {
i = 2;
}
if (azimutHall >= 56 && azimutHall <
79) {
i = 3;
}
if (azimutHall >= 79 && azimutHall <
101) {
i = 4;
}
if (azimutHall >= 101 && azimutHall
< 124) {
i = 5;
}
if (azimutHall >= 124 && azimutHall
< 146) {
i = 6;
}
if (azimutHall >= 146 && azimutHall
< 169) {
i = 7;
}
if (azimutHall >= 169 && azimutHall
< 191) {
i = 8;
}
if (azimutHall >= 191 && azimutHall
< 214) {
i = 9;
}
if (azimutHall >= 214 && azimutHall
< 236) {
i = 10;
}
if (azimutHall >= 236 && azimutHall
< 259) {
i = 11;
}
if (azimutHall >= 259 && azimutHall
< 281) {
i = 12;
}
if (azimutHall >= 281 && azimutHall
< 304) {
i = 13;
}
if (azimutHall >= 304 && azimutHall
< 326) {
i = 14;
}
if (azimutHall >= 326 && azimutHall
< 349) {
i = 15;
}
return i;
}
//
Funktion: SunAngles für die Berechnung von Azimut und
Elevation
float SunAngles (float Deklination, float B, float kwert, float pi,
float ZeitSeitMittag, float
& Elevation) {
float DK =
Deklination;
float cosdec = cos(DK);
float sindec = sin(DK);
float lha = ZeitSeitMittag * (1.0027379 - 1 / 365.25) * 15 * kwert;
float coslha = cos(lha);
float sinlha = sin(lha);
float coslat = cos(B);
float sinlat = sin(B);
float N = -cosdec * sinlha;
float D = sindec * coslat - cosdec * coslha * sinlat;
Elevation = asin(sindec * sinlat + cosdec * coslha * coslat); // Höhe
des Sonnenmittelpunktes über dem Horizont
float Azimut =
atan2(N, D);
if (Azimut < 0)
{
Azimut += 2 * pi;
}
return (Azimut / kwert);
}
//
Funktion: BerechneZeitgleichung
float BerechneZeitgleichung (int yearday) {
float Zeitgleichung
= -0.170869921174742 * sin(0.0336997028793971 * yearday
+ 0.465419984181394) - 0.129890681040717 * sin(0.0178674832556871 * yearday - 0.167936777524864);
return
Zeitgleichung;
}
//
Funktion: BerechneDeklination
float BerechneDeklination(int yearday) {
float Deklination =
0.409526325277017 * sin(0.0169060504029192 * (yearday
- 80.0856919827619));
return Deklination;
}
//
Funktion: BerechneZeitdifferenz
float BerechneZeitdifferenz(float h, float pi, float
latitude, float kwert, float Deklination) {
float
Zeitdifferenz;
Zeitdifferenz = 12 * acos((sin(h)
- sin(latitude * kwert) *
sin(Deklination)) / (cos(latitude * kwert) * cos(Deklination))) / pi;
return
Zeitdifferenz;
}
//
Funktion: BerechneTageslaenge
float BerechneTageslaenge (float
Zeitdifferenz, float Zeitgleichung, float longitude, int Zeitzone, float &
Aufgang, float & Untergang) {
float AufgangOrtszeit = 12 - Zeitdifferenz - Zeitgleichung;
float
UntergangOrtszeit = 12 + Zeitdifferenz - Zeitgleichung;
Aufgang = AufgangOrtszeit
- longitude / 15 + Zeitzone;
Untergang = UntergangOrtszeit - longitude / 15 + Zeitzone;
float Tageslaenge = Untergang - Aufgang;
return Tageslaenge;
}
//
Funktion: MotorenSteuerung, Ansteuerung von Steppermotor
und Servo
int
MotorenSteuerung (float Azimut, float
Elevation, int sensorValue,
int AziH, int AziB, int
Grenzwert, const int stepsV, const int
stepsR, const int stepsSR)
{
int azimutHall;
int elevation;
elevation = map(Elevation, 90, 0, 120, 3); // Kalibrierung der Servoposition
(elevation)
constrain(elevation, 0, 120); // Begrenzung des
Servo-Wertebereichs
if (sensorValue >= 0 && sensorValue
< 510) {
azimutHall = map(sensorValue, 251, 510, 90,
180); // Skalierung der Hall-Sensorwerte in
Gradwerte 90-180 Grad
}
if (sensorValue >= 510 && sensorValue
<= 900) {
azimutHall = map(sensorValue, 510, 770, 180,
270); // Skalierung der
Hall-Sensorwerte in Gradwerte 180-270 Grad
}
constrain(azimutHall, 45, 315); // Begrenzung des
Azimut-Soll-Wertebereichs
if (Elevation >
0) {
// Solange Elevation > 0 --> Sonne steht über dem Horizont
servo2.write(elevation); // Schreiben der Servoposition (Nachführung)
if ((int)azimutHall < (int)Azimut) { // Azimut-Nachführung:
Wenn Hall-Sensorwert kleiner als Azimut, dann:
digitalWrite(pwmA, HIGH); // Stepper Strom
an
digitalWrite(pwmB, HIGH); // Stepper Strom
an
digitalWrite(4,
HIGH); //
LED grün an
myStepper.step(stepsV); // Step one revolution
in one direction
delay (500);
digitalWrite(4,
LOW); //
LED grün aus
digitalWrite(7,
LOW); //
LED rot aus
digitalWrite(pwmA, LOW); // Stepper stromlos schalten
digitalWrite(pwmB, LOW); // Stepper stromlos schalten
}
if ((int)azimutHall > (int)Azimut) { // Azimut-Nachführung:
Wenn Hall-Sensorwert grösser als Azimut, dann:
digitalWrite(pwmA, HIGH);
digitalWrite(pwmB, HIGH);
servo2.write(elevation);
digitalWrite(7,
HIGH); //
LED rot an
myStepper.step(stepsR); // Step one revolution
in one direction
delay (500);
digitalWrite(7,
LOW); //
LED rot aus
digitalWrite(4,
LOW); //
LED grün aus
digitalWrite(pwmA, LOW);
digitalWrite(pwmB, LOW);
}
}
else {
// Sonne ist untergegangen --> Rückführung auf Anfangsposition
if (Elevation
< 0 && (azimutHall - 1) >=
Grenzwert) { // Azimut-Rückführung: Wenn Elevation <
-1 (Sonne ist untergegangen), dann:
delay(500);
digitalWrite(pwmA, HIGH);
digitalWrite(pwmB, HIGH);
digitalWrite(6,
HIGH); //
LED blau an
myStepper.step(stepsSR); // Step one revolution
in one direction
delay (500);
digitalWrite(6,
LOW); //
LED blau aus
digitalWrite(pwmA, LOW);
digitalWrite(pwmB, LOW);
delay(500);
}
}
if (AziH = AziB) { //
Damit alle LEDs aus sind beim Erreichen der Zielposition (azimutHall
= Azimut)
digitalWrite(4,
LOW);
digitalWrite(7,
LOW);
}
return azimutHall; // Rückgabewert: azimutHall
}
//
Funktion: Solarspannung
float
Solarspannung(float pvmod) //
Funktion: Solarspannung
{
float Volt;
Volt = pvmod / 1023
* 4.75; //
Umrechnung Analogwert in Spannungswert (Volt)
return Volt;
}