Interfacer l'écran LCD1602-RGB de WaveShare

Interfacer l'écran LCD1602-RGB de WaveShare

Cette semaine, on teste un petit écran LCD fabriqué par Waveshare qui a la particularité d'avoir une interface I2C. On n'a pas trop de doutes sur la possibilité de l'interfacer avec un Yocto-I2C mais on s'attend à ce que ce ne soit pas trivial dans la mesure où les contrôleurs d'écran aiment bien faire les difficiles et réclament généralement une initialisation correcte avant d'accepter de fonctionner.



Cette difficulté est due au fait que même s'ils sont souvent vendus ensemble, les dalles et leurs contrôleurs sont rarement issus du même fabriquant. Les contrôleurs sont conçus pour être génériques et ainsi pouvoir piloter différents types de dalles, d'où des options qu'il faut initialiser correctement si on veut que ça marche.

L'écran LCD1602-RGB

L'écran de Waveshare se présente sous la forme d'un PCB de 90x32mm avec un écran à cristaux liquides de 2 lignes de 16 caractères. La zone affichable fait environ 60x13mm. Il est rétro-éclairé avec des LED RGB. Le port I2C est accessible via un connecteur et un câble avec le connecteur idoine est fourni. Connecter l'écran avec un Yocto-I2C est juste trivial.

L'écran LCD1602-RGB de WaveShare connecté à un Yocto-I2C
L'écran LCD1602-RGB de WaveShare connecté à un Yocto-I2C


Vous pouvez alimenter l'écran avec le 3.3V du bus I2C, mais vous pouvez aussi utiliser le 5V de la fonction powerOutput du Yocto-I2C, vous obtiendrez un contraste très légèrement meilleur.

Le rétro éclairage

L'écran est rétro-éclairé par des LED RGB pilotées par un chip PCA9633 de NXP. Assez logiquement, ce chip a une interface I2C. Son adresse est configurable, mais sur cet écran elle vaut 0xC0 (sur 8 bits). Après avoir reconnu son adresse sur le bus I2C, le PCA9633 s'attend à recevoir un byte de commande, organisé comme suit:

  • Bit 7: Auto-incrément du pointeur de registres si mis à 1
  • Bit 6-5: options d'auto-incrément
  • Bit 4 : toujours 0
  • Bit 3-0: pointeur de registres

il y a douze registres, mais ceux qui nous intéressent le plus sont les suivants

  • 0x00 : Mode register 1
  • 0x02 : Brightness control LED0
  • 0x03 : Brightness control LED1
  • 0x04 : Brightness control LED2
  • 0x08 : LEDOUT (LED driver output state)

Pour information, la LED 0 est bleue, la LED 1 est rouge et la LED 2 est verte.

Pour initialiser le chip, il faut

  1. Mettre le mode register 1 à zéro pour sortir le chip de son mode basse énergie
  2. Mettre LEDOUT à 0xff pour rendre les LED contrôlables individuellement

Ce qui donne les commandes I2C suivantes pour initialiser le PCA9633

{S}C00000{P} {S}C008FF{P}


Pour contrôler les LED, il faut mettre les valeurs de luminosité dans le registre LED0, LED1 et LED2. On peut le faire en une fois si on utilise l'auto-incrément. Par exemple pour la LED bleue à 0x33, le LED Verte à 0x55 et la LED Rouge à 0x77 on utilisera la commande

{S}C082335577{P}


Le rétro-éclairage c'est bon, ça marche
Le rétro-éclairage c'est bon, ça marche



L'affichage

L'affichage proprement dit est géré par un chip AiP31068 de Wuxi I-CORE Electronics Co., Ltd, un fabricant chinois à priori inconnu, mais on peut heureusement trouver un datasheet sur le site de Newhaven Display, un concurrent de WaveShare.

L'adresse sur 8 bits du AiP31068 est 0x7C. Après avoir reconnu son adresse sur le bus I2C, le contrôleur attend un byte qui lui indiquera si le/les bytes suivants décrivent une commande ou des données.

  • 0x80: commande
  • 0x40: données


Les commandes ont un format particulier, elles sont encodées sur 1 byte, le premier bit à un en partant du bit le plus significatif indique la commande et les suivants sont les paramètres de la commande en question. Ainsi:

  • bit 0 à 1 : commande Clear display
  • bit 1 à 1 : commande Return home
  • bit 2 à 1 : commande Entry mode set
  • bit 3 à 1 : commande Display ON/OFF control
  • bit 4 à 1 : commande Cursor or Display Shift
  • bit 5 à 1 : commande Function Set
  • bit 6 à 1 : commande Set CGRAM Address
  • bit 7 à 1 : commande Set DDRAM Address


Initialisation

Le datasheet donne la séquence de commandes à réaliser pour initialiser le chip, et ça, ça mérite d'être signalé parce que tous les fabricants ne se donnent pas forcément la peine de décrire cette séquence. Par contre, il y a des petites incohérences entre l'exemple donné et le reste du datasheet. Mais voici la séquence ce qu'on a réussi à faire marcher.

  1. Appeler la commande "Function Set" avec la valeur 8 pour configurer le contrôleur en mode 4 bits, 2 lignes et caractère de 5x8 pixels. La commande I2c est donc:

    {S}7C8028{P}

  2. Appeler la commande "Display ON/OFF control" avec la valeur 4 pour allumer l'écran

    {S}7C800C{P}

  3. Appeler la commande "Clear display" pour effacer l'écran

    {S}7C8001{P}

  4. Appeler la commande "Cursor or Display Shift" avec la valeur 0x4 pour que curseur se déplace automatiquement de gauche à droite et le pointeur de mémoire écran s'incrémente automatiquement.

    {S}7C8014{P}


Placer le curseur/ pointeur de données au bon endroit

La mémoire d'affichage de l'écran se trouve en DDRAM, bien que l'écran n'ait que 2x16 caractères, l'AiP31068 a en fait 128 bytes de mémoire. Les 64 (0x40) premiers pour la première ligne et les 64 suivants pour la deuxième. Pour placer le pointeur de données aux coordonnées (col, ligne) il faut envoyer le byte 0x80 + col + ligne*64. Par exemple,

  • Pour placer le curseur au début de la première ligne et on utilisera

    {S}7C8080{P}

  • Pour le placer au début de la seconde on utilisera

    {S}7C80C0{P}

  • Pour le placer sur la position n°4 de la seconde ligne on utilisera (attention, on compte a partir de la position 0, la position 4 est donc le cinquième caractère)

    {S}7C80C4{P}


Ecrire des caractères

Pour écrire des caractères, il faut placer le curseur à l'endroit désiré à l'aide de la fonction décrite précédemment, puis envoyer les données sous forme de codes ASCII. Attention, cette fois, on envoie des données, pas une commande, le byte juste après l'adresse doit donc être 0x40. Ainsi, pour écrire "1234" (ascii 0x31..0x34) en haut à gauche, on appellera

{S}7C8080{P} {S}7C4031323334{P}



Une Classe Python

On peut maintenant synthétiser tout ce qu'on a appris sur cet écran LCD1602-RGB en une petite classe Python qui permettra d'utiliser facilement les fonctionnalités de base. Si vous n'aimez pas Python, vous pouvez facilement la traduire dans le langage de votre choix.

class  WaveshareLCD1602RGB:
   def __init__(self,i2cPort):
      errmsg = YRefParam()
      self.i2cPort = i2cPort
      ## init I2C port
      self.i2cPort.set_i2cMode("400kbps")
      self.i2cPort.set_i2cVoltageLevel(YI2cPort.I2CVOLTAGELEVEL_3V3)
      self.i2cPort.reset()
      ## init PCA9633 RGB driver
      #Mode1=0x00
      self.i2cPort.writeLine("{S}C00000{P}")
      #ouput=0xFF , all led dimming/blinking can be controlled individually
      self.i2cPort.writeLine("{S}C008FF{P}")
      ## init AiP31068 driver
      # function set call : 4bit mode, LCD 2lines , 5x8 dots
      self.i2cPort.writeLine("{S}7C8028{P}")
      YAPI.Sleep(1, errmsg)
      # Display ON/OFF control: display on, cursor off, blick off
      self.i2cPort.writeLine("{S}7C800C{P}")
      YAPI.Sleep(1, errmsg)
      # clear display
      self.i2cPort.writeLine("{S}7C8001{P}")
      YAPI.Sleep(1,errmsg)
      # left to right, AC pointer increased
      i2cPort.writeLine("{S}7C8014{P}")
      YAPI.Sleep(1,errmsg)

   def setColor(self,r,g,b):
     self.i2cPort.writeLine("{S}C082"+'{:02x}'.format(b)+'{:02x}'.format(g)+'{:02x}'.format(r)+"{P}")

   def setCursorPosition(self,col,line):
     # warning  line can be 0 or 1 only
     self.i2cPort.writeLine("{S}7C80"+'{:02x}'.format(0x80|line*40| col)+"{P}")

   def write(self,line1,line2):
     for i in range(0,2):
       self.setCursorPosition(0,i)
       command = "{S}7C40"   # about to write data
       if i==1 : line1=line2 # quick and dirty swap
       for j in range(0,len(line1)):
          command =command+ '{:02x}'.format(ord(line1[j]))
       for j in  range( len(line1),16):
          command = command + "20" ## pad with spaces
       command=command+ "{P}"
       self.i2cPort.writeLine(command)


Et un code d'exemple pour l'utiliser:

from yocto_api import *
from yocto_i2cport import *

errmsg = YRefParam()
if YAPI.RegisterHub("usb", errmsg) != YAPI.SUCCESS:
    sys.exit("init error" + errmsg.value)

i2cPort = YI2cPort.FirstI2cPort()
if i2cPort is None:
    sys.exit('No module with I2C port found')

display = WaveshareLCD1602RGB(i2cPort)
display.setColor(255,0,0)
display.write("    Hello","      World!")
YAPI.FreeAPI()


Ça marche!
Ça marche!


Conclusion

En 2024, ce genre d'écran peut paraître un peu obsolète, le contraste et l'angle de vue sont loin d'être aussi bons que ce que l'on peut obtenir avec un écran OLED ou e-paper moderne. Cependant, si vous avez besoin d'afficher quelques dizaines de caractères sur un petit écran avec, en option, la possibilité de changer la couleur du rétro-éclairage pour attirer l'attention de votre utilisateur, l'écran LCD1602-RGB de WaveShare connecté à un Yocto-I2C pourrait bien faire votre affaire.

Bien que le plus dur soit fait, notez que la petite classe qu'on a écrite ici ne fait que le minimum syndical. Non seulement elle ne fait aucune vérification de paramètres ou d'erreur, mais l'écran est semble-t-il capable de faire des choses plus sophistiquées, comme afficher un curseur clignotant, et faire défiler les lignes horizontalement.

Commenter aucun commentaire Retour au blog












Yoctopuce, get your stuff connected.