Playing with MPU-6050
Oggi ho intenzione di giocare un po con un sensore di accelerazione.
Facendo un po di ricerche, mi ha incuriosito il 6050 che integra in un solo chip un accelerometro, un giroscopio e un sensore di temperatura.
Il datasheet riporta:
- Digital-output X-, Y-, and Z-Axis angular rate sensors (gyroscopes) with a user-programmable fullscale range of ±250, ±500, ±1000, and ±2000°/sec
- External sync signal connected to the FSYNC pin supports image, video and GPS synchronization
- Integrated 16-bit ADCs enable simultaneous sampling of gyros
- Enhanced bias and sensitivity temperature stability reduces the need for user calibration
- Improved low-frequency noise performance
- Digitally-programmable low-pass filter
- Gyroscope operating current: 3.6mA
- Standby current: 5µA
- Factory calibrated sensitivity scale factor
- User self-test
Accelerometer Features - The triple-axis MEMS accelerometer in MPU-60X0 includes a wide range of features:
- Digital-output triple-axis accelerometer with a programmable full scale range of ±2g, ±4g, ±8g and ±16g
- Integrated 16-bit ADCs enable simultaneous sampling of accelerometers while requiring no external multiplexer
- Accelerometer normal operating current: 500µA
- Low power accelerometer mode current: 10µA at 1.25Hz, 20µA at 5Hz, 60µA at 20Hz, 110µA at 40Hz
- Orientation detection and signaling
- Tap detection
- User-programmable interrupts
- High-G interrupt
- User self-test
Additional Features The MPU-60X0 includes the following additional features:
- 9-Axis MotionFusion by the on-chip Digital Motion Processor (DMP)
- Auxiliary master I2C bus for reading data from external sensors (e.g., magnetometer)
- 3.9mA operating current when all 6 motion sensing axes and the DMP are enabled
- VDD supply voltage range of 2.375V-3.46V
- Flexible VLOGIC reference voltage supports multiple I2C interface voltages (MPU-6050 only)
- Smallest and thinnest QFN package for portable devices: 4x4x0.9mm
- Minimal cross-axis sensitivity between the accelerometer and gyroscope axes
- 1024 byte FIFO buffer reduces power consumption by allowing host processor to read the data in bursts and then go into a low-power mode as the MPU collects more data Digital-output temperature sensor
- User-programmable digital filters for gyroscope, accelerometer, and temp sensor
- 10,000 g shock tolerant 400kHz Fast Mode I2C for communicating with all registers
- 1MHz SPI serial interface for communicating with all registers (MPU-6000 only)
- 20MHz SPI serial interface for reading sensor and interrupt registers (MPU-6000 only)
MotionProcessing:
- Internal Digital Motion Processing™ (DMP™) engine supports 3D MotionProcessing and gesture recognition algorithms
- The MPU-60X0 collects gyroscope and accelerometer data while synchronizing data sampling at a user defined rate. The total dataset obtained by the MPU-60X0 includes 3-Axis gyroscope data, 3Axis accelerometer data, and temperature data. The MPU’s calculated output to the system processor can also include heading data from a digital 3-axis third party magnetometer.
- The FIFO buffers the complete data set, reducing timing requirements on the system processor by allowing the processor burst read the FIFO data. After burst reading the FIFO data, the system processor can save power by entering a low-power sleep mode while the MPU collects more data.
- Programmable interrupt supports features such as gesture recognition, panning, zooming, scrolling, tap detection, and shake detection
- Digitally-programmable low-pass filters
- Low-power pedometer functionality allows the host processor to sleep while the DMP maintains the step count.
Clocking:
- On-chip timing generator ±1% frequency variation over full temperature range
- Optional external clock inputs of 32.768kHz or 19.2MHz
Per fare le mie prove ho acquistato su Amazon ( LINK ) il seguente modulino (Gy-521) che integra il sensore e componenti "di contorno" per agevolare l' interconnessione del modulo senza circuteria esterna:
In particolare tale scheda integra:
- Un regolatore di tensione da 5 a 3.3V
- Resistore di pull-up sulle linee SCL e SCA
- Resistore di pull-down sul pin AD0 del sensore interno
- Led per segnalazione corretta alimentazione
Il modulo MPU-6050 e Arduino Uno comunicheranno tramite bus I2C.
Le modalità di funzionamento del modulo sono 2:
- Sincrona - I dati di accelerazione e rotazione vengono forniti su richiesta (tramite il bus I2C). Praticamente sarà l' Arduino a "richiedere" i dati quando sarà necessario
- DPM (asincrona) è l'acronimo di Digital Motion Process. In questo caso i dati vendono inviati quando il sensore lo riterrà "opportuno". Il processore, inoltre riesce a rilevare anche delle gesture più complesse del semplice movimento come zoom, scorrimento o scuotimento/vibrazioni.
In giro per la rete esistono una marea di librerie per utilizzare al meglio l'una o l'altra modalità, ma siccome il mio scopo è capire come funziona questo piccolo sensore, cercherò di non utilizzare delle librerie già scritte da altri.
Piedinatura della Gy-521
- VCC - Positivo alimentazione 5V
- GND - Negativo alimentazione
- SCL - Serial Clock (usato per interfaccia I2C)
- SDA - Serial Data (usato per interfaccia I2C)
- XDA - Serial Data (usato per collegamento a sensori esterni)
- XCL - Serial Clock (usato per collegamento a sensori esterni)
- AD0 - Indirizzo slave I2C. Utilizzato in caso di più sensori 6050. Consente di cambiare l'indirizzo I2C tra 0x68 e 0x69
- INT - Indica quando il dato è disponibile per la lettura
Schema elettrico
Implementati i pochi collegamenti il primo step è capire se sto procedendo in modo corretto.
Decido quindi di fare una scansione delle periferiche I2C caricando sull' Arduino nano il seguente codice:
#include <Wire.h>
void setup()
{
Wire.begin();
Serial.begin(9600);
Serial.println("\nI2C Scanner");
}
void loop()
{
byte error, address;
int nDevices;
Serial.println("Scanning...");
nDevices = 0;
for(address = 1; address < 127; address++ )
{
// The i2c_scanner uses the return value of
// the Write.endTransmisstion to see if
// a device did acknowledge to the address.
Wire.beginTransmission(address);
error = Wire.endTransmission();
i f (error == 0)
{
Serial.print("I2C device found at address 0x");
i f (address<16)
Serial.print("0");
Serial.print(address,HEX);
Serial.println(" !");
nDevices++;
}
else i f(error==4)
{
Serial.print("Unknow error at address 0x");
i f (address<16)
Serial.print("0");
Serial.println(address,HEX);
}
}
i f (nDevices == 0)
Serial.println("No I2C devices found\n");
else
Serial.println("done\n");
delay(5000); // wait 5 seconds for next scan
}
Ecco il risultato:
I2C Scanner
Scanning...
I2C device found at address 0x68 !
done
Adesso non mi resta che passare alla parte software vera e propria.
Mi pongo come primo obiettivo quello di catturare i dati di accelerazione, rotazione. Per ora lascio ignoro la temperatura. Li leggerò tramite la porta seriale e li rappresenterò su un grafico.
Setup del modulo
Come prima cosa è necessario eseguire la fase di setup del modulo (impostazioni del modulo, giroscopio e accelerometro).
Per il modulo, è necessario definire se si intende utilizzare la funzionalità di "Sleep", il tipo di source per il clock e l'abilitazione del sensore di temperatura. Quando detto è possibile definirlo dal registro 6B (pagina 40 del datasheet):
1) Disabilito la modalità di sleep; "Cycle" identifica la modalità di "risveglio" del modulo, ma avendo disabilitato lo sleep mode, anche Cycle lo setto a zero.
2) Disabilito il sensore di temperatura
3) Per il clock utilizzo l'oscillatore interno di 8MHZ in accordo alla seguente tabella:
Il codice relativo è:
Wire.beginTransmission(MPU); // Inizializzo la comunicazione I2C
Wire.write(0x6B); // Registro 6B
Wire.write(0x00); // setto il registro 6B a 0
Wire.endTransmission(true); // fine comunicazione
Per giroscopio e accelerometro, dal datasheet leggo le seguenti righe:
Quindi per il giroscopio, registro 1B (pagina 14):
I bit 7,6 e 5 per ora li ignoro. I bit 4 e 3 invece li setto secondo la seguente tabella a 2:
FS_SEL | Range fondo scala |
0 | ± 250 °/s |
1 | ± 500 °/s |
2 | ± 1000 °/s |
3 | ± 2000 °/s |
Il relativo codice è:
Wire.beginTransmission(MPU);
Wire.write(0x1B); // Registro GYRO_CONFIG
Wire.write(0x10); // Setto il registro a 10 esadecimale, 00010000 binario (1000deg/s full scale)
Wire.endTransmission(true);
Per l'accelerometro, registro 1C, imposto un fondo scala di ±8g (pagina 15):
I bit 7,6 e 5 per ora li ignoro. I bit 4 e 3 invece li setto secondo la seguente tabella a 2:
FS_SEL | Range fondo scala |
0 | ± 2g |
1 | ± 4g |
2 | ± 8g |
3 | ± 16g |
Il relativo codice è:
Wire.beginTransmission(MPU);
Wire.write(0x1C); // Registro ACCEL_CONFIG
Wire.write(0x10); // Setto il registro a 10 esadecimale, 00010000 binario (+/- 8g full scale)
Wire.endTransmission(true);
Riassumendo, il setup() diventa:
void setup() {
Serial.begin(19200);
Wire.begin();
Wire.beginTransmission(MPU); // Inizializzo la comunicazione I2C
Wire.write(0x6B); // Registro 6B
Wire.write(0x00); // setto il registro 6B a 0
Wire.endTransmission(true); // fine comunicazione
Wire.beginTransmission(MPU);
Wire.write(0x1B); // Registro GYRO_CONFIG
Wire.write(0x10); // Setto il registro a 10 esadecimale, 00010000 binario (1000deg/s full scale)
Wire.endTransmission(true);
Wire.beginTransmission(MPU);
Wire.write(0x1C); // Registro ACCEL_CONFIG
Wire.write(0x10); // Setto il registro a 10 esadecimale, 00010000 binario (+/- 8g full scale)
Wire.endTransmission(true);
}
La prima misurazione
Per eseguire la prima misurazione dell'accelerazione è necessario leggere 6 registri. 2 per ogni asse. Idem per il giroscopio.
Per fortuna i registri sono contigui.
Dal datasheet, alla pagina 29 leggo:
A pagina 30:
A pagina 31:
Il codice è il seguente:
#include <Wire.h>
const int MPU = 0x68; // Indirizzo I2C MPU6050
float AccX, AccY, AccZ;
float GyroX, GyroY, GyroZ;
void setup() {
Serial.begin(19200);
Wire.begin();
Wire.beginTransmission(MPU); // Inizializzo la comunicazione I2C
Wire.write(0x6B); // Registro 6B
Wire.write(0x00); // setto il registro 6B a 0
Wire.endTransmission(true); // fine comunicazione
Wire.beginTransmission(MPU);
Wire.write(0x1B); // Registro GYRO_CONFIG
Wire.write(0x10); // Setto il registro a 10 esadecimale, 00010000 binario (1000deg/s full scale)
Wire.endTransmission(true);
Wire.beginTransmission(MPU);
Wire.write(0x1C); // Registro ACCEL_CONFIG
Wire.write(0x10); // Setto il registro a 10 esadecimale, 00010000 binario (+/- 8g full scale)
Wire.endTransmission(true);
}
void loop() {
Wire.beginTransmission(MPU);
Wire.write(0x3B); // Parto dal registr 3B (ACCEL_XOUT_H)
Wire.endTransmission(false);
Wire.requestFrom(MPU, 6, true);
AccX = Wire.read() << 8 | Wire.read(); // asse X
AccY = Wire.read() << 8 | Wire.read(); // asse Y
AccZ = Wire.read() << 8 | Wire.read(); // asse Z
Wire.beginTransmission(MPU);
Wire.write(0x43); // Parto dal registro 0x43
Wire.endTransmission(false);
Wire.requestFrom(MPU, 6, true);
GyroX = Wire.read() << 8 | Wire.read(); // asse X
GyroY = Wire.read() << 8 | Wire.read(); // asse Y
GyroZ = Wire.read() << 8 | Wire.read(); // asse Z
Serial.print(AccX);
Serial.print(";");
Serial.print(AccY);
Serial.print(";");
Serial.print(AccZ);
Serial.print(";");
Serial.print(GyroX);
Serial.print(";");
Serial.print(GyroY);
Serial.print(";");
Serial.print(GyroZ);
Serial.println("");
delay (300);
}
Posizionando il sensore in posizione orizzontale, su un tavolo stabile, in ambiente silenzioso e senza traffico, l'output è in seguente:
La prima cosa che salta all'occhio è che le rilevazioni non sono stabili e spesso distanti dallo zero.
Interpretazione dei numeri
Accelerazione
Avendo impostato il fondo scala a 8g, osservando la tabella:
è necessario dividere l'accelerazione rilevata per 4096.
Rotazione
Avendo impostato un fondo scala di ± 1000 °/s, osservando la tabella:
è necessario dividere per 32.8.
NOTA: forse è scontato, ma è bene ribadire che i valori forniti dal giroscopio non rappresentano la rotazione in valore assoluto, ma l'accelerazione angolare!
Accelerazione X,Y:
Rotazione
Conclusioni
Il giroscopio e l'accelerometro sono entrambi molto sensibili. Inoltre le misurazioni sono affette da rumore.
Il giroscopio va alla deriva con il tempo; l'accelerometro si stabilizza con il tempo.
Dalle prove che ho eseguito, l'applicazione di un filtro di Kalman è un ottimo rimedio a quando detto.