Skip to Content

Rafraîchissement partiel de l’écran e-Paper

Rafraîchissement partiel de l’écran e-Paper

Dans ce tutoriel, vous apprendrez comment effectuer un rafraîchissement partiel rapide d’un écran e-Paper. Les principaux avantages des écrans e-Paper sont leur faible consommation d’énergie et leur excellente lisibilité. Mis à part la gamme de couleurs limitée, leur plus grand inconvénient est la lenteur du taux de rafraîchissement.

Il faut généralement 2 à 4 secondes pour qu’un écran e-Paper noir et blanc rafraîchisse l’intégralité de son contenu, et pour les écrans couleur, cela prend beaucoup plus de temps (>20 secondes). Le rafraîchissement complet s’accompagne aussi de beaucoup de scintillements, ce qui est gênant.

Heureusement, vous pouvez effectuer un rafraîchissement partiel, qui est non seulement beaucoup plus rapide (0,3 seconde) mais évite aussi le scintillement de l’écran. Pour toute application pratique qui met à jour le contenu de l’écran plus d’une fois toutes les 10 minutes environ, il est clairement préférable d’utiliser un rafraîchissement partiel.

Cependant, implémenter un rafraîchissement partiel sur un écran e-Paper peut être assez complexe. Ce tutoriel vous montre comment rafraîchir une ou plusieurs zones de l’écran, comment combiner le rafraîchissement partiel avec le mode deep sleep, et comment rafraîchir partiellement des pixels individuels pour du tracé.

Commençons par les pièces nécessaires.

Pièces requises

Pour les pièces requises, j’ai listé un ancien ESP32 lite, qui est obsolète mais que vous pouvez encore trouver à bas prix. Il existe un modèle successeur (Amazon) avec des spécifications améliorées. Mais tout autre ESP32, ESP8266 ou Arduino avec suffisamment de mémoire fonctionnera également.

Écran e-Paper 2,9″

ESP32 lite Lolin32

ESP32 lite

USB data cable

Câble USB de données

Dupont wire set

Jeu de fils Dupont

Half_breadboard56a

Plaque d’essai (breadboard)

Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.

Écran e-Paper

Pour ce projet, j’utilise un module d’écran e-Paper 2,9″, avec une résolution de 296×128 pixels et un contrôleur intégré avec interface SPI.

Front and Back of 2.9" e-Paper display module
Face avant et arrière du module d’écran e-Paper 2,9″

Notez que la plupart des modules d’écran e-Paper avec SPI ont un petit interrupteur ou cavalier qui permet de passer du SPI 4 fils au SPI 3 fils. Nous allons utiliser ici le SPI 4 fils par défaut.

Back of display module with SPI-interface and controller
Arrière du module d’écran avec interface SPI et contrôleur

Le module d’écran fonctionne en 3,3V ou 5V, a un courant de veille très faible de 0,01µA et ne consomme qu’environ 26,4mW lors du rafraîchissement/mise à jour de son contenu. Cela signifie que vous pouvez faire fonctionner un écran e-Paper longtemps même avec une petite batterie.

Pour plus d’informations sur les écrans e-Paper, consultez notre tutoriel Interfacing Arduino To An E-ink Display.

Connexion et test de l’e-Paper

Commençons par connecter et tester le fonctionnement de l’e-Paper. L’image suivante montre le câblage complet pour l’alimentation et le SPI.

Connecting e-Paper to ESP32 via SPI
Connexion de l’e-Paper à l’ESP32 via SPI

Voici un tableau récapitulatif des connexions pour plus de commodité. Notez que vous pouvez alimenter l’écran en 3,3V ou 5V, mais l’ESP32-lite ne fournit que du 3,3V et les lignes de données SPI doivent être en 3,3V !

Écran e-PaperESP32 lite
CS/SS5
SCL/SCK 18
SDA/DIN/MOSI23
BUSY15
RES/RST2
DC0
VCC3,3V
GNDG

Installer la bibliothèque GxEPD2

Avant de pouvoir dessiner ou écrire sur l’écran e-Paper, nous devons installer deux bibliothèques. La Adafruit_GFX est une bibliothèque graphique de base qui fournit un ensemble commun de primitives graphiques (texte, points, lignes, cercles, etc.). Et la GxEPD2 fournit le pilote graphique pour contrôler un écran e-Paper via SPI.

Installez simplement les bibliothèques de la manière habituelle. Après installation, elles devraient apparaître dans le Library Manager comme suit.

Adafruit_GFX and GxEPD2 libraries in Library Manager
Bibliothèques Adafruit_GFX et GxEPD2 dans le Library Manager

Code de test

Voici un code de test simple qui affiche le texte « Makerguides » sur l’écran. Regardez d’abord le code complet, puis nous en discuterons.

#define ENABLE_GxEPD2_GFX 0

#include "GxEPD2_BW.h"

//CS(SS)=5, SCL(SCK)=18, SDA(MOSI)=23, BUSY=15, RES(RST)=2, DC=0
GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));

void setup() {
  epd.init(115200, true, 50, false);
  epd.setRotation(1);
  epd.setTextColor(GxEPD_BLACK);   
  epd.setTextSize(2);
  epd.setFullWindow();

  epd.fillScreen(GxEPD_WHITE);     
  epd.setCursor(20, 20);
  epd.print("Makerguides");  
  epd.display();
  epd.hibernate();
}

void loop() {}

Décomposons le code en ses composants pour comprendre son fonctionnement.

Constantes et bibliothèques

Le code commence par définir une constante ENABLE_GxEPD2_GFX à 0. Vous pouvez la mettre à 1, ce qui, selon la documentation, permet à la classe de base GxEPD2_GFX de passer des références ou pointeurs à l’instance d’affichage en paramètre. Mais cela ajoute environ 1,2k de code inutile ici, donc elle est mise à 0.

#define ENABLE_GxEPD2_GFX 0

Ensuite, nous incluons le fichier d’en-tête GxEPD2_BW.h pour l’écran e-Paper noir et blanc. Si vous avez un écran 3 couleurs, vous incluriez GxEPD2_3C.h, ou GxEPD2_4C.h pour un écran 4 couleurs, et GxEPD2_7C.h pour un écran 7 couleurs.

#include "GxEPD2_BW.h"

Objet d’affichage

La ligne suivante est importante. Elle crée l’objet d’affichage et dépend du type ou de la marque de l’écran. J’ai essayé un WeAct et un WaveShare, et la ligne suivante fonctionne pour les deux.

GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));

La bibliothèque Readme for GxEPD2 liste tous les écrans supportés et vous pouvez trouver les détails dans les fichiers d’en-tête, par exemple GxEPD2.h.

Fonction setup

Tout le travail se fait dans la fonction setup. D’abord, on configure les paramètres de l’écran comme la vitesse de communication, la rotation, la police, la couleur du texte et la couleur de fond. Si vous avez des problèmes de rafraîchissement, vous devrez peut-être ajuster les paramètres de la fonction init().

void setup() {
  epd.init(115200, true, 50, false);
  epd.setRotation(1);
  epd.setTextColor(GxEPD_BLACK);   
  epd.setTextSize(2);
  epd.setFullWindow();

  epd.fillScreen(GxEPD_WHITE);     
  epd.setCursor(20, 20);
  epd.print("Makerguides");  
  epd.display();
  epd.hibernate();
}

Ensuite, on remplit l’écran en blanc, on positionne le curseur, on écrit le texte, puis on met le pilote d’affichage en mode hibernation. Cela coupe l’alimentation de l’écran et met le contrôleur en mode deep-sleep (pas l’ESP32, seulement l’écran !).

Fonction loop

La fonction loop() est vide dans cet exemple car le contenu de l’écran est créé dans la fonction setup() et n’a pas besoin d’être mis à jour en continu.

void loop() {}

Téléverser et exécuter le code

Maintenant, nous sommes prêts à téléverser et exécuter le code. Sélectionnez la carte dans le gestionnaire de cartes. Dans mon cas, c’est la WEMOS LOLIN32 Lite listée dans les pièces requises :

WEMOS LOLIN32 Lite selected in Board Manager
WEMOS LOLIN32 Lite sélectionnée dans le Board Manager

Puis appuyez sur téléverser et après quelques scintillements, votre écran devrait afficher le texte suivant :

Test output on e-Paper display
Sortie de test sur écran e-Paper

Rafraîchissement complet

Juste pour montrer à quel point un rafraîchissement complet d’un écran e-Paper est mauvais, nous allons utiliser le code suivant. Il affiche le texte « msec:  » suivi des millisecondes écoulées depuis le démarrage du microcontrôleur.

#define ENABLE_GxEPD2_GFX 0

#include "GxEPD2_BW.h"

GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));

void setup() {
  epd.init(115200, true, 50, false);
  epd.setRotation(1);
  epd.setTextSize(2);
  epd.setTextColor(GxEPD_BLACK);
}

void loop() {
  epd.fillScreen(GxEPD_WHITE);
  epd.setCursor(40, 60);
  epd.print("msec: ");
  epd.setCursor(150, 60);
  epd.print(millis());
  epd.display();
  epd.hibernate();
}

Le contenu de l’écran est mis à jour et rafraîchi dans la boucle principale aussi vite que possible. La vidéo suivante montre ce que cela donne. Comme vous pouvez le voir, il y a beaucoup de scintillements et le rafraîchissement prend presque 3 secondes.

Full refresh of e-Paper display
Rafraîchissement complet de l’écran e-Paper

Donc, pour toute application avec des mises à jour fréquentes, un rafraîchissement complet est trop lent pour être utile.

Rafraîchissement partiel d’une zone unique

Au lieu d’effectuer un rafraîchissement complet à chaque mise à jour de la valeur des millisecondes, on peut faire un rafraîchissement partiel. Le code suivant effectue un rafraîchissement complet une fois au démarrage, puis ne rafraîchit que la zone de l’écran affichant la valeur des millisecondes :

#define ENABLE_GxEPD2_GFX 0

#include "GxEPD2_BW.h"

GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));

void drawFull(const void* pv) {
  epd.setFullWindow();
  epd.setCursor(40, 60);
  epd.print("msec: ");
}

void drawPartial(const void* pv) {
  uint16_t x = 120, y = 50, w = 130, h = 34;
  epd.setPartialWindow(x, y, w, h);
  epd.setCursor(x + 30, y + 10);
  epd.print(millis());
  epd.drawRect(x, y, w, h, GxEPD_BLACK);
}

void setup() {
  epd.init(115200, true, 50, false);
  epd.setRotation(1);
  epd.setTextSize(2);
  epd.setTextColor(GxEPD_BLACK);
  epd.drawPaged(drawFull, 0);
}

void loop() {
  epd.drawPaged(drawPartial, 0);
  epd.hibernate();
}

La vidéo suivante montre le rafraîchissement partiel. Comme vous pouvez le voir, le scintillement a disparu et la mise à jour des millisecondes est beaucoup plus rapide (< 1 sec).

Partial refresh of e-Paper display
Rafraîchissement partiel de l’écran e-Paper

Un rafraîchissement complet a lieu au démarrage du code, avec le scintillement et la lenteur habituels, mais cela ne se produit qu’une seule fois.

Dessiner par pages

Il existe différentes façons d’implémenter un rafraîchissement complet ou partiel. Utiliser la fonction drawPaged() est probablement la plus élégante et la plus simple. Elle prend une fonction drawCallback et un vecteur de paramètres pv pour effectuer un redessin paginé :

void drawPaged(void (*drawCallback)(const void*), const void* pv)

Dans la fonction drawCallback, on appelle soit etFullWindow() pour un rafraîchissement complet, soit setPartialWindow(x, y, w, h) pour un rafraîchissement partiel.

Utiliser drawPaged() a aussi l’avantage de gérer les opérations de dessin de manière économe en mémoire, ce qui est particulièrement utile pour les grands écrans ou quand la RAM est limitée. Il dessine le contenu en plusieurs petites étapes (pages) au lieu de rafraîchir tout l’écran d’un coup.

Fonction drawFull

Si vous regardez la fonction drawFull(), vous verrez que nous appelons setFullWindow(), positionnons le curseur puis affichons le texte statique "msec: ".

void drawFull(const void* pv) {
  epd.setFullWindow();
  epd.setCursor(40, 60);
  epd.print("msec: ");
}

Dans la fonction setup, on initialise l’écran, définit ses propriétés, puis on appelle drawPaged(drawFull, 0) pour effectuer le rafraîchissement complet.

void setup() {
  epd.init(115200, true, 50, false);
  epd.setRotation(1);
  epd.setTextSize(2);
  epd.setTextColor(GxEPD_BLACK);
  epd.drawPaged(drawFull, 0);
}

Le vecteur de paramètres pv est mis à 0, car nous ne passons aucun paramètre. Mais vous pourriez fournir la police, la couleur, le texte ou d’autres informations pertinentes à la fonction drawFull ici.

Fonction drawPartial

La principale différence entre drawPartial() et drawFull() est que setPartialWindow(x, y, w, h) est appelé pour limiter la zone d’affichage à rafraîchir.

void drawPartial(const void* pv) {
  uint16_t x = 120, y = 50, w = 130, h = 34;
  epd.setPartialWindow(x, y, w, h);
  epd.setCursor(x + 30, y + 10);
  epd.print(millis());
  epd.drawRect(x, y, w, h, GxEPD_BLACK);
}

Ensuite, on positionne le curseur, affiche le texte des millisecondes et dessine un rectangle pour marquer la fenêtre de rafraîchissement.

Enfin, la fonction drawPartial() est appelée via drawPaged(drawPartial, 0) dans la boucle principale, ce qui provoque un rafraîchissement répété des millisecondes affichées.

void loop() {
  epd.drawPaged(drawPartial, 0);
  epd.hibernate();
}

Notez que tout ce que vous dessinez en dehors de la zone partielle ne sera pas affiché. Dans drawPartial(), nous positionnons le curseur à l’intérieur de la zone et affichons la valeur des millisecondes dans cette zone.

Pour montrer la zone de rafraîchissement partiel, nous dessinons un rectangle autour. Cependant, ce n’est pas tout à fait exact ! Plus d’explications dans la section suivante.

Alignement de la fenêtre de rafraîchissement

À cause des limitations d’adressage des contrôleurs d’écran e-Paper, les coordonnées de la fenêtre de rafraîchissement doivent être alignées sur des multiples de 8. Plus précisément,

  • x et w doivent être multiples de 8 pour les rotations d’écran 0 ou 2,
  • y et h doivent être multiples de 8 pour les rotations 1 ou 3.

La fonction setPartialWindow(x, y, w, h) vous permet de fournir des valeurs arbitraires mais les aligne en interne comme requis. Cela signifie que la fenêtre de rafraîchissement réelle peut être plus grande que celle spécifiée.

C’est important car cela signifie que des parties de l’écran seront mises à jour alors que vous ne le souhaitiez pas forcément. L’image ci-dessous montre la zone de rafraîchissement prévue (rectangle noir) et la zone réelle (rectangle blanc rempli).

Intended and actual refresh window
Fenêtre de rafraîchissement prévue et réelle

Comme vous le voyez, la hauteur de la fenêtre réelle est plus grande que celle spécifiée. Si vous concevez la mise en page de votre contenu, vous devez en tenir compte. Surtout que la fonction drawRect() remplit entièrement le fond de la fenêtre réelle en blanc. Cela signifie qu’elle efface le contenu en dehors de la fenêtre spécifiée mais à l’intérieur de la fenêtre réelle.

Si vous voulez reproduire l’expérience ci-dessus, voici le code. Il inverse simplement la couleur de fond et du texte du redessin complet pour rendre visible la fenêtre réelle de rafraîchissement.

#define ENABLE_GxEPD2_GFX 0

#include "GxEPD2_BW.h"

GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));

void drawFull(const void* pv) {
  epd.setFullWindow();
  epd.fillScreen(GxEPD_BLACK);
  epd.setCursor(40, 60);
  epd.print("msec: ");
}

void drawPartial(const void* pv) {
  uint16_t x = 120, y = 50, w = 130, h = 34;
  epd.setTextColor(GxEPD_BLACK);
  epd.setPartialWindow(x, y, w, h);
  epd.setCursor(x + 30, y + 10);
  epd.print(millis());
  epd.drawRect(x, y, w, h, GxEPD_BLACK);
}

void setup() {
  epd.init(115200, true, 50, false);
  epd.setRotation(1);
  epd.setTextSize(2);
  epd.setTextColor(GxEPD_WHITE);
  epd.drawPaged(drawFull, 0);
}

void loop() {
  epd.drawPaged(drawPartial, 0);
  epd.hibernate();
}

Rafraîchissement partiel avec deep sleep

Les écrans e-Paper sont souvent utilisés dans des projets alimentés par batterie, où le microcontrôleur passe généralement en deep-sleep. Comment combiner le deep-sleep d’un ESP32 avec le rafraîchissement partiel d’un écran e-Paper est le sujet de cette section.

La principale difficulté est que l’on veut effectuer un rafraîchissement complet uniquement au premier démarrage (reset) de l’ESP32, mais à chaque réveil du deep-sleep, on veut faire un rafraîchissement partiel. Le code suivant y parvient. Regardez-le rapidement, puis nous détaillons.

#define ENABLE_GxEPD2_GFX 0

#include "GxEPD2_BW.h"

GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));

RTC_DATA_ATTR bool initial = true;

void drawFull(const void* pv) {
  epd.setFullWindow();
  epd.setCursor(40, 60);
  epd.print("msec: ");
  initial = false;
}

void drawPartial(const void* pv) {
  uint16_t x = 120, y = 50, w = 130, h = 34;
  epd.setPartialWindow(x, y, w, h);
  epd.setCursor(x + 30, y + 10);
  epd.print(millis());
  epd.setCursor(x + 100, y + 10);
  epd.print(random(10));  
  epd.drawRect(x, y, w, h, GxEPD_BLACK);
}

void setup() {
  epd.init(115200, initial, 50, false);
  epd.setRotation(1);
  epd.setTextSize(2);
  epd.setTextColor(GxEPD_BLACK);
  if (initial)
    epd.drawPaged(drawFull, 0);
}

void loop() {
  epd.drawPaged(drawPartial, 0);
  epd.hibernate();  
  esp_sleep_enable_timer_wakeup(1000);
  esp_deep_sleep_start();
}

Le code est fondamentalement le même que dans l’exemple précédent, avec quelques changements importants.

D’abord, on définit une variable initial stockée en RTC memory, ce qui signifie que sa valeur est conservée en deep-sleep.

RTC_DATA_ATTR bool initial = true;

La variable initial est initialisée à true et après le premier rafraîchissement complet via drawFull() , elle est mise à false. Cela signifie initial=true après un reset, mais false après un réveil du deep-sleep.

Ensuite, il faut indiquer à l’écran qu’il ne doit pas faire de rafraîchissement complet après le deep-sleep. La fonction init(serial_diag_bitrate, initial, reset_duration pulldown_rst_mode) a le paramètre initial pour cela :

epd.init(115200, initial, 50, false); 

Nous avons presque fini. Dans la fonction setup, on effectue un redessin complet seulement si initial==true. Ce qui signifie uniquement après un reset, pas après un réveil du deep-sleep.

void setup() {
  ...
  if (initial)
    epd.drawPaged(drawFull, 0);
}

Enfin, dans la fonction loop, on met l’ESP32 en veille après avoir effectué un rafraîchissement partiel.

void loop() {
  epd.drawPaged(drawPartial, 0);
  epd.hibernate();  
  esp_sleep_enable_timer_wakeup(1000);
  esp_deep_sleep_start();
}

Selon la documentation de la fonction init(), il suffit de s’assurer que l’alimentation de l’écran est maintenue pendant le deep-sleep. Appeler hibernate, comme montré ci-dessus, est correct. La vidéo suivante montre le résultat.

Partial refresh of e-Paper display with deep-sleep
Rafraîchissement partiel de l’écran e-Paper avec deep-sleep

Comme millis() se réinitialise pendant le deep-sleep et affiche une valeur constante de 151 millisecondes, j’affiche aussi un nombre aléatoire (0..9) après la valeur msec pour rendre le rafraîchissement visible.

Rafraîchissement partiel de plusieurs zones

Parfois, on veut rafraîchir partiellement plusieurs zones à différents endroits et moments. Par exemple, vous pouvez vouloir rafraîchir l’affichage d’une horloge chaque seconde, mais celui d’une mesure de température toutes les 5 minutes.

Étendre l’exemple précédent pour rafraîchir plusieurs zones est simple. Voici un code complet qui rafraîchit la valeur des millisecondes affichée dans deux zones différentes.

#define ENABLE_GxEPD2_GFX 0

#include "GxEPD2_BW.h"

GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));

void drawText(int16_t x, int16_t y, const char *text) {
  int16_t x1, y1;
  uint16_t w, h;
  epd.getTextBounds(text, x, y, &x1, &y1, &w, &h);
  epd.setPartialWindow(x1, y1, w, h);
  epd.setCursor(x, y);
  epd.print(text);   
  epd.drawRect(x1, y1, w, h, GxEPD_BLACK); 
}

void drawFull(const void* pv) {
  epd.setFullWindow();
  epd.setCursor(40, 60);
  epd.print("msec: ");
}

void drawPartial1(const void* pv) {
  char text[16];
  sprintf(text, "1: %8d", millis());
  drawText(120, 60, text);
}

void drawPartial2(const void* pv) {
  char text[16];
  sprintf(text, "2: %8d", millis());
  drawText(120, 40, text);
}

void setup() {
  epd.init(115200, true, 50, false);
  epd.setRotation(1);
  epd.setTextColor(GxEPD_BLACK);
  epd.setTextSize(2);
  epd.drawPaged(drawFull, 0);
}

void loop() {  
  epd.drawPaged(drawPartial1, 0);
  epd.drawPaged(drawPartial2, 0);
  epd.hibernate();
}

Comme vous le voyez, on définit simplement deux fonctions drawPartial1() et drawPartial2() qui sont appelées dans la boucle pour effectuer le rafraîchissement partiel de deux zones différentes.

void loop() {  
  epd.drawPaged(drawPartial1, 0);
  epd.drawPaged(drawPartial2, 0);
  epd.hibernate();
}

Pour rendre les choses un peu plus intéressantes et pratiques, j’ai ajouté une fonction drawText() qui calcule la boîte englobante d’un texte donné, définit la fenêtre de rafraîchissement aux mêmes dimensions, affiche le texte et dessine la boîte englobante :

void drawText(int16_t x, int16_t y, const char *text) {
  int16_t x1, y1;
  uint16_t w, h;
  epd.getTextBounds(text, x, y, &x1, &y1, &w, &h);
  epd.setPartialWindow(x1, y1, w, h);
  epd.setCursor(x, y);
  epd.print(text);   
  epd.drawRect(x1, y1, w, h, GxEPD_BLACK); 
}

Si vous utilisez ce code, assurez-vous que le texte a toujours la même longueur. Un texte qui rétrécit entre les mises à jour peut laisser des artefacts d’affichage, car la boîte englobante et la zone de rafraîchissement deviennent plus petites. Dans le code ci-dessus, j’utilise le spécificateur de format "%8d" pour garantir une longueur constante malgré les nombres changeants. La vidéo ci-dessous montre le code en action :

Partial refresh of multiple e-Paper display areas
Rafraîchissement partiel de plusieurs zones d’écran e-Paper

Notez que le rafraîchissement prend maintenant deux fois plus de temps, car nous effectuons deux mises à jour indépendantes. Vous ne pouvez pas utiliser deux fenêtres de rafraîchissement différentes (setPartialWindow) dans la même fonction de dessin. Mais vous pourriez spécifier une fenêtre plus grande englobant les deux contenus à mettre à jour. Dans ce cas, vous n’auriez pas trop à vous soucier des différentes longueurs de texte.

Rafraîchissement partiel avec tampon d’écran

Pour finir, je voulais implémenter un simple traceur de données, par exemple pour surveiller la température dans le temps. L’image suivante montre à quoi ressemblera le traceur. Il a un titre fixe « Temperature », un axe x fixe, et des valeurs de température simulées qui changent dynamiquement.

Temperature plotter on e-Paper display
Traceur de température sur écran e-Paper

Idéalement, on tracerait simplement des pixels individuels (points de données) avec une fenêtre de rafraîchissement partielle d’un pixel. Cependant, cela ne fonctionne pas, car les dimensions de la fenêtre de rafraîchissement sont limitées aux multiples de 8 (voir plus haut). Si vous essayez, vous verrez que la fenêtre plus grande efface certains pixels tracés précédemment et des parties de l’axe.

Pour contourner ce problème, on dessine d’abord sur une « toile » (canvas). Une toile est essentiellement un tampon d’écran que l’on peut mettre à jour en arrière-plan. Le rafraîchissement partiel prend alors une petite zone de la toile pour mettre à jour l’écran.

Partial refresh using canvas
Rafraîchissement partiel avec toile

Le code suivant contient l’implémentation complète de ce petit traceur de température.

#define ENABLE_GxEPD2_GFX 0

#include "GxEPD2_BW.h"

// W, H flipped due to setRotation(1)
const int W = GxEPD2_290_BS::HEIGHT;
const int H = GxEPD2_290_BS::WIDTH;

const uint16_t WHITE = GxEPD_WHITE;
const uint16_t BLACK = GxEPD_BLACK;

GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));
GFXcanvas1 canvas(W, H);

void initCanvas() {
  canvas.setTextColor(BLACK);
  canvas.fillScreen(WHITE);
  
  canvas.setTextSize(2);
  canvas.setCursor(10, 10);
  canvas.print("Temperature");

  for (int i = 0; i < 10; i++) {
    int x = 10 + i * W / 10, y = H / 2;
    canvas.setTextSize(1);
    canvas.setCursor(x - 2, y + 10);
    canvas.print(i);
    canvas.drawLine(x, y - 5, x, y + 5, BLACK);
  }
  canvas.drawLine(10, H / 2, W - 10, H / 2, BLACK);
}

void drawCanvas() {
  epd.drawBitmap(0, 0, canvas.getBuffer(), W, H, WHITE, BLACK);
}

void drawFull(const void* pv) {
  epd.setFullWindow();
  drawCanvas();
}

void drawPartial(const void* pv) {
  static int16_t x = 10;
  x += 1;
  if (x > W - 10) x = 10;
  int16_t y = (H / 2) + (x / 10.0) * sin(x / 10.0);
  canvas.writePixel(x, y, BLACK);
  epd.setPartialWindow(x, y, 1, 1);
  drawCanvas();
}

void setup() {
  initCanvas();
  epd.init(115200, true, 50, false);
  epd.setRotation(1);
  epd.drawPaged(drawFull, 0);
}

void loop() {
  epd.drawPaged(drawPartial, 0);
  epd.hibernate();
}

D’abord, on définit des constantes d’aide pour les dimensions de l’écran et les couleurs de premier plan et d’arrière-plan. Notez que largeur et hauteur sont inversées, car on fait une rotation de l’écran (setRotation(1)) dans la fonction setup.

// W, H flipped due to setRotation(1)
const int W = GxEPD2_290_BS::HEIGHT;
const int H = GxEPD2_290_BS::WIDTH;

const uint16_t WHITE = GxEPD_WHITE;
const uint16_t BLACK = GxEPD_BLACK;

Objet canvas

L’étape suivante importante est la création de l’objet canvas avec les mêmes dimensions que l’écran. Comme mon écran e-Paper n’a que deux couleurs (noir, blanc), on crée un canvas en 1 bit (GFXcanvas1).

GFXcanvas1 canvas(W, H);

Si votre écran a plus de couleurs ou de niveaux de gris, vous devez créer un canvas adapté. Consultez la Adafruit GFX Graphics Library documentation pour plus de détails.

initCanvas et drawCanvas

La fonction initCanvas() écrit simplement le titre et dessine l’axe x sur la toile, sans rien afficher. L’affichage réel est fait par drawCanvas(), qui copie la toile en bitmap sur l’écran.

drawFull et drawPartial

La fonction drawFull() effectue un rafraîchissement complet, tandis que la fonction drawPartial() effectue un rafraîchissement partiel. Lors du rafraîchissement complet, on dessine le titre et l’axe. Lors du rafraîchissement partiel, on dessine les points de données individuels.

La formule suivante crée les données de température simulées y, et x est le temps simulé.

y = (H / 2) + (x / 10.0) * sin(x / 10.0);

L’important est que l’on écrit le point de données (x,y) comme pixel sur la toile, puis on définit une fenêtre de rafraîchissement juste autour de ce pixel et on dessine la toile :

void drawPartial(const void* pv) {
  ...
  canvas.writePixel(x, y, BLACK);
  epd.setPartialWindow(x, y, 1, 1);
  drawCanvas();
}

Comme la fenêtre de rafraîchissement partielle est fixée à (x,y,1,1), on ne rafraîchit pas tout l’écran mais seulement une petite zone autour du pixel (rappelez-vous la limitation du multiple de 8). Cependant, comme cette zone est prise de la toile, elle contient ce qui a déjà été dessiné et n’efface pas le contenu existant sur l’écran. La vidéo suivante montre le traceur en action :

Data plotter on e-Paper display
Traceur de données sur écran e-Paper

Vous pouvez voir que la courbe traverse l’axe x sans l’effacer et il n’y a pas de scintillement. Bien que le rafraîchissement ne soit pas très rapide (environ 1 seconde), cela suffit pour un traceur de température.

Notez que le deep-sleep ne peut pas être facilement ajouté à ce code. Il faudrait stocker toute la toile en mémoire RTC, qui est généralement trop petite (8K). Vous pourriez stocker seulement les points de données, mais alors la fonction de redessin partiel serait plus complexe, car il faudrait redessiner toute la courbe. Alternativement, vous pouvez stocker la toile sur une carte SD ou une autre mémoire externe plus grande.

Recommandations

La bibliothèque Waveshare Manual fournit les recommandations suivantes pour l’utilisation d’un écran e-Paper (résumé et reformulé) :

Rafraîchissement complet : L’écran e-Paper scintille plusieurs fois pendant le processus de rafraîchissement (le nombre de scintillements dépend du temps de rafraîchissement), ce qui permet d’éliminer les images fantômes pour obtenir le meilleur rendu.

Rafraîchissement partiel : Dans ce cas, l’écran ne scintille pas pendant le rafraîchissement. Après plusieurs rafraîchissements partiels, un rafraîchissement complet doit être effectué pour éliminer les images résiduelles. Sinon, ces images peuvent devenir permanentes.

Il est recommandé de régler l’intervalle de rafraîchissement de l’écran e-ink à au moins 180 secondes (sauf pour les produits supportant le rafraîchissement partiel).

Après un rafraîchissement, il est conseillé d’éteindre ou d’hiberner l’écran. Cela prolonge sa durée de vie et réduit la consommation d’énergie.

Pour un écran e-ink trois couleurs, il est recommandé de mettre à jour l’écran au moins une fois toutes les 24 heures.

Les écrans e-Paper sont recommandés pour un usage intérieur et non extérieur. Si vous souhaitez l’utiliser en extérieur, placez-le à l’ombre et recouvrez complètement la partie blanche de la bande de connexion de l’écran avec du ruban adhésif 3M.

Conclusions

Dans ce tutoriel, vous avez appris à effectuer des rafraîchissements partiels d’un écran e-Paper pour éviter le scintillement et obtenir des temps de rafraîchissement plus rapides. Nous avons vu comment faire un rafraîchissement partiel pour une ou plusieurs zones, en combinaison avec le deep-sleep et des mises à jour pixel par pixel.

Vous pouvez facilement étendre l’exemple du traceur en ajoutant un vrai capteur de température comme le BME280 au projet. Consultez le tutoriel Weather Station on e-Paper Display pour plus de détails. Notez que vous pouvez utiliser des toiles séparées pour la température, l’humidité et la pression atmosphérique, puis basculer entre elles pour afficher différentes données.

Si vous avez des questions, n’hésitez pas à les poser dans la section commentaires.

Bon bricolage ; )