Accelerometro MMA8451Q, calcolo dell’orientamento ed accelerazioni in G sui 3 assi

Oggigiorno ogni dispositivo “mobile” come smartphone tablet etc, dispone di accelerometri interni che possono fornire informazioni riguardo all’orientamento del dispositivo oppure la forza G alla quale il dispositivo è sottosposto. Da questo ne derivano funzioni come “contapassi”, comandi attivati con lo “shake” l’orientamento dello schermo eccetera.

orientamento accelerometro

Ma prima di utilizzarlo è necessario capire esattamente come funziona. Immaginate di appoggiare su di un tavolo perfettamente piano (in bolla, come si dice dalle mie parti) il dispositivo. A questo punto esso, se la tavola è perfettamente ferma e piana, è soggetto alla sola forza di gravità, che va dall’alto verso il basso. Questa forza è un’accelerazione costante, di 9,81m/s verso il basso, il che equivale ad 1G: il dispositivo a riposo quindi, indicherà +1G lungo l’asse che punta verso il centro della Terra. Se indica +1G sull’asse Z, significa che è steso, mentre se indica +1G sull’asse X o sull’asse Y, vuol dire che è stato appoggiato sul tavolo su uno o l’altro lato. Chiaramente vanno calcolati anche i punti intermedi: ad esempio, se tenessi fermo il dispositivo su di uno spigolo ma perfettamente verticale, questo indicherebbe 0,70G su un asse e 0,70G sull’altro! (si, non va dimezzato ma calcolato con il seno o coseno del vettore) Se invece lo mantenessi sullo spigolo inclinandolo anche in profondità, parte dell’accelerazione sarebbe letta anche dall’asse Z (caso non illustrato in figura).

zero-gravity-flight

Tralasciando l’inclinazione, se tenendo ben stretto il dispositivo lo spostate velocemente verso il basso, assecondando la forza di gravità, per un breve periodo probabilmente indicherà 0G. Il dispositivo non rileva più l’accelerazione dovuta alla forza di gravità perché la state assecondando, quindi si trova effettivamente in assenza di peso, un pò come i voli a gravità zero organizzati dalla Nasa, dove un aereo viene appositamente pilotato in picchiata per ottenere il medesimo effetto.

Ma come fa un dispositivo miniaturizzzato di questo tipo a rilevare le accelerazioni sui vari assi? Un ingrandimento di un estratto dal documento tecnico può chiarirci le idee:

chip accelerometer details

In poche parole il microchip al suo interno dispone di microscopiche lamelle in grado di flettere quando il dispositivo viene sottoposto ad un’accelerazione, anche quella di gravità: immaginatele “pendere verso il basso”. Muovendosi, si allontanano o si avvicinando alla loro superficie di riferimento, variando così la capacità elettrica tra le due superfici, esattamente come avviene in un normale condensatore variabile meccanico. Replicando lo stesso meccanismo per tutti e 3 gli assi che descrivono lo spazio tridimensionale, possiamo ottenere tutte le informazioni che ci servono per ricavare la direzione e l’intensità del vettore della forza che sta agendo sul dispositivo.

12756-00

Prendiamo ad esempio questa “breakout board” con il piccolissimo accelerometro smd montato a bordo. I pin di uscita, comodamente distribuiti con passo 2.54, sono abbastanza chiari: GND, +3.3V, SCL e SDA per l’interfaccia I2C. Una volta collegati i rispettivi pin alla vostra board di controllo, ed appositamente settata l’interfaccia I2C a seconda del vostro editor, possiamo iniziare a comunicare con il dispositivo!

Prima di tutto, nel mio caso usando CodeWarrior e Processor Expert, ho settato la comunicazione I2C:

Immagine

Notare che ho settato la frequenza di SCL sotto i 100khz, nonostante sia possibile salire fino a 400khz, per non aver problemi di sorta.

Successivamente ho creato due semplici funzioni per inviare e ricevere byte al dispositivo (l’indirizzo standard è 0x77) utilizzando la funzione interrupt del driver I2C, il quale resetta un flag terminata la rispettiva operazione di invio o ricezione dati:

// due interrupt utilizzati per confermare
// quando è possibile terminare la comunicazione
void I2C2_OnReceiveData(void)
{
	DataReceivedFlg = TRUE; /* Set DataReceivedFlg flag */
}

void I2C2_OnTransmitData(void)
{
	 DataTransmittedFlg = TRUE; /* Set DataTransmittedFlg flag */
}


// leggo uno o più registri da device
void I2C_ReadReg(char regaddr, char *data, short dataSize) {
  char res;
  word ret;
 
  // setto il puntatore al registro desiderato
  res = I2C2_SendBlock(&regaddr, 1U, &ret);
  
  // attendo fino al termine della trasmissione dati
  while (!DataTransmittedFlg) {} /* Wait until data is sent */
  DataTransmittedFlg = FALSE;
 
  // leggo il numero di byte richiesti
  res = I2C2_RecvBlock(data, dataSize, &ret);
  
  // attendo fino al termine della trasmissione
  while (!DataReceivedFlg) {} /* Wait until data is received received */
  DataReceivedFlg = FALSE;
  
  // termino la comunicazione  
  I2C2_SendStop();
}

// scrivo un byte in un registro
void I2C_WriteReg(char regaddr, char val) {
  char buf[2], res;
  word ret;

  buf[0] = regaddr; // indirizzo del registro da scrivere
  buf[1] = val;     // valore da scrivere nel registro
  res = I2C2_SendBlock(&buf, 2U, &ret); // trasmetto i due byte
  
  // attendo il termine della trasmissione
  while (!DataTransmittedFlg) {} 
  DataTransmittedFlg = FALSE;
  
  // chiudo la comunicazione
  I2C2_SendStop(); 
}

Ora che abbiamo semplificato il tutto creando due funzioni per comunicare con il dispositivo, possiamo inizializzarlo e per far ciò ho analizzato il datasheet ricavando quanto segue:

// setto ad 1 il bit di reset
I2C_WriteReg(0x2B, 0x40); 

// attendo finché il reset non è completato
while (reg_data & 0x40) 
{
	I2C_ReadReg(0x2B, &reg_data, 1);
}

// disablito fast-mode per ottenere risultato a 14bit
I2C_WriteReg(0x2A, 0x02);
// setto il range a 4g
I2C_WriteReg(0x0E, 0x02;
// alta risoluzione
I2C_WriteReg(0x2B, 0x02);
// attiva l'accelerometro (riquadro giallo nella tabella registri)
I2C_WriteReg(0x2A, 0x01);

Il dispositivo è stato inizializzato con il range +-4G, quindi, sempre da datasheet verifico che devo dividere il valore grezzo (RAW) letto per 2048:

accelerometer range

// definisco alcune variabili
#include "math.h" //necessario per la funzione abs()  
float g_x, g_y, g_z, g_dir;

//leggo i 6 byte con le informazioni per i 3 assi
I2C_ReadReg(0x01, &xyz, 6);

// unisco gli 8bit del primo carattere
// con i successivi 6bit del secondo carattere
g_x= (((xyz[0]<<8) | xyz[1])>>2)/2048;
g_y= (((xyz[2]<<8) | xyz[3])>>2)/2048;
g_z= (((xyz[4]<<8) | xyz[5])>>2)/2048;

// con questa serie di condizioni stimo
// un possibile orientamento del dispositivo
if ((abs(g_z) < 5) && (g_x > 5) && (abs(g_y) < 4)) { g_dir = 1; } 
if ((abs(g_z) < 5) && (g_x < -5) && (abs(g_y) < 4)) { g_dir = 2; }
if ((abs(g_z) < 5) && (g_y > 5) && (abs(g_x) < 4)) { g_dir = 3; } 
if ((abs(g_z) < 5) && (g_y < -5) && (abs(g_x) < 4)) { g_dir = 4; }

A questo punto in g_x, g_y, g_z trovo il valore numerico float dell’accelerazione per asse in G, esempio “1.0”. Mentre in g_dir trovo la rotazione dell’orientamento consigliata per un eventuale schermo, i cui quattro valori corrispondono a 0, 90, 180, 270 gradi. Questa ultima serie di condizioni l’ho estratta da un documento tecnico.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *