Maledette funzioni!!

Help software! Scusate il titolo :0, ma mi sono imbattuto in uno di quei problemi software che i vari siti che spiegano il C non trattano o io non sono stato bravo nel cercare.
Si tratta del passaggio di un risultato calcolato in una funzione al programma chiamante per mezzo degli argomenti. In breve il programmino di prova con la funzione è il seguente:

#include <math.h>

  float dis ;
void setup(){
  Serial.begin(9600);
   float la1 = 45.78956;
   float lo1 =  9.36424;
   float la2 = 45.78997;
   float lo2 =  9.36514;
   float direct;
   float dis =  Distance(la1, lo1, la2, lo2, direct);
  Serial.println(dis,DEC);
  Serial.println(direct,DEC);
}
void loop(){
  
}
////////////////////////////////////////////////////////////////////////
  static float Distance(float lat1, float lon1, float lat2, float lon2, float dir){
  float rt = 6371000;  // raggio terra in m
  float pi = 3.141592654;
  float coslat = cos((lat1+lat2)*pi/360);
  float dx = rt*(lon2-lon1)*coslat*pi/180;
  float dy = rt*(lat2-lat1)*pi/180;
  float dist = sqrt(dx*dx + dy*dy);
  dir = atan2(dx,dy);  // da -pi a +pi  artg(dx/dy)
  Serial.println(dir,DEC);
  return(dist);
  }

I valori da passare al main sono: "dist", che viene passato tramite il return, e "dir", che viene passato tramite gli argomenti.
I numeri che escono sono:
0.9900114059
83.4928741455
0.0000000000
a dimostrazione che il valore della variabile dir non viene passato al main.

P.S.
per i curiosi, sto costruendo un GPS+bussola.

Ciao Paolo,
un paio di domande ...

  1. Passi la variabile float direct, ma non l'hai inizializzata quindi, probabilmente, vale sempre zero.

  2. Sei conscio del fatto che il float su Arduino ha una scarsa precisione (solo 32 bit e che "Floats have only 6-7 decimal digits of precision. That means the total number of digits, not the number to the right of the decimal point." ) e NON è adatto a fare calcoli di distanze tramite coordinate GPS senza una enorme perdita di precisione ?

Quindi, ad esempio, totalmente inutile scrivere :

float pi = 3.141592654;

dato che la precisione è comunque al massimo di 6-7 cifre (leggi bene la nota in inglese ... "... total number of digits") !

Per inciso, se prima di stampare il valore aggiungessi una Serial.print con una descrizione del valore che stai stampando ... renderesti più comprensibili i risultati :wink:

Guglielmo

paolometeo2:
Help software! Scusate il titolo :0, ma mi sono imbattuto in uno di quei problemi software che i vari siti che spiegano il C non trattano o io non sono stato bravo nel cercare.
I numeri che escono sono:
0.9900114059
83.4928741455
0.0000000000
a dimostrazione che il valore della variabile dir non viene passato al main.

Premesso che questo calcolo richiede la matematica a 64 bit (double) che non è disponibile su Arduino, a meno che non ti accontenti di lavorare sulle grandi distanze con risoluzione kilometrica, dir non può essere restituito al main visto che la return è per dist, se dir deve essere disponibile anche al main allora deve essere una variabile globale.
Togli quello static alla funzione che non serve a nulla.

Sul problema della precisione ho riflettuto, concludendo che la quinta cifra decimale è sufficiente per una rappresentazione in gradi delle coordinate, infatti se un grado di latitudine corrisponde a circa 100 km, 1m (massima precisione per un GPS) sarà un 100000 esimo di grado. Per quanto riguarda i calcoli intermedi mi riprometto di fare delle prove. Le costanti le ho scritte con più cifre giusto per annotazione.
Ho letto che double e float sono sempre di 4 byte su Arduino, vero?
Per quanto riguarda i valori che una funzione restituisce al main, se ho capito bene, solo uno può ritornare con il nome della funzione, mentre negli argomenti non posso passare nient'altro. E' giusto? Altrimenti uso variabili globali.

paolometeo2:
Sul problema della precisione ho riflettuto, concludendo che la quinta cifra decimale è sufficiente per una rappresentazione in gradi delle coordinate, infatti se un grado di latitudine corrisponde a circa 100 km, 1m (massima precisione per un GPS) sarà un 100000 esimo di grado.

Paolo, ricorda che gli errori .... si accumulano ... su ogni operazione che fai tronchi il risultato ... non so alla fine che precisione ti rimane ...
Astro, che conosce bene l'argomento, ti ha indicato un errore di .... centinaia di Km ...

paolometeo2:
Per quanto riguarda i calcoli intermedi mi riprometto di fare delle prove. Le costanti le ho scritte con più cifre giusto per annotazione.
Ho letto che double e float sono sempre di 4 byte su Arduino, vero?

Se hai letto la mia risposta precedente hai tutte le indicazioni ... :wink:

paolometeo2:
Per quanto riguarda i valori che una funzione restituisce al main, se ho capito bene, solo uno può ritornare con il nome della funzione, mentre negli argomenti non posso passare nient'altro. E' giusto? Altrimenti uso variabili globali.

In realtà il discorso è più complesso ... ad una funzione i parametri si possono passare "by value" o "by reference", e, nel secondo caso, la funzione può addirittura modificare il valore dei parametri ... ma ... credo di mettere "troppa carne al fuoco" ... :wink:

Guglielmo

Ti posso confermare che Arduino proprio litiga con float con tanti numeri decimali.
Potresti provare a convertire tutto in unsigned long, tipo a 32 bit, e poi in fase di stampa mettere il punto decimale dove ti serve. Oppure, usare unsigned long long, a 64 bit. Non è un tipo nativamente supportato dal micro per cui la dimensione dello sketch cresce molto, però se necessiti di grossa precisione, potrebbe essere una soluzione rapida.

gpb01:

paolometeo2:
Per quanto riguarda i valori che una funzione restituisce al main, se ho capito bene, solo uno può ritornare con il nome della funzione, mentre negli argomenti non posso passare nient'altro. E' giusto? Altrimenti uso variabili globali.

In realtà il discorso è più complesso ... ad una funzione i parametri si possono passare "by value" o "by reference", e, nel secondo caso, la funzione può addirittura modificare il valore dei parametri ... ma ... credo di mettere "troppa carne al fuoco" ... :wink:
Guglielmo

Quotone.
Praticamente, devi usare i puntatori se vuoi poter modificare una variabile passata come parametro. Perciò occhio che con i puntatori è un attimo fare casino.
Se vuoi saperne di più, cerca su libri di C o su internet, il classico esempio dello swap (cambio) dei contenuti di due variabili passati ad una funzione.

In Swap1 lo scambio non avviene perchè in a e b ci sono le copie dei valori passati

int x=1,y=3;
swap1(x,y);      // contenuto delle variabili swap non funziona
Serial.print(x); Serial.print(" ");Serial.println(y);
swap2(&x,&y);    // indirizzo delle variabili
Serial.print(x); Serial.print(" ");Serial.println(y);
...
void swap1(int a,int b)
{ int tmp=a;
  a=b;
  b=tmp;            
}
void swap2(int *a,int *b)
{ int tmp=*a;
  *a=*b;
  *b=tmp;            
}

Il C++ ammette poi questa sintassi che semplifica:

int x=1,y=3;
swap3(x,y);      // sembra che passi il contenuto delle variabili ma i parametri prendono l'indirizzo perchè così vuole la funzione
                      // non dipende più dal chiamante ricordarsi di mettere &x e &y come per swap2()
...
void swap3(int &a,int &b)
{ int tmp=a;
  a=b;
  b=tmp;            
}

Nid ... ma secondo te la mia frase ...

... credo di mettere "troppa carne al fuoco" ...

era buttata li a caso o sapevo quello che dicevo (dato che conosco Paolo) ???

Provaci a ragionare va ...

Guglielmo

Ho scritto la prima frase, poi ... non ho resistito :smiley:

Ma @paolo non è abbastanza skillato? Fà molti interventi nel forum

@nid69ita, grazie per lo skillato, ma purtroppo nel software sono rimasto a vecchi concetti. In passato usavo molto le "subroutine" dove passavo tutte le variabili che volevo in input e output. Adesso devo dimenticare quel modo di programmare semplice =(!

Quotone.
Praticamente, devi usare i puntatori se vuoi poter modificare una variabile passata come parametro. Perciò occhio che con i puntatori è un attimo fare casino.
Se vuoi saperne di più, cerca su libri di C o su internet, il classico esempio dello swap (cambio) dei contenuti di due variabili passati ad una funzione.

Volevo proprio evitare di usare i puntatori e concentrarmi più sul problema numerico, infatti Guglielmo mi ha capito.
@leo72, ascolterò i tuoi consigli e proverò ad usare i diversi tipi di variabile, per ora ho risolto il problema della funzione usando una variabile globale e una restituita tramite il nome della funzione. Oppure userò tutte variabili globali, che per far le prove di precisione va bene lo stesso.
Intanto vi ringrazio tutti e vi farò sapere presto cosa tiro fuori.
ciao
paolo

paolometeo2:
...
Volevo proprio evitare di usare i puntatori e concentrarmi più sul problema numerico, infatti Guglielmo mi ha capito.
...

Paolo, hai i miei contatti e sei piuttosto vicino ... se ti serve una mano, veramente NON fare complimenti ... in un'ora e mezza sei qui da me, magari, se è un fine settimana, ci facciamo una bella grigliata e ... ti do tutto il supporto che ti serve :wink:

Guglielmo

Grazie Guglielmo dell'invito. Lascio passare le vacanze e poi ci sentiamo.

Anch'io voglio venire alla grigliata !!! XD XD XD XD

pablos:
Anch'io voglio venire alla grigliata !!! XD XD XD XD

Tu mica abiti qui vicino ...

Guglielmo

Ragazzi, come promesso vi metto al corrente degli sviluppi sulla precisione del calcolo delle distanze terrestri su Arduino.
Per fare i confronti ho usato, sia la mia fidata calcolatrice HP 11C con 10 cifre significative, sia un programma in Fortran che usa i real*8 ovvero floating point a 64 bit. Non solo, ma una delle distanze ottenute l'ho misurata anche con il "righello" di Google Earth. I risultati sono molto confortanti e li ho scritti come commenti sul codice qui sotto. (Le distanze sono in metri e le direzioni in gradi rispetto al N)

#include <math.h>
     float direct;
     float dis;
void setup(){
  Serial.begin(9600);
/*
   float la1 = 45.78956;
   float lo1 =  9.36424;
   float la2 = 45.78897;
   float lo2 =  9.36514;
   */
//   dis = 95.6; direct = 133.1     HP:  95.8   133.2  Fortran 95.6 133.1
//   Google Earth 96  133
   
   /*
   float la1 = 45.78956;
   float lo1 =  9.36424;
   float la2 = 45.77297;
   float lo2 =  9.36514; 
   */
   // dis = 1846.1  direct = 177.8   HP:  1846  177.8  Fortran 1846.1  177.8
 /*  
   float la1 = 45.78956;
   float lo1 =  9.36424;
   float la2 = 45.78957;
   float lo2 =  9.36514;
*/
   // dis = 69.8  direct = 89    FORTRAN  69.8  89.0
   float la1 = 45.78956;
   float lo1 =  9.36424;
   float la2 = 45.78937;
   float lo2 =  9.36426;

//  dis = 20.8  direct = 180   FORTRAN 20.8  direzione 180

   dis =  Distance(la1, lo1, la2, lo2);
   Serial.print(" distanza = ");
  Serial.println(dis);
  Serial.print(" direzione = ");
  Serial.println(direct);
}
void loop(){
  
}
////////////////////////////////////////////////////////////////////////
  float Distance(float lat1, float lon1, float lat2, float lon2){
  float rt = 6371000;  // raggio terra in m
  float pi = 3.141592654;
  float coslat = cos((lat1+lat2)*pi/360);
  float dx = rt*(lon2-lon1)*coslat*pi/180;
  float dy = rt*(lat2-lat1)*pi/180;
  float dist = sqrt(dx*dx + dy*dy);
  //
  if(dist < 4){
    direct = 0;
  }
  else{
        if(abs(dx) < 4 && dy >= 0){
              direct = 360;
         }
         else if(abs(dx) < 4 && dy < 0){
              direct = 180;
         }
         else{
              direct = 180*atan(dy/dx)/pi;
              if(dx > 0){
                 direct = 90 - direct;
              }
              if(dx < 0){
                 direct = 270 - direct;
              }  
         }
    }
      return(dist);
  }
  1. quindi hai risolto continuando ad usare i float. E cos'era allora che non andava?
  2. abituati a fare i confronti float con float, quindi questo if:
if (dist < 4)

fallo diventare:

if (dist < 4.0)
  1. l'Arduino già predefinisce pi greco come costante PI.
  2. nell'Arduino hai già delle costanti predefinite per la conversione gradi/radianti e viceversa, DEG_TO_RAD e RAD_TO_GRAD.

Trovi le costanti nel file /hardware/arduino/cores/arduino/Arduino.h

Il problema era iniziato con le variabili passate attraverso le funzioni, poi si è spostato sulla precisione dei calcoli fatti con le latitudini e longitudini di un GPS per calcolare distanze e direzioni. Adesso, dopo queste prove, sono abbastanza sicuro che i float di Arduino sono abbastanza precisi da garantirmi qualche metro, che è la stessa precisione di un buon GPS.
Il mio progetto consiste nel mettere insieme il GPS Shield di Adafruit, la bussola LSM303 di Pololu e il LCD Color di Sparkfun, per fare uno strumento che ti dice dove sei e quanto sei distante da un punto di coordinate note, oltre alla direzione che devi prendere per raggiungere il punto. E' più da tracking che da navigazione in auto, ma i navigatori spesso non ti permettono certe funzioni, E poi vuoi mettere il gusto di costruirselo da solo!
Grazie dei consigli sulle costanti.
Paolo

P.S.
per chi volesse vedere i tre pezzi in funzione, ho un paio di immagini qui:

http://www.meteoenergia.it/GPS/03082013382.jpg
http://www.meteoenergia.it/GPS/03082013384.jpg

Ma all'alimentazione ci hai pensato?

Dovendo essere portatile, dovrò alimentarlo con una batteria, magari ricaricabile con la presa 12 V dell'auto. Non ho ancora misurato l'assorbimento di tutto il trabiccolo.