mardi 21 janvier 2014

Autour du Raspberry Pi



Le Raspberry Pi comporte un header 13x2 pins donnant accès à un bus I2C et il est possible de trouver, sur eBay, un tas de petites cartes comportant des capteurs, compatibles avec ce bus, pour quelques euros. On peut ainsi mesurer l'accélération en 3D, la giration, le champ magnétique, la température, l'humidité (pas encore trouvé en I2C), etc...

Par exemple, la micro-carte GY-271 mesure le champ magnétique 3D avec une résolution de l'ordre du milli-Gauss.


Elle utilise le composant HMC5883L de Honeywell. Il fait 3x3 mm² et est épais de moins d'un millimètre!

Avant toute chose, il faut savoir que la possibilité de développement d'applications utilisant le bus I2C n'est pas activée par défaut. Il y a quelques opérations à effectuer : il faut installer les packages i2c-tools et libi2c-dev, ajouter deux lignes dans /etc/modules, commenter une ligne dans /etc/modprobe.d/raspi-blacklist.conf. Voir 'Guide to interfacing a Gyro and Accelerometer with a Raspberry Pi'.

La connexion physique du module est très simple : il suffit de repérer les quatre headers utiles sur le Raspberry Pi : 3.3V, Ground, sda, scl et leur correspondant sur la micro carte. Avec des fils 'femelle'-'femelle', c'est trivial (là, je n'avais que des mâle-femelle, j'ai dû chipoter un peu).

Une fois connecté, on peut faire :
xofc@raspberrypi ~ $ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 1e -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --                         
On constate que le HMC5883L est bien vu à l'adresse 0x1e. Le code minimal, en 'C' pour accéder au composant ressemble à quelque chose comme
#include <unistd.h>  
#include <errno.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <linux/i2c-dev.h>  
#include <sys/ioctl.h>  
#include <fcntl.h>  
  
const char *I2C_BUS_DEV = "/dev/i2c-1";

void crash(const char *str)
    {
    perror(str);
    exit(-1);
    }
int i2c_rw(int fd, int reg)
    {
    int    x;

    if ((x = i2c_smbus_read_word_data(fd, reg)) < 0)
        return(-1);
    return(((x&0xff)<<8) + ((x&0xff00)>>8));
    }
int main()  
    {  
    int    fd;
    int    res;
    int    ira, irb, irc; /* Identification registers : 'H','4','3' */
    int    mag_x, mag_y, mag_z;
  
    if ((fd = open(I2C_BUS_DEV, O_RDWR)) < 0)
        crash(I2C_BUS_DEV);
  
    if (ioctl(fd, I2C_SLAVE, 0x1e) < 0)  /* 0x1e = hmc5883l address */
        crash("I2C_SLAVE 0x1e hmc5883l");  

    if ((ira = i2c_smbus_read_byte_data(fd, 10)) < 0)
        crash("reading IRA");
    if ((irb = i2c_smbus_read_byte_data(fd, 11)) < 0)
        crash("reading IRB");
    if ((irc = i2c_smbus_read_byte_data(fd, 12)) < 0)
        crash("reading IRC");
    printf("Identification Registers A-B-C = 0x%02x-0x%02x-0x%02x ('%c'-'%c'-'%c')\n", ira, irb, irc, ira, irb, irc);

    if ((res = i2c_rw(fd, 10)) < 0)
        crash("rw error\n");
    printf("IR A-B = 0x%04x\n", res);

    /* set control register : CRA = 0x70 - CRB = 0x00 */
    if ((res = i2c_smbus_write_byte_data(fd, 0, 0x70)) < 0)
        crash("CRA");
    if ((res = i2c_smbus_write_byte_data(fd, 1, 0x00)) < 0)
        crash("CRB");

    /* Mode = 0x01 Go! */
    if ((res = i2c_smbus_write_byte_data(fd, 2, 0x01)) < 0)
        crash("Mode");

    usleep(6000); /* wait command to complete */

    /* check status - command complete */
    if ((res = i2c_smbus_read_byte_data(fd, 9)) < 0)
        crash("status");
    if (!(res & 0x01))
        crash("**** Not ready!\n");

    if ((mag_x = i2c_rw(fd, 3)) < 0)
        crash("reading Mag X\n");
    if ((mag_z = i2c_rw(fd, 5)) < 0)
        crash("rw error\n");
    if ((mag_y = i2c_rw(fd, 7)) < 0)
        crash("rw error\n");

    printf("Mag_xyz= %5d %5d %5d\n", (short)mag_x, (short)mag_y, (short)mag_z);

    close(fd);
    exit (0);
    }
Et se compile avec la commande :
$ gcc -o hmc5883l -l rt hmc5883l.c
En fait, ce code fait un peu plus que le minimum puisqu'il commence par aller chercher l'identité du composant en allant lire les 'Identity Registers' qui sont bien 'H', '4', '3'. Qu'il en lit ensuite deux à la fois pour vérifier que i2c_smbus_read_word_data() ne lit pas les octets d'un mot dans le bon ordre (il faut les inverser). Il configure ensuite les 2 registres de configuration, lance la commande, attend 6000 micro-secondes, lit les résultats et les affiche. Le programme n'utilise que i2clib (et n'utilise pas de #define's par paresse; il faut consulter la datasheet pour vérifier à quoi correspondent les données numériques). Il est aussi un peu radical du côté du traitement des erreurs : si quelque chose ne se passe pas comme prévu, il affiche un message et abandonne. Le but n'est pas ici de faire un programme complet mais de faire le programme le plus simple possible pour aller chercher la valeur de mesures.

Mais donc, c'est très simple. Pour s'en tenir au principal, on a :
    fd = open(I2C_BUS_DEV, O_RDWR));         /* i2c access */
    ioctl(fd, I2C_SLAVE, 0x1e);              /* connect chip */
    i2c_smbus_write_byte_data(fd, 0, 0x70);  /* Config Reg A = max sensibility */
    i2c_smbus_write_byte_data(fd, 1, 0x00);  /* Config Reg B = default */
    i2c_smbus_write_byte_data(fd, 2, 0x01);  /* Go! */
    usleep(6000);                            /* wait a little */
    mag_x = i2c_smbus_read_word_data(fd, 3); /* read values */
    mag_z = i2c_smbus_read_word_data(fd, 5);
    mag_y = i2c_smbus_read_word_data(fd, 7);
À noter que i2c_smbus_read_word_data() ne lit pas les octets dans le bon ordre, une histoire d'Indiens...

Ce qui n'est pas traité non plus, c'est la calibration, l'usage des différentes gammes de puissance du champ magnétique (le traitement des overflows), la traduction en vecteur indiquant la direction du Nord magnétique.

En tripotant le montage dans le champ magnétique terrestre, on obtient les premières mesures 'brutes' suivantes :
Il faudrait faire des mesures avec le capteur à plat à différents angles et voir si on peut retrouver les angles à partir de mag_x et mag_y (quelles valeurs de base?, multiplicateurs? (est-ce linéaire?) et probablement calculer un arc tangente(val_y/val_x)).

Note: à partir de l'hiver 2014-2015, le bus i2c est passé sous 'device tree'. Le moyen le plus simple de refaire fonctionner l'i2c est d'utiliser
$ sudo raspi-config
d'aller dans 'Advanced Options' et d'autoriser i2c (enable device tree & i2c).