Dopo circa 30 giorni ho finalmente ricevuto il pacco dalla Cina, acquisto di pochi dollari su eBay, con i due moduli ordinati:
- 10DOF Module 9-axis Attitude Indicator L3G4200D ADXL345 HMC5883L BMP085 Module
- GPS U-blox NEO-6M Module
Questa sera ho deciso di giocare un po’ con il 10DOF, un piccolo moduletto molto compatto con a bordo:
- L3G4200D – Giroscopio 3 assi
- ADXL345 – Accelerometro tre assi
- HMC5883L – Compasso digitale 3 assi
- BMP085 – Sensore barometrico e termometro
Il tutto accessibile mediante il medesimo bus I2C, ai seguenti indirizzi:
I2C Address 0x1E è il HMC5883L
I2C Address 0×53 è il ADXL345
I2C Address 0×69 è il L3G4200D
I2C Address 0×77 è il BMP085
Bus I2C
Per iniziare ho deciso di provare a collegare il modulo al mio BeagleBone Black, via bus I2C, e provare ad interrogare il sensore barometrico/termometro BMP085.
ConnessioniPer prima cosa, i cablaggi. Cercando lo schema “pinouts” della BeagleBoard Black, scopro che ci sono ben due bus I2C disponibili, sul bus P9 (posto a sinistra, con il connettore RJ45 in alto):
- I2C BUS 1 ai pin 17 (SCL) e 18 (SDA)
- I2C BUS 2 ai pin 19 (SCL) e 20 (SDA)
[alert style=”grey”] Informazioni più dettagliate sul bus I2C nella Beagle Board Bone al sito http://datko.net/2013/11/03/bbb_i2c/ [/alert]
Considerando che il modulo può essere alimentato direttamente dai 3.3V forniti dalla BeagleBoard, ho preso la mia bella breadboard ed ho collegato i 4 fili richiesti per iniziare a giocare:
- PIN 2 = GND
- PIN 4 = Vcc 3V
- PIN 17 = SCL
- PIN 18 = SDA
Gli altri piedini del 10 DOF non sono utilizzati. Accendiamo ovviamente la BBB e, da root, abilitiamo il bus I2C con il seguente comando:
echo BB-I2C1 > /sys/devices/bone_capemgr.9/slots
A questo punto possiamo iniziare ad esplorare il fantastico bus i2c con il comando ‘i2cdetect’:
root@arm:~/C# i2cdetect -l i2c-0 i2c OMAP I2C adapter I2C adapter i2c-1 i2c OMAP I2C adapter I2C adapter i2c-2 i2c OMAP I2C adapter I2C adapter root@arm:~/C#
In questo caso ci interessa il bus 2 (i2c-2):
root@arm:~/C# i2cdetect -r 2 WARNING! This program can confuse your I2C bus, cause data loss and worse! I will probe file /dev/i2c-2 using read byte commands. I will probe address range 0x03-0x77. Continue? [Y/n] 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 1e -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- 53 -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- 69 -- -- -- -- -- -- 70: -- -- -- -- -- -- -- 77 root@arm:~/C#
Come vedete, il sistema identifica con successo i dispotivi agli indirizzi 0x1e, 0x53,0x69 e 0x77 (descritti prima, all’inizio dell’articolo).
[alert style=”gray”] A questa pagina trovate molta documentazione in merito alle implementazioni su Linux delll’interfaccia I2C: https://www.kernel.org/doc/Documentation/i2c/dev-interface[/alert]
BMP085
Il barometro/termometro digitale della Bosch BMP085 si trova all’indirizzo 0x77. Come si può leggere direttamente sul datasheet (BMP085_DataSheet_Rev.1.0_01July2008), come prima cosa è necessario prelevare i parametri di calibrazione, contenuti nei registri da 0xAA a 0xBE. Per farlo:
void BMP085_getCalibration() { ac1 = I2C_readWord(BMP085_I2C_ADDR,0xAA); ac2 = I2C_readWord(BMP085_I2C_ADDR,0xAC); ac3 = I2C_readWord(BMP085_I2C_ADDR,0xAE); ac4 = I2C_readWord(BMP085_I2C_ADDR,0xB0); ac5 = I2C_readWord(BMP085_I2C_ADDR,0xB2); ac6 = I2C_readWord(BMP085_I2C_ADDR,0xB4); b1 = I2C_readWord(BMP085_I2C_ADDR,0xB6); b2 = I2C_readWord(BMP085_I2C_ADDR,0xB8); mb = I2C_readWord(BMP085_I2C_ADDR,0xBA); mc = I2C_readWord(BMP085_I2C_ADDR,0xBC); md = I2C_readWord(BMP085_I2C_ADDR,0xBE); }
Per prelevare i dati relativi alla temperatura, si invia il comando 0x3E sul registro 0xF4, si attede almeno 4,5 msec e poi si legge il valore sui registri 0xF6 (MSB) e 0xF7 (LSB):
float BMP085_readTemp() { long x1, x2, b5; int res; float temp; I2C_writeWord(BMP085_I2C_ADDR,0xF4,0x2E); usleep(45000); // Wait at least 4,5 ms res = I2C_readWord(BMP085_I2C_ADDR,0xF6); x1 = ((long)res - (long)ac6)*(long)ac5 >> 15; x2 = ((long)mc << 11)/(x1 + md); b5 = x1 + x2; temp = ((b5 + 8) >> 4); // Temp in 0.1°C temp = temp / 10; printf("Temperature: %4.2f °C\n",temp); return temp; }
Il dato presente nel registro deve essere poi elaborato secondo l’algoritmo indicato nel datasheet, ottenendo la temperatura in valori di 0.1°C.
Per il valore della pressione atmosferica, si scrive 0x34 nel registro 0xF4, si attende 5 msecs e si legge il contenuto dei registri 0xF6 (MSB),0xF7 (LSB),0xF8 (XLSB):
float BMP085_readPress() { unsigned long res; unsigned char msb,lsb,xlsb; long x1, x2, x3, b3, b6, p; unsigned long b4, b7; I2C_writeWord(BMP085_I2C_ADDR,0xF4,0x34); usleep(5000); // Wait at least 5 ms msb = I2C_readWord(BMP085_I2C_ADDR,0xF6); lsb = I2C_readWord(BMP085_I2C_ADDR,0xF7); xlsb = I2C_readWord(BMP085_I2C_ADDR,0xF8); res = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8); b6 = b5 - 4000; x1 = (b2 * (b6 * b6)>>12)>>11; x2 = (ac2 * b6)>>11; x3 = x1 + x2; b3 = ((((long)ac1)*4 + x3) + 2)>>2; x1 = (ac3 * b6)>>13; x2 = (b1 * ((b6 * b6)>>12))>>16; x3 = ((x1 + x2) + 2)>>2; b4 = (ac4 * (unsigned long)(x3 + 32768))>>15; b7 = ((unsigned long)(res - b3) * (50000)); if (b7 < 0x80000000) p = (b7<<1)/b4; else p = (b7/b4)<<1; x1 = (p>>8) * (p>>8); x1 = (x1 * 3038)>>16; x2 = (-7357 * p)>>16; p += (x1 + x2 + 3791)>>4; printf("Pressure: %d Pa\n",p); return p; }
L’implementazione in C degli algoritmi l’ho trovata a questa pagina web: http://bildr.org/2011/06/bmp085-arduino/ e riporta anche la procedura per il calcolo dell’altitudine, partendo dal valore della pressione atmosferica.
HMC5883L
HMC5883L’HMC5883L è un compasso digitale prodotto dalla Honeywell, con un grado di precisione da 1 a 2°. Le specifiche relative sono sul datasheet (HMC5883L), dove ci sono anche tutte le istruzioni relative agli algoritmi per il recupero delle coordinate spaziali.
Un buon tutorial, dal quale ho preso spunto per il codice, lo trovate a questa pagina web: https://www.sparkfun.com/tutorials/301
Come prima fase, si procede con l’inizializzazione del dispositivo, che si trova all’indirizzo 0x1E del bus I2C:
void HMC5883_init() { I2C_writeWord(HMC5883_I2C_ADDR,0x02,0x00); }
Praticamente si attiva il modo di lettura continuo. Da qui, si procede con le richieste delle coordinate spaziali:
void HMC5883_getSpatialCoords() { int x,y,z; I2C_setAddress(HMC5883_I2C_ADDR); i2c_smbus_write_byte_data(i2cHandle, 0x03, 0x00); x = I2C_readWord(HMC5883_I2C_ADDR,0x03); z = I2C_readWord(HMC5883_I2C_ADDR,0x05); y = I2C_readWord(HMC5883_I2C_ADDR,0x07); printf("X:%d Y:%d Z:%d\n",x,y,z); }
L3G4200D
L3G4200DL’L3G4200D è un giroscopio su 3 assi prodotto dalla SGmicroelectronics che ci permette di conoscere la posizione nello spazio del nostro modulo.
Nel relativo Datasheet (L3G4200D) trovate tutte le specifiche tecniche e le informazioni per l’uso dello stesso. Ho comunque trovato a questa pagina web: https://www.sparkfun.com/products/10612 del codice di esempio per l’uso.
In particolare, durante la fase di inizializzazione, possiamo verificare la corretta comunicazione con il modulo chiedendo il contenuto del registro 0x0F, che deve corrispondere a 0x3D:
// 0: 250 dps - 1: 500 dps - 2: 2000 dps int L3G4200D_init(char fullScale) { if(I2C_readByte(L3G4200D_I2C_ADDR,0x0F)!=0xD3) { printf("ERROR communicating with L3D4200D !\n"); return -1; } // Enable x, y, z and turn off power down: I2C_writeByte(L3G4200D_I2C_ADDR, 0x20, 0b00001111); // If you'd like to adjust/use the HPF, you can edit the line below to configure CTRL_REG2: I2C_writeByte(L3G4200D_I2C_ADDR, 0x21, 0b00000000); // Configure CTRL_REG3 to generate data ready interrupt on INT2 // No interrupts used on INT1, if you'd like to configure INT1 // or INT2 otherwise, consult the datasheet: I2C_writeByte(L3G4200D_I2C_ADDR, 0x22, 0b00001000); // CTRL_REG4 controls the full-scale range, among other things: fullScale &= 0x03; I2C_writeByte(L3G4200D_I2C_ADDR, 0x23, fullScale<<4); // CTRL_REG5 controls high-pass filtering of outputs, use it if you'd like: I2C_writeByte(L3G4200D_I2C_ADDR, 0x24, 0b00000000); }
Il parametro “fullScale” indica quale velocità di acquisizione deve essere impostata sul giroscopio (dps = data per second). A questo punto non ci resta che prelevare i dati relativi alla posizione:
void L3G4200D_getGyroValues() { int x,y,z; x = (I2C_readByte(L3G4200D_I2C_ADDR, 0x29)&0xFF)<<8; // MSB x |= (I2C_readByte(L3G4200D_I2C_ADDR, 0x28)&0xFF); // LSB y = (I2C_readByte(L3G4200D_I2C_ADDR, 0x2B)&0xFF)<<8; y |= (I2C_readByte(L3G4200D_I2C_ADDR, 0x2A)&0xFF); z = (I2C_readByte(L3G4200D_I2C_ADDR, 0x2D)&0xFF)<<8; z |= (I2C_readByte(L3G4200D_I2C_ADDR, 0x2C)&0xFF); printf("L2D4200D = X:%d Y:%d Z:%d\n",x,y,z); }
ADXL345
ADXL345
Il chip ADXL345 è un accelerometro statico prodotto dalla Analog Devices. Permette di misurare il grado di accelerazione sui tre assi X,Y,Z, fino ad un massimo di 16 g (16 volte la normale forza di gravità, 9,8 m/sec²).
Le specifiche e funzioni le trovate sul relativo datasheet (ADXL345) mentre esempi per il codice, anche se per Arduino, l’ho trovati a questa pagina web: https://github.com/jenschr/Arduino-libraries/tree/master/ADXL345
Da qui sono partito con lo sviluppo delle mie routines, ricordando che l’ADXL345 si trova all’indirizzo 0x53 del bus I2C:
void ADXL345_init() { // Set +/- 4G range by writing the value 0x01 to the DATA_FORMAT register. I2C_writeByte(ADXL345_I2C_ADDR,0x31,0x01); // Put the ADXL345 into Measurement Mode by writing 0x08 to the POWER_CTL register. I2C_writeByte(ADXL345_I2C_ADDR,0x2D,0x08); }
A questo punto non ci rimane altro che leggere i valori dell’accelerazione nei relativi registri:
void ADXL345_readAccel() { int x,y,z; // each axis reading comes in 10 bit resolution, ie 2 bytes. LSB first, MSB next x = (I2C_readByte(ADXL345_I2C_ADDR, 0x33)&0xFF)<<8; // MSB x |= (I2C_readByte(ADXL345_I2C_ADDR, 0x32)&0xFF); // LSB y = (I2C_readByte(ADXL345_I2C_ADDR, 0x35)&0xFF)<<8; // MSB y |= (I2C_readByte(ADXL345_I2C_ADDR, 0x34)&0xFF); // LSB z = (I2C_readByte(ADXL345_I2C_ADDR, 0x37)&0xFF)<<8; // MSB z |= (I2C_readByte(ADXL345_I2C_ADDR, 0x36)&0xFF); // LSB printf("ADXL345 = X:%d Y:%d Z:%d\n",x,y,z); }
The code
Senza alcuna pretesa di eleganza, ho scritto un veloce programmino da compilare direttamente sulla BBB, che potete usare come base per i vostri esperimenti sul bus i2c e che ho già utilizzato per gli spezzoni di codice in questo post.
Potete scaricarlo liberamente da questo link: dof10_i2c.tar.gz
Scompattate il tar.gz (“tar xzf [filename]“) compilate il sorgente i2c.c con un semplice “make”. Non dovrebbe darvi alcun errore (speriamo !).
[alert style=”yellow”] Prima di poter compilare il programma dovete installare sulla BBB le build-essential e la libreria di sviluppo libi2c-dev[/alert]
Vai, le sperimentazioni sono iniziate: buon divertimento !