Écran OLED I2C SSD1306
Toujours autant sympa ces petits écrans OLED: ils sont faciles à trouver, ne coûtent quasiment rien, très facile à interfacer en micro-python et d’une sobriété exemplaire comparé à un écran LCD ou TFT. Dans cet exemple nous allons utiliser un petit écran OLED I2C SSD1306 128x64 pixels monochrome que nous allons interfacer avec un ESP32 (les programmes fonctionnent aussi avec un Raspberry PICO). J’utilise une version PYBSTICK ESP32-C3 mais n’importe quelle autre ESP32 ou Raspberry PICO conviendra, sous micro-python.
Attention à bien prendre une version OLED SSD1306 et non pas une version SSH1106: je me suis fait avoir avec une seconde commande et ça ne fonctionne pas du tout avec des SSH1106.
Circuit électronique
Avec un écran OLED 2IC il n’y a que 4 fils à relier à la carte: GND, VCC (+3.3v), SCI et SDA: rien de plus !
Code micropython
Bibliothèque ssd1306
Pour piloter l’écran en micropython sur un ESP32, il faut récupérer une bibliothèque très utile que l’on trouve ici sur le Github micropython-esp32: recopiez ce code à la racine de la mémoire flash de votre ESP32 et nommez-le bien ssd1306.py.
Afficher une image
Pour afficher des images sur l’écran il va falloir ruser un peu car la bibliothèque ssd1306 fonctionne avec des frame buffer, grâce auxquels on peut écrire du texte et dessiner quelques figures géométriques basiques (points, lignes, cercles, ellipses, rectangles, polygones). Pour afficher une image, il faudra la convertir en byte array (tableau de codes binaires) (noir et blanc) aux bonnes dimensions pour l’afficher sur l’écran OLED via un frame buffer.
La conversion d’une image (jpg ou png ou tout autre format) en byte array se fait tout simplement grâce à cet excellent convertisseur en ligne:
- Vous chargez de préférence une image que vous avez déjà redimensionnée et seuillée en binaire noir & blanc via Gimp ou tout autre logiciel de retouche photo.
- Dans les settings vous choisissez un background noir ou blanc, puis vous redimensionnez l’image au besoin, vous pouvez jouer avec des options de recadrage et rotations.
- A ce stade vous devriez voir l’aperçu de l’image finale.
- dans la section output, choisissez: plein bytes - Horizontal - 1 bite per pixel: c’est précisément le format attendu dans un frame buffer.


Il suffit alors de récupérer la longue liste des codes hexadécimaux et de les recopier dans la section ‘data’ des dictionnaires de la classe Logo du code micropython logo.py ci-dessous (à créer sur votre ESP32)
import framebuf
class Logo():
def __init__(self):
''' contructor
https://javl.github.io/image2cpp/ --> convert image into bytearray
output format: plein bytes - Horizontal - 1 bite per pixel
'''
# logo dictionary
self.__logos = {
# 'nom_logo' : { 'size': (width, hight)
# data : [ ... hex code 1 byte = 8 horizontal pixel ... ]
# }
# self.__logos['nom_logo']['size'] retourne la taille sous forme de tuple (width, height)
# self.__logos['nom_logo']['data'] retourne la liste des code hexa de l'image
'papsdroid':
{'size': (44,64),
'data':[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xcf, 0x9f, 0x00, 0x00, 0x00, 0x0f,
0xcf, 0x9f, 0x00, 0x00, 0x00, 0x0f, 0xcf, 0x9f, 0x00, 0x00, 0x00, 0x0f, 0xcf, 0x9f, 0x00, 0x00,
0x00, 0x0f, 0xcf, 0x9f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00, 0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00, 0x07, 0xcf,
0xcf, 0x9f, 0x3e, 0x00, 0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00, 0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00,
0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xc0, 0x0f, 0x80, 0x3e, 0x00, 0x07, 0xc0, 0x0f, 0x80, 0x3e, 0x00, 0x07, 0xc0,
0x0f, 0x80, 0x3e, 0x00, 0x07, 0xc0, 0x0f, 0x80, 0x3e, 0x00, 0x07, 0xc0, 0x0f, 0x80, 0x3e, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xcf, 0xcf, 0x9f,
0x3e, 0x00, 0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00, 0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00, 0x07, 0xcf,
0xcf, 0x9f, 0x3e, 0x00, 0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00, 0x07, 0xcf, 0xcf, 0x9f,
0x3e, 0x00, 0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00, 0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00, 0x07, 0xcf,
0xcf, 0x9f, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0xc0, 0x00, 0x00, 0x3e, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x3e, 0x00, 0x07, 0xc0, 0x00, 0x00,
0x3e, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x3e, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00,
0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00, 0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00, 0x07, 0xcf, 0xcf, 0x9f,
0x3e, 0x00, 0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00, 0x07, 0xcf, 0xcf, 0x9f, 0x3e, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xcf, 0x9f, 0x00, 0x00,
0x00, 0x0f, 0xcf, 0x9f, 0x00, 0x00, 0x00, 0x0f, 0xcf, 0x9f, 0x00, 0x00, 0x00, 0x0f, 0xcf, 0x9f,
0x00, 0x00, 0x00, 0x0f, 0xcf, 0x9f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]},
'pi': {'size': (16,16),
'data':[0x0f, 0xfe, 0x3f, 0xff, 0x7f, 0xff, 0xff, 0xfe, 0x06, 0x70, 0x06, 0x60, 0x06, 0x60, 0x0e, 0x60,
0x0e, 0x60, 0x0e, 0x60, 0x0e, 0x60, 0x1e, 0x71, 0x1e, 0x71, 0x1e, 0x7f, 0x1c, 0x3e, 0x00, 0x08]},
'micropython':
{ 'size': (64,64),
'data': [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x50, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x3b, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x4c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x7f, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x87, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x0c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x41, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x3c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0xf0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x19, 0xe1, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x35, 0xe7, 0xfc, 0x00, 0x00,
0x00, 0x00, 0x3f, 0xa5, 0xef, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc9, 0xff, 0xff, 0x80, 0x00,
0x00, 0x03, 0xff, 0xe5, 0xff, 0xff, 0x80, 0x00, 0x00, 0x07, 0xff, 0x8d, 0xff, 0xfc, 0x80, 0x00,
0x00, 0x07, 0xff, 0x81, 0xff, 0xf9, 0x80, 0x00, 0x00, 0x07, 0xff, 0xc3, 0xff, 0xe2, 0x80, 0x00,
0x00, 0x07, 0xbe, 0x3b, 0xff, 0xcd, 0xc0, 0x00, 0x00, 0x0f, 0x66, 0x03, 0xff, 0x37, 0xe0, 0x00,
0x00, 0x0f, 0x3e, 0x03, 0xff, 0xc7, 0xe0, 0x00, 0x00, 0x1f, 0xe3, 0x83, 0xd0, 0x9f, 0xf0, 0x00,
0x00, 0x0f, 0xfe, 0x7a, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x0f, 0xfe, 0x02, 0xff, 0xff, 0xf0, 0x00,
0x00, 0x1f, 0xfe, 0x01, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x7b, 0xff, 0x03, 0xff, 0xff, 0xdc, 0x00,
0x01, 0xfd, 0xff, 0xcd, 0x7f, 0xfb, 0x9f, 0x00, 0x0f, 0xff, 0x7f, 0x00, 0xff, 0xff, 0x3f, 0xc0,
0x3f, 0xff, 0x90, 0x80, 0xfa, 0x32, 0xff, 0xf8, 0x3f, 0xff, 0xf0, 0x7c, 0x74, 0x27, 0xff, 0xcc,
0x67, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xfe, 0x7c, 0x7c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xfc,
0x7f, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xfc, 0x7f, 0xe7, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xfc,
0x1f, 0xfc, 0xff, 0xff, 0xff, 0xfb, 0xff, 0xa0, 0x0f, 0xff, 0x9f, 0xff, 0xff, 0xdf, 0xfc, 0x80,
0x0c, 0xff, 0xf3, 0xff, 0xfe, 0x7f, 0xf4, 0x70, 0x0e, 0xdf, 0xfc, 0x7f, 0xf3, 0xff, 0xa3, 0x60,
0x00, 0xcf, 0xff, 0x9f, 0xdf, 0xfc, 0x97, 0x00, 0x00, 0x6c, 0xbf, 0xf2, 0x7f, 0xf4, 0x30, 0x00,
0x00, 0x0c, 0xdf, 0xfc, 0xff, 0xa3, 0x20, 0x00, 0x00, 0x00, 0xcf, 0xff, 0xfc, 0xb3, 0x00, 0x00,
0x00, 0x00, 0x4c, 0xff, 0xfc, 0x30, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x9f, 0xf1, 0x20, 0x00, 0x00,
0x00, 0x00, 0x00, 0x83, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]},
}
def get_logo(self, name:str) -> framebuf.FrameBuffer:
''' return logo by name into a framebuffer to display
'''
w,h = self.__logos[name]['size']
data = bytearray(self.__logos[name]['data'])
fbuf = framebuf.FrameBuffer(data, w, h, framebuf.MONO_HLSB)
return fbuf
Ce code va permettre de transformer un byte array obtenu depuis ce convertisseur en frame buffer, afin d’être affiché directement sur l’écran OLED. Dans cet exemple j’ai 3 images:
- logo papsdroid en 44x64 pixels
- le logo de micropython en 64x64 pixels
- le symbole pi en 16x16 pixels
Ce dictionnaire de logos peut être facilement modifié pour enregistrer plein d’autres images. Respectez bien la structure du dictionnaire avec ces 3 exemples.
Test d’affichage sur écran OLED
ce code va permettre de tester l’affichage sur votre écran OLED. Dans un premier temps il va afficher un rectangle plein tout blanc pendant 3 secondes: ceci permet de visuellement voir s’il y a des pixels morts à l’écran. Ensuite vont s’afficher 3 images définies dans la classe Logo et du texte, le tout encadré dans un rectangle.
Copier et coller ce code sur votre ESP32, nommez le test_oled.py. Attention l’exécution de ce code n’est possible que si vous avez bien préalablement créé les programmes ssd1306.py et logo.py:
from machine import Pin, SoftI2C
from ssd1306 import SSD1306_I2C
from time import sleep
from logo import Logo
class Oled(SSD1306_I2C):
def __init__(self, scl_pin:int, sda_pin:int, width:int=128, height:int=64):
self.i2c = SoftI2C(scl=Pin(scl_pin), sda=Pin(sda_pin))
self.width = width
self.height = height
super().__init__(self.width, self.height, self.i2c)
self.logos = Logo()
def test_screen(self):
''' fill the full screen 2s : visual control of the dead pixel '''
self.fill(1)
self.show()
sleep(3)
self.fill(0)
self.show()
def display_logo(self, name_logo:str, x:int=0, y:int=0):
''' display 'logo' at position (x,y)) '''
fbuf = self.logos.get_logo(name_logo)
self.blit(fbuf, x, y)
if __name__ == "__main__":
oled = Oled(scl_pin=1, sda_pin=0)
oled.test_screen()
oled.display_logo('papsdroid',0,0)
oled.display_logo('pi',112,40)
oled.display_logo('micropython', 45 ,20)
oled.text('papsdroid', 45, 8 )
oled.rect(0,0,128,64,1)
oled.show()