E-paper, SPI & RAM
Lorsque j'ai voulu faire mon réseau de capteurs de température, il me fallait afficher sur un écran les informations. Pour que ça puisse marcher sur pile, je me suis dit que j'allais essayer un écran en papier électronique. Sauf que...
Introduction
Pour afficher la température provenant de mes capteurs répartis un peu partout, à l'intérieur de la maison comme à l'extérieur, un écran en papier électronique ou e-paper me paraissait une bonne idée :
- Ça ne consomme pas d'énergie, et donc c'est très favorable pour utiliser des piles.
- La lisibilité est remarquable.
- On voit même des écrans en couleur apparaitre !
Comme moi, vous avez dû remarquer ça sur les étiquettes électroniques de nos supermarchés, on voit souvent du rouge à présent, ainsi que des écrans qui sont relativement grands, le tout fonctionnant manifestement sur pile, et téléchargé à distance pour la mise à jour par-dessus le marché.
Vu le nombre d'étiquettes dans un supermarché, au moins des milliers, voire des dizaines de milliers, je m'attends à me procurer ce genre d'écran à un coût très faible, genre quelques euros, car sinon l'investissement serait vraiment trop important.
Eh bien non. C'est assez cher.
Les "makers" trouveront relativement facilement ces écrans e-paper chez Waveshare, car les logiciels (les drivers) sont également fournis pour des cartes de développement courantes comme les Arduinos, et heureusement car il faut son permis de conduire pour s'en servir comme on verra plus loin.
- Un peu moins d'une dizaine d'euros pour un petit écran, et il ne faut pas oublier d'acheter la carte d'interface avec (car il faut générer des tensions assez importantes avec de la circuiterie externe à l'écran).
- Et on arrive facilement à une quarantaine d'euros pour un écran de 5 pouces en 4 couleurs...
Avec au moins une dizaine de capteurs, il me faudrait un écran assez grand pour tout afficher, surtout que j'aimerais bien voir les courbes de températures journalières, les min-max de la journée et les records min-max, ceci pour faire le lien avec ma consommation d'énergie pour le chauffage.
Tant qu'à faire et à essayer, j'ai aussi décidé de réaliser un capteur (pas la station de réception) qui afficherait directement la température de la pièce, en plus de la transmettre. J'ai donc fait l'acquisition :
- d'une petite carte "2.15inch" 296x160 pixels,
affichage 6 cm x 3.2 cm noir/blanc/rouge - d'une grande carte "5.83inch" 648x480 pixels,
affichage 12 cm x 8.8 cm noir/blanc/rouge/jaune
Waveshare ne sont pas les seuls, vous trouverez aussi GooDisplay, Pervasive Displays et certainement d'autres.
De nombreux revendeurs sévissent, genre Adafruit, Sparkfun et consorts, mais aussi d'autres marques dont on se demande si ce sont des produits originaux. En tout cas, en France, c'est forcément un revendeur. Et si vous cherchez sur Aliexpress ou Amazon, ce sera la jungle des revendeurs (et aussi un moyen de payer moins cher en étant attentif).
On trouve aussi des grands écrans très chers comme ceux de Samsung ou chez le pionnier Eink.
Voici le résultat de mes expérimentations. On est loin de la facilité, et c'est assez mal exploité, voire mal conçu en réalité, les gars ne sont pas très malins.
Fonctionnement du e-paper
EPD (Electronic Paper Display), E-ink, e-paper, papier électronique, ...
C'est très mal expliqué sur le site de Waveshare, ou alors c'est paumé dans leurs dizaines de pages, avec plein de produits différents. Et vous trouverez rarement des sites qui vous expliquent ça correctement, je vais tenter de faire mieux ça ne pourra pas être pire.
Le papier électronique sont des écrans « électrophorétiques » qui déplacent des billes électriquement chargées sous l'action d'un champ électrique :
Le gros avantage est qu'une fois le déplacement réalisé, on peut couper le courant, l'affichage reste stable, d'où la dénomination de "papier électronique" ou e-paper.
Il existe pas mal de variantes pour réaliser du papier électronique, mais je vais rester sur du simple. Vous trouverez des détails sur Wikipédia par exemple.
Fantômes
Pour l'instant, ça parait simple : on applique un champ électrique, les billes se déplacent, et c'est terminé.
Sauf que les billes ne se déplacent pas si facilement, et peuvent éventuellement rester « collées » d'un côté ou de l'autre de l'écran, du fait de divers effets parasites comme une accumulation de charges sur les grilles d'application du champ électrique. C'est ce qu'on appelle le "ghost effect", ou "image fantôme" car l'image précédente reste encore un peu visible, des billes ne s'étant pas correctement déplacées.
Pour éliminer cela, chaque fabriquant possède sa recette, et ça n'est pas si facile car cet effet dépend beaucoup de la technique de fabrication des billes et des grilles qui appliquent le champ électrique. L'idée consiste à appliquer un champ électrique alternatif assez puissant pour « secouer » suffisamment les billes afin de les détacher des bords.
Troisième couleur et
rafraichissement d'écran
Ici, une fois que les rouges sont placées, on finit par mettre les blanches/noires du bon côté.
Au passage, notez les tensions de ±15 volts requises.
Ce n'est qu'un exemple, ce qu'il faut retenir, c'est que :
- Forcément, la séquence est complexe et sera programmée quelque part, avec des temps variables suivant le procédé de fabrication et la température, et que chaque écran individuel aura probablement sa séquence particulière, il n'y aura pas de séquence "universelle".
- Et que forcément, ça se verra, l'écran va clignoter comme un sauvage à ce moment-là, et c'est aussi le moment où la consommation électrique se produira.
Si vous voulez vous servir d'un écran e-paper pour afficher des données fréquemment, par exemple toutes les secondes,
alors il vous faudra chercher un produit qui permettre le "partial refresh", et ce sera souvent des petits écrans.
Mais pas de la couleur.
Les vendeurs font la promotion de cette possibilité quand elle existe, sinon, méfiez-vous. Épluchez la datasheet avant d'acheter.
Tension d'alimentation
La panoplie des écrans TFT est utilisée pour piloter chaque pixel individuellement, et donc je m'attends à retrouver des puces de pilotage directement déposées sur l'écran, et ça n'a pas raté.
Prenons l'exemple de Waveshare et de l'écran 2.15inch que j'ai acheté, avec ses 296 lignes de 160 colonnes. On y retrouve, mais pas directement (c'est planqué dans un wiki) :
- la datasheet de l'écran (2.15inch e-Paper (B) Data Sheet)
- le schéma électrique du module d'interface (driver board schematic)
Waveshare annonce une tension d'alimentation de 3.3 volt, mais également 5 volts si on utilise le module d'interface. Alors effectivement, on retrouve sur le module un circuit spécifique pour adapter la tension des signaux de commande, ainsi qu'un régulateur de tension LDO réglé à 3.3 volts.
Le module d'interface est indispensable car c'est aussi là que sont produites les tensions relativement élevées, utiles pour rafraichir l'écran. La dizaine de volts requise est produite grâce à un montage élévateur de tension, totalement indépendant et directement connecté à l'écran. Autrement dit, l'élévateur de tension est totalement décorrélé de la tension annoncée de 3.3 volts.
Et quand on lit la datasheet (je sais, c'est casse-pied), on s'aperçoit que la tension opérationnelle se situe entre 2.2 volts et 3.7 volts, avec une tension de cœur logique de 1.8 volt (très classique), mais sans explications !
L'explication est niaise. Les gens de Waveshare ont recopié sans discernement la datasheet de la puce qui pilote le TFT, celle avec qui on traite réellement. Ils doivent vraiment nous prendre pour des abrutis.
La puce vraiment utile de l'écran « 2.15inch e-Paper (B) » est la suivante :
- SSD1680 de Solomon Systech (mais pas de datasheet dispo)
- On peut trouver la datasheet du SSD1680 chez Crystalfontz, un fournisseur de solutions pour piloter les écrans.
Vous retrouverez cette puce certainement dans pas mal d'autres implémentations d'écran de papier électronique, ce qui est quelque part une bonne nouvelle car la programmation sera quasiment identique...
Le diagramme nous éclaire sur l'organisation de la puce. On va laisser de côté toute la sauce qui concerne la génération des ondes de programmation des pixels (en rose), et remarquer certaines choses :
- La puce possède son propre régulateur de tension, ainsi que l'attirail nécessaire pour générer les autres tensions utiles, mais il était impossible d'intégrer la bobine et le transistor particulier pour réaliser un élévateur de tension.
- La puce fonctionne à 1.8 volt. La tension externe requise est de 2.2 volts minimum, et elle sert surtout à adapter les signaux d'entrées/sorties.
- La puce possède un capteur de température interne, mais permet d'utiliser un autre capteur externe via une interface I2C. Il faut ignorer totalement cela pour notre usage, et l'I2C n'est pas utilisable pour autre chose.
- La puce possède une mémoire OTP (One Time Programmable) qui va pouvoir stocker les bonnes formes d'onde, les bonnes séquences de tensions qui feront fonctionner correctement l'affichage. On n'y touchera pas !
- Une RAM est intégrée, divisée en 2 banques, une pour gérer le noir/blanc, l'autre pour le rouge, au total 2 bits par pixel, mais chaque banque est accessible séparément. On doit remplir la RAM, puis lorsque l'on enverra l'ordre « Master Activation » (la fameuse commande 0x20), la puce déroulera la séquence de tensions qui déplacera les billes colorées, celle qui prend un certain temps, et même un temps certain.
Conclusion sur la tension : ça fonctionne à partir de 2.2 volts.
Je le confirme par l'expérience.
Pour utiliser correctement le module sous basse tension, le plus simple est de court-circuiter le régulateur de tension LDO. Reliez simplement l'entrée Vin et la sortie Vout, et il deviendra inopérant.
C'est important de garder la partie de la circuiterie qui pilote la mise sous tension à l'aide de la commande PWR.
- Lorsque PWR est à 1, tout est alimenté avec la tension que vous fournirez (le 2.2 volt).
- Lorsque PWR est à 0, alors un transistor coupe totalement l'alimentation, et le l'ensemble du module consomme alors un courant inférieur au microampère.
De plus, il faut mettre hors tension l'écran e-paper, car si vous le laissez sous tension, alors des charges peuvent s'accumuler indûment, et rendre votre écran malade, n'arrivant plus à déplacer les billes électrostatiques.
Transfert d'images
À un moment ou à un autre, il faudra envoyer la valeur de chaque pixel depuis l'unité de calcul (votre microcontrôleur, pour moi ce sera un MSP430) vers l'écran, ici la RAM de la puce SSD1680.
Ceci est réalisé après avoir envoyé les commandes d'initialisation, dans le cas présent à travers le bus SPI. Il existe un paquet de commandes, décrites dans la datasheet, et la plupart sont d'un intérêt relatif pour le programmeur, intéressons-nous à celles vraiment utiles.
Bug Master Activation
J'ai constaté que l'envoi de la commande « Master Activation (0x20) » n'était pas exécutée tant qu'un autre ordre n'était pas envoyé (ça se voit facilement en debug, avec une exécution pas à pas).
Pour contourner ça, ajouter juste après un ordre bidon, par exemple la commande « NOP No operation (0x7F) » est naturellement désignée pour faire ça.
C'est le même cinéma pour le remplissage automatique de la RAM (avec un damier, ou tout à la même valeur) [Auto-Write 0x46 et 0x47].
Pourquoi tant de haine ?
Adressage
- Chaque valeur de pixel doit être placée à une adresse donnée de la RAM du SSD1680. Ceci est réalisé avec les commandes (0x4E & 0x4F) qui envoient la position en X et la position en Y.
- L'envoi des données se fait avec deux commandes (0x24 & 0x26), une pour chaque banque de pixels, car nous avons la banque des pixels noirs et blancs, et la banque des pixels rouges. La donnée est placée juste après l'envoi de la commande.
Envoi par paquets
Comme il serait très long et fastidieux d'envoyer pour chaque pixel l'adresse où écrire (soit deux valeurs, ici 5 bits pour les X [160 colonnes] et 9 bits pour les Y [296 lignes]), puis la donnée (ici un premier bit, 0 pour le noir ou 1 pour le blanc, puis un second bit, 1 pour le rouge et 0 pour rien), les données sont transmises par paquet :
- On envoie 8 bits d'un coup, soit un octet pour 8 pixels. Ici, il s'agit de 8 pixels consécutifs
sur la même ligne.
- Du coup, l'adresse envoyée pour les X est le numéro de colonne divisé par 8 (= décalé de 3 bits), autrement dit 0 pour les pixels 1..8, [1] pour les pixels 9..16, ... [19 = 0x15] pour les pixels 153..160
- Pour les Y, c'est le numéro de ligne moins 1, soit 0 pour la première ligne, 295 = 0x127 pour la dernière.
- Oui, les informaticiens comptent à partir de 0, le premier est le numéro 0, et en hexadécimal.
- On peut envoyer autant d'octets que l'on souhaite les uns à la suite des autres, inutile d'envoyer à chaque fois l'adresse.
L'écriture de 8 bits d'un coup dans la RAM, sans tenir compte de ce qui est déjà écrit dedans, est catastrophique pour le programmeur, et les concepteurs de la puce n'ont pas été très malin sur ce coup-là.
En effet, il est impossible d'allumer un pixel sans dézinguer les 7 autres. Quelle valeur mettre ? Il faut donc avoir en mémoire, du côté du microcontrôleur, la valeur initiale des 7 autres afin de pouvoir combiner correctement.
Il aurait suffi qu'il existe une commande qui réalise une opération logique comme un « OU » avec les valeurs de la RAM avant écriture pour qu'on s'en sorte... Mais non, il y a que dalle.
L'envoi par paquet suppose une convention sur l'incrémentation des adresses pour chaque octet envoyé. La simplicité veut qu'il s'agisse de pixels consécutifs, autrement dit si vous venez d'envoyer un octet pour les pixels 9..16 de la cinquième ligne, alors l'octet suivant concerne les pixels 17..24 de la même ligne.
C'est effectivement ce qui se passe par défaut, l'adresse des X avance à chaque envoi. Et arrivé en fin de ligne (adresse 19 en X), on s'attend à revenir en début de ligne (on revient à l'adresse 0 en X), et à augmenter d'un cran l'adresse en Y (on passe de la cinquième à la sixième ligne, par exemple).
Incrémentation d'adresse
Mais on peut reprogrammer ce bel ordonnancement :
- On peut choisir d'incrémenter (sens normal) ou décrémenter l'adresse des X. Autrement dit aller à reculons.
- On peut choisir d'incrémenter ou décrémenter l'adresse des Y. Autrement dit monter au lieu de descendre.
- On peut choisir d'incrémenter en premier les X, puis les Y (sens normal, horizontal) ou incrémenter d'abord les Y, puis les X (on avance par colonne, et non par ligne, sauf qu'il s'agit de paquets de 8 pixels en ligne).
Ça s'ajuste avec la commande 0x11.
Avec cette possibilité, c'est plus facile de manipuler les rotations de l'écran.
Définition de la fenêtre
Mais la puce offre une possibilité supplémentaire : le choix d'une fenêtre rectangulaire. Ceci dans le but de pouvoir remplir seulement une petite section de la RAM de manière simple.
Le mécanisme est simple : quand on arrive en bord de fenêtre, autrement dit que le compteur d'adresse atteint la valeur extrême (maximale ou minimale), alors le compteur reprend « de l'autre côté ». C'est de toute manière ce qui est fait avec la totalité de la RAM.
Les valeurs de bord sont ajustées avec les commandes 0x44 pour les X, et 0x45 pour les Y.
C'est particulièrement pratique pour remplir un petit secteur, par exemple un logo dans un coin, il suffit d'appeler l'image et de transférer les pixels.
Bug d'incrémentation
Sauf que j'ai constaté un comportement, probablement un bug, très gênant : j'ai l'impression que l'incrémentation en Y est toujours négative, et du coup le démarrage commence toujours « en bas de la fenêtre », et à reculons.
La commande 0x46 donne un indice sur l'organisation bizarre de tout ça. Manifestement, l'origine est en bas à gauche (et pas en haut à gauche) en ce qui concerne les "gates et sources", en fait les lignes et colonnes reliées à la puce. Ça se voit avec l'origine du damier...
Taille de RAM et images
Si vous avez déjà envoyé des données à un écran, vous saisirez rapidement qu'en pratique, pour des raisons de vitesse, on calcule d'abord une image localement avec le microcontrôleur en manipulant les données dans une mémoire tampon locale.
Autrement dit, on calcule l'image à transférer avant d'envoyer le paquet.
En effet, s'il fallait à chaque fois faire une requête sur un bus externe pour accéder à un pixel, ça deviendrait très vite très long à faire. Et cela suppose que l'on puisse non seulement écrire, mais aussi lire la mémoire de l'écran facilement.
Il est quasiment inévitable d'avoir à lire la mémoire d'écran pour calculer l'image. Si vous voulez changer la valeur d'un seul pixel, comme ils sont organisés en groupe de 8 pixels dans un octet, et que vous ne pouvez écrire que 8 pixels à la fois, alors il faut chercher la valeur du groupe de 8 (= lire un octet de la mémoire d'écran), mettre à jour le pixel cible mais surtout garder inchangés les 7 autres pixels, sinon vous allez les effacer en renvoyant le groupe de 8 à la mémoire d'écran.
Cette histoire-là n'arrive pas quand vous chargez d'un bloc une fenêtre avec une image, mais c'est le seul cas avec l'écriture d'un texte (qui écrasera la section où il est écrit). Si vous voulez afficher une courbe, alors vous allez calculer chaque point (x,y) de la courbe, et placer ce point dans le magnifique tableau où vous aurez déjà tracé les axes. Et en traçant la courbe, il ne faut pas flinguer les pixels environnants, les autres courbes, ni les axes d'ailleurs.
Pour l'écran 2.15inch de 160x296 pixels, cela fait une mémoire, pour une seule banque (il en faut une seconde pour le rouge), de 47 360 bits, soit 5920 octets. C'est beaucoup trop pour la plupart des microcontrôleurs où on a plutôt 512 octets, 10 fois moins.
Et c'est le carnage pour l'écran 5.83inch de 648x480 pixels, il faut deux fois 38 880 octets...
Interface SPI
Tous les échanges entre le microcontrôleur et la puce SSD1680 qui pilote le papier électronique se font par l'interface SPI, qui est très classiquement et basiquement une interface dite « full-duplex ».
Outre les broches de sélection et parfois quelques autres signaux supplémentaires, indépendants du protocole SPI, trois fils sont nécessaires :
- Une horloge est donnée obligatoirement par le circuit maitre, ici le microcontrôleur.
- MOSI (Master Out Slave In) est un fil permettant d'acheminer les données du maitre vers l'esclave.
- MISO (Master In Slave Out) achemine les données en sens inverse.
Le maitre peut donc simultanément envoyer et recevoir des données, c'est pour ça que ça s'appelle full-duplex.
C'est toujours le maitre qui donne les coups d'horloge.
Le maitre aurait aussi pu envoyer une donnée sur MOSI en même temps que de recevoir une donnée sur MISO.
Ces abrutis ont utilisé la même broche pour recevoir les données ET en envoyer. Regardez bien le diagramme de la puce : la broche SDA est bidirectionnelle.
Ce qui, au passage, pose un gros conflit si jamais vous demandez à recevoir des données du SSD1680,
car il va vouloir prendre possession du fil MOSI, dont la tension est imposée par votre microcontrôleur,
et un court-circuit (momentané) pourrait se produire...
Mais ça n'a pas l'air de gêner Waveshare. De toutes manières, faut faire avec.
Astuce pour marcher en half-duplex
L'échange de données simultanément est interdit, forcément.
Pour que cette histoire de court-circuit puisse correctement fonctionner, non seulement les échanges de données simultanés sont interdits (de facto chez l'esclave), et vaut mieux programmer les broches du microcontrôleur de manière adéquate, à savoir, pour le MSP430 :
- Au début, envoi d'une donnée sur MOSI. On place alors la broche MISO en haute impédance (en entrée, comme elle doit l'être), mais on reprogrammera cette broche de manière que le protocole SPI soit ignoré (P1SEL à zéro).
- Lorsque les 8 bits de données auront été envoyé, alors on placera la broche MOSI, qui sortait les données, en haute impédance, on la reprogramme de manière que le protocole SPI l'ignore (P1SEL), et simultanément, on remet l'autre broche en MISO, et du coup la machinerie SPI n'y voit que du feu, les données sont reçues normalement.
- Et quand les 8 bits (ou plus) sont reçus, on remet dans l'état initial, MOSI actif, MISO éteint.
Finalement
Il va falloir que je regarde de plus près les autres esclaves dans mon cas particulier pour voir si c'est compatible.
Mais il est probable que je vais m'arranger, dans le cas du thermomètre simple, pour ne pas avoir à utiliser la RAM du SSD1680 en lecture. Je n'ai que des chiffres à afficher, et quelques logos, j'éviterai les recouvrements.
Pour le récepteur et son gros afficheur, j'aurai un peu plus de mémoire, je pourrai probablement travailler par petits secteurs non recouvrants. Mais je vais regarder ça de plus près.
Pour l'instant, je suis bon pour reprogrammer deux bibliothèques de routines :
- Une série où j'écris directement et une seule fois en RAM.
- Un complément où je pourrai lire un bloc de 8 pixels pour pouvoir écrire un seul pixel sans flinguer les autres.
- Et récupérer les routines de dessins classiques, déjà fournies par Waveshare dans son énorme paquet de routines disponible sur le web, énorme parce qu'ils ont un exemple pour chaque afficheur existant multiplié par le nombre de cartes supportées (Arduino et consort), et ça finit par faire beaucoup.
Et je limiterai salement les rafraichissements d'image. Pas de pendule affichant les secondes, ni même les minutes. Je mettrai à jour uniquement s'il y a assez de données modifiées. Par exemple, pour un simple thermomètre, tant que la valeur est inchangée, inutile de faire quoi que soit.
Je suis bon pour faire de la programmation astucieuse afin de prendre en compte les limitations mémoire.
Vous serez peut-être intéressé par plus d'information concernant les interfaces et les puces de contrôle de ces afficheurs en papier électronique ?
La suite lorsque j'aurai effectivement réalisé l'ensemble. Je ne sais pas encore quand.