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).
Rappel : Le 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ément : Le bus I2C sur la Raspberry
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 :
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 :
sudo apt-get install -y python-smbus
sudo apt-get install -y i2c-tools
Attention : Blacklist 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 :
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 :
# blacklist !
#blacklist spi-bcm2708
#blacklist i2c-bcm2708
Attention : Configuration 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 :
dtparam=i2c1=on
dtparam=i2c_arm=on
Fondamental : Reboot
Une fois ces opérations effectuées, il faut redémarrer la Raspberry :
sudo reboot
Test d'une communication I2C à partir des outils du Shell
Présence d'esclaves I2C connectés à la Raspberry
Attention : Piège !
Les premières Raspberry utilisent le port 0 au lieu du port 1. Dans ce cas, la commande devient :
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 :
char filename[20];
sprintf(filename, "/dev/i2c-%c",mNoBus);
if((mFileI2c=open(filename, O_RDWR))==-1) { // ouvre le fichier virtuel d'accès à l'I2C
qDebug("Erreur ouverture acces au bus I2C");
return -1;
} // 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 :
if(ioctl(mFileI2c, I2C_SLAVE, addr)!=0) { // Règle le driver I2C sur l'adresse.
qDebug("Erreur ioctl acces au bus I2C");
return -1;
} // 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 :
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
// ci2c.h
//Needed for I2C port
//Needed for I2C port
//Needed for I2C port
//Needed for I2C port
class CI2c : public QObject
{
Q_OBJECT
public:
// creation destruction de l'objet
static CI2c *getInstance(QObject *parent = 0, char no = '1');
static void freeInstance();
int lire(unsigned char addr, unsigned char *buffer, int lg);
int ecrire(unsigned char addr, unsigned char *buffer, int lg);
int init();
int getNbLink();
QMutex mutexI2c;
private:
explicit CI2c(QObject *parent = 0, char noBus = '1');
int mAddr; // Adresse du composant I2C
char mNoBus; // No d'accès au fichier /dev
int mFileI2c; // descripteur du fichier i2C
int mNbLink;
static CI2c *mSingleton;
};
// CI2C_H
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.
Rappel : C++
Une méthode statique est indépendante d'un objet. Elle peut être appelée à tout moment de la manière suivante :
mI2c = CI2c::getInstance(this, no);
Méthode : Classe 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.
Fondamental : Pourquoi 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
CI2c::CI2c(QObject *parent, char noBus) :
QObject(parent)
{
mNoBus = noBus;
mNbLink=0;
} // constructeur
CI2c * CI2c::mSingleton = NULL;
int CI2c::lire(unsigned char addr, unsigned char *buffer, int lg)
{
if(ioctl(mFileI2c, I2C_SLAVE, addr)!=0) { // Règle le driver I2C sur l'adresse.
qDebug("Erreur ioctl acces au bus I2C");
return -1;
} // if ioctl
bzero(buffer, lg);
QMutexLocker lock(&this->mutexI2c); // verrouillage du mutex. Il est libéré en sortie de méthode
int nb=read(mFileI2c, buffer, lg);
// qDebug() << "CI2c:lire: " << buffer[0] << " " << buffer[1] << buffer[2] << " " << buffer[3] << buffer[4] << " " << buffer[5];
return nb;
} // lire
int CI2c::ecrire(unsigned char addr, unsigned char *buffer, int lg)
{
if(ioctl(mFileI2c, I2C_SLAVE, addr)!=0) { // Règle le driver I2C sur l'adresse.
qDebug("Erreur ioctl acces au bus I2C");
return -1;
} // if ioctl
QMutexLocker lock(&this->mutexI2c); // verrouillage du mutex. Il est libéré en sortie de méthode
int nb=write(mFileI2c, buffer, lg);
// qDebug() << "CI2c:ecrire: nb=" << nb << " : " << buffer[0] << " " << buffer[1] << buffer[2];
return nb;
} // ecrire
int CI2c::init()
{
char filename[20];
sprintf(filename, "/dev/i2c-%c",mNoBus);
if((mFileI2c=open(filename, O_RDWR))==-1) { // ouvre le fichier virtuel d'accès à l'I2C
qDebug("Erreur ouverture acces au bus I2C");
return -1;
} // if open
return mFileI2c;
} // init
int CI2c::getNbLink()
{
return mNbLink;
} // getNbLink
CI2c *CI2c::getInstance(QObject *parent, char no)
{
if (mSingleton == NULL)
{
qDebug("L'objet CI2c sera créé !");
mSingleton = new CI2c(parent, no);
mSingleton->init();
mSingleton->mNbLink=1;
}
else
{
mSingleton->mNbLink++;
qDebug("singleton already created!");
}
return mSingleton;
} // getInstance
void CI2c::freeInstance()
{
if (mSingleton != NULL)
{
mSingleton->mNbLink--;
if (mSingleton->mNbLink==0) {
close(mSingleton->mFileI2c);
delete mSingleton;
mSingleton = NULL;
} // if mNbLink
} // if null
} // freeInstance
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()
etecrire()
sont protégées par le Mutex.