MODULI: Esempio ACCELEROMETRO 3 ASSI

Per provare il codice presentato in questa pagina occorre avere a disposizione un accellerometro a 3 assi come quello rappresentato in figura (modulo GY-61)

 

Gli accelerometri misurano l'accelerazione solitamente dovuta al movimento. Quando sono in stato di quiete l'unica accelerazione che il sensore rileva è quella di gravità. Un accelerometro misura il cambio di velocitá e non di quanto si é spostato un oggetto.

Il nostro modulo GY-61 utilizza l'accelerometro ADXL335 (vedi datasheet).  Gli accelerometri analogici hanno tante uscite analogiche quanti sono gli assi gestiti e pertanto un accelerometro a 3 assi avrà 3 uscite. Ogni uscita fornisce un valore in tensione direttamente proporzionale al valore di accelerazione (espresso in g) misurato lungo il rispettivo asse.

Ad esempio supponiamo di avere un accelerometro analogico in grado di misurare fino ad un minimo/massimo +/-1g e funzionante con una tensione di 3V allora avremo in uscita questi valori su uno degli assi:
- circa 1.5V quando l’accelerazione vale 0g (valore “centrale”),
- 3V quando vale 1g,
- 0V quando vale -1g.

Gli accelerometri analogici vanno interfacciati con il microcontrollore (MCU) mediante di convertitore analogico digitale (A/D). Uno dei problemi che si riscontra su tali dispositivi è la questione del rumore sulle uscite ed è possibile osservarlo come una continua oscillazione dei valori in uscita dagli assi anche in stato di quiete. Generalmente per ridurre il rumore si ricorre ad elaborate funzioni matematiche come il filtro di Kalman.

Gli accelerometri digitali hanno generalmente una linea di comunicazione seriale I2C o SPI [SPI e I2C sono dei bus seriali sincroni e questo implica che oltre ai dati deve girare un clock. Il clock è fornito da un master e gli slave lo subiscono. La SPI usa 4 segnali: un clock (SCK), 2 dati (in uscita (SDO) ed in ingresso (SDI) e un segnale che dice se si tratta di un master o di uno slave (SS). I2C invece usa 2 segnali: uno per il clock (SCL) e l'altro per idati (SDA) e che è bidirezionale] e più raramente abbiamo una UART. In questi casi il valore di accelerazione di ogni asse è contenuto in uno o più registri e si interroga quindi l’accelerometro per avere il valore misurato sugli assi.

La scelta tra un accelerometro digitale e un analogico è dettata unicamente dal fatto se è possibile  “sacrificare” o meno 3 ingressi analogici. Solitamente l’utilizzo di un accelerometro analogico risulta più semplice.

Come funziona un accelerometro

Per avere uno schema mentale che ci aiuti a visualizzare in qualche modo come opera un accelerometro a 3 assi, immaginiamolo come costituito da una sfera al centro di un cubo, sospesa da 3 molle che la attraversano, agganciate a loro volta al centro di ogni faccia del cubo:

Muovendo il cubo nello spazio, la sfera si muoverà al suo interno allungando e comprimendo, le molle che la tengono sospesa. La misurazione del grado di compressione delle molle permette di stabilire se c’è stata un’accelerazione (ovvero una variazione di velocità) nella direzione in cui si trova la molla compressa e quindi anche di quantificarla. Con un accelerometro a 3 assi è possibile rilevare le variazioni di velocità nelle 3 direzioni dello spazio.

Gli assi di riferimento dell’accelerometro si originano al centro della sfera, per cui si possono misurare valori di accelerazione positivi o negativi in dipendenza dal verso in cui l’accelerazione è diretta. In un accelerometro a 3 assi, la situazione nel mondo reale non sarà quella rappresentata nella figura precedente ma piuttosto questa:

La sfera attratta verso il basso dalla forza di gravità determinerà una compressione della parte inferiore della molla orientata lungo l'asse Z.

Un corpo in stato di quiete, quindi, subisce sempre un’accelerazione verso il basso che vale 9.822 m/s2 (o 9.822 N/Kg). Questo valore di accelerazione, dovuto all’interazione gravitazionale, è anche indicato come g (scritto in minuscolo, per distinguerlo dalla G maiuscola che rappresenta la costante di gravitazione universale 6,67x10-11 m3/kgs2).

Per i cellulari non è necessario utilizzare un accelerometro a 3 assi dal momento che le applicazioni che fanno uso di questo “strumento” sono sempre sviluppate su due dimensioni per cui, in questi dispositivi, abbiamo generalmente un accelerometro a 2 assi. In un accelerometro a due assi non è presente la “molla” che attraversa la “sfera” lungo l’asse Z, per cui non è possibile misurare il valore di accelerazione lungo tale asse.

Come determinare l'inclinazione

Oltre a misurare il valore di accelerazione tale dispositivo può anche essere utilizzato come dispositivo per misurare l’inclinazione di un corpo, anzi questo è forse il suo utilizzo più diffuso.

L’accelerometro è in grado sia di misurare l’accelerazione statica (ad esempio quando lo ruotiamo sul posto) che l’accelerazione dinamica (ad esempio: lo fissiamo su un’automobile in corsa). Per applicazioni di tilt sensing (ovvero la misurazione dell’inclinazione di un corpo) viene appunto sfruttata la misurazione dell’accelerazione statica.

Assumiamo quindi che la forza di gravità sia l’unico valore di accelerazione che agisce su un corpo e ignoriamo i valori di accelerazione superiori ad 1g. Immaginiamo di ruotare il nostro cubo attorno all’asse X e di tenerlo fermo con una certa angolazione: la sfera ora comprimerà due molle, quella lungo l’asse Y e quella lungo l’asse Z (la molla lungo l’asse X mantiene l'allungamento originale poichè la componente gravitazionale rispetto a tale asse resta invariato. Possiamo quindi considerare la misura lungo l'asse X come il valore pari a zero).

Si può facilmente notare che il valore di accelerazione (rappresentato dalla compressione delle 2 molle) letto lungo ciascun asse Y e Z  vale meno di 1g per cui si può stabilire che il cubo è in qualche modo è inclinato. Questa assunzione è valida solo se il corpo non è in movimento, stiamo difatti tenendo conto di accelerazioni statiche, ovvero dovute esclusivamente alla forza di gravità terrestre. Misurando il valore di accelerazione lungo gli assi, facendo uso di un po’ di trigonometria, è possibile stabilire di quanti gradi è ruotato il corpo e quindi il dispositivo all’interno del quale l’accelerometro è montato.

Roll - Pitch - Yaw (Rollio - Beccheggio - imbardata)

Introduciamo ora altri concetti relativi alla rotazione di un corpo nello spazio, soprattutto per capire fino a che punto può essere utile un accelerometro per la misurazione dell’ inclinazione. Un corpo può rotare nello spazio lungo 3 assi, facendo riferimento alla dinamica applicata ai velivoli si parla più propriamente di rollio, beccheggio e imbardata. Osservate questa immagine:

Per convenzione la rotazione del velivolo lungo l’asse X (in rosso) del velivolo si chiama rollio (Roll). La rotazione lungo l’asse Y del velivolo (in blu) si chiama beccheggio (Pitch) e la rotazione lungo l’asse Z del velivolo (in verde) si chiama imbardata (Yaw).

Immaginiamo ora che gli assi X e Y del veivolo si trovino sul piano dell’orizzonte e l’asse Z sia perpendicolare a questo piano.

Un accelerometro a 3 assi è in grado di rilevare l’angolo di rollio e l’angolo di beccheggio ma non l’angolo di imbardata per quanto appena detto dato che la rotazione intorno a Z non provoca una variazione degli angoli che gli assi formano rispetto all’orizzonte e di conseguenza variazioni sui valori di accelerazione gravitazionale cui i singoli assi X Y sono sottoposti.

Vediamo un ulteriore esempio: prendiamo il cubo dell’immagine sottostante e facciamolo ruotare unicamente lungo l’asse Z. Le componenti gravitazionali associate agli assi Y e X non cambiano (gli assi X e Y continuano a mantenere lo stesso valore di angolazione rispetto al piano dell’orizzonte) e pertanto le molle restano con gli stessi valori di tensione.

Per questo motivo non è possibile usare un accelerometro quando occorre anche rilevare l’angolo di imbardata. Per fare questo esistono i giroscopi che sono altri strumenti (ben più costosi!) che servono in maniera più specifica a rilevare gli angoli di rotazione. I giroscopi sono indispensabili sugli elicotteri per evitare la rotazione indesiderata intorno all’asse di rotazione dell’elica principale. Un altro sistema, meno preciso, per rilevare l’angolo di imbardata potrebbe essere quello di utilizzare un magnetometro (una bussola elettronica).

Calcoli per il tilt sensing

Vediamo ora come sfruttare i valori in uscita da un accelerometro per misurare l’angolo di inclinazione. Partiamo con un accelerometro ad un solo asse e immaginiamo di averlo in una condizione iniziale nella quale l’asse X è parallelo all’orizzonte, per cui tutta la forza di gravità è applicata esclusivamente sull’unico asse.

In questo stato di quiete l’accelerometro misura 1g sull’unico asse di misura a disposizione. Supponiamo ora di ruotare l’asse x di un certo angolo θ rispetto al piano dell’orizzonte :

Valutiamo  ora la componente della forza di gravità lungo l'asse X (X-Out dell'accellerometro). L’asse X dell’accelerometro è inclinato di un angolo θ rispetto all’orizzonte.

Il massimo valore di accelerazione di gravità che può agire su un corpo vale 1g. La forza che ora agisce sull’asse X dell’accelerometro è rappresentata dal segmento Ax (in viola) che, in base al primo teorema dei triangoli rettangoli (un cateto è uguale al prodotto dell’ipotenusa per il seno dell’angolo opposto o per il coseno dell’angolo adiacente) vale:

Dove il seno(θ) è la proiezione sull'asse Y del segmento unitario che forma un angolo θ con l'asse X.

Se vogliamo raffigurare in un grafico la lunghezza di tale proiezione in funzione dell'angolo θ otteniamo la seguente sinusoide:

 Dato che stiamo ragionando sull’accelerazione statica e 1g è un termine unitario, per comodità possiamo omettere la g e sostituirla con 1. La formula precedente diventa

Sfruttando la formula inversa possiamo ricavare l’angolo θ che l’asse X forma con l’orizzonte:

Arcsen è la funzione inversa del seno (arcoseno). Ricordo che θ è misurato in radianti, per avere il valore in gradi basta moltiplicarlo per 180/Π (pigreco)

Utilizzando accelerometri ad un singolo asse, la sensibilità (intesa in questo caso specifico come la variazione del valore in uscita dall’accelerometro in funzione della variazione dell’inclinazione) diminuisce sempre di più man mano l’angolo aumenta: più ci avvicianiamo a ±90° ad una grande variazione di angolo corrisponde una piccola variazione di accelerazione. Questo è evidente nella figura sottostante:

Per apprezzare quindi bene il valore dell’angolo in questi estremi è necessario un accelerometro davvero molto sensibile altrimenti quando siamo prossimi all'angolo retto il valore calcolato con la funzione arcoseno risulterà estremamente impreciso.

Un’altra forte limitazione di un accelerometro a singolo asse è data dal fatto che non è possibile misurare valori di accelerazione su tutti i 360° dal momento che il valore di uscita per un’inclinazione pari a θ (N°) è pari a quello ottenuto per un inclinazione π-θ (180-N°). In altre parole non è possibile distinguere l’inclinazione in un verso o nell’altro. Per alcune applicazioni può anche andar bene, per altre invece no.



Per ovviare a questi “problemi” si ricorre ad un accelerometro a 2 assi (oppure utilizzando un secondo accelerometro a singolo asse ma montato in maniera tale che l’asse di misura sia ortogonale all’asse di misura del primo accelerometro). Nel caso di un accelerometro a due assi, a seguito ad una rotazione θ dell'asse X, avremo questa situazione:

Consideriamo le componenti di accelerazione gravitazionale Ax e Ay rispettivamente lungo gli assi X e Y:



Sia φ l’angolo che l’asse Y dell’accelerometro forma con l’orizzonte. E' evidente la relazione:

φ = 90°+θ

dato che i due assi sono ortogonali tra loro. L'accelerazione Ay  impressa sull'asse Y può essere calcolata allo stesso modo di Ax per cui:

Ay=sin(φ)

Considerando l’angolo opposto (come visibile nella precedente figura) possiamo riscrivere il valore di Ay in funzione θ ovvero:



La somma vettoriale di Ax e Ay (non la somma algebrica) deve fornire il valore di 1 (1g). La somma vettoriale di due vettori a e b è così definita:



dove α indica l’angolo che i due vettori formano tra loro. Poichè i due assi sono tra loro ortogonali segue che il coseno di α sia zero e quindi la relazione precedente diventa semplicemente:



Al posto di a e b sostituiamo i vettori Ax e Ay. La somma vettoriale deve fornire 1g di accelerazione per cui l'equazione diventa:

L’uso di un accelerometro a 2 assi porta 2 vantaggi:

1) Abbiamo detto che con un solo asse la sensibilità diminuisce man mano ci avviciniamo ad una inclinazione pari a ±90°. Questo continua ad essere vero anche con 2 assi, ma essendo questi assi ortogonali tra loro, quando la sensibilità di un asse diminuisce, la sensibilità dell’altro asse aumenta:

Poichè il valore di accelerazione sull’asse X è proporzionale al seno dell’angolo di inclinazione  θ mentre il valore di accelerazione sull’asse Y è proporzionale al coseno dell’angolo di inclinazione  θ segue che le due sensibilità si compensino. Il loro rapporto è rappresentato dalla seguente relazione:



Utilizzando la funzione inversa alla tangente (arcotangente) possiamo ricavare il valore di θ:



Questa formula restituisce un valore di θ molto più preciso rispetto alla θ=arcsen(Ax) dal momento che abbiamo introdotto un secondo asse che ci garantisce una sensibilità costante in tutto l’arco di misura.

2) L’altro vantaggio di avere 2 assi è quello che ora possiamo distinguere l’inclinazione in tutto l’arco dei 360° (con un solo asse era possibile stabilire, basandosi sul segno di Ax, solo se si è nei quadranti in alto (I e II) o in basso (III e IV) :

Questo è possibile dal momento che in ogni quadrante i valori di accelerazione anche se uguali in modulo, hanno diverse combinazioni di segno.

Ora passiamo ad un accelerometro a 3 assi. Qui la situazione si fa più complessa ma sicuramente più ricca di informazioni. Misurando i valori di accelerazione sui 3 assi possiamo determinare l’orientamento dell’oggetto nello spazio (questa affermazione non è proprio corretta visto che l'imbardata non è determinabile):

Il nostro oggetto non si muoverà più all’interno di una circonferenza ma all’interno di una sfera: la forza di gravità agisce ora su 3 assi di misura, per cui nei calcoli bisogna tenere conto di tutti e 3 gli assi. Qui mi limito esclusivamente a fornire le formule per i calcoli:

Codice Sorgente A

Obiettivo progetto: Costruire un progetto che consenta di calibrare l'accelerometro. La calibrazione non modifica l'output del sensore ma permette di allineare correttamente le uscite del sensore agli assi di riferimento x, y e z.

L'accelerazione di gravità può essere misurata in unità di forza gravitazionale "g" dove 1g rappresenta l'attrazione gravitazionale sulla superficie terrestre.

soluzione: Costruiamo dapprima il seguente circuito (la resistenza è da 10kΩ). Utilizzeremo uno scatolino trasparente per meglio "pilotare" il nostro accelerometro.

Ecco il codice che ci permette di calibrare correttamente il nostro accellerometro

#define DELTA 3
#define N_CAMPIONE 20
#define x_PIN A0  // Cavo Arancio
#define y_PIN A1  // Cavo Verde
#define z_PIN A2  // Cavo Giallo
#define Calibrate_PIN 2

// Inizialmente si pone il minimo 1023 e il massimo 0 - Successivamente
// dopo diverse prove ottimiziamo il range e troviamo come valori
// accettabili quelli compresi in un range da 270 a 410.
// HO IMPOSTATO GIA' IL QUESTO RANGE PER SEMPLIFICARE LE MIE SPIEGAZIONI
#define MINSENSOR 270
#define MAXSENSOR 410
// Variabili globali utilizzate per individuare i valori
// minini e massimi letti sugli ingressi A0 (x) A1 (y) e A2 (z) 
int xMin = MINSENSOR;
int xMax = MAXSENSOR;
int yMin = MINSENSOR;
int yMax = MAXSENSOR;
int zMin = MINSENSOR;
int zMax = MAXSENSOR;

int p_xMin = -1;
int p_xMax = -1;
int p_yMin = -1;
int p_yMax = -1;
int p_zMin = -1;
int p_zMax = -1; 
int p_x = -1;
int p_y = -1;
int p_z = -1;

void setup() 
{
  // Si osservi che in questo progetto la linea successiva
  // e' stata disabilitata (e in questo caso il range e' 270-410)
  // analogReference(EXTERNAL);
  Serial.begin(9600);
  pinMode(Calibrate_PIN, INPUT);
}
 
void loop() 
{
  int buttonState = digitalRead(Calibrate_PIN);
  boolean Cambiato=false;
  int x = LeggiSensoreAsse(x_PIN);
  int y = LeggiSensoreAsse(y_PIN);
  int z = LeggiSensoreAsse(z_PIN);
  if (buttonState==HIGH)
  {
      CalibraAccellerometro(x,y,z);
      Cambiato=true;
  }
  Cambiato=Cambiato || (p_xMin!=xMin);
  Cambiato=Cambiato || (p_yMin!=yMin);
  Cambiato=Cambiato || (p_zMin!=zMin);
  Cambiato=Cambiato || (p_xMax!=xMax);
  Cambiato=Cambiato || (p_yMax!=yMax);
  Cambiato=Cambiato || (p_zMax!=zMax);
  Cambiato=Cambiato || (abs(p_x-x)>DELTA);
  Cambiato=Cambiato || (abs(p_y-y)>DELTA);
  Cambiato=Cambiato || (abs(p_z-z)>DELTA);
  if (Cambiato)
  { 
    Serial.print("Range Valori (min,max): X = ["+String(xMin)+","+String(xMax)+"]");
    Serial.print(" Y = ["+String(yMin)+","+String(yMax)+"]");
    Serial.println(" - Z = ["+String(zMin)+","+String(zMax)+"]");

    Serial.print("Lettura attuale (x,y,z) = ("+String(x)+","+String(y)+","+String(z)+") :: ");
    // Converto le letture in millesimi di g (gravità)
    long xScaled = map(x, xMin, xMax, -1000, 1000);
    long yScaled = map(y, yMin, yMax, -1000, 1000);
    long zScaled = map(z, zMin, zMax, -1000, 1000);
  
    // riscalo ad una frazione di g
    float xAccel = round((xScaled-xScaled%10) / 1000.0);
    float yAccel = round((yScaled-yScaled%10) / 1000.0);
    float zAccel = round((zScaled-zScaled%10) / 1000.0);
    Serial.print(xAccel);
    Serial.print("G, ");
    Serial.print(yAccel);
    Serial.print("G, ");
    Serial.print(zAccel);
    Serial.println("G");
  }
  p_x = x;
  p_y = y;
  p_z = z; 
  p_xMin = xMin;
  p_xMax = xMax;
  p_yMin = yMin;
  p_yMax = yMax;
  p_zMin = zMin;
  p_zMax = zMax; 
  delay(500);
}
 
// Effettuo più letture per ridurre il rumore
// e restituisco la media
int LeggiSensoreAsse(int assePin)
{
  long Lettura = 0;
  analogRead(assePin); //Lettura a vuoto
  delay(1);
  for (int i = 0; i < N_CAMPIONE; i++)
  {
    Lettura += analogRead(assePin);
    delay(1);
  }
  return Lettura/N_CAMPIONE;
}
 
// Determina il range dei valori x,y e z quando
// il push button è premuto
void CalibraAccellerometro(int x, int y, int z)
{
  Serial.println("Hai premuto il bottone - Calibro...");
  if (x < xMin) xMin = x;
  if (x > xMax) xMax = x;
  if (y < yMin) yMin = y;
  if (y > yMax) yMax = y;
  if (z < zMin) zMin = z;
  if (z > zMax) zMax = z;
}

Si osservi nel codice appena proposto l'uso di una delle due tecniche utilizzate per limitare gli errori dovuti al rumore.
A tale scopo possiamo seguire due strade (o entrambe!):
- la prima è quella di usare il pin
AREF dell’Arduino UNO per dare al convertitore analogico digitale (DAC) di arduino un riferimento di tensione più basso (di default è 5Vdc). In questo caso occorre creare un ponte tra il pin AREF e il pin power di 3.3Vdc. Anche nel sorgente dobbiamo indicare che intendiamo usare il pin AREF per le operazioni di conversione Analogico-Digitale. L’istruzione che si utilizza è analogReference(EXTERNAL); Usando la tensione 3.3Vdc come riferimento per la conversione analogico digitale otteniamo una precisione maggiore.
- l’altra tecnica è quella di effettuare diverse letture ed eseguire una media dei valori letti  (è quella adottata nel nostro programma).

Iniziamo con l'agganciare il nostro sensore ad un valido supporto che ci consenta di valutare i valori di risposta in corrispondenza dei tre assi X, Y e Z. Montiamo il sensore in modo su un blocco o uno scatolino di materiale rigido come mostrato in figura. La dimensione non è importante, purché tutti i lati siano tra loro ad angolo retto.

Iniziamo con il posizionare il blocco su una superficie piana (ad esempio un tavolo robusto)  in modo che l'asse Y del sensore (serigrafato sul modulo) sia concorde con la forza di gravità.Valutiamo quindi l'asse y (verso concorde alla forza di gravità)

Leggiamo i valori. Avendo posto il range (min,max) dei valori in modo corretto otteniamo un output coerente (-g sull'asse Y  e zero su  tutti gli altri).

Valutiamo ora l'asse y (verso discorde alla forza di gravità) ribaltando il blocco

Adesso calibriamo l'asse Z (verso concorde alla forza di gravità)

e quindi l'asse Z (verso discorde alla forza di gravità)

Concludiamo con l'asse X (verso concorde alla forza di gravità)

e l'asse X (verso discorde alla forza di gravità)

Una volta che tutti e sei i lati sono stati campionati, i valori registrati nelle variabili p_xMin, p_xMax, p_yMin, p_yMax, p_zMin, p_zMax ad ogni pressione del pulsante di calibrazione rappresenteranno gli estremi di uscita reali per i valori +/- g lungo ogni asse. Riassumento abbiamo:

Codice Sorgente B

Obiettivo progetto: Costruire un progetto che consenta, sfruttando i movimenti di un accelerometro,  di simulare nel seguente file di Excel

il rollio (ROLL) e il beccheggio  (PITCH) di un modellino di aereoplano.

soluzione: Riutilizziamo lo stesso circuito del precedente esempio ma agganciamo al sensore un oggetto a forma di aereoplano.

Ecco il codice che ci permette di mandare gli opportuni comandi (angoli) al file di Excel per simulare i movimenti del nostro modellino di veivolo

// massimo scostamento per considerare una lettura cambiata
#define DELTA 3 
#define N_CAMPIONE 20
#define x_PIN A0  // Cavo Arancio
#define y_PIN A1  // Cavo Verde
#define z_PIN A2  // Cavo Giallo
#define Calibrate_PIN 2

#define MINSENSOR 1023
#define MAXSENSOR 0

const float PiGreco=3.141593;
// Imposto i valori di Massimo e Minimo ottenuti per ogni asse.
// In xMed, yMed e zMed metto le letture quando l'asse x,y e z risulta posta
// ad angolo retto rispetto alla forza di gravità (posizioni sul piano 
// dell'orizzonte))
int xMin=405;
int xMax=608;
int yMin=409;
int yMax=613;
int zMin=417;
int zMax=617;
int xMed=507;
int yMed=511;
int zMed=516;
    
// Variabili per registrare i valori minimi e massimi reali. 
// Li memorizzo solo per sfizio poichè questi risentono di accelerazioni
// esterne alla gravità
int r_xMin = MINSENSOR;
int r_xMax = MAXSENSOR;
int r_yMin = MINSENSOR;
int r_yMax = MAXSENSOR;
int r_zMin = MINSENSOR;
int r_zMax = MAXSENSOR;
// Variabili per valutare il cambio di valore
int p_x = -1;
int p_y = -1;
int p_z = -1;


void setup() 
{
  // Utilizzo la tensione 3.3v come riferimento
  analogReference(EXTERNAL);
  Serial.begin(57600);
  pinMode(Calibrate_PIN, INPUT);
  StampaConfigurazione();
}
 
void loop() 
{
  int buttonState = digitalRead(Calibrate_PIN);
  float theta, phi, xGradi, yGradi, zGradi;
  float argPhi, argTheta;
  boolean Cambiato=false;
  int x = LeggiSensoreAsse(x_PIN);
  int y = LeggiSensoreAsse(y_PIN);
  int z = LeggiSensoreAsse(z_PIN);
  AggiornaRange(x,y,z);  // valuto per mio interesse i range reali
  if (buttonState==HIGH) // e li mostro premendo il pushbutton
     StampaConfigurazione();
  // Considero cambiati i valori se la differenza supera
  // un certo valore DELTA
  Cambiato=Cambiato || (abs(p_x-x)>DELTA);
  Cambiato=Cambiato || (abs(p_y-y)>DELTA);
  Cambiato=Cambiato || (abs(p_z-z)>DELTA);
  if (Cambiato)
  { 
      xGradi = mapfloat(x, xMin, xMax, xMed, -90, 90);
      yGradi = mapfloat(y, yMin, yMax, yMed, -90, 90);
      zGradi = mapfloat(z, zMin, zMax, zMed, -90, 90);
      // Phi = angolo asse y e asse forza di gravità (ROLL)
      // Theta = angolo asse x e asse forza di gravità (PITCH)
      argTheta=sqrt(yGradi*yGradi+zGradi*zGradi);
      argPhi=sqrt(xGradi*xGradi+zGradi*zGradi);
      if (argTheta==0)
        theta=0;
      else
        theta=atan(xGradi/argTheta)*(180/PiGreco);
      if (argPhi==0)
        phi=0;
      else
        phi=atan(yGradi/argPhi)*(180/PiGreco);
      Serial.print("START;");
      Serial.print("X="+String(x)+";Y="+String(y)+";Z="+String(z)+";");        
      Serial.print("X^Z=");
      Serial.print(theta); // PITCH
      Serial.print(";Y^Z=");
      Serial.print(phi);   // ROLL
      Serial.print(";X-="+String(r_xMin)+";X+="+String(r_xMax)+";");        
      Serial.print("Y-="+String(r_yMin)+";Y+="+String(r_yMax)+";");        
      Serial.println("Z-="+String(r_zMin)+";Z+="+String(r_zMax)+";STOP;");
  }
  p_x = x;
  p_y = y;
  p_z = z; 
  delay(20);
}

// --------------------------------------------------------------------------- 
// Decodifica non lineare degli angoli poiche' considera anche il punto medio
// che corrisponde all'angolo 0 (orizzonte) per aumentare la precisione. Potevo
// utilizzare anche il tuning sugli angoli: 30, 45 e 60 gradi
// --------------------------------------------------------------------------- 
float mapfloat(long v, long in_min, long in_max, long in_med, long out_min, long out_max)
{
  float out_med=(out_max-out_min)/2+out_min;
  if (v < in_min) v=in_min; // Elimino i valori fuori dal range Min, Max
  if (v > in_max) v=in_max;
  if (v < in_med)
    return ((float)(v - in_min) * (float)(out_med - out_min) / (float)(in_med - in_min) + out_min);
  else
    return (float)(v - in_med) * (float)(out_max - out_med) / (float)(in_max - in_med) + out_med;
}

void StampaConfigurazione()
{
    delay(10);
    Serial.print("CONFIG;X-="+String(xMin)+";X+="+String(xMax)+";");        
    Serial.print("Y-="+String(yMin)+";Y+="+String(yMax)+";");        
    Serial.println("Z-="+String(zMin)+";Z+="+String(zMax)+";STOP;");
}

// ---------------------------------------------------------------------- 
// Effettuo più letture per ridurre il rumore
// Restituisco la media delle letture scartando la prima
// ---------------------------------------------------------------------- 
int LeggiSensoreAsse(int assePin)
{
  long Lettura = 0;
  analogRead(assePin); //Lettura a vuoto
  delay(1);
  for (int i = 0; i < N_CAMPIONE; i++)
  {
    Lettura += analogRead(assePin);
    delay(1);
  }
  return Lettura/N_CAMPIONE;
}

// ---------------------------------------------------------------------- 
// Aggiorna il range reale di X, Y e Z. Questo range non è affidabile
// poichè muovendo il sensore anche accuratamente si generano
// delle accelerazioni aggiuntive a quella gravitazionale che generano errori
// sulla stima finale
// ---------------------------------------------------------------------- 
void AggiornaRange(int x, int y, int z)
{
  if (x < r_xMin) r_xMin = x;
  if (x > r_xMax) r_xMax = x;
  if (y < r_yMin) r_yMin = y;
  if (y > r_yMax) r_yMax = y;
  if (z < r_zMin) r_zMin = z;
  if (z > r_zMax) r_zMax = z;
}

Sul serial monitor visualizzerà i seguenti risultati.