Communications par le bus I2C

Il n'est pas prévu d'expliquer ici le fonctionnement du bus I2C en détail, d'autant plus que la gestion de la communication sur le bus est assurée par un composant piloté par le driver système, ce qui rend transparent le fonctionnement bas niveau du bus (couche liaison de données).

RappelLe Bus I2C

Il s'agit d'un bus série synchrone. 2 signaux sont utilisés : SDA et SCL.

SDA : Permet la communication des données.

SCL : Horloge.

ComplémentLe bus I2C sur la Raspberry

Les signaux se trouvent sur les bornes 3 et 5 du GPIO de la Raspberry.

GPIO pins diagram
GPIO pins diagram

Installation du bus I2C sur la Raspberry

Installation du module I2C pour le noyau Linux

Vous avez 2 possibilités :

  • En utilisant l'utilitaire de configuration raspi-config. Il suffit de suivre les indications du logiciel.

  • A la main comme décrit ci-dessous.

Installation manuelle du module I2C pour le noyau Linux

Il faut ouvrir le fichier modules pour y inscrire les modules téléchargés manquant :

1
sudo nano /etc/modules

Les modules à ajouter sont : i2c-bcm2708 et i2c-dev.

Vous pouvez les ajouter à la fin du fichier en veillant à ne pas les inscrire deux fois.

Installation des utilitaires I2C

Téléchargez les paquets suivants :

1
sudo apt-get install -y python-smbus
2
sudo apt-get install -y i2c-tools

AttentionBlacklist des modules

Sur certaines distributions, il existe un fichier nommé raspi-blacklist.conf qui permet d'interdire l'utilisation de certains modules, combien même sont-ils déclarés dans le fichier modules.

Il faut vérifier que les modules utiles ne soient pas interdits :

1
sudo nano /etc/modprobe.d/raspi-blacklist.conf

Il suffit de placer en commentaire (ajout d'un # en début de ligne) les modules concernés :

1
# blacklist !
2
3
#blacklist spi-bcm2708
4
#blacklist i2c-bcm2708

AttentionConfiguration de la Raspberry au démarrage

Au démarrage de la Raspberry, des paramètres de fonctionnement de la Raspberry sont lus à partir du fichier /boot/config.txt.

Il faut que les lignes suivantes s'y trouvent pour configurer la Pi à utiliser le bus I2C :

1
dtparam=i2c1=on
2
dtparam=i2c_arm=on

FondamentalReboot

Une fois ces opérations effectuées, il faut redémarrer la Raspberry :

1
sudo reboot

Test d'une communication I2C à partir des outils du Shell

Présence d'esclaves I2C connectés à la Raspberry

L'utilitaire i2cdetect permet d'identifier l'adresse des modules I2C connectés à la carte Raspberry :

1
sudo i2cdetect -y 1
Utilisation de i2cdetect
Utilisation de i2cdetect

Ici, 2 esclaves sont connectés aux adresses 0x40 et 0x70 (0x = préfixe hexadécimale).

AttentionPiège !

Les premières Raspberry utilisent le port 0 au lieu du port 1. Dans ce cas, la commande devient :

1
sudo i2cdetect -y 0

Heureusement, cela ne concerne que les cartes ayant 256Mo de RAM.

Les nôtres disposent toutes de 512 Mo de RAM, donc nous utilisons le port 1.

Programmation d'une communication par bus I2C

Comme pour les GPIO, nous utiliserons le système de fichiers comme interface de lecture/écriture dans le bus I2C.

Le fichier d'accès au bus I2C est : /dev/i2c-1. (sur les vieilles versions /dev/i2c-0).

Il faut tout d'abord ouvrir ce fichier en lecture et écriture :

1
    char filename[20];
2
    sprintf(filename, "/dev/i2c-%c",mNoBus);
3
    if((mFileI2c=open(filename, O_RDWR))==-1) {  // ouvre le fichier virtuel d'accès à l'I2C
4
        qDebug("Erreur ouverture acces au bus I2C");
5
        return -1;
6
    } // if open
Définir le numéro de l'esclave cible

Pour régler le numéro d'esclave avec lequel communiquer, il faut utiliser la fonction linux ioctl(), qui sert à effectuer des opérations d'entrées/sorties spécifiques à un périphérique, le contrôleur I2C dans notre cas. Le fragment de code suivant permet donc cela :

1
    if(ioctl(mFileI2c, I2C_SLAVE, addr)!=0) {  // Règle le driver I2C sur l'adresse.
2
        qDebug("Erreur ioctl acces au bus I2C");
3
        return -1;
4
    } // if ioctl

Le paramètre 2 de la fonction ioctl() (I2C_SLAVE) est la commande spécifique au composant I2C, et permet l'initialisation de l'esclave.

Le paramètre 3 est le numéro de l'esclave choisi.

Lecture / écriture sur le bus.

L'opération de lecture ou d'écriture sur le bus utilise les fonctions standards read() et write(). Exemple :

1
    int nb=read(mFileI2c, buffer, lg);

lg : Nombre d'octets à lire.

buffer : Adresse de stockage.

Dans le cadre d'un développement C++, il est nécessaire de construire une classe I2C. En voici un exemple :

Le fichier ci2c.h
1
// ci2c.h
2
#ifndef CI2C_H
3
#define CI2C_H
4
5
#include <QObject>
6
#include <QMutex>
7
#include <QMutexLocker>
8
#include <QDebug>
9
#include <stdio.h>
10
#include <unistd.h>				//Needed for I2C port
11
#include <fcntl.h>				//Needed for I2C port
12
#include <sys/ioctl.h>			//Needed for I2C port
13
#include <linux/i2c-dev.h>		//Needed for I2C port
14
#include <strings.h>
15
16
class CI2c : public QObject
17
{
18
    Q_OBJECT
19
20
public:
21
    // creation destruction de l'objet
22
    static CI2c *getInstance(QObject *parent = 0, char no = '1');
23
    static void freeInstance();
24
25
    int lire(unsigned char addr, unsigned char *buffer, int lg);
26
    int ecrire(unsigned char addr, unsigned char *buffer, int lg);
27
    int init();
28
    int getNbLink();
29
    QMutex mutexI2c;
30
31
private:
32
    explicit CI2c(QObject *parent = 0, char noBus = '1');
33
    int mAddr;   // Adresse du composant I2C
34
    char mNoBus;   // No d'accès au fichier /dev
35
    int mFileI2c;  // descripteur du fichier i2C
36
    int mNbLink;
37
    static CI2c *mSingleton;
38
};
39
40
#endif // CI2C_H
41

Cette classe est exceptionnelle.

Remarquez que :

  • Le constructeur est dans la section privée de la classe. Cela signifie que l'instanciation d'un objet ne sera pas habituelle mais passera par l'invocation obligatoire d'une méthode statique.

  • Qu'il existe 1 méthode statique publique pour l'instanciation de l'objet. Son rôle est d'assurer l'unicité de l'instanciation d'un objet CI2c. La raison en est expliquée plus bas.

  • Qu'il existe 1 méthode statique publique pour libérer l'objet. Son rôle est d'assurer la destruction de l'objet que lorsque plus aucun objet n'utilise le bus I2c.

RappelC++

Une méthode statique est indépendante d'un objet. Elle peut être appelée à tout moment de la manière suivante :

1
mI2c = CI2c::getInstance(this, no);
MéthodeClasse singleton

Cette classe CI2c est une classe singleton.

Cela signifie qu'il n'est possible de ne créer qu'un seul objet de cette classe. Les objets qui l'utilisent devront se partager l'unique objet CI2c. Pourquoi ? L'explication est ci-après.

La méthode statique getInstance() procède la première fois à l'instanciation de l'objet, les fois suivantes à la délivrance de l'adresse de l'objet déjà existant.

La classe gère le nombre d'objets utilisant l'objet CI2c.

Lorsqu'un objet ne désire plus utiliser le bus I2C, un appel de la méthode freeInstance() est requis.

La méthode statique freeIntance() se contente de décrémenter le compteur d'objets utilisateur. Lorsqu'il devient nul, l'objet unique est alors effacé de la mémoire.

FondamentalPourquoi l'utilisation d'une classe singleton ?

Parce que cette classe est nécessaire pour protéger les accès concurrents au bus I2C, représenté par le fichier /dev/i2c-1.

Ce fichier ne peut être ouvert qu'une seule fois (fonction open()).

Imaginez le développement suivant :

  • 2 capteurs I2C sont connectés au bus (numéro d'esclave différents).

  • 1 objet pour chaque capteur. Chacun des objets initialisera un objet CI2c, provoquant l'ouverture du fichier /dev/i2c-1.

L'accès au fichier /dev/i2c-1 sera donc en concurrence entre les 2 objets. Cela n'est pas possible.

Afin d'empêcher 2 ouvertures de fichier simultanées, chaque objet utilisera le même objet I2C. Ainsi, l'appel à la fonction d'ouverture du fichier /dev/i2c-1 ne se fera qu'une fois.

Mais cela n'est pas suffisant.

La plupart du temps, les objets "capteur" sont des processus (ou thread) et par conséquent fonctionnent simultanément.

Il est donc nécessaire de garantir l'accès exclusif au bus I2C pour chaque objet. C'est la raison d'être d'un Mutex, ressource système garantissant l'accès exclusif à une ressource.

Globalement, la solution apportée par cette technique de développement a pour but de sérialiser les accès au bus I2C (les uns après les autres).

Le fichier ci2c.cpp
1
#include "ci2c.h"
2
3
CI2c::CI2c(QObject *parent, char noBus) :
4
    QObject(parent)
5
{
6
    mNoBus = noBus;
7
    mNbLink=0;
8
} // constructeur
9
10
CI2c * CI2c::mSingleton = NULL;
11
12
int CI2c::lire(unsigned char addr,  unsigned char *buffer, int lg)
13
{
14
    if(ioctl(mFileI2c, I2C_SLAVE, addr)!=0) {  // Règle le driver I2C sur l'adresse.
15
        qDebug("Erreur ioctl acces au bus I2C");
16
        return -1;
17
    } // if ioctl
18
    bzero(buffer, lg);
19
    QMutexLocker lock(&this->mutexI2c);  // verrouillage du mutex. Il est libéré en sortie de méthode
20
21
    int nb=read(mFileI2c, buffer, lg);
22
//    qDebug() << "CI2c:lire: " << buffer[0] << " " << buffer[1] << buffer[2] << " " << buffer[3] << buffer[4] << " " << buffer[5];
23
    return nb;
24
} // lire
25
26
int CI2c::ecrire(unsigned char addr, unsigned char *buffer, int lg)
27
{
28
    if(ioctl(mFileI2c, I2C_SLAVE, addr)!=0) {  // Règle le driver I2C sur l'adresse.
29
        qDebug("Erreur ioctl acces au bus I2C");
30
        return -1;
31
    } // if ioctl
32
33
    QMutexLocker lock(&this->mutexI2c);  // verrouillage du mutex. Il est libéré en sortie de méthode
34
35
    int nb=write(mFileI2c, buffer, lg);
36
//    qDebug() << "CI2c:ecrire: nb=" << nb << " : " << buffer[0] << " " << buffer[1] << buffer[2];
37
    return nb;
38
} // ecrire
39
40
int CI2c::init()
41
{
42
    char filename[20];
43
    sprintf(filename, "/dev/i2c-%c",mNoBus);
44
    if((mFileI2c=open(filename, O_RDWR))==-1) {  // ouvre le fichier virtuel d'accès à l'I2C
45
        qDebug("Erreur ouverture acces au bus I2C");
46
        return -1;
47
    } // if open
48
    return mFileI2c;
49
} // init
50
51
int CI2c::getNbLink()
52
{
53
    return mNbLink;
54
} // getNbLink
55
56
CI2c *CI2c::getInstance(QObject *parent, char no)
57
{
58
    if (mSingleton == NULL)
59
    {
60
        qDebug("L'objet CI2c sera créé !");
61
        mSingleton =  new CI2c(parent, no);
62
        mSingleton->init();
63
        mSingleton->mNbLink=1;
64
    }
65
    else
66
    {
67
        mSingleton->mNbLink++;
68
        qDebug("singleton already created!");
69
    }
70
    return mSingleton;
71
} // getInstance
72
73
void CI2c::freeInstance()
74
{
75
    if (mSingleton != NULL)
76
      {
77
            mSingleton->mNbLink--;
78
            if (mSingleton->mNbLink==0) {
79
                close(mSingleton->mFileI2c);
80
                delete mSingleton;
81
                mSingleton = NULL;
82
            } // if mNbLink
83
      } // if null
84
} // freeInstance
85

Remarques concernant le fichier ci-dessus :

  • La classe QMutexLocker permet l'utilisation du Mutex. Le déverrouillage du Mutex est automatique, en sortie de méthode.

  • La méthode getInstance() appelle le constructeur de la classe (procède donc à l'instanciation de l'objet) que lorsque l'objet qu'aucun objet de ce type n'a jamais été créé.

  • La méthode init() permet l'ouverture du fichier /dev/i2c-1.

  • Les méthodes lire() et ecrire() sont protégées par le Mutex.