Afficheur OLED SSD1306 : comparons
Le but de cet article n'est pas de faire un nième tutorial sur les afficheurs OLED SSD1306, mais plutôt de faire une comparaison des différentes librairies existantes.
Comme vous l'avez peut-être remarqué la librairie AdaFruit occupe une place phénoménale en mémoire RAM. Il existe heureusement une alternative.
J'ai déjà parlé de ces petits afficheurs :
https://riton-duino.blogspot.com/2018/09/lcd-tft-et-arduino.html
Voir 4. Les petits afficheurs OLED
1. Câblage
Le câblage est simple (je ne vais pas vous faire un schéma) :- VCC sur 3.3V de l'ARDUINO
- GND sur GND de l'ARDUINO
- SDA sur A4 de l'ARDUINO
- SCL sur A5 de l'ARDUINO
Pour vérifier l'adresse I2C de l'afficheur (en général 0x3C), il suffit d'utiliser ce sketch :
https://gist.github.com/tfeldmann/5411375
2. Les librairies
Dans ce comparatif nous allons essayer d'écrire une application affichant les objets suivants :- un titre centré en haut de l'écran
- un texte justifié à gauche
- un texte justifié à droite
- un texte suivi d'une valeur, grande taille, justifié à gauche
- un indicateur de capacité de batterie en haut à gauche
- un logo en haut à droite
Quand ces applications seront écrites nous comparerons la facilité de mise en œuvre et l'occupation mémoire.
1.1. Librairie AdaFruit
Cette librairie peut être récupérée ici :https://github.com/adafruit/Adafruit_SSD1306
Voici le sketch :
#define SSD1306_128_64
#include <Adafruit_SSD1306.h>
Adafruit_SSD1306 display(-1);
void setup()
{
Serial.begin(115200);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextColor(WHITE);
}
void loop()
{
display.clearDisplay();
printCentered("SSD1306 TUTO", 0, 1);
displayLogo(0, 100);
printLeft("LEFT JUSTIFIED", 20, 1);
printRight("RIGHT JUSTIFIED", 30, 1);
printValue(100, 50, 2);
displayBatteryLevel(0, 0, 0);
displayLogo(112, 0);
display.display();
delay(500);
}
void printLeft(const char *s, int y, int size)
{
display.setTextSize(size);
display.setCursor(0, y);
display.print(s);
}
void printRight(const char *s, int y, int size)
{
int16_t x1, y1;
uint16_t w, h;
display.setTextSize(size);
display.getTextBounds(s, 0, 0, &x1, &y1, &w, &h);
display.setCursor(display.width() - w, y);
display.print(s);
}
void printCentered(const char *s, int y, int size)
{
int16_t x1, y1;
uint16_t w, h;
display.setTextSize(size);
display.getTextBounds(s, 0, 0, &x1, &y1, &w, &h);
display.setCursor((display.width() - w) / 2, y);
display.print(s);
}
void printValue(int value, int y, int size)
{
char s[20];
int16_t x1, y1;
uint16_t w, h;
sprintf(s, "VALUE: %d", value);
display.getTextBounds(s, 0, 0, &x1, &y1, &w, &h);
display.setTextSize(size);
display.setCursor(0, y);
display.print(s);
}
static const unsigned char PROGMEM logo16_glcd_bmp[] =
{ B00000000, B11000000,
B00000001, B11000000,
B00000001, B11000000,
B00000011, B11100000,
B11110011, B11100000,
B11111110, B11111000,
B01111110, B11111111,
B00110011, B10011111,
B00011111, B11111100,
B00001101, B01110000,
B00011011, B10100000,
B00111111, B11100000,
B00111111, B11110000,
B01111100, B11110000,
B01110000, B01110000,
B00000000, B00110000
};
void displayLogo(int x, int y)
{
display.drawBitmap(x, y, logo16_glcd_bmp, 16, 16, WHITE);
}
void displayBatteryLevel(int batteryLevel, int x, int y)
{
static bool flash = false;
if (flash || batteryLevel > 10) {
display.drawRect(x, y, 6, 16, WHITE);
display.fillRect(x, y + ((16 - batteryLevel / 6)), 6, batteryLevel / 6, WHITE);
}
flash = !flash;
}
1.2. Librairie Greiman
Cette librairie peut être récupérée ici :https://github.com/greiman/SSD1306Ascii.git
Voici le sketch :
#include "SSD1306Ascii.h"
#include "SSD1306AsciiAvrI2c.h"
SSD1306AsciiAvrI2c display;
void setup()
{
Serial.begin(115200);
display.begin(&Adafruit128x64, 0x3C);
display.setFont(System5x7);
}
void loop()
{
printCentered("SSD1306 TUTO", 0, 1);
displayLogo(0, 100);
printLeft("LEFT JUSTIFIED", 2, 1);
printRight("RIGHT JUSTIFIED", 3, 1);
printValue(100, 6, 2);
displayBatteryLevel(0, 0, 0);
displayLogo(112, 0);
delay(500);
}
void printLeft(const char *s, int y, int size)
{
if (size == 1) {
display.set1X();
}
else {
display.set2X();
}
display.setCursor(0, y);
display.print(s);
}
void printRight(const char *s, int y, int size)
{
size_t w;
if (size == 1) {
display.set1X();
}
else {
display.set2X();
}
w = display.strWidth(s);
display.setCursor(display.displayWidth() - w, y);
display.print(s);
}
void printCentered(const char *s, int y, int size)
{
size_t w;
if (size == 1) {
display.set1X();
}
else {
display.set2X();
}
w = display.strWidth(s);
display.setCursor((display.displayWidth() - w) / 2, y);
display.print(s);
}
void printValue(int value, int y, int size)
{
char s[20];
size_t w;
sprintf(s, "VALUE: %d", value);
w = display.strWidth(s);
if (size == 1) {
display.set1X();
}
else {
display.set2X();
}
display.setCursor(0, y);
display.print(s);
}
void displayLogo(int x, int y)
{
// rien ici pour l'instant
}
GLCDFONTDECL(Batt6x14) = {
0x0, 0x0, // size of zero indicates fixed width font,
6, // width
14, // height
0x20,
7,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // space
0xff, 0x01, 0x01, 0x01, 0x01, 0xff, 0x3f, 0x20, 0x20, 0x20, 0x20, 0x3f, // Battery Empty
0xff, 0x01, 0x01, 0x01, 0x01, 0xff, 0x3f, 0x38, 0x38, 0x38, 0x38, 0x3f, // Battery 20%
0xff, 0x01, 0x01, 0x01, 0x01, 0xff, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, // Battery 40%
0xff, 0xc1, 0xc1, 0xc1, 0xc1, 0xff, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, // Battery 60%
0xff, 0xf1, 0xf1, 0xf1, 0xf1, 0xff, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, // Battery 80%
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, // Battery 100%
};
void displayBatteryLevel(int batteryLevel, int x, int y)
{
static bool flash = false;
display.set1X();
display.setCursor(x, y);
if (flash || batteryLevel > 10) {
display.setFont(Batt6x14);
display.print((char)(' ' + 1 + round(batteryLevel / 20)));
display.setFont(System5x7);
}
else {
display.setFont(Batt6x14);
display.print(' ');
display.setFont(System5x7);
}
flash = !flash;
}
3. Comparaison
3.1. Occupation mémoire
Le résultat de la compilation avec la librairie AdaFruit :Le croquis utilise 14222 octets (44%) de l'espace de stockage de programmes. Le maximum est de 32256 octets.
Les variables globales utilisent 1544 octets (75%) de mémoire dynamique, ce qui laisse 504 octets pour les variables locales. Le maximum est de 2048 octets.
La mémoire disponible faible, des problèmes de stabilité pourraient survenir.
On sait déjà que l'application sera très limitée en taille mémoire dynamique (les variables globales).
Avec les nouvelles versions de la librairie AdaFruit le buffer dédié à l'écran est alloué dynamiquement par la méthode begin(), ce qui fait que lors de la compilation on a l'impression qu'il y a suffisamment de mémoire :
if ((!buffer) && !(buffer = (uint8_t *)malloc(WIDTH * ((HEIGHT + 7) / 8))))
return false;
Dans le cas d'un écran 128x32 : ((HEIGHT + 7) / 8) = 624 bytes. Après compilation la quantité de mémoire disponible devra être au minimum de 624 bytes + de l'espace pour la pile. On se fait assez vite piéger.
Avec un écran de 128x64 il faudra 1136 bytes + la pile.
Le résultat de la compilation avec la librairie Greiman :
Le croquis utilise 5852 octets (18%) de l'espace de stockage de programmes. Le maximum est de 32256 octets.
Les variables globales utilisent 274 octets (13%) de mémoire dynamique, ce qui laisse 1774 octets pour les variables locales. Le maximum est de 2048 octets.
On remarque vite que l'application est beaucoup moins limitée en taille mémoire dynamique, et que l'on sera beaucoup plus à l'aise pour créer des variables globales.
D'un point de vue occupation mémoire FLASH (programme), la librairie AdaFruit n'est pas catastrophique, mais la quantité de mémoire RAM (données) qu'elle monopolise limite son utilisation à des applications utilisant peu de données globales ou statiques.
Le développement d'une application conséquente avec la librairie AdaFruit se fera donc de préférence sur une carte ARDUINO MEGA, alors que la librairie Greiman se contentera facilement d'un ATMEGA328.
- on efface la zone tampon avec clearDisplay()
- on écrit avec print(), drawRect(), fillRect(), etc.
- on affiche avec display()
La librairie ne met à jour que les pixels qui ont changé par rapport au dernier affichage.
Avec cette librairie on a donc tendance à ré-afficher la totalité de l'écran, car le rafraîchissement est invisible.
La librairie Greiman travaille différemment, en direct avec l'écran
- on n'efface pas
- on écrit avec print() et l'affichage se fait dans la foulée
Avec cette librairie on préfère afficher sans effacer la totalité de l'écran, car le rafraîchissement serait trop visible.
Une autre différence de taille (c'est le cas de le dire) est que la librairie AdaFruit est capable de positionner son curseur en absolu, en nombre de pixels X et Y.
Avec la librairie Greiman, si le positionnement se fait en absolu dans le sens X (colonnes), il se fait en nombre de lignes de texte (une ligne faisant 8 pixels) dans le sens Y (lignes). La librairie Greiman est tout de même capable de faire du scrolling sur l'axe X, en pixels.
En bref, la librairie Greiman favorise un travail en ligne de texte, la librairie AdaFruit favorise un travail en mode graphique pur.
La librairie Greiman n'oblige pas à effacer un message précédemment affiché avant de le remplacer par un autre, par contre si le nouveau message est plus court que le précédent, la fin du premier message subsistera. On peut combler (padding) avec des espaces avant ou après le message, suivant que l'on justifie à droite, à gauche ou au centre.
On peut aussi positionner le curseur avec la méthod setCursor(x, y) et effacer la ligne avec la méthode clearToEOL() avant de ré-afficher :
display.setCursor(0, 2);
display.clearToEOL();
display.print("A LINE");
display.setCursor(0, 3); display.clearToEOL(); display.print("ANOTHER LINE");
La méthode clear() permet d'effacer une zone. Dans cet exemple on affiche deux lignes avec un retour à la ligne '\n', après avoir effacé les lignes 2 à 3 :
display.setCursor(0, 2);
display.clear(0, display.displayWidth(), 2, 3);
display.print("A LINE\nANOTHER LINE");
La méthode clearField permet aussi d'effacer une zone. Cet exemple fait la même chose que le précédent :
display.setCursor(0, 2);
display.clearField(0, display.displayWidth(), 2);
display.print("A LINE\nANOTHER LINE");
Les trois méthodes produisent le même résultat.
Pour effacer, l'indicateur, on se contente de ne pas l'afficher.
Dans le sketch de la version Greiman l'indicateur de niveau batterie est réalisé à l'aide d'une police de caractères spéciale (Batt6x14) que j'ai fabriqué moi-même.
Cette police possède 7 caractères dont le premier est 0x20 : un ESPACE.
Pour afficher l'indicateur 0%, 20%, 40%, 60%, 80%, 100% il suffit d'afficher le caractère ESPACE (' ' ou 0x20) + n (0x21, 0x22, 0x23, 0x24, 0x25, 0x26).
Le premier caractère ESPACE (0x20) sert à effacer le bitmap dans le cas où l'on veut faire clignoter l'indicateur, comme dans l'exemple ci-dessus.
AdaFruit propose en outre un outil Windows de création de bitmaps :
https://learn.adafruit.com/monochrome-oled-breakouts/arduino-library-and-examples
Je ne l'ai pas essayé, ne possédant pas de machine Windows et n'ayant pas du tout l'intention d'utiliser ce système d'exploitation dépassé (alergie profonde). Je vous laisse faire avec plaisir.
Avec la librairie Greiman, il n'y a pas de possibilité d'affichage de bitmaps, mais on pourrait en créer (avec plus ou moins de facilité) en utilisant la même méthode que pour l'indicateur de capacité batterie, comme vu précédemment.
Cordialement
Henri
Les variables globales utilisent 274 octets (13%) de mémoire dynamique, ce qui laisse 1774 octets pour les variables locales. Le maximum est de 2048 octets.
On remarque vite que l'application est beaucoup moins limitée en taille mémoire dynamique, et que l'on sera beaucoup plus à l'aise pour créer des variables globales.
D'un point de vue occupation mémoire FLASH (programme), la librairie AdaFruit n'est pas catastrophique, mais la quantité de mémoire RAM (données) qu'elle monopolise limite son utilisation à des applications utilisant peu de données globales ou statiques.
Le développement d'une application conséquente avec la librairie AdaFruit se fera donc de préférence sur une carte ARDUINO MEGA, alors que la librairie Greiman se contentera facilement d'un ATMEGA328.
3.2. Principe de fonctionnement
La librairie AdaFruit travaille avec un espace mémoire tampon interne à la librairie (ce qui explique la taille mémoire occupée en RAM).- on efface la zone tampon avec clearDisplay()
- on écrit avec print(), drawRect(), fillRect(), etc.
- on affiche avec display()
La librairie ne met à jour que les pixels qui ont changé par rapport au dernier affichage.
Avec cette librairie on a donc tendance à ré-afficher la totalité de l'écran, car le rafraîchissement est invisible.
La librairie Greiman travaille différemment, en direct avec l'écran
- on n'efface pas
- on écrit avec print() et l'affichage se fait dans la foulée
Avec cette librairie on préfère afficher sans effacer la totalité de l'écran, car le rafraîchissement serait trop visible.
Une autre différence de taille (c'est le cas de le dire) est que la librairie AdaFruit est capable de positionner son curseur en absolu, en nombre de pixels X et Y.
Avec la librairie Greiman, si le positionnement se fait en absolu dans le sens X (colonnes), il se fait en nombre de lignes de texte (une ligne faisant 8 pixels) dans le sens Y (lignes). La librairie Greiman est tout de même capable de faire du scrolling sur l'axe X, en pixels.
En bref, la librairie Greiman favorise un travail en ligne de texte, la librairie AdaFruit favorise un travail en mode graphique pur.
La librairie Greiman n'oblige pas à effacer un message précédemment affiché avant de le remplacer par un autre, par contre si le nouveau message est plus court que le précédent, la fin du premier message subsistera. On peut combler (padding) avec des espaces avant ou après le message, suivant que l'on justifie à droite, à gauche ou au centre.
On peut aussi positionner le curseur avec la méthod setCursor(x, y) et effacer la ligne avec la méthode clearToEOL() avant de ré-afficher :
display.setCursor(0, 2);
display.clearToEOL();
display.print("A LINE");
display.setCursor(0, 3); display.clearToEOL(); display.print("ANOTHER LINE");
La méthode clear() permet d'effacer une zone. Dans cet exemple on affiche deux lignes avec un retour à la ligne '\n', après avoir effacé les lignes 2 à 3 :
display.setCursor(0, 2);
display.clear(0, display.displayWidth(), 2, 3);
display.print("A LINE\nANOTHER LINE");
La méthode clearField permet aussi d'effacer une zone. Cet exemple fait la même chose que le précédent :
display.setCursor(0, 2);
display.clearField(0, display.displayWidth(), 2);
display.print("A LINE\nANOTHER LINE");
Les trois méthodes produisent le même résultat.
3.3. Affichage d'un indicateur de capacité batterie
Dans le sketch de la version AdaFruit l'indicateur de niveau batterie est réalisé simplement en dessinant un cadre puis un rectangle plein à l'intérieur de ce cadre.Pour effacer, l'indicateur, on se contente de ne pas l'afficher.
Dans le sketch de la version Greiman l'indicateur de niveau batterie est réalisé à l'aide d'une police de caractères spéciale (Batt6x14) que j'ai fabriqué moi-même.
Cette police possède 7 caractères dont le premier est 0x20 : un ESPACE.
Pour afficher l'indicateur 0%, 20%, 40%, 60%, 80%, 100% il suffit d'afficher le caractère ESPACE (' ' ou 0x20) + n (0x21, 0x22, 0x23, 0x24, 0x25, 0x26).
Le premier caractère ESPACE (0x20) sert à effacer le bitmap dans le cas où l'on veut faire clignoter l'indicateur, comme dans l'exemple ci-dessus.
3.4. Routines graphiques
La librairie AdaFruit possède un jeu de routines graphiques assez complet que la librairie Greiman ne possède pas.AdaFruit propose en outre un outil Windows de création de bitmaps :
https://learn.adafruit.com/monochrome-oled-breakouts/arduino-library-and-examples
Je ne l'ai pas essayé, ne possédant pas de machine Windows et n'ayant pas du tout l'intention d'utiliser ce système d'exploitation dépassé (alergie profonde). Je vous laisse faire avec plaisir.
Avec la librairie Greiman, il n'y a pas de possibilité d'affichage de bitmaps, mais on pourrait en créer (avec plus ou moins de facilité) en utilisant la même méthode que pour l'indicateur de capacité batterie, comme vu précédemment.
4. Conclusion
Personnellement, depuis que j'ai découvert la librairie Greiman, je l'utilise en lieu et place de la librairie AdaFruit, trop gourmande. Avec un afficheur de 128x64 pixels, mon besoin n'est pas de créer des oeuvres d'art graphique.Cordialement
Henri
Pile poil ce que je cherchais !
RépondreSupprimerUn grand merci pour cet excellent tuto :)
Merci et bonne continuation.
SupprimerA bientôt.
Bonjour
RépondreSupprimerje souhaiterai utiliser la bibliothèque SSD1306Ascii pour piloter un afficheur oled en I2C et ceci par un ATtiny85. Quelles modifications devrait on apporter dans votre sketch pour le rendre compatible avec cette puce?
Merci par avance pour votre aide.
La librairie SSD1306Ascii ne compilera pas avec un ATTINY85 qui a un I2C particulier. Il vaut mieux se tourner vers une solution dédiée.
SupprimerPeut-être : https://www.instructables.com/ATTiny85-connects-to-I2C-OLED-display-Great-Things/
Merci pour la réponse rapide.
SupprimerC'est vraiment dommage :(
La bibliothèque SSD1306Ascii me semblait idéale pour mes futures applications(du texte uniquement).
Mais bon on fera sans...
La librairie SSD1306_minimal du lien précédent sait afficher du texte.
Supprimeroui tout à fait, mais à priori on ne peut pas changer la taille du texte et je ne trouve beaucoup de doc ne serait ce les instructions...
SupprimerAu sujet de SSD1306Ascii j'ai cru comprendre qu'elle n'est pas compatible avec le core David A. Mellis que j'ai installé et que je souhaite garder donc ce n'est pas gagné...
En fait il y a une possibilité : il faut utiliser Wire
Supprimer#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"
Voir l'exemple HelloWorldWire.ino
Est ce que la librairie Adafruit rafraichit tout l'écran lorsque seule un (petit ) rectangle est modifie (ex: on dessine un tout petit bonhomme qui se déforme -sprite-; pour faire une animation).
RépondreSupprimerEst ce u'elle est compatible avec des cartes généreusement dotées de RAM (ESPxx, picopi entre autres qui ont 100 fois plus de RZM qu'un UNO canale historico)
La librairie Adafruit rafraîchit uniquement la zone modifiée.
SupprimerJ'utilise couramment des SSD1306 avec des ESP32, et la librairie Adafruit. Je ne vois pas pourquoi la quantité de RAM poserait un problème, bien au contraire.
Bonjour et bravo pour votre site très instructif.
RépondreSupprimerje trouve des afficheurs Oled 0.96'' soit géré par SSD1306 et plus récemment par SSD1315
mais les SSD 1315 ne semble pas fonctionner avec la librairie Greiman.
j'ai deux questions:
1---y a-t-il une astuce pour les faire fonctionner avec cette librairie ?
2---Peut-on (via logiciel) reconnaître la puce 1306 ou 1315 ?
merci de votre réponse
Bonjour. Je n'ai jamais essayé le SSD1315. Les commandes ont l'air pourtant assez proches de celles du SSD1306, ce qui semble logique.
SupprimerEventuellement, il faudrait essayer la librairie
ssd1306xled, qui supporte apparemment les deux .
Bonjour,
RépondreSupprimerJ'ai un écran 1306 mais il ne prend pas en charge la librairie Greiman : l'affichage est illisible.
Pourriez vous m'indiquez quel écran vous utilisez et où l'avez vous acheté ?
J'ai crée une issue sur le github de la librairy si jamais:
https://github.com/greiman/SSD1306Ascii/issues/122
Merci,
Tiphaine
J'utilise celui-ci :
Supprimerhttps://fr.aliexpress.com/item/1859110655.html?spm=a2g0o.order_detail.order_detail_item.5.34007d56TFml6e&gatewayAdapt=glo2fra