L’inseguitore solare astronomico è un progetto pensato per orientare un pannello solare nella direzione del sole in qualsiasi punto del nostro pianeta esso sia installato, attraverso calcoli astronomici che, utilizzando i valori dei Latitudine e Longitudine oltre che Data e Ora, possano determinare la sua posizione relativa rispetto ai pannelli, in modo tale da garantire una produzione elevata.
La fase iniziale si è basata sulla ricerca in quanto è necessario effettuare calcoli astronomici di precisione per ottenere i valori di Altezza Solare ed Azimut di interesse. Per determinare la posizione
del sole analiticamente, infatti, occorre stabilire il numero di gradi di elevazione rispetto alla superficie terrestre (Altezza Solare) e l’angolo tra il piano verticale passante per l’astro e quello orizzontale passante per il punto di osservazione, con riferimento a nord (Azimut).
L’inseguitore solare non presenta un solo Pannello Solare, ma 2, di identico tipo.
Uno rivolto direttamente verso il sole ed un secondo che raccoglie la luce solare concentrata da una parabola riflettente. In questo modo oltre ad una maggiore produzione di energia ci è possibile anche valutare la differenza tra produzione diretta ed a concentrazione.
L’APPROCCIO ASTRONOMICO
Il funzionamento dell’Inseguitore Solare Astronomico si basa sugli studi astronomici che permettono, in un preciso momento ed in un punto geografico preciso della Terra, di calcolare l’altezza del Sole all’orizzonte (Altezza Solare) ed il suo angolo con il Nord sul piano equatoriale (Azimut).
Le coordinate del luogo (Latitudine e Longitudine) in cui l’inseguitore è installato, oltre a data e ora, sono determinati dal modulo GPS GY-NEO6MV2, e passati all’Arduino Mega 2560 che esguirà i calcoli e controllerà i motori.
La parte di programma sviluppato in C++ con Arduino IDE che realizza questi calcoli è il seguente:
// Calcoli Astronomici per determinare l'Azimut e Altezza Solare
//GPS Virtuale
// -------------------------
String Latitudine = "44.4359"; // Latitudine Castelnovo Monti
String Longitudine = "10.4026";// Longitudine Castelnovo Monti
String Dir_Lat = "N";
String Dir_Lon = "E";
String Altitudine = "700.5";
String Hour = "6"; // ORA SOLARE
String Minute = "13";
String Second = "27";
String Day = "31";
String Month = "1";
String Year = "2023";
String Data, Ora;
// AltezzaSolare e Azimut
// -------------------------
//float Lon_meridiano = 15.0; //0.26179939 meridiano di riferimento 15°0'0" (Etna) || DA INDIVIDUARE per l località considerata
int Lon_meridiano;
float tempo = 0.0;
float tempo_luogo = 0.0;
float mezzogiorno_luogo;
float EqT = 0.0;
float angoloOrario = 0.0; //angolo orario Rad
float declinazione = 0.0; //declinazione Rad
float altezzaSolare = 0.0; //altezza solare Rad
float azimut = 0.0; //azimut solare Rad
int pro;
float inRad;
float Y;
// Offset hours from gps time (UTC)
int ora_legale = 1; // da settare!!!!
// il GPS considera già l'offset!
const int offset = 1; // Central European Time (rispetto a Greenweck)
// -----------------------------------------------
// Funzioni --------------------------------------
// -----------------------------------------------
//GPS
//-------------------------------------------------
void lettura_GPS() {
Serial.print("Data = ");Serial.print(Day);Serial.print("/");Serial.print(Month);Serial.print("/");Serial.println(Year);
Serial.print("Orario = ");Serial.print(Hour);Serial.print(":");Serial.print(Minute);Serial.print(":");Serial.println(Second);
Serial.print("Latitudine = ");Serial.print(Latitudine);
Serial.print(" ");Serial.println(Dir_Lat);
// correggo Longitudine
float LonF = Longitudine.toFloat();
if(Dir_Lon == "W") Longitudine = "-" + Longitudine;
if(Dir_Lon == "E" && Longitudine.toFloat() > 180) Longitudine = "-" + String(360 - LonF);
Serial.print("Longitudine = ");Serial.print(Longitudine);
Serial.print(" ");Serial.println(Dir_Lon);
Serial.print("Altitudine = ");Serial.println(Altitudine);Serial.println("");
}
int longitudineMeridianoFuso(String Longitudine){
float deltaLongitudine = Longitudine.toFloat() - int(Longitudine.toFloat()/15)*15;
if(deltaLongitudine < 7.5 && deltaLongitudine > -7.5) Lon_meridiano = int(Longitudine.toFloat()/15)*15;
else if(deltaLongitudine > 7.5) Lon_meridiano = int(Longitudine.toFloat()/15)*15 + 15;
else if(deltaLongitudine < -7.5) Lon_meridiano = int(Longitudine.toFloat()/15)*15 - 15;
else Lon_meridiano = 0;
return Lon_meridiano;
}
// Calcolo Altezza Solare e Azimut
//-------------------------------------------------
int progressivo(){
//calcolo del numero progressivo corrispondente al giorno dell’anno
pro = 0;
int anno = 1900 + Year.toInt();
int mese = Month.toInt();
int giorno = Day.toInt();
int bise = bisestile(anno);
if (mese==1){pro=giorno;}
if (mese==2){pro=0*30+1*31+giorno;}
if (mese==3){pro=0*30+1*31+giorno+bise;}
if (mese==4){pro=0*30+2*31+giorno+bise;}
if (mese==5){pro=1*30+2*31+giorno+bise;}
if (mese==6){pro=1*30+3*31+giorno+bise;}
if (mese==7){pro=2*30+3*31+giorno+bise;}
if (mese==8){pro=2*30+4*31+giorno+bise;}
if (mese==9){pro=2*30+5*31+giorno+bise;}
if (mese==10){pro=3*30+5*31+giorno+bise;}
if (mese==11){pro=3*30+6*31+giorno+bise;}
if (mese==12){pro=4*30+6*31+giorno+bise;}
return pro;
}
int bisestile(int a){
//funzione per scoprire se l’anno in corso è bisestile
int bis = 28;
if(a%4==0){
if(a%100==0){
if(a%400==0) bis = 29;
else bis = 28;
}
else bis = 29;
}
else bis = 28;
return bis;
}
void posizione_Sole(){
/*
* 24 Fusi orari, ognuno composto da 15 Meridiani a distanza di 1° tra uno e l'altro
*
* Equazione del tempo: EqT = 229,18(0,000075+0,001868cos@-0,032077sin@-0,014615cos2@-0,040849sin2@)
* dove @ = (2PI/365)*(N-1)
*
* Latitudine: LAT
* Longitudine: LON
* Angolo Orario: AO = 15*(h_conv + ((EqT-4*(longitudineMeridianoFuso-LON))/60))-180
* Declinazione Solare: DS = 0.006918-0.399912cos(@)+0.070257sin(@)-0.006758cos(2@)-0.002697cos(3@)+0.00148sin(3@)
*
* sin(AS) = sin(LAT)sin(DS)+cos(LAT)cos(DS)cos(AO)
* cos(Az) = (cos(AO)cos(DS)sin(LAT)-sin(DS)cos(LAT))/cos(AS)
*
* dove:
* g = 360°/365 con angoli espressi in ° o 2pi/365 con angoli espressi in Rad
* N = giorno progressivo dell'anno
* @ = (2PI/365)*(N-1)
*/
float Hour_f;
if(Month.toInt() >= 4 && Month.toInt() <=10) Hour_f = Hour.toFloat() + ora_legale; //-----------SOLO QUANDO C'E' L'ORA LEGALE!!!!
else Hour_f = Hour.toFloat();
tempo = Hour_f + (Minute.toFloat()/60) + (Second.toFloat()/3600);
inRad = (2*PI/360);
Y = (2*PI/365)*(progressivo()-1);
EqT = 229.18*(0.000075+0.001868*cos(Y)-0.032077*sin(Y)-0.014615*cos(2*Y)-0.040849*sin(2*Y));
tempo_luogo = tempo -((EqT-4*(longitudineMeridianoFuso(Longitudine)-Longitudine.toFloat()))/60);
mezzogiorno_luogo = 12.00 -((EqT-4*(longitudineMeridianoFuso(Longitudine)-Longitudine.toFloat()))/60);
angoloOrario = 15*(tempo + ((EqT-4*(longitudineMeridianoFuso(Longitudine)-Longitudine.toFloat()))/60))-180; //angolo orario °
declinazione = (0.006918-0.399912*cos(Y)+0.070257*sin(Y)-0.006758*cos(2*Y)-0.002697*cos(3*Y)+0.00148*sin(3*Y))*(365/(2*PI)); //declinazione Deg
altezzaSolare = asin((sin(Latitudine.toFloat()*inRad)*sin(declinazione*inRad))+(cos(Latitudine.toFloat()*inRad)*cos(declinazione*inRad)*cos(angoloOrario*inRad)))/inRad; //altezza solare
azimut = (acos(((cos(angoloOrario*inRad)*cos(declinazione*inRad)*sin(Latitudine.toFloat()*inRad))-(sin(declinazione*inRad)*cos(Latitudine.toFloat()*inRad)))/cos(altezzaSolare*inRad))/inRad); //azimut Rad
if(tempo < mezzogiorno_luogo) azimut = 180 - azimut;
else azimut = 180 + azimut;
Serial.println("Posizione Sole:"); Serial.println("----------------------------------------");
Serial.print("Ora = "); Serial.print(tempo); Serial.println(" (Hd)");
Serial.print("Ora_luogo = "); Serial.print(tempo_luogo); Serial.println(" (Hd)");
Serial.print("Mezzogiorno_luogo = "); Serial.print(mezzogiorno_luogo); Serial.println(" (Hd)");
Serial.print("Longitudine_meridiano_fuso = "); Serial.print(longitudineMeridianoFuso(Longitudine)); Serial.println(" Gradi");
Serial.print("Equazione_Tempo = "); Serial.print(EqT); Serial.println(" (Md)");
Serial.print("Angolo_Orario = "); Serial.print(angoloOrario); Serial.println(" Gradi");
Serial.print("Declinazione = "); Serial.print(declinazione); Serial.println(" Gradi");
Serial.print("Altezza_Solare = "); Serial.print(altezzaSolare); Serial.println(" Gradi");
Serial.print("Azimut = "); Serial.print(azimut); Serial.println(" Gradi");
Serial.println("----------------------------------------");Serial.println("");
}
void setup() {
//init seriale hardware
Serial.begin(9600);
Serial.println("INSEGUITORE SOLARE ASTRONOMICO SIM");
Serial.println("----------------------------------");
Serial.println("");
}
void loop() {
// Calcolo Coord Solari
posizione_Sole();
while(1);
}
Una volta determinate le coordinate solari il sistema le confronta con i valori di Inclinazione determinata con l’Accelerometro ADXL335 utilizzato come inclinometro, e l’Azimut misurato con la Bussola Elettronica CMP12. Se questi valori non coincidono, i motori dei 2 assi X e Y lungo i quali l’inseguitore può muoversi si movimentano riportando il sistema ai valori determinati, cioè rivolto verso il sole.
MODULO IoT
Una volta determinate Altezza Solare e Azimut, e posizionato i pannelli solari nella direzione del sole, il sistema memorizza i propri dati inviandoli, attraverso applicazioni PHP che fungono da API (), ad un Database posizionato su un server web.
L’invio dei dati è possibile attraverso l’utilizzo del Modulo GSM SIM900, che attraverso una SIM dati permette all’inseguitore di connettersi alla rete e di potersi quindi collegarsi al Database.
Questi dati sono quindi visualizzati da una pagina internet che funge da monitoraggio dei valori Geografici, Ambientali e di produzione Energetica.
E’ stata anche realizzata una App sviluppata in C# che permette di visualizzare l’andamento della produzione nelle 24 ore, sia in forma di dati che graficamente, e di fare impostazioni manuali sul sistema.
ELEMENTI PRINCIPALI CHE COSTITUISCONO L’INSEGUITORE SOLARE
Microcontrollore Arduino
La gestione dell’inseguitore è affidata ad un microcontrollore a 8 bit Arduino Mega 2560 con processore ATmega2560 16MHz il quale dispone di 54 pin di Input/Output digitali di cui 15 utilizzabili come uscite PWM (Pulse Width Modulation) e 16 ingressi analogici. Per la comunicazione con altri dispositivi è dotato di 4 pin seriali hardware, oltre alla possibilità di utilizzare una comunicazione I2C (abbreviazione di Inter Integrated Circuit) e SPI. I²C (pr. i-quadro-ci o i-due-ci), è un sistema di comunicazione seriale bifilare utilizzato tra circuiti integrati. Il classico bus I²C è composto da almeno un componente master ed uno slave (risp. letteralmente “capo, padrone” e “sottoposto, schiavo”). La situazione più frequente vede un singolo master e più slave; possono tuttavia essere usate architetture multimaster e multislave in sistemi più complessi. Il bus è stato sviluppato dalla Philips nel 1982 e, dopo la realizzazione di centinaia di componenti e sistemi negli anni ’80, nel 1992 è stata prodotta la prima versione del protocollo che ha subìto diversi aggiornamenti ed ha generato bus simili, uno dei quali per motivi squisitamente commerciali, di brevetto Intel, nel 1995. Di fatto i due standard si assomigliano in molti aspetti quali l’arbitraggio del bus (cioè la scelta nel caso di più possibili master di quale dispositivo debba assumere questa funzione di controllo del bus), l’alta impedenza in condizione di rilascio bus, il sistema di indirizzamento ed il protocollo ACK/NACK (vi sono però differenze da tenere presenti). Serial Peripheral Interface (SPI) è un protocollo dati seriale sincrono utilizzato dai microcontrollori per comunicare rapidamente con uno o più dispositivi periferici su brevi distanze. Inoltre, sono disponibili una connessione USB e un jack di alimentazione. L’oscillatore interno che si occupa di gestire il funzionamento è a cristallo da 16 MHz. Per quanto riguarda la memoria sono a disposizione 4096 byte di EEPROM che non viene cancellata allo spegnimento della scheda. La tensione di Input/Output vale 5 V mentre la tensione di ingresso nominale può variare da 7 V a 12 V
Ricevitore GPS
La ricezione dei dati geografici del luogo in cui l’inseguitore solare si trova è compito del modulo GPS
GY-NEO6MV2 dotato di un’apposita antenna. Per un corretto funzionamento è necessario fornire un’alimentazione compresa tra 3 V e 5 V. I piedini dedicati all’elaborazione dei dati sono un trasmettitore (Tx) e un ricevitore (Rx) da collegare ad Arduino.
Bussola Elettronica CPSS12
Un modulo dedicato all’implementazione di una bussola elettronica CMPS12 fornisce il valore in gradi relativo alla posizione orizzontale dell’inseguitore solare da confrontare con il calcolo dell’azimut per verificare un posizionamento corretto.
Inclinometro ADXL335
Un modulo che funge da inclinometro GY-61 ADXL335 rileva l’inclinazione del pannello solare affinché sia possibile confrontare tale valore espresso in gradi con l’altezza solare calcolata verificando, così, la correttezza del posizionamento. La tensione di alimentazione può variare da 3 V a 5 V e i piedini da connettere ad Arduino su cui il sensore fornisce i dati corrispondono agli assi x, y, z; l’inclinazione viene determinata a partire dai valori degli assi x e y.
Modulo GSM SIM900
Un modulo GSM SIM900 con antenna si occupa di caricare i dati riguardanti altezza solare e azimut su un database per poi costruire un grafico grazie all’applicazione C# e del download di valori relativi al luogo in cui si desidera installare l’inseguitore solare. La tensione di alimentazione del modulo può giungere sino a 12 V e i piedini che ne determinano il funzionamento sono un trasmettitore (Tx) e un ricevitore (Rx) da collegare ad Arduino.
Controller Motori Ponte H L298
La gestione dei motori è regolata da un ponte H pilotato da un segnale PWM generato da Arduino. La PWM è una forma di modulazione digitale che permette di ottenere una tensione variabile che dipende dal duty cycle (rapporto tra la durata dell’impulso positivo e quello dell’intero periodo) ed è impiegata per controllare la velocità dei due motori collegati al dispositivo. Inoltre è possibile comandare l’inversione della polarità dei motori in corrente continua mediante un sistema di interruttori interni, settando ad un valore alto o basso il pin a cui questa funzione del ponte è collegata, senza ricorrere ad un’inversione manuale dei poli.
La presenza di 4 fine corsa consente al software di interrompere il movimento dei motori quando sono premuti per evitare guasti nel caso in cui la rotazione e l’inclinazione del pannello risulti eccessiva.
PIEDINATURA DEI COLLEGAMENTI:
Pin A0 | inclinometro: asse x | |
Pin A1 | inclinometro: asse y | |
Pin A2 | inclinometro: asse z | |
Pin A8 | tensione pannello a concentrazione | |
Pin A9 | tensione pannello diretto | |
Pin 43 | accensione modulo GSM da software | |
Pin 11 | direzione_1 motore azimut | |
Pin 12 | direzione_2 motore azimut | |
Pin 10 | velocità motore azimut | |
Pin 6 | direzione_1 motore altezza solare | |
Pin 5 | direzione_2 motore altezza solare | |
Pin 9 | velocità motore altezza solare | |
Pin 50 | modulo GSM (TX) | |
Pin 53 | modulo GSM (RX) | |
Pin 2 | fine corsa altezza solare minima | |
Pin 3 | fine corsa altezza solare massima | |
Pin 16 | modulo GPS (RX della scheda) | |
Pin 17 | modulo GPS (TX della scheda) | |
Pin 18 | fine corsa azimut verso E | |
Pin 19 | fine corsa azimut verso W | |
Pin 20 | bussola (SDA) | |
Pin 21 | bussola (SCL) |
Ogni componente è dotato di un piedino di alimentazione (VCC) piedino che funge da massa (GND).
PROGRAMMA ARDUINO COMPLETO
/* Programma Inseguitore Solare Astronomico:
* Realizza i Calcoli Astronomici per determinare l'Azimut e Altezza Solare
* Confronta i valori calcolati con quelli letti dai sensori (Inclinometro e Bussola)
* Movimenta gli assi per portare i Pannelli Solari in direzione del Sole
* Invia al Database sul Server Web i valori Geografici, Ambientali e di produzione
*/
#include
#include
#include
Stepper stepperAltezza(48, 7, 8); //motore altezza solare
Stepper stepperAzimut(48, 9, 10); //motore azimut
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
int contatore = 0;
//contatore della funzione safeMode()
int byteGPS=-1;
//variabile della funzione sincronizzaGps
char linea[100] = "";
//variabile della funzione sincronizzaGps
char comandoGPR[7] = "$GPRMC";
//variabile della funzione sincronizzaGps
int cont=0;
//variabile della funzione sincronizzaGps
int bien=0;
//variabile della funzione sincronizzaGps
int conta=0;
//variabile della funzione sincronizzaGps
int indices[13];
//variabile della funzione sincronizzaGps
int posizioneAltezza[2];
//posizione attuale [1] , posizione precedente [0]
int posizioneAzimut[2];
//posizione attuale [1] , posizione precedente [0]
int fcInizioAltezza = LOW;
//fine corsa iniziale altezza pin 6
int fcInizioAzimut = LOW;
//fine corsa iniziale azimut pin 13
int fcFineAltezza = LOW;
//fine corsa finale altezza pin 6
int fcFineAzimut = LOW;
//fine corsa finale azimut pin 13
int avanti = 0;
//pulsante avanti nero
int indietro = 0;
//pulsanti indietro rosso / conferma introduzione coordinate
int ore = -1;int minuti = -1;int secondi = -1; //inizializzazione tempo
int giorno = 0;int mese = 0;int anno = 0;
//inizializzazione data
int lag = -1;int lam = -1;int las = -1;
//inizializzazione latitudine ° ' "
int logg = -1;int lom = -1;int los = -1;
//inizializzazione longitudine ° ' "
float latitudine = -1.0;
//inizializzazione variabile della latitudine
float longitudine = -1.0;
//inizializzazione variabile della longitudine
float meridiano = 15.0;
//0.26179939 meridiano di riferimento 15°0'0" (Etna)
boolean semaforoCoordinate = true;
boolean semaforoGps = true;
int soglia = 40;
//limite vento: 10m/s corrispondono a 40
int wind = 0;
//inizializza la variabile che contiene il valore del vento
int altezzaMin = 0;
//inizializza posizione in gradi della minima altezza
int altezzaMax = 90;
//inizializza altezza massima
int azimutEst = 135;
//inizializza l’angolo azimutale iniziale
int azimutOvest = -135;
//inizializza l’angolo azimutale finale
int altezzaOstacolo1 = 0;
//inizializza l’altezza dell’ostacolo1
int azimutOstacolo1 = 135;
//inizializza l’azimut dell’ostacolo1
int altezzaOstacolo2 = 0;
//inizializza l’altezza dell’ostacolo2
int azimutOstacolo2 = -135; ;
//inizializza l’azimut dell’ostacolo2
float tempo = 0.0;
float equazioneTempo = 0.0;
float deltaLongitudine = 0.0;
float mezzogiorno = 0.0;
//sull'orologio dell'osservatore; considero l'ora solare
float angoloOrario = 0.0;
//angolo orario
float declinazione = 0.0;
//declinazione
float altezzaSolare = 0.0;
//altezza solare
float azimut = 0.0;
//azimut
void setup(){
Serial.begin(4800);
//inizializza la serila a 4800 per comunicare con il GPS
stepperAltezza.setSpeed(10);
stepperAzimut.setSpeed(10);
lcd.begin(2, 16);
for (int i=0;i<100;i++){
linea[i]=' ';}
posizioneAltezza[0]=altezzaMin;
posizioneAzimut[0]=azimutEst;
posizioneAltezza[1]=altezzaMin;
posizioneAzimut[1]=azimutEst;
}
void loop(){
if (!safeMode()){
int selettore = analogRead(3);
if (selettore <= 128){
//richiama la funzione automatico
if ((giorno > 0)&&(mese > 0)&&(anno > 0)&&(ore >= 0)&&(minuti >= 0)&&(secondi >= 0)&&(lag >=
0)&&(lam >= 0)&&(las >= 0)&&(logg >= 0)&&(lom >= 0)&&(los >= 0)&&(DateTime.available())){
semaforoCoordinate = true;
semaforoGps = true;
automatico();
}
else {
lcd.setCursor(0, 0);
lcd.print("dati non validi!");
lcd.setCursor(0, 1);
lcd.print(" sincronizzare ");
delay(100);
semaforoGps = true;
semaforoCoordinate = true;
}
}
if ((selettore > 128)&&(selettore <= 298)) {
// richiama la funzione manuale
lcd.setCursor(0, 0);
lcd.print(" funzionamento ");
lcd.setCursor(0, 1);
lcd.print(" manuale ");
delay(100);
avanti = analogRead(2); // leggo lo stato del pulsante avanti
indietro = analogRead(1); // leggo lo stato del pulsante indietro
if ((avanti >=512)&&(indietro >=512)) reset();
else manuale();
}
if ((selettore > 298)&&(selettore <= 426)) {
// richiama la funzione pc
lcd.setCursor(0, 0);
lcd.print("sincronizzazione");
lcd.setCursor(0, 1);
lcd.print(" manuale ");
//delay(2000);
if (semaforoCoordinate == true){
digitaCoordinate();
}
}
if ((selettore > 426)&&(selettore <= 768)) {
// richiama la funzione gps
lcd.setCursor(0, 0);
lcd.print("sincronizzazione");
lcd.setCursor(0, 1);
lcd.print(" con GPS ");
sincronizzaGps();
}
if (selettore > 768) {
// richiama la funzione ostacoli
lcd.setCursor(0, 0);
lcd.print(" inserimento ");
lcd.setCursor(0, 1);
lcd.print(" ostacoli ");
ostacoli();
}
}
}
boolean safeMode(){
//funzione salvataggio
int result = false;
wind = analogRead(4);
//ingresso analogico del segnale presenza vento
if (wind > soglia){
int fcwind = digitalRead(6);
if (fcwind == LOW) {
stepperAltezza.step(-1);
contatore++;
}
result = true;
lcd.setCursor(0, 0); lcd.print(" vento forte ");
lcd.setCursor(0, 1); lcd.print(" safety mode ");
}
else {
if (contatore > 0){
contatore--;
stepperAltezza.step(+1);
result = true;
lcd.setCursor(0, 0);
lcd.print(" vento nornale ");
lcd.setCursor(0, 1);
lcd.print(" return back ");
}
else {result = false;}
}
return result;
}
void sincronizzaGps(){
//funzione sincronizza con GPS
while (Serial.available() && (semaforoGps == true)){
byteGPS=Serial.read();
linea[conta]=byteGPS;
// If there is serial port data, it is put in the buffer
conta++;
if (byteGPS==13){
// If the received byte is = to 13, end of transmission
cont=0;
bien=0;
for (int i=1;i<7;i++){
// Verifies if the received command starts with $GPR
if (linea[i]==comandoGPR[i-1]){
bien++;
}
}
if(bien==6){
// If yes, continue and process the data
for (int i=0;i<100;i++){
if (linea[i]==','){
// check for the position of the "," separator
indices[cont]=i;
cont++;
}
if (linea[i]=='*'){
// ... and the "*"
indices[12]=i;
cont++;
}
}
int ver = indices[1];
if (linea[ver+1]=='V'){
//dati validi
lcd.setCursor(0, 0);
lcd.print(" dati validi ");
lcd.setCursor(0, 1);
lcd.print("OK!");
delay(1000);
int j=indices[0];
char c=linea[j+1]; ore = 10*(c-'0');c=linea[j+2];
ore = ore + (c-'0')+1;
c=linea[j+3];
minuti = 10*(c-'0');c=linea[j+4]; minuti = minuti + (c-'0');
c=linea[j+5];
secondi = 10*(c-'0');c=linea[j+6]; secondi = secondi + (c-'0');
j=indices[2];
c=linea[j+1];
lag = 10*(c-'0');c=linea[j+2];
lag = lag + (c-'0');
c=linea[j+3];
lam = 10*(c-'0');c=linea[j+4];
lam = lam + (c-'0');
c=linea[j+6];
las = 10*(c-'0');c=linea[j+7];
las = las + (c-'0');
las = round(las*0.6);
j=indices[4];
c=linea[j+1];logg = 100*(c-'0');c=linea[j+2];logg = logg + (10*(c-'0'));c=linea[j+3];logg = logg + (c-'0');
c=linea[j+4];
lom = 10*(c-'0');c=linea[j+5];
lom = lom + (c-'0');
c=linea[j+7]
;los = 10*(c-'0');c=linea[j+8];
los = los + (c-'0');
los = round(los*0.6);
j=indices[8];
c=linea[j+1];
giorno = 10*(c-'0');c=linea[j+2]; giorno = giorno + (c-'0');
c=linea[j+3];
mese = 10*(c-'0');c=linea[j+4];
mese = mese + (c-'0');
c=linea[j+5];
anno = 10*(c-'0');c=linea[j+6];
anno = anno + (c-'0');
semaforoGps = false;
time_t sincro = DateTime.makeTime(secondi, minuti, ore, giorno, mese, anno );
DateTime.sync(sincro);
latitudine = lag + (lam/60.0) + (las/3600.0);latitudine = radianti(latitudine);
longitudine = logg + (lom/60.0) + (los/3600.0);//longitudine = radianti(longitudine);
reset();
}
}
conta=0;
// Reset the buffer
for (int i=0;i<100;i++){
linea[i]=' ';
}
}
}
}
void manuale(){
//funzione manuale
lcd.setCursor(0, 0); lcd.print(" funzionamento ");
lcd.setCursor(0, 1); lcd.print(" manuale ");
int motore = analogRead(0);
if (motore <= 512){
//seleziona motore altezza
avanti = analogRead(2);
// leggo lo stato del pulsante avanti
indietro = analogRead(1);
// leggo lo stato del pulsante indietro
if (avanti >=512){
//richiesta avanti
if ((posizioneAltezza[1]+1) <= altezzaMax){
//vedo se la richiesta è corretta
fcFineAltezza = digitalRead(6);
if (fcFineAltezza == LOW){
stepperAltezza.step(+1);
delay(10);
posizioneAltezza[0]=posizioneAltezza[1];
posizioneAltezza[1]=posizioneAltezza[1]+1;
}
else {
lcd.setCursor(0, 0);
lcd.print("errore posizione");
lcd.setCursor(0, 1);
lcd.print(" resettare ");
reset();
}
// segnala errore
}
/*else {lcd.setCursor(0, 0);
lcd.print("errore posizione");
lcd.setCursor(0, 1);
lcd.print(" resettare ");
reset();}*/
// segnala errore
}
if (indietro >=512){
//richiesta indietro
if ((posizioneAltezza[1]-1) >= altezzaMin){
fcInizioAltezza = digitalRead(6);
if (fcInizioAltezza == LOW){
stepperAltezza.step(-1);
delay(10);
posizioneAltezza[0]=posizioneAltezza[1];
posizioneAltezza[1]=posizioneAltezza[1]-1;
}
else {
lcd.setCursor(0, 0);
lcd.print("errore posizione");
lcd.setCursor(0, 1);
lcd.print(" resettare ");
reset();
}
// segnala errore
}/*else {lcd.setCursor(0, 0);
lcd.print("errore posizione");
lcd.setCursor(0, 1);
lcd.print(" resettare ");
reset();}*/
// segnala errore
}
}
else {
//seleziona motore azimut
avanti = analogRead(2);
// leggo lo stato del pulsante avanti
indietro = analogRead(1);
// leggo lo stato del pulsante indietro
if (avanti >=512){
//richiesta avanti
if ((posizioneAzimut[1]-1) >= azimutOvest){ //>-135 corretto con >=-135
fcFineAzimut = digitalRead(13);
if (fcFineAzimut == LOW){
stepperAzimut.step(-1);
delay(10);
posizioneAzimut[0]=posizioneAzimut[1];
posizioneAzimut[1]=posizioneAzimut[1]-1;
}
else {
lcd.setCursor(0, 0);
lcd.print("errore posizione");
lcd.setCursor(0, 1);
lcd.print(" resettare ");
reset();
}
// segnala errore
}
/*else {lcd.setCursor(0, 0);
lcd.print("errore posizione");
lcd.setCursor(0, 1);
lcd.print(" resettare ");
reset();}*/
// segnala errore
}
if (indietro >=512){
//richiesta indietro
if ((posizioneAzimut[1]+1) <= azimutEst){
fcInizioAzimut = digitalRead(13);
if (fcInizioAzimut == LOW){
stepperAzimut.step(+1);
delay(10);
posizioneAzimut[0]=posizioneAzimut[1];
posizioneAzimut[1]=posizioneAzimut[1]+1;
}
else {
lcd.setCursor(0, 0);
lcd.print("errore posizione");
lcd.setCursor(0, 1);
lcd.print(" resettare ");
reset();
}
// segnala errore
}
/*else {lcd.setCursor(0, 0);
lcd.print("errore posizione");
lcd.setCursor(0, 1);
lcd.print(" resettare ");
reset();}*/
// segnala errore
}
}
}
void reset(){
//funzione reset
lcd.setCursor(0, 0); lcd.print(" reset in corso ");
lcd.setCursor(0, 1); lcd.print("wait");
fcInizioAltezza = digitalRead(6);
//fine corsa iniziale motore altezza
while (fcInizioAltezza != HIGH){
wind = analogRead(4);
if (wind > soglia){break;}
stepperAltezza.step(-1);
delay(100);
fcInizioAltezza = digitalRead(6);
}
int alCount = 1;
while (alCount <= altezzaMin){
wind = analogRead(4);
if (wind > soglia) break;
stepperAltezza.step(+1);
delay(100);
alCount++;
}
fcInizioAzimut = digitalRead(13);
//fine corsa iniziale motore azimut
while (fcInizioAzimut != HIGH){
wind = analogRead(4);
if (wind > soglia) break;
stepperAzimut.step(+1);
delay(100);
fcInizioAzimut = digitalRead(13);
}
int azCount = 1;
while (azCount <= (135 - azimutEst)){
wind = analogRead(4);
if (wind > soglia) break;
stepperAzimut.step(-1);
delay(100);
azCount++;
}
posizioneAltezza[0]=altezzaMin;
//reset posizione iniziale
posizioneAzimut[0]=azimutEst;
//reset posizione iniziale
posizioneAltezza[1]=altezzaMin;
//reset posizione attuale
posizioneAzimut[1]=azimutEst;
//reset posizione attuale
lcd.clear();
}
void automatico(){
//funzionamento automatico
unsigned long prevtime = DateTime.now();
while( prevtime == DateTime.now() );
//attende un secondo
DateTime.available();
lcd.setCursor(0, 1); lcd.print(" : : ");
lcd.setCursor(0, 1); stampaData(DateTime.Hour);
lcd.setCursor(3, 1); stampaData(DateTime.Minute);
lcd.setCursor(6, 1); stampaData(DateTime.Second);
if ((DateTime.Hour==22)&&(DateTime.Minute==00)&&(DateTime.Second==00)){
reset();
lcd.setCursor(0, 0); lcd.print(" good nigth ");
lcd.setCursor(0, 1); lcd.print(" see tomorrow ");
delay(3000);
lcd.setCursor(0, 0); lcd.print("");
lcd.setCursor(0, 1); lcd.print("");
}
else {
if (DateTime.Second==0){
tempo = float(DateTime.Hour) + (float(DateTime.Minute)/60) + (float(DateTime.Second)/3600);
equazioneTempo = -(9.87*sin(2*2*PI*(progressivo()-81)/365.0)-7.67*sin(2*PI*(progressivo()-1)/365.0));
deltaLongitudine = meridiano - longitudine;
mezzogiorno = 12.00 + (equazioneTempo/60.0) + (4/60.0)*(deltaLongitudine);
//mezzogiorno del
luogo
angoloOrario = (mezzogiorno - tempo)*(2*PI)*15/360;
//angolo orario
declinazione = 0.4092797*sin((2*PI)*(284+progressivo())/365);
//declinazione
altezzaSolare =
arcsin(sin(latitudine)*sin(declinazione)+cos(latitudine)*cos(declinazione)*cos(angoloOrario));
//altezza solare
azimut = arcsin(cos(declinazione)*sin(angoloOrario)/cos(altezzaSolare));
//azimut
if ((azimutOvest < gradi(azimut))&&(gradi(azimut) azimutOstacolo1){
if ((gradi(altezzaSolare) > altezzaOstacolo1)&&(gradi(altezzaSolare) > altezzaMin)){
posizione();
}
else {
lcd.setCursor(0, 0);
lcd.print("altezza > ");
lcd.setCursor(0, 1);
lcd.print("altezzaOstacolo1");
}
}
if (gradi(azimut) < azimutOstacolo2){
if ((gradi(altezzaSolare) > altezzaOstacolo2)&&(gradi(altezzaSolare) > altezzaMin)){
posizione();
}
else {
lcd.setCursor(0, 0);
lcd.print("altezza > ");
lcd.setCursor(0, 1);
lcd.print("altezzaOstacolo2");
}
}
if ((gradi(azimut)azimutOstacolo2)){
if (gradi(altezzaSolare) > altezzaMin){
posizione();
}
else {
lcd.setCursor(0, 0);
lcd.print("altezza > ");
lcd.setCursor(0, 1);
lcd.print("altezza minima ");
}
}
}
else {
lcd.setCursor(0, 0);
lcd.print("azimut fuori ");
lcd.setCursor(0, 1);
lcd.print("dai limiti ");
}
}
}
}
void digitaCoordinate(){
//funzione inserimento manuale coordinate
indietro = analogRead(1);
// leggo lo stato del pulsante indietro
lcd.setCursor(0, 0); lcd.print("dd | FW | NEXT");
lcd.setCursor(0, 1); lcd.print(" |black| red ");
if (giorno == 0){giorno = 1;}
giorno = ciclo(giorno,31,1,0,0);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("mm | FW | NEXT");
lcd.setCursor(0, 1); lcd.print(" |black| red ");
if (mese == 0){mese = 1;}
mese = ciclo(mese,12,1,0,0);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("yy | FW | NEXT");
lcd.setCursor(0, 1); lcd.print(" |black| red ");
if (anno == 0){anno = 9;}
anno = ciclo(anno,29,9,0,0);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("hh | FW | NEXT");
lcd.setCursor(0, 1); lcd.print(" |black| red ");
if (ore == -1){ore = 0;}
ore = ciclo(ore,23,0,0,0);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("mm | FW | NEXT");
lcd.setCursor(0, 1); lcd.print(" |black| red ");
if (minuti == -1){minuti = 0;}
minuti = ciclo(minuti,59,0,0,0);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("ss | FW | NEXT");
lcd.setCursor(0, 1); lcd.print(" |black| red ");
if (secondi == -1){secondi = 0;}
secondi = ciclo(secondi,59,0,0,0);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("la°| FW | NEXT");
lcd.setCursor(0, 1); lcd.print(" |black| red ");
if (lag == -1){lag = 0;}
lag = ciclo(lag,89,0,0,0);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("la' | FW | NEXT");
lcd.setCursor(0, 1); lcd.print(" |black| red ");
if (lam == -1){lam = 0;}
lam = ciclo(lam,59,0,0,0);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("la''| FW | NEXT");
lcd.setCursor(0, 1); lcd.print(" |black| red ");
if (las == -1){las = 0;}
las = ciclo(las,59,0,0,0);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("lo° | FW | NEXT");
lcd.setCursor(0, 1); lcd.print(" |black| red ");
if (logg == -1){logg = 0;}
logg = ciclo(logg,359,0,3,0);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("lo' | FW | NEXT");
lcd.setCursor(0, 1); lcd.print(" |black| red ");
if (lom == -1){lom = 0;}
lom = ciclo(lom,59,0,0,0);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("lo''| FW | NEXT");
lcd.setCursor(0, 1); lcd.print(" |black| red ");
if (los == -1){los = 0;}
los = ciclo(los,59,0,0,0);
reset();
time_t sincro = DateTime.makeTime(secondi, minuti, ore, giorno, mese, anno );
DateTime.sync(sincro);
latitudine = lag + (lam/60.0) + (las/3600.0);latitudine = 2*PI*latitudine/360;
longitudine = logg + (lom/60.0) + (los/3600.0);//longitudine = 2*PI*longitudine/360;
semaforoCoordinate = false;
}
void ostacoli(){
//funzione inserimento coordinate ostacoli
indietro = analogRead(1);
// leggo lo stato del pulsante indietro
lcd.setCursor(0, 0); lcd.print("altezza minima ");
lcd.setCursor(0, 1); lcd.print(" ");
altezzaMin = ciclo(altezzaMin,90,0,0,0);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("azimut est ");
lcd.setCursor(0, 1); lcd.print(" ");
azimutEst = ciclo(azimutEst,0,135,3,1);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("azimut ovest ");
lcd.setCursor(0, 1); lcd.print(" ");
azimutOvest = ciclo(azimutOvest,0,-135,33,0);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("altezza ostac.1 ");
lcd.setCursor(0, 1); lcd.print(" ");
altezzaOstacolo1 = ciclo(altezzaOstacolo1,90,1,0,0);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("azimut ostac.1 ");
lcd.setCursor(0, 1); lcd.print(" ");
azimutOstacolo1 = ciclo(azimutOstacolo1,0,135,3,1);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("altezza ostac.2 ");
lcd.setCursor(0, 1); lcd.print(" ");
altezzaOstacolo2 = ciclo(altezzaOstacolo2,90,1,0,0);
indietro = 0;
lcd.setCursor(0, 0); lcd.print("azimut ostac.2 ");
lcd.setCursor(0, 1); lcd.print(" ");
azimutOstacolo2 = ciclo(azimutOstacolo2,0,-135,33,0);
indietro = 0;
}
int progressivo(){
//calcolo del numero progressivo corrispondente al giorno dell’anno
int pro = 0;
int anno = 1900 + int(DateTime.Year);
int mese = int(DateTime.Month);
int giorno = int(DateTime.Day);
int bise = bisestile(anno);
if (mese==1){pro=giorno;}
if (mese==2){pro=0*30+1*31+giorno;}
if (mese==3){pro=0*30+1*31+giorno+bise;}
if (mese==4){pro=0*30+2*31+giorno+bise;}
if (mese==5){pro=1*30+2*31+giorno+bise;}
if (mese==6){pro=1*30+3*31+giorno+bise;}
if (mese==7){pro=2*30+3*31+giorno+bise;}
if (mese==8){pro=2*30+4*31+giorno+bise;}
if (mese==9){pro=2*30+5*31+giorno+bise;}
if (mese==10){pro=3*30+5*31+giorno+bise;}
if (mese==11){pro=3*30+6*31+giorno+bise;}
if (mese==12){pro=4*30+6*31+giorno+bise;}
return pro;
}
int bisestile(int a){
//funzione per scoprire se l’anno in corso è bisestile
int bis = 28;
if(a%4==0){
if(a%100==0){
if(a%400==0) bis = 29;
else bis = 28;
}
else bis = 29;
}
else bis = 28;
return bis;
}
int gradi( float x){
//trasforma i radianti in gradi
int gra = round(x*360.0/(2*PI));
return gra;
}
float radianti( float x){
//trasforma I gradi in radianti
float rad = 2*PI*x/360.0;
return rad;
}
float arcsin(float x){
//funzione arcsin()
float rad = 2 *arctan(x/(1+sqrt(1-pow(x,2))));
return rad;
}
float arctan(float x){
//funzione arctan()
float rad = 0.0;
for (int i = 0 ; i <= 200 ; i=i+1 ){
rad = rad + (pow(-1,i)*pow(x,2*i+1))/(2*i+1);} // con |x|<1
return rad;
}
void stampaValore(int x){
//stampa sul display LCD
if (x < 10){
lcd.setCursor(0, 1);
lcd.print("0");
lcd.setCursor(1, 1);
lcd.print(x);
}else {lcd.setCursor(0, 1);
lcd.print(x);}
}
void stampaValore3(int x){
//stampa sul display LCD
if (x < 10){
lcd.setCursor(0, 1);
lcd.print("00");
lcd.setCursor(2, 1);
lcd.print(x);
}
else {
if ((x >=10)&&(x < 100)){
lcd.setCursor(0, 1);
lcd.print("0");
lcd.setCursor(1, 1);
lcd.print(x);
}
else{
lcd.setCursor(0, 1);
lcd.print(x);
}
}
}
void stampaValore33(int x){
//stampa sul display LCD
x=-x;
if (x < 10){
lcd.setCursor(0, 1);
lcd.print("-00");
lcd.setCursor(3, 1);
lcd.print(x);
}
else {
if ((x >=10)&&(x < 100)){
lcd.setCursor(0, 1);
lcd.print("-0");
lcd.setCursor(2, 1);
lcd.print(x);
}else{lcd.setCursor(0, 1);
lcd.print(-x);
}
}
}
void stampaData(byte digits){
if(digits < 10)
lcd.print('0');
lcd.print(digits,DEC);
}
int ciclo(int a, int b, int c, int d, int e){
//funzione utilizzata dal caso d’uso inserisci coordinate
while (indietro <= 512){
//se non è stato premuto il tasto di conferma
wind = analogRead(4);
if (wind > soglia){break;}
if (d == 0){stampaValore(a);}
if (d == 3){stampaValore3(a);}
if (d == 33){stampaValore33(a);}
delay(200);
// ritardo per la pressione del tasto
avanti = analogRead(2); // leggo lo stato del pulsante avanti
if (avanti >= 512){
if (e == 0){a++;
if (a > b){a = c;}
}
if (e == 1){
a--;
if (a < b){a = c;}
}
}
indietro = analogRead(1);
}
return a;
}
void posizione(){
//funzione utilizzata dal caso d’uso funzionamento automatico
lcd.setCursor(0, 0); lcd.print(" ");
lcd.setCursor(0, 0); lcd.print("Alt");
lcd.setCursor(4, 0); lcd.print(gradi(altezzaSolare));
lcd.setCursor(8, 0); lcd.print("Azi");
lcd.setCursor(12, 0); lcd.print(gradi(azimut));
if (posizioneAltezza[1] != gradi(altezzaSolare)){ //la posizione attuale è diversa da quella precedente
int passi = gradi(altezzaSolare) - posizioneAltezza[1];
if (passi>0){ //devo incrementare la posizione
fcFineAltezza = digitalRead(6);
boolean uscitaRegolare = true;
while ((fcFineAltezza == LOW) && (passi > 0)){ //il finecorsa non è premuto oppure ho fatto tutti i
passi richiesti
stepperAltezza.step(+1);
fcFineAltezza = digitalRead(6);
passi--;
posizioneAltezza[0] = posizioneAltezza[1]; //registro la posizione attuale dell'altezza solare
posizioneAltezza[1] = posizioneAltezza[1]+1;
if ((fcFineAltezza == HIGH) && (passi > 0)) uscitaRegolare = false;
delay(100);
}
if (uscitaRegolare == false) {
invioSMS();
reset();
}//segnala l'errore che il sistema comanda il movimento ma il generatore è già arrivato a finecorsa
}
else {
if (passi<0){ //devo decrementare la posizione
fcInizioAltezza = digitalRead(6);
boolean uscitaRegolare = true;
while ((fcInizioAltezza == LOW) && (passi < 0)){ //il finecorsa non è premuto oppure ho fatto tutti i
passi richiesti
stepperAltezza.step(-1);
fcInizioAltezza = digitalRead(6);
passi++;
posizioneAltezza[0] = posizioneAltezza[1]; //registro la posizione attuale dell'altezza solare
posizioneAltezza[1] = posizioneAltezza[1]-1;
if ((fcInizioAltezza == HIGH) && (passi > 0)) uscitaRegolare = false;
delay(100);
}
if (uscitaRegolare == false) {
invioSMS();
reset();
}
}
} //segnala l'errore che il sistema comanda il movimento ma il generatore è già arrivato a
finecorsa
}
if (posizioneAzimut[1] != gradi(azimut)){
int passi = posizioneAzimut[1] - gradi(azimut);
if (passi>0){ //devo incrementare la posizione
fcFineAzimut = digitalRead(13);
boolean uscitaRegolare = true;
while ((fcFineAzimut == LOW) && (passi > 0)){ //il finecorsa non è premuto oppure ho fatto tutti i
passi richiesti
stepperAzimut.step(-1);
fcFineAzimut = digitalRead(6);
passi--;
posizioneAzimut[0] = posizioneAzimut[1]; //registro la posizione attuale dell'altezza solare
posizioneAzimut[1] = posizioneAzimut[1]-1;
if ((fcFineAzimut == HIGH) && (passi > 0)) uscitaRegolare = false;
delay(100);
}
if (uscitaRegolare == false) {
invioSMS();
reset();
}//segnala l'errore che il sistema comanda il movimento ma il generatore è già arrivato a finecorsa
}
else{
if (passi<0){ //devo decrementare la posizione
fcInizioAzimut = digitalRead(13);
boolean uscitaRegolare = true;
while ((fcInizioAzimut == LOW) && (passi < 0)){ //il finecorsa non è premuto oppure ho fatto tutti i
passi richiesti
stepperAzimut.step(+1);
fcInizioAzimut = digitalRead(6);
passi++;
posizioneAzimut[0] = posizioneAzimut[1]; //registro la posizione attuale dell'altezza solare
posizioneAzimut[1] = posizioneAzimut[1]+1;
if ((fcFineAzimut == HIGH) && (passi > 0)) uscitaRegolare = false;
delay(100);
}
if (uscitaRegolare == false) {
invioSMS();
reset();
}//segnala l'errore che il sistema comanda il movimento ma il generatore è già arrivato a finecorsa
}
}
}
}
Progetto realizzato dagli studenti: Luigi Negri e Nicola Sassi – AS 2021-22