Retrouvez cet article dans : Linux Magazine Hors série 23
Le but de cet article est la compré-hension du fonctionnement des ports série sous LINUX. L’article abordera également la configuration des ports série depuis le shell sh ainsi qu’en langage C.
Le bus i2c est parfaitement pris en charge par le noyau Linux. En effet, le projet Lm_sensors (http://secure.netroedge.com/~lm78/) développant la prise en charge et la gestion des capteurs présents dans un PC a, par effet de bord, largement contribué à faciliter l’accès à ce bus.
i2c est l’acronyme de Inter-Integrated Circuit. Il s’agit d’un bus développé au début des années 80 par Philips. L’objectif poursuivi était de minimiser et standardiser les liaisons entre les circuits intégrés numériques de ses produits comme les magnétoscopes, les TV, la Hi-fi, etc. Depuis, le bus a " fait son trou " et est maintenant présent un peu partout. On trouve également au détail une gamme complète de composants i2c ou compatibles (la désignation " i2c " est une marque déposée par Philips). Le bus i2c est normalisé et également utilisé sous la forme de SMBus ou du DDS permettant la reconnaissance automatique du moniteur par la carte graphique et le système d’exploitation.
Physiquement, le bus i2c se compose de quatre lignes :
- Une alimentation en +5 V permettant, dans la plupart des cas, d’alimenter directement les composants présents sur le bus comme les mémoires EEPROM ou les latchs ;
- Une masse (référence) ;
- Une ligne d’horloge permettant la synchronisation du bus et la transmission des données nommée " SCL " ;
- Une ligne de donnée nommée " SDA ".
Les interfaces électriques sont de type collecteur ouvert et on utilise une résistance de pull-up au +5 V. Cela signifie que, lorsque la sortie d’une interface est au niveau logique 0, elle n’est connectée à rien. Cela permet ici la présence de plusieurs " maîtres " sur le bus et évite les conflits entre composants.
i2c est un bus. Cela signifie que les périphériques i2c y sont connectés en parallèle (figure 1). Il est donc possible d’avoir, sur un même bus, plusieurs composants avec lesquels dialoguer. Sur le bus, le maître est le donneur d’ordre et l’esclave celui qui y répond. L’esclave ne fait que répondre. S’il se met à " parler " de lui-même et donner des ordres, il devient maître.

Fig. 1
i2c permet la présence de plusieurs maîtres sur un seul et même bus.
Protocole
Avant de voir le plus simple, qu’est l’utilisation via le support Linux, voyons l’aspect plus délicat qu’est le protocole lui-même. Si vous souhaitez simplement contrôler des composants i2c depuis GNU/Linux, vous n’aurez pas forcément besoin de le maîtriser. Il en va tout autrement si vous souhaitez faire communiquer un microcontrôleur en i2c avec des composants compatibles ou avec votre PC sous GNU/Linux. Dans tous les cas, cependant, une certaine compréhension du protocole est toujours utile.
Le protocole de communication repose sur la transmission d’une succession de bits cadencée par la ligne SCL. La figure 2 présente une trame i2c complète. Avant d’envoyer des données sur le bus, un composant doit s’assurer de sa disponibilité. Pour ce faire, il vérifie que les lignes SDA et SCL sont au repos à l’état haut. Si c’est le cas, il définit la condition de départ avec un changement d’état de SDA sans influer sur SCL (S sur la figure 2). Ensuite, il peut transmettre l’adresse du composant i2c auquel il s’adresse sur 7 bits (en bleu).
Les composants i2c ont tous une adresse fixe propre à leur modèle qui peut être déclinée en utilisant des broches spécifiques. La partie fixe de l’adresse est constituée des bits 3 à 6 et la partie variable de 0 à 2. On peut ainsi avoir sur un même bus 8 composants identiques avec des adresses différentes. Après l’adresse suit un bit de lecture (SDA haut) ou d’écriture (SDA bas). Le composant esclave pointé par l’adresse envoyée sur le bus par le maître accuse réception en mettant SDA à l’état bas alors que le maître le maintient à l’état haut. Ceci n’est possible que grâce à l’architecture même du bus i2c et à ses résistances de pull-up.

Fig. 2

Fig. 3
 Lors d’une lecture commandée par le maître, l’esclave se met alors à transmettre des données sur le bus. La figure 3 montre les données sur la ligne SDA et la séparation maître/esclave de l’émission de celles-ci. Un bit d’acquittement (accusé réception) imposé par le maître suit chaque groupe de 8 bits transmis par l’esclave.
Le bit d’acquittement est très important. Il permet au receveur (maître ou esclave) de signifier qu’il a bien reçu une information (adresse ou donnée). Un bit d’acquittement est émis par le receveur en mettant SDA au niveau bas lors de l’impulsion d’horloge (toujours contrôlée par le maître) alors que l’émetteur libère SDA (niveau haut). Dans le cas précis d’un maître recevant des données d’un esclave, le dernier octet reçu n’est pas acquitté. L’esclave libère alors le bus pour que le maître puisse générer une condition d’arrêt. La figure 4 présente un diagramme provenant de la documentation du capteur de température DS75 de Maxim Dallas. On voit clairement la réception des deux octets de données concernant la température. Le premier octet est correctement acquitté par le maître et le second non.
La condition d’arrêt marque la libération du bus par le maître. Alors que SDA et SCL sont tous deux au niveau bas, SDA est passé au niveau haut, puis il est fait de même pour SCL. Maître et esclave retournent alors à un état de veille en attendant une nouvelle condition de départ, les deux lignes étant au repos (niveau haut). L’écriture de données par le maître n’est pas très différente si ce n’est pour le bit d’acquittement. Dans ce cas, c’est le maître qui libère le SDA (état haut) et l’esclave qui accuse réception en mettant la ligne à l’état bas. En fin de transmission, le maître génère la condition d’arrêt.
Composants et bus i2c
Je l’ai dit précédemment, il existe une vaste gamme de composants directement compatibles i2c. Il en existe pour tout usage, comme la mémoire statique PCF8571P (128 x 8 bits) ou EEPROM PCF8582C2P (256 x 8 bits). Mais les composants les plus intéressants à mon goût restent les PCF8574P et PCF8574AP.
Identiques en dehors de leurs 4 bits d’adresse fixe, ils permettent tout simplement de disposer, par composant, de 8 lignes de données parallèles. Pour quelques 4 euros par pièce, vous pouvez ainsi vous créer un système d’entrées/sorties de 16 fois 8 bits soit 128 lignes adressables individuellement. Certes, le bus i2c n’offre pas les performances d’une carte industrielle, mais vous pourrez ainsi, sur un seulet même bus, développer tout type d’applications. Je pense naturellement à tout ce qui est du ressort de la domotique, puisqu’il est facile d’adapter les indications données dans l’article sur la carte à relais de ce type de composants.

Fig. 4
Autre point fort du bus i2c, il dispose de quelques composants intéressants en termes de détection de température. Des capteurs comme le DS75, LM78 ou DS1631 coûtent quelques euros et offrent une précision remarquable. Amateur d’aquariophilie, à vos fers à souder et à vos claviers... Enfin, notons qu’il existe des composants plus complexes comme le SAA1064 permettant de piloter quatre afficheurs LED 7 segments.

Fig. 5
 Un bus i2c est sans doute déjà présent dans votre PC. Si vous avez de la chance, vous disposerez d’un connecteur SMBus de quatre broches directement sur la carte mère. Le SMBus est une déclinaison de l’i2c, mais ne supporte qu’une vitesse de 100kHz, alors que les dernières spécifications i2c précisent également des vitesses de 400kHz et 3.4MHz. Cependant, à notre échelle de mise en œuvre, on peut considérer que SMBus et i2c sont équivalents.
Si vous n’avez pas de chance, le SMBus sera difficilement accessible sur la carte mère. La méthode la plus simple (la moins dangereuse pour l’équipement) est d’accéder au bus via une EEPROM placée sur une barrette mémoire (figure 5). Cette EEPROM est programmée par le BIOS pour stocker différentes informations comme par exemple le timing de la mémoire. Mais le plus intéressant ici est de pouvoir souder directement des fils sur les lignes SDA et SCL (SMBus DATA et SMBus Clock) et ainsi utiliser le bus. Notez cependant que la manipulation est délicate et risquée. La soudure elle-même est dangereuse et dépend du type d’EEPROM (ici une 24c02 de 256x8 en boîtier SO8). Quant à l’utilisation du bus, vous n’aurez pas droit à l’erreur, car un conflit d’adresse, un court-circuit ou une surtension serait fatale à l’EEPROM, la barrette de mémoire ou pire... à la carte mère.
Notez que la société Analog Devices fabrique un kit permettant une connexion identique. L’adaptateur prend la forme d’une barrette DIMM se plaçant sur la carte mère. La technique est toujours la même mais en version " propre ".
Adaptateur parallèle
Si vous ne souhaitez pas prendre de risques, la meilleure solution est d’utiliser un adaptateur parallèle/i2c. Le schéma est donné en figure 6. Il s’agit de l’adaptateur reconnu par le support i2c de Linux sous la désignation " Philips adapter ". L’adaptateur permet de faire du PC un maître ou un esclave sur le bus. Le circuit est relativement simple, le sextuple inverseur 74LS05 ne fait qu’inverser les signaux et les six résistances de pull-up seront des 10 K Ohms. Vous pouvez également utiliser des 3300 ou 4700 Ohms. Notez la présence de la LED et sa résistance de 330 Ohms (à recalculer selon la LED).

Fig. 6
Nous avons ainsi la correspondance suivante :

Vous l’aurez compris, l’adaptateur ne contient absolument pas la logique nécessaire à en faire une interface i2c pour le PC. Ce sera directement le support i2c du noyau Linux qui se chargera de piloter le port parallèle de manière adéquate.
La figure 7 présente un typon pour l’adaptateur. Vous pouvez également le télécharger au format PDF à l’URL http://www.lefinnois.net/pcb_i2c.pdf

Fig. 7
i2c et Linux
Déjà présent dans la série 2.4 du noyau, le support i2c s’est largement étoffé avec la série 2.6. En jetant un coup d’œil dans l’arborescence des modules d’un noyau de 2.6.12, par exemple, on y trouve une large collection de modules. Le sous-répertoire busses contient les éléments permettant la prise en charge de la plupart des bus présents sur les cartes mère. La machine de test ayant servi à cet article (un AMD Athlon XP 2400+ sur une carte mère ASRock) dispose d’un bus i2c interne supporté par le module i2c-sis96x.
L’adaptateur parallèle décrit précédemment est supporté par le module i2c-parport. On notera également l’existence de i2c-parport-light ne reposant pas sur le support parport, mais utilisant directement outb/inb sur l’adresse E/S du port. Le module développé par Jean Delvare supporte plusieurs types d’adaptateurs qui se connectent tous au port parallèle se distinguant par l’utilisation différente des lignes du port. Celui décrit ici est le type 0 correspondant au " Philips adapter ". Un simple modinfo i2c-parport vous listera les adaptateurs supportés.
La première chose à faire est de charger le module permettant l’émulation de l’interface. C’est ce module qui remplacera la logique interne d’une véritable interface. Nous chargerons ce support avec une option nous permettant d’avoir un minimum d’informations intéressantes sur ce qui se passe :
|
|
% modprobe i2c_algo_bit i2c_debug=3 bit_test=1
% modprobe i2c_dev |
Nous chargeons également le support /dev nous permettant d’accéder aux bus depuis l’espace utilisateur. Enfin, nous pouvons charger le support de l’adaptateur parallèle :
|
|
% modprobe i2c-parport type=0
% modprobe i2c-sis96x |
Dès à présent, nous pouvons utiliser les outils du projet Lm_sensors pour obtenir des informations sur le ou les bus i2c en présence :
|
|
% i2cdetect
[...]
Installed I2C busses:
i2c-1 unknown SiS96x SMBus adapter at 0x0c00
i2c-0 unknown Parallel port adapter |
Nous avons ici deux bus. Le premier (0) est notre adaptateur parallèle, le second est celui intégré à la carte mère. Notez que la machine de test dispose de trois ports parallèles, mais seul un bus est détecté, celui où se trouve l’adaptateur. Le journal d’activité du noyau nous en apprend plus :
|
|
kernel: i2c-algo-bit.o: (0) scl=1, sda=0
kernel: i2c-algo-bit.o: Parallel port adapter seems to be busy.
kernel: i2c-parport: Unable to register with I2C
kernel: i2c-algo-bit.o: (0) scl=1, sda=0
kernel: i2c-algo-bit.o: Parallel port adapter seems to be busy.
kernel: i2c-parport: Unable to register with I2C
kernel: i2c-algo-bit.o: (0) scl=1, sda=1
kernel: i2c-algo-bit.o: (1) scl=1, sda=0
kernel: i2c-algo-bit.o: (2) scl=1, sda=1
kernel: i2c-algo-bit.o: (3) scl=0, sda=1
kernel: i2c-algo-bit.o: (4) scl=1, sda=1
kernel: i2c-algo-bit.o: Parallel port adapter passed test. |
Le module i2c-algo-bit procède à des tests sur les lignes SDA et SCL en mode lecture/écriture afin de s’assurer de la présence du circuit. Ainsi, nous pouvons utiliser librement les autres ports à notre disposition. Ce comportement est obtenu grâce au paramètre bit_test=1 du module i2c-algo-bit. i2cdetect nous permet également de scanner un bus. Il suffit de lui passer en argument le numéro du bus :
|
|
% i2cdetect 0
WARNING! This program can confuse your I2C bus,
cause data loss and worse!
I will probe file /dev/i2c-0.
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:Â Â Â Â Â Â Â Â Â XX XX XX XX XX XX XX XX XX XX XX XX XX
10: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
20: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
30: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
40: XX XX XX XX XX XX XX XX 48 XX XX XX XX XX XX XX
50: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
60: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
70: XX XX XX XX XX XX XX XX |
Notez le message d’avertissement. Cette commande comporte en effet un risque, puisque le seul moyen de trouver les composants i2c sur le bus est de leur adresser un message et d’attendre un acquittement de leur part. Ce type de manipulations, selon le modèle de composant peut soulever certains problèmes. On évitera donc d’utiliser la fonction de scan sur le bus interne d’un PC. Perturber le fonctionnement des EEPROM des mémoires serait vraiment une très mauvaise chose. Les adresses " vides " sont marquées XX et là où se trouve un composant son adresse est affichée. Ici, nous avons un composant à l’adresse 0x48.
Il s’agit tout simplement de l’objet de cet article, une sonde de température DS75 (figure 8). Inutile de présenter le diagramme de connexion puisqu’il suffit de connecter toutes les pattes. Les trois broches d’adresse sont reliées à la masse et le connecteur OS (la sortie du thermostat) reste non connecté.
Bien qu’il s’agisse d’un bus, notre montage relie directement la sonde à l’adaptateur (figure 9). Nous pourrons, au besoin greffer un autre composant au bus.
Notez que la longueur des fils entre l’adaptateur et la sonde peut être source de problème. Il faudra, en effet, avec de simples fils ne pas dépasser 50 cm. Au-delà , le recyclage d’un câble réseau donne de bons résultats.

Fig. 8
Programmation i2c
Le module i2c-dev permet l’accès aux bus depuis l’espace utilisateur. Il existe deux méthodes d’accès documentées dans le support i2c du noyau (Documentation/i2c/dev-interface).
La première consiste à utiliser les fonctions i2c_smbus_read_word_data et i2c_smbus_write_word_data décrites dans linux/i2c.h.
Il n’est normalement pas recommandé d’inclure les fichiers d’en-tête du noyau dans une application, mais c’est le seul moyen à notre disposition, puisque la Libc GNU ne connaît pas le bus i2c.
L’autre solution consiste à utiliser les opérations de lecture/écriture sur le périphérique /dev ainsi que les ioctl. La première étape consiste à ouvrir le fichier :
|
|
 if ((file = open(filename,O_RDWR)) < 0) {
fprintf(stderr,"Open Error : %s (%d)\n",
strerror(errno),errno);
exit(EXIT_FAILURE);
} |
Fig. 9
On spécifie ensuite l’adresse du composant i2c esclave auquel on souhaite accéder :
|
|
#define I2C_SLAVE 0x0703
if (ioctl(file,I2C_SLAVE,0x48) < 0) {
fprintf(stderr,"I2C_SLAVE Error : %s (%d)\n",
strerror(errno),errno);
exit(EXIT_FAILURE);
} |
Il ne nous reste plus qu’à nous adresser au composant. La documentation du DS75 est claire, pour lire une température sur le composant, il suffit de l’adresser en lecture.
Cependant, nous préférons configurer le DS75 avant lecture. Celui-ci dispose de plusieurs résolutions entre 9 et 12 bits. Nous choisissons, bien sûr, la plus grande résolution. Pour ce faire, nous écrivons la valeur 0x60 (voir doc page 9) dans le registre 0x01 du DS75 :
|
|
char buf[10];
buf[0]=0x01;
buf[1]=0x60;
if (write(file,buf,2) != 2) {
fprintf(stderr,"Write Error : %s (%d)\n",
strerror(errno),errno);
exit(EXIT_FAILURE);
} |
Nous écrivons tout simplement les deux valeurs dans le fichier. Cette écriture provoque dans le DS75 le pointage du registre de configuration.
Si nous lisons le composant maintenant, nous obtiendrons la valeur du registre de configuration. Il nous faut donc, avant lecture de la température, replacer le pointeur sur le registre nous informant de la température détectée, puis lire ce registre :

Fig. 10
|
|
 buf[0]=0x00;
if (write(file,buf,1) != 1) {
fprintf(stderr,"Write Error : %s (%d)\n",
strerror(errno),errno);
exit(EXIT_FAILURE);
}
if (read(file,buf,2) != 2) {
fprintf(stderr,"Read Error : %s (%d)\n",
strerror(errno),errno);
exit(EXIT_FAILURE);
} |
Nous obtenons dans buf les deux octets qui nous intéressent, le MSB dans buf[0] et LSB dans buf[1]. Nous pouvons alors décoder la température grâce à la documentation du DS75 :

|
|
 int mantisse, exposant;
float deg = 0;
exposant = buf[0] & 0x7f;
mantisse = buf[1] >> 4;
if(mantisse & 1) deg+=0.0625;
if(mantisse & 2) deg+=0.125;
if(mantisse & 4) deg+=0.25;
if(mantisse & 8) deg+=0.50;
deg+=exposant;
if(buf[0] & 0x80) deg-=128;
printf("ioctl:\t%3.2f\n",deg); |
Le bit 15 (bit 7 de buf[0]) nous indique une température négative. Les autres bits nous donnent l’exposant de la température, sa valeur entière en degrés. Nous décalons buf[1] de 4 bits vers la droite pour faciliter le calcul de la mantisse. Nous obtenons au final la température en degrés sous la forme d’un type float. Voici un code identique utilisant les fonctions de l’API i2c du noyau :
|
|
if (i2c_smbus_write_byte_data(file,0x01,0x60)<0)
res = i2c_smbus_read_word_data(file,0x00); |
Utilisation de la sonde
Le code de lecture du DS75, une fois finalisé et utilisant getopt pour prendre en argument le fichier /dev et l’adresse du composant, nous permettra la relève des températures à intervalle régulier.
Il nous suffira alors d’utiliser les outils RRDtool nous permettant de construire une base de données " Round Robin " (les plus récentes données écrasant les plus anciennes).
Ainsi, toutes les 5 minutes, via cron, nous pourrons relever la température et l’intégrer dans la base. Nous pourrons ensuite générer un graphique (figure 10) ou utiliser les données statistiques à notre guise.
Bien entendu, un résultat similaire peut être obtenu avec MRTG, mais RRDtool est plus souple d’utilisation tant en termes de stockage des données que de représentation graphique.
Retrouvez cet article dans : Linux Magazine Hors série 23