I2C : petit manuel du développeur
Préambule
Le bus I2C a déjà été abordé dans un précédent chapitre pour tenter d’expliquer « grossièrement » le fonctionnement de celui-ci sans trop entrer dans les détails.
Le développement de pilotes matériels fait appel à de nombreuses connaissances très différentes et nécessitera une maîtrise plus poussée du fonctionnement du bus I2C.
D’une façon générale, un bus est vu comme un périphérique lent, infiniment plus lent que la mémoire. C’est donc un monde où il faut communiquer le moins possible, mais avec le plus d’efficacité possible. Il n’est pas rare de devoir manipuler des bits dans un pilote.
Dans un pilote, le code est à cheval entre une fonction Python de haut niveau comme read_temperature() et la machinerie nécessaire pour encoder la requête, l’envoyer sur le bus I2C, réceptionner les données et en faire quelque chose de compréhensible (transformer l’information en entier, valeur décimale en bits et vice versa).
Ce n’est pas un sujet facile à vulgariser !
Ce chapitre s’attardera sur les concepts à maîtriser pour le portage d’un pilote vers MicroPython.
Introduction
Déjà abordé à de multiples reprises (cf. Programmer - Bus I2C), le bus I2C est un bus permettant de brancher de nombreux périphériques par l’intermédiaire d’une interface 3 fils avec SDA pour le transport des bits de données et SCL pour le transport du signal d’horloge et la masse.
Bus I2C de la carte MicroPython Pyboard
La carte Pyboard dispose par ailleurs de deux bus I2C pouvant être manipulés par l’intermédiaire de l’API et la classe I2C proposée par le module machine.
Transmettre l’information, un véritable défi
Les informations sont transmises bit à bit sur un bus I2C et, fondamentalement, les données transférées sont des octets. Comme chaque octet est encodé sur 8 bits, un bus I2C est uniquement capable de transférer une donnée numérique dont la valeur évolue et est comprise 0 et 255. C’est très limité pour transmettre de grandes valeurs, des nombres négatifs ou même des valeurs décimales.
Cela n’est qu’une partie de la limitation technique, puisque les périphériques I2C peuvent être, dans une certaine mesure, paramétrés (un peu comme régler un appareil photo avant la prise de vue).
Cela met en évidence deux choses :
-
Qu’il y a un protocole...
Manipulation de données
L’une des tâches fondamentales du pilote est de transformer des types évolués (des entiers, valeurs signées, nombres réels float) en octets et vice versa. À noter que le mot « octets » porte un « s », cela signifie qu’une donnée est souvent encodée sur plusieurs octets.
Il y a donc toute une série de techniques à maîtriser, et connaître leurs équivalents Arduino (donc C) est toujours pertinent.
Compte tenu du faible débit d’un bus I2C, il est souvent nécessaire de manipuler des bits, ce qui permet d’activer une ou plusieurs options (en activant les bits correspondants) et en ne transmettant au final qu’un seul octet plutôt qu’une volée d’octets.
1. Octet et représentation binaire
Le but n’étant pas de faire un rappel complet sur l’arithmétique binaire, il paraît opportun de revenir sur la décomposition d’un octet en bits.
Un octet (byte en anglais) est composé de 8 bits numérotés de 0 à 7. Le bit le plus faible est toujours à droite et le bit le plus fort à gauche.
On parle de poids car chacun des bits a un poids (valeur numérique) qui influence la valeur totale contenue dans l’octet.
L’exemple suivant reprend un octet avec ses 8 bits, chacun des 8 bits étant fixé à 1 ou 0. Cet exemple donne beaucoup d’informations sur la décomposition binaire et la conversion vers une valeur en base 10 (nettement plus commode dans notre système de calcul).
Encodage binaire sur un octet
Avec un octet, il est possible d’encoder les valeurs entre 0 et 255.
En accumulant des octets, il est possible d’avoir des représentations en 16, 32 bits ou plus encore. Une valeur 16 bits est communément appelée un mot (word en anglais) et permet d’encoder une valeur entre 0 et 65535.
Encodage binaire sur deux octets
Il est également possible d’encoder des valeurs négatives dans une suite de bits (octet, mots, etc.). Dans ce cas, il faut employer un encodage particulier nommé « complément à 2 », point qui sera abordé ultérieurement.
2. Représentation binaire et hexadécimale
Représentation...
Bus I2C
1. Niveau d’API
Pour rappel, l’API I2C de MicroPython prend en charge trois niveaux d’API du bus I2C :
-
fonctions primitives pour les lectures/écritures brutes sur le bus,
-
opérations standards supportant des lectures/écritures à une adresse,
-
opérations mémoire supportant des lectures/écritures dans un registre.
Il est possible de consulter le détail de la documentation de l’API I2C dans la documentation des classes MicroPython (cf. Classes MicroPython courantes - La classe I2C).
Fonctions primitives
Ces méthodes permettent d’envoyer des données brutes sur le bus à l’aide de I2C.readinto(buf) et I2C.write(buf). Il n’y a pas encore de notion d’adresse dans les appels. Par conséquent, l’adresse du périphérique à contacter doit être précisée dans le premier octet de données lors d’un appel à write().
Ces primitives, concernant les bus I2C logiciels (Bit Banging I2C), se montreront utiles lors du portage de code en provenance d’AVR (microcontrôleur) où il y a rarement des implémentations avec des fonctions I2C de haut niveau. L’emploi des fonctions primitives permet souvent de garder un squelette de code presque identique durant le portage d’un langage C vers MicroPython.
Opérations standards
Ces méthodes permettent d’envoyer (ou réceptionner) des données en précisant l’adresse du périphérique concerné. Parmi les quelques méthodes...
Rétroportage CircuitPython (TSL2591)
Cette section propose de faire un rétroportage d’un pilote CircuitPython vers MicroPython. En effet, bien que CircuitPython soit dérivé de MicroPython, l’API de CircuitPython n’est pas identique à celle proposée par le firmware MicroPython d’origine.
Cela n’empêche pas d’adapter le code CircuitPython pour qu’il utilise l’API MicroPython. Voici l’exemple du rétroportage du pilote CircuitPython pour le capteur de luminosité TSL2591.
Breakout TSL2591 d’Adafruit
Le TSL2591 est un capteur I2C permettant de mesurer précisément le niveau de luminosité sur une très large gamme de mesures, de 188 µlux jusqu’à 88000 lux.
Ce capteur TSL2591 est abordé en détail dans le chapitre sur les capteurs et interfaces (cf. Capteurs et interface - Interface I2C, voir point « TSL2561 / TSL2591 »).
1. Localiser le pilote CircuitPython
Adafruit Industries propose des pilotes CircuitPython pour de nombreuses cartes breakout de sa gamme sur le GitHub suivant : https://github.com/adafruit/Adafruit_CircuitPython_Bundle/tree/master/libraries/drivers
Le pilote CircuitPython du TSL2591 est disponible ici : https://github.com/adafruit/Adafruit_CircuitPython_TSL2591
2. Rétroportage étape par étape
Voici une description étape par étape du rétroportage (backport) du TSL2591. La version MicroPython de la bibliothèque est d’ores et déjà disponible sur le GitHub suivant : https://github.com/mchobby/esp8266-upy/tree/master/tsl2591
a. Mise en place
Une fois la source CircuitPython identifiée, la première chose à faire est de préparer la mise en place de la future bibliothèque et son répertoire d’accueil.
Dans le cas présent, un répertoire /esp8266-upy/tsl2591 est créé dans les sources du dépôt esp8266-upy (déjà abordé à plusieurs reprises dans l’ouvrage).
Les sous-répertoires examples et lib sont créés et destinés à recevoir les exemples ainsi que la bibliothèque.
/esp8266-upy/tsl2591
├── docs
│ └── _static
│ ...
Poursuivre l’exploration I2C
Bien conscient que ce chapitre est relativement court pour un sujet aussi complexe que celui-ci, il est possible de poursuivre l’exploration de divers portages I2C sur le GitHub esp8266-upy (https://github.com/mchobby/esp8266-upy) avec une section reprenant uniquement les pilotes I2C (https://github.com/mchobby/esp8266-upy/blob/master/docs/indexes/drv_by_intf_I2C.md).
Le pilote source utilisé est généralement mentionné dans l’en-tête de la bibliothèque. Il est donc possible de poursuivre son apprentissage par comparaison des sources avec le pilote MicroPython.