Il y a trois semaines on vous a présenté le nouveau Yocto-MaxiKnob qui permet de d'interfacer des panneaux de commande assez facilement. Cette semaine, on vous montre comment intégrer des afficheurs alpha-numériques dans votre panneau de commande en utilisant un seul module Yoctopuce: le Yocto-I2C.
L'idée est d'utiliser des afficheurs basés sur le driver de LED HT16K33 de Holtek qui peut piloter jusqu'à 128 LEDs. Pour les besoins de la démonstration, on a choisi les nôtres chez Adafruit, n'importe quel autre modèle basé sur le même chip fonctionnera de la même manière, mais les constantes à utiliser pour piloter les LEDs pourront différer.
Afficheurs basé HT16K33 d'Adafruit
.
Le HT16K33
Une étude attentive du datasheet pour le HT16K33 nous révèle qu'il s'agit d'un chip assez facile à piloter en I2C. Les registres d'adresse font 4 bits de large et les paramètres de configuration font 4 bits de large aussi. En d'autres termes, chaque commande de configuration tient sur un seul byte.
- Son adresse I2C varie entre 0x70 et 0x77 suivant la configuration des entrées S2,S1 et S0
- Pour initialiser le chip il faut:
- Allumer l'horloge en écrivant 0x1 à l'adresse 0x2
- Configurer la pin INT/ROW en écrivant 0x0 à l'adresse 0xA
- Allumer l'écran en écrivant 0x1 à l'adresse 0x8
- Configurer l'intensité à 50% en écrivant 0x8 à l'adresse 0xE
- Pour allumer les LEDS, il faut écrire l'offset de départ (généralement 0) à l'adresse 0 suivi des bytes de données où chaque bit correspond à une led. Maximum 16 bytes en tout.
On peut donc facilement écrire un début de classe Python qui s'instancie avec une fonction YI2cPort qui permet d'initialiser le chip HT16K33 et de lui envoyer des données:
i2cPort = None
def __init__(self,I2C_port,addr): # YI2cPort function and A2A1A0 (0..7)
self.addr= (0x70 | ( addr & 3)) <<1
self.addrStr= format (self.addr , '02x')
self.i2cPort=I2C_port
self.i2cPort.set_i2cMode("400kbps")
self.i2cPort.set_i2cVoltageLevel(YI2cPort.I2CVOLTAGELEVEL_3V3)
self.i2cPort.reset()
self.i2cPort.writeLine("{S}"+self.addrStr+"21{P}") # wake up
self.i2cPort.writeLine("{S}"+self.addrStr+"A0{P}") # INT/ROW config
self.i2cPort.writeLine("{S}"+self.addrStr+"81{P}") # Display ON
self.i2cPort.writeLine("{S}"+self.addrStr+"E8{P}") # Intensity = 50%
def sendData(self,rawdata):
cmd ='{S}'+format(self.addr, '02x')+"00";
for i in range(0,len(rawdata)) :
cmd=cmd+format(rawdata[i], '02x')
cmd+="{P}"
self.i2cPort.writeLine(cmd)
Par contre, il est important de comprendre que la correspondance entre chaque bit de données et les LED dépend de la façon le fabricant de l'afficheur a interconnecté les LEDs et le HT16K33. Cependant, même si le fabriquant a "oublié" de documenter cette correspondance, il est assez facile de la déterminer empiriquement en testant les bits un par un.
Adafruit 7-Segment LED Backpack
On trouve chez Adafruit un petit PCB basé HT16K33, sur lequel on peut souder un afficheur 7 segments à 4 digits. Chaque digit correspond à un byte situé à une adresse paire de la mémoire HT16K33, les deux points du centre sont traités comme un caractère à part, stocké à l'adresse 0x04. Pour chaque digit, les bits sont organisés comme suit:
Organisation du 7-Segment LED Matrix Backpack d'Adafruit
Par exemple, on si on veut afficher un 1 : il faut envoyer le byte 0x02 + 0x04 = 0x06, si on veut afficher un 2 il faut envoyer 0x01+0x02 +0x40 +0x10 + 0x08 = 0x5B, plus généralement la liste de constantes correspondant aux digits 0 à 9 est 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F. On peut donc écrire une petite fonction qui écrit n'importe entier entre -999 et 9999 sur l'afficheur:
digits = [0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F]
def showInteger(self, number):
number = round(number)
if number < -999: raise Exception("min value is -999")
if number > 9999: raise Exception("max value is 9999")
data = [0] * 10 #array of 10 zeros
number = str(number)
index = 8
for i in range(0, len(number)):
if number[-i - 1] == "-": # negative sign
data[index] = 64
else:
data[index] = self.digits[ord(number[-i - 1]) - 48];
index = index - 2
if index == 4: index = 2 # skip middle colon
self.sendData(data)
Adafruit 14-segment LED Backpack
Adafruit vend aussi une version 14 segments de son petit board. Cette fois, chaque digit est piloté par deux bytes consécutifs organisés de la manière suivante:
Organisation du 14-segment LED Alphanumeric Backpack d'Adafruit
Par exemple, si l'on souhaite afficher un M majuscule, on obtient 0x1000 + 0x2000 + 0x0001 + 0x0004 + 0x0200 + 0x0400 = 0x3605, il faudra donc envoyer les bytes 0x36 puis 0x05.
A partir de là, on peut construire une représentation pour tous les caractères ASCII de 32 à 127 et écrire une fonction qui permette d'afficher une chaîne de caractères arbitraire sur l'afficheur. La seule subtilité étant la gestion du point décimal qui n'est pas forcément un caractère à part entière sur l'afficheur.
# representation for ASCII characters 32..127
font = [0x0000, 0x0006, 0x0220, 0x12CE, 0x12ED, 0x0C24, 0x235D, 0x0400,
0x2400, 0x0900, 0x3FC0, 0x12C0, 0x0800, 0x00C0, 0x0000, 0x0C00,
0x0C3F, 0x0006, 0x00DB, 0x008F, 0x00E6, 0x2069, 0x00FD, 0x0007,
0x00FF, 0x00EF, 0x1200, 0x0A00, 0x2400, 0x00C8, 0x0900, 0x1083,
0x02BB, 0x00F7, 0x128F, 0x0039, 0x120F, 0x00F9, 0x0071, 0x00BD,
0x00F6, 0x1200, 0x001E, 0x2470, 0x0038, 0x0536, 0x2136, 0x003F,
0x00F3, 0x203F, 0x20F3, 0x00ED, 0x1201, 0x003E, 0x0C30, 0x2836,
0x2D00, 0x1500, 0x0C09, 0x0039, 0x2100, 0x000F, 0x0C03, 0x0008,
0x0100, 0x1058, 0x2078, 0x00D8, 0x088E, 0x0858, 0x0071, 0x048E,
0x1070, 0x1000, 0x000E, 0x3600, 0x0030, 0x10D4, 0x1050, 0x00DC,
0x0170, 0x0486, 0x0050, 0x2088, 0x0078, 0x001C, 0x2004, 0x2814,
0x28C0, 0x200C, 0x0848, 0x0949, 0x1200, 0x2489, 0x0520, 0x3FFF]
def showString(self, string):
data = [0, 0, 0, 0, 0, 0, 0, 0]
n = 0
index = 0
while n < len(string):
if index >= 8:
raise Exception("String \"" + string + "\" cannot be displayed ")
code = ord(string[n])
if code < 32 or code > 127: raise \
Exception("Character \"" + string[n] + "\" at position #" + str(
n + 1) + " of string \"" + string + "\" cannot be displayed ");
digit = 0
if code == 46: digit = 0x4000 # decimal point
digit = digit | self.font[code - 32]
n = n + 1
if code != 46 and n < len(string) and string[n] == ".":
digit = digit | 0x4000
n = n + 1
data[index] = digit & 0xff
data[index + 1] = digit >> 8
index = index + 2
self.sendData(data)
Plusieurs afficheurs à la fois
On sait maintenant comment piloter un de ces afficheurs à l'aide d'un Yocto-I2C, mais on n'a évidement pas forcément envie d'utiliser un Yocto-I2C par afficheur. Il se trouve que I2C est un BUS et qu'on peut plus ou moins choisir l'adresse I2C du HT16K33 en agissant sur les entrées S0,S1,S2 du chip. Du coup il est possible d'en connecter plusieurs sur le même Yocto-I2C pour peu qu'on leur affecte des adresses différentes. Sur les produits d'Adafruit, il suffit de ponter les contacts A2, A1, A0. Les ponts mettent à 1 les bits correspondant de l'adresse I2C du HT16K33.
L'adresse de celui de gauche est 0x70, celle de celui de droite est 0x71
On peut donc connecter jusqu'à 8 HT16K33 sur le même BUS I2C. Notez que certaines versions du HT16K33 n'ont que deux bits d'adresse configurables.
On peut connecter plusieurs afficheurs au même Yocto-I2C
La même chose en vrai
Veillez tout de même à ne pas dépasser la limite de courant (~200mA) que peut délivrer Yocto-I2C, ou alors utilisez une alimentation externe.
Conclusion
Une fois qu'on a compris comment marchent ces afficheurs alpha-numériques basés HT16K33, il est assez facile d'écrire le code qui permet de les piloter depuis un Yocto-I2C. On vous a même écrit une classe complète en Python qui vous permettra de contrôler les afficheurs 7 segments et 14 segments d'Adafruit, mais aussi les matrices 8x8 et 16x8 basées sur le même chip. Si Python n'est pas votre langage de prédilection, le code est suffisamment court et simple pour pouvoir être traduit facilement.
Une dernière remarque, les afficheurs d'Adafruit sont censés pouvoir marcher aussi bien en 3.3V qu'en 5V, mais on a remarqué si on les alimente en 5V tout en communiquant en 3.3V comme le permet le Yocto-I2C, ils ont un peu tendance à planter, on n'a pas pris le temps d'en chercher la raison. Par contre, ils fonctionnent parfaitement en 3.3V au prix d'une légère baisse de luminosité.