Cette semaine, on vous présente un nouveau module qui vient compléter notre gamme d'interfaces électriques. Il s'agit du Yocto-I2C qui vous permettra d'interfacer quantité de petits circuits électroniques bien utiles. On sait que certains d'entre vous l'attendent avec impatience....
Les bus I2C sont très courants en électronique digitale: beaucoup de circuits électroniques périphériques sont fournis avec cette interface: capteurs, mémoires, petits écrans etc.. Le gros intérêt d'I2C par rapport à son concurrent direct, SPI, c'est qu'il n'a besoin que de quatre fils, y compris l'alimentation. Pour tout vous dire, la plupart des capteurs Yoctopuce utilisent des senseurs I2C.
Le Yocto-I2C
Le Yocto-I2C se présente sous la forme d'un module de 50x20mm. Il offre un port I2C qui peut fonctionner au choix en 3.3V ou 1.8V. Il dispose aussi d'une alimentation supplémentaire qui peut fournir du 5V, 3.3V et 1.8V, elle vous permettra au besoin d'alimenter votre périphérique avec une tension différente de celle du bus I2C, ce qui est un cas relativement courant.
Le Yocto-I2C
Comme pour toutes nos interfaces électriques digitales, non seulement vous pouvez piloter le Yocto-I2C directement depuis votre programme, mais vous pouvez aussi utiliser le système de jobs intégré pour que le Yocto-I2C interroge vos périphériques de manière autonome et vous présente les résultats à la manière d'un capteur Yoctopuce, avec tous les avantages qui vont avec: callbacks, enregistreur de données, calibration, etc. ...
Vous l'avez peut-être noté, le Yocto-I2C n'est pas censé fonctionner directement avec un bus I2C 5V, nous avons fait ce choix de design non seulement pour des raisons techniques, mais aussi parce que les périphériques I2C 5V ne sont pas très courants. Sans compter qu'il y a de bonnes chances pour qu'un port I2C slave prévu pour fonctionner en 5V fonctionne aussi très bien en 3.3V. Dans le pire des cas il est possible de s'en sortir en intercalant un translateur de niveau.
Attention, le Yocto-I2C est conçu pour se comporter comme un maitre I2C, vous ne pourrez pas l'utiliser pour émuler un esclave I2C. En revanche, vous pourrez parfaitement contrôler plusieurs esclaves I2C avec le même module pour autant que ces esclaves fonctionnent avec la même tension et aient des adresses I2C différentes.
Exemples d'application:
Voici quelques exemples d'applications toutes simples.
Module RTC, DS3231 d'Adafruit
Adafruit commercialise un petit breakout board hébergeant un chip Real Time Clock (RTC) DS3231 de Maxim.
Le DS3231 sur le breakout board d'Adafruit
Interfacer ce chip avec un Yocto-I2C est un bon exercice: L'addresse I2C du chip est 0x68, les secondes, minutes, heures, jour de la semaine, jour du mois, mois, année sur deux digits, sont stockés en BCD dans les registres 0x00 à 0x06, avec une petite subtilité: le changement de siècle (1900->2000) est stocké dans le bit 7 du registre 5. Voici un exemple en Python qui met à l'heure le RTC puis le relit toutes les secondes.
from yocto_api import *
from yocto_i2cport import *
def getRtcTime(i2cPort):
i2cdata = [0x00] # start at register 0
buff = i2cPort.i2cSendAndReceiveBin(0x68,i2cdata,7)
s = (buff[0] & 0xf) + 10*(buff[0]>>4)
m = (buff[1] & 0xf) + 10*(buff[1]>>4)
h = (buff[2] & 0xf) + 10*(buff[2]>>4)
D = (buff[4] & 0xf) + 10*(buff[4]>>4)
M = (buff[5] & 0xF) + 10*((buff[5] & 0x070)>>4)
Y= (buff[6] & 0xF) + 10*(buff[6]>>4) +1900
if (buff[5] &0x80)!=0: Y=Y+100
return datetime.datetime(Y,M,D,h,m,s)
def setRtcTime(i2cPort):
i2cdata = []
now = datetime.datetime.now()
centuryBit =0x00
if (now.year>=2000): centuryBit=0x80
i2cdata.append(0) # start at register 0
i2cdata.append( (now.second %10) | ((now.second //10)<<4))
i2cdata.append( (now.minute %10) | ((now.minute //10)<<4))
i2cdata.append( (now.hour %10) | ((now.hour //10)<<4))
i2cdata.append( now.weekday() )
i2cdata.append( (now.day %10) | ((now.day //10)<<4))
i2cdata.append( (now.month %10) | ((now.month //10)<<4) | centuryBit)
i2cdata.append( ((now.year %100) %10) | (((now.year %100) //10)<<4))
i2cPort.i2cSendBin(0x68,i2cdata)
# init API
errmsg = YRefParam()
if YAPI.RegisterHub("usb", errmsg) != YAPI.SUCCESS:
sys.exit("init error" + errmsg.value)
# find I2C port
i2cPort = YI2cPort.FirstI2cPort()
if i2cPort is None:
sys.exit('No module connected (check cable)')
# configure I2C Port
i2cPort.set_i2cMode("400kbps")
i2cPort.set_i2cVoltageLevel(YI2cPort.I2CVOLTAGELEVEL_3V3);
setRtcTime(i2cPort)
while True:
YAPI.Sleep(1000,errmsg)
print(getRtcTime(i2cPort))
Lidar-Lite V3, de Garmin
Le Lidar-Lite V3, commercialisé par Garmin, est un petit télémètre laser capable de mesurer des distances jusqu'à 40 mètres. En plus d'une interface analogique, il dispose d'une interface I2C.
Le Lidar-Lite V3, de Garmin
Officiellement, le Lidar-Lite V3 fonctionne en 5V, mais tant que les fils ne sont pas trop long, il fonctionne très bien si on l'alimente en 5V alors qu'on fait fonctionner le bus I2C en 3.3V.
Configuration du Port I2C pour le Lidar-Lite V3
L'adresse I2C du Lidar-Lite est 0x62. Pour obtenir une mesure, il faut écrire 0x04 dans le registre 0x00, attendre que le bit 0 du registre 0x01 passe à zéro, puis lire la distance en cm, codée sur deux bytes dans le registre 0x8f. On peut facilement mettre à profit le système de jobs du Yocto-I2C pour lire la valeur du senseur et la mapper par exemple sur le Generic Sensor 1 du module. On peut faire ça avec un job constitué de trois tâches:
- Tâche 1 (périodique, une fois, pour initialiser)
- assert !isset($state)
- compute $state = 0
- Tâche 2 (périodique, 200ms, pour lancer la mesure)
- assert $state == 0
- writeLine @62:0004
- expect 62:{A}{A}{A}
- compute $state = 1
- Tâche 3 (périodique, 50ms, pour attendre le résultat)
- assert $state == 1
- writeLine@62:01xx
- expect 62:{A}{A} 62:{A}($ready:BYTE)
- assert($ready & 1) == 0
- writeLine @62:8Fxxxx
- expect 62:{A}{A} 62:{A}($1:WORDB)
- $state = 0
Définition du job pour lire le Lidar-Lite
Afficheur Adafruit 0.8" 8x16 LED Matrix
Adafruit commercialise un petit afficheur basé sur deux matrices de LEDS 8x8 et piloté par un chip HT16K33 qui fonctionne en 3.3v.
Utilisation avec le petit afficheur Adafruit 0.8" 8x16 LED Matrix
Après une petite séquence de d'initialisation, il suffit d'écrire une série de 16 bytes à partir du registre 0x00 pour les voir s'afficher sur les LED. Attention, les bytes sont mappés verticalement et ils sont entrelacés, du coup la fonction d'affichage ressemble plus à une formule cabalistique qu'à un honnête exemple de démonstration. Voici une petite demo qui affiche un entier arbitraire entre 0 et 9999.
from yocto_i2cport import *
def HT16K33_init(i2cPort):
#clock enable, ROW/IN is a led ouput,dimming 16/16,blinking off, display off
i2cdata = [0x21,0xA0,0xEF,0x80]
for i in range(0,4) : i2cPort.i2cSendBin(0x70, [i2cdata[i]])
#clean display buffer
i2cdata = [0] * 17
i2cPort.i2cSendBin(0x70, i2cdata)
#blinking off, display on
i2cdata = [0x81]
i2cPort.i2cSendBin(0x70, i2cdata);
def HT16K33_display_value(i2cPort,value):
value=int(value)
if value<0: value=0
font= [0x3e223e,0x3e0000,0x2e2a3a,0x3e2a22,0x38101e, # digits 0..9
0x3a2a2e,0x3a2a3e,0xe3202,0x3e2a3e,0x3e2a2e]
i2cdata = [0] * 17 # empty memory buffer
for x in range(0,4):
digit = value % 10
value= value//10
for j in range(0,4):
i2cdata[1+2*j+(x*17//2&0xf)]= (font[digit] >> 8*(3-j) ) & 0xff;
i2cPort.i2cSendBin(0x70, i2cdata);
# init API
errmsg = YRefParam()
if YAPI.RegisterHub("usb", errmsg) != YAPI.SUCCESS:
sys.exit("init error" + errmsg.value)
# find I2C port
i2cPort = YI2cPort.FirstI2cPort()
if i2cPort is None:
sys.exit('No module connected (check cable)')
# configure I2C Port
i2cPort.set_i2cMode("400kbps")
i2cPort.set_i2cVoltageLevel(YI2cPort.I2CVOLTAGELEVEL_3V3);
HT16K33_init(i2cPort)
for i in range (0,10000):
HT16K33_display_value(i2cPort,i)
YAPI.Sleep(100,errmsg)
Encore plus fort...
Comme on peut mettre plusieurs esclaves I2C sur le même bus I2C, rien ne nous empêche de mettre les deux exemples précédents ensemble.
L'afficheur d'Adafruit et le Lidar-Lite sur le même bus I2C
L'idée est simplement de lire les valeurs du Lidar-Lite que le Yocto-I2C a mappé sur le genericSensor1 et de les afficher:
i2cPort = YI2cPort.FirstI2cPort()
if i2cPort is None:
sys.exit('No module connected (check cable)')
serial = i2cPort.get_module().get_serialNumber()
power = YPowerOutput.FindPowerOutput(serial+".powerOutput")
power.set_voltage(YPowerOutput.VOLTAGE_OUT5V); # power the Lidar-Lite
sensor = YGenericSensor.FindGenericSensor(serial+".genericSensor1")
# configure I2C Port
i2cPort.set_i2cMode("400kbps")
i2cPort.set_i2cVoltageLevel(YI2cPort.I2CVOLTAGELEVEL_3V3);
# start the LIDAR handling job on the device
# result is mapped on generic sensor #1
i2cPort.set_currentJob("lidar.job")
HT16K33_init(i2cPort)
while True:
value =sensor.get_currentValue()
HT16K33_display_value(i2cPort,value)
YAPI.Sleep(200,errmsg)
Et voila le résultat:
Deux périphériques sur le même bus I2C
Note à l'attention des débutants
Si vous n'avez jamais interfacé de périphérique I2C, vous seriez peut-être tenté de croire qu'il vous suffira de lui connecter un Yocto-I2C pour que les données apparaissent magiquement. Les produits Yoctopuce sont très plug-and-play, mais pas à ce point. Il vous faudra malheureusement étudier attentivement le datasheet du périphérique que vous voulez utiliser pour comprendre et implémenter son protocole I2C. Le principe de base des protocoles I2C est toujours le même, mais chaque interface a ses particularités. Notez que la qualité d'un composant et celle de son datasheet sont souvent étroitement liés, le temps passé à étudier soigneusement un datasheet avant de choisir un composant a de bonnes chance d'être récompensé par une implémentation sans mauvaise surprise... vous ne pourrez pas dire qu'on ne vous a pas prévenus :-)
Conclusion
La plupart des ordinateurs de développement tels que les Raspberry PI, Beaglebone, Arduino etc.. disposent déjà de ports I2C exploitables par l'utilisateur. Maintenant vous pouvez en faire autant depuis un ordinateur classique, et toujours sans avoir à installer de drivers :-)