Écran OLED SSD1306

Câbler l'écran, trouver son adresse, puis afficher quelque chose d'utile : un menu, des yeux animés ou votre propre image. Tous les codes compilent avec la bibliothèque Adafruit SSD1306.

En bref

ContrôleurSSD1306, écran OLED monochrome
Résolutions courantes128×64 (0,96 pouce) et 128×32 (0,91 pouce)
InterfaceI²C sur les modules à 4 broches, SPI sur ceux à 7 broches
Adresse I²C0x3C la plupart du temps, 0x3D sur certains modules
Alimentation du module3,3 V, ou 5 V selon le module : vérifie la sérigraphie et, en cas de doute, utilise 3,3 V
Mémoire d’image1 024 octets en 128×64 (la bibliothèque Adafruit garde une copie de l’écran en RAM)

Sur une carte avec peu de RAM (Arduino Uno : 2 Ko), le tampon de 1 Ko de la bibliothèque Adafruit pèse lourd. Voir Bibliothèques pour les alternatives.

Câblage

Version I²C (4 broches)

Broche écranESP32 (30 broches)Arduino Uno / Nano
GNDGNDGND
VCC3V35V (ou 3,3 V)
SCLGPIO 22A5
SDAGPIO 21A4

La plupart des modules embarquent déjà les résistances de rappel de l'I²C. Sur ESP32, les broches sont libres : appelle Wire.begin(SDA, SCL) avant display.begin() pour utiliser d'autres GPIO. C'est ce que font le Badge Cloneur, le Mètre Numérique (GPIO 21 et 22) et la Mini Arcade (SDA sur le GPIO 22, SCL sur le 21). Sur ESP32-S3 ou ESP32-C3, les broches par défaut dépendent de la carte : précise-les toujours.

Version SPI (7 broches)

Broche écranESP32
GNDGND
VCC3V3
D0 (SCK)GPIO 18
D1 (MOSI)GPIO 23
RESGPIO 17 (au choix)
DCGPIO 16 (au choix)
CSGPIO 5 (au choix)

Le SPI est plus rapide que l'I²C, utile pour les animations. Le constructeur devient Adafruit_SSD1306 display(128, 64, &SPI, DC, RES, CS);, et display.begin(SSD1306_SWITCHCAPVCC) n'a plus besoin d'adresse.

Trouver l'adresse I²C

Si l'écran reste noir, commence par là. Ce scanner liste les adresses qui répondent dans le moniteur série (115 200 bauds).

i2c_scanner.inoTélécharger
/*
 * Scanner I2C — Kaihatsu Lab
 * Affiche dans le moniteur série (115200 bauds) les adresses qui répondent.
 * Un écran SSD1306 répond en général à 0x3C, parfois à 0x3D.
 */
#include <Wire.h>

// ESP32 : SDA = 21 et SCL = 22 par défaut. Changez ces valeurs si votre câblage est différent.
const int PIN_SDA = 21;
const int PIN_SCL = 22;

void setup() {
  Serial.begin(115200);
#if defined(ARDUINO_ARCH_ESP32)
  Wire.begin(PIN_SDA, PIN_SCL);
#else
  Wire.begin();   // Uno / Nano : SDA = A4, SCL = A5
#endif
}

void loop() {
  Serial.println(F("Scan I2C..."));
  uint8_t found = 0;

  for (uint8_t address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    if (Wire.endTransmission() == 0) {   // 0 = un périphérique a répondu
      Serial.print(F("Périphérique trouvé à l'adresse 0x"));
      if (address < 16) Serial.print('0');
      Serial.print(address, HEX);
      if (address == 0x3C || address == 0x3D) Serial.print(F("  <- probablement l'écran OLED"));
      Serial.println();
      found++;
    }
  }

  if (found == 0) {
    Serial.println(F("Rien détecté : vérifie VCC, GND, SDA et SCL."));
  }
  Serial.println();
  delay(3000);
}

Résultat attendu avec un écran branché :

Scan I2C...
Périphérique trouvé à l'adresse 0x3C  <- probablement l'écran OLED
  • Utilise cette adresse dans display.begin(SSD1306_SWITCHCAPVCC, 0x3C).
  • Certaines fiches indiquent 0x78 ou 0x7A : c'est la même adresse écrite sur 8 bits (0x3C et 0x3D décalées d'un bit). Dans le code Arduino, garde toujours la forme sur 7 bits.
  • Aucune adresse trouvée : vérifie VCC, GND, puis SDA et SCL (ils sont souvent inversés).

Yeux animés

Un regard qui se déplace, des clignements et quatre humeurs. L'aperçu ci-dessous suit la même logique que le code : essaie les humeurs avant de téléverser.

eyes.inoTélécharger
/*
 * Yeux animés — Kaihatsu Lab
 * SSD1306 128x64 (I2C). Regard qui se déplace, clignements, quatre humeurs.
 * Bouton optionnel entre PIN_MOOD et GND pour changer d'humeur à la main.
 *
 * Écran avec des broches I2C personnalisées (ESP32) : appelle
 * Wire.begin(SDA, SCL) dans setup() AVANT display.begin().
 */
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_W  128
#define SCREEN_H  64
#define OLED_ADDR 0x3C
Adafruit_SSD1306 display(SCREEN_W, SCREEN_H, &Wire, -1);

const uint8_t PIN_MOOD = 25;          // bouton optionnel (INPUT_PULLUP)
const bool    AUTO_MOOD = true;       // change d'humeur toute seule

// ---------- Forme des yeux ----------
const int EYE_W = 36;
const int EYE_H = 36;
const int EYE_R = 10;                 // arrondi des coins
const int EYE_GAP = 12;               // espace entre les deux yeux
const int LOOK_MAX_X = 12;            // amplitude du regard (pixels)
const int LOOK_MAX_Y = 8;

enum Mood : uint8_t { MOOD_NORMAL, MOOD_HAPPY, MOOD_ANGRY, MOOD_TIRED, MOOD_COUNT };
Mood mood = MOOD_NORMAL;

// ---------- État de l'animation ----------
float lookX = 0, lookY = 0;           // position actuelle du regard
int   targetX = 0, targetY = 0;       // position visée
float openness = 1.0f;                // 1 = ouvert, 0 = fermé
uint8_t blinkPhase = 0;               // 0 = repos, 1 = se ferme, 2 = se rouvre
float lidAmount = 0;                  // intensité de la paupière (0 à 1)
uint32_t nextBlink = 1500, nextLook = 0, nextMood = 7000, lastFrame = 0;
bool lastButton = false;

void setMood(Mood m) {
  mood = m;
  lidAmount = 0;                      // la paupière se remet en place en douceur
}

// ---------- Dessin ----------
void drawEye(int cx, int cy, bool isLeft) {
  int h = max(3, (int)(EYE_H * openness));
  int x = cx - EYE_W / 2;
  int y = cy - h / 2;
  int r = min(EYE_R, h / 2);
  display.fillRoundRect(x, y, EYE_W, h, r, SSD1306_WHITE);

  int lid = (int)(lidAmount * EYE_H * 0.45f);   // hauteur de la paupière
  if (lid < 1) return;

  switch (mood) {
    case MOOD_TIRED:                  // coins extérieurs abaissés
      if (isLeft) display.fillTriangle(x, y, x + EYE_W, y, x, y + lid, SSD1306_BLACK);
      else        display.fillTriangle(x, y, x + EYE_W, y, x + EYE_W, y + lid, SSD1306_BLACK);
      break;
    case MOOD_ANGRY:                  // coins intérieurs abaissés
      if (isLeft) display.fillTriangle(x, y, x + EYE_W, y, x + EYE_W, y + lid, SSD1306_BLACK);
      else        display.fillTriangle(x, y, x + EYE_W, y, x, y + lid, SSD1306_BLACK);
      break;
    case MOOD_HAPPY:                  // le bas de l'œil remonte en arc
      display.fillRoundRect(x - 2, y + h - lid, EYE_W + 4, EYE_H, EYE_R, SSD1306_BLACK);
      break;
    default:
      break;
  }
}

void drawEyes() {
  display.clearDisplay();
  int cy = SCREEN_H / 2 + (int)lookY;
  int leftX  = SCREEN_W / 2 - (EYE_W + EYE_GAP) / 2 + (int)lookX;
  int rightX = SCREEN_W / 2 + (EYE_W + EYE_GAP) / 2 + (int)lookX;
  drawEye(leftX,  cy, true);
  drawEye(rightX, cy, false);
  display.display();
}

// ---------- Animation ----------
void updateEyes(uint32_t now) {
  // Regard : nouvelle cible de temps en temps, avec un déplacement lissé
  if (now >= nextLook) {
    targetX = random(-LOOK_MAX_X, LOOK_MAX_X + 1);
    targetY = random(-LOOK_MAX_Y, LOOK_MAX_Y + 1);
    if (random(3) == 0) { targetX = 0; targetY = 0; }   // parfois, droit devant
    nextLook = now + random(1200, 3500);
  }
  lookX += (targetX - lookX) * 0.25f;
  lookY += (targetY - lookY) * 0.25f;

  // Clignement en deux temps : fermeture rapide, réouverture un peu plus lente
  if (blinkPhase == 0 && now >= nextBlink) blinkPhase = 1;
  if (blinkPhase == 1) {
    openness -= 0.34f;
    if (openness <= 0.08f) { openness = 0.08f; blinkPhase = 2; }
  } else if (blinkPhase == 2) {
    openness += 0.28f;
    if (openness >= 1.0f) { openness = 1.0f; blinkPhase = 0; nextBlink = now + random(2000, 5500); }
  }

  // Paupières : montent doucement quand l'humeur n'est pas neutre
  float lidTarget = (mood == MOOD_NORMAL) ? 0.0f : 1.0f;
  lidAmount += (lidTarget - lidAmount) * 0.2f;
}

void setup() {
  pinMode(PIN_MOOD, INPUT_PULLUP);
  randomSeed(micros());

  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
    for (;;) delay(1000);             // écran introuvable : lance le scanner I2C
  }
  Wire.setClock(400000);              // I2C rapide pour une animation fluide
  display.clearDisplay();
  display.display();
}

void loop() {
  uint32_t now = millis();

  // Changement d'humeur : bouton ou automatique
  bool button = (digitalRead(PIN_MOOD) == LOW);
  if (button && !lastButton) { setMood((Mood)((mood + 1) % MOOD_COUNT)); nextMood = now + 7000; }
  lastButton = button;
  if (AUTO_MOOD && now >= nextMood) { setMood((Mood)((mood + 1) % MOOD_COUNT)); nextMood = now + 7000; }

  // 30 images par seconde
  if (now - lastFrame >= 33) {
    lastFrame = now;
    updateEyes(now);
    drawEyes();
  }
}

Personnaliser

RéglageEffet
EYE_W, EYE_HTaille de chaque œil en pixels (36 × 36 par défaut).
EYE_RArrondi des coins : 4 donne des yeux carrés, 18 des yeux ronds.
EYE_GAPEspace entre les deux yeux.
LOOK_MAX_X, LOOK_MAX_YAmplitude du regard.
AUTO_MOODMets false pour ne changer d’humeur qu’avec le bouton.
Wire.setClock(400000)I²C à 400 kHz : indispensable pour rester fluide.

Outils image

Tout se passe dans votre navigateur : rien n'est envoyé nulle part.

Convertir une image en tableau C

Colle le tableau dans votre sketch, puis affiche-le avec display.drawBitmap(0, 0, logo, LOGO_W, LOGO_H, SSD1306_WHITE). Les dimensions doivent rester celles de la conversion.

bitmap_example.inoTélécharger
/*
 * Afficher une image convertie — Kaihatsu Lab
 * Colle le tableau généré par le convertisseur à la place de `logo`.
 * Les dimensions doivent être identiques à celles utilisées à la conversion.
 */
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_W  128
#define SCREEN_H  64
#define OLED_ADDR 0x3C
Adafruit_SSD1306 display(SCREEN_W, SCREEN_H, &Wire, -1);

#define LOGO_W 16
#define LOGO_H 16
// Exemple : un petit cadre 16 x 16. Remplacez par votre tableau.
const unsigned char logo[] PROGMEM = {
  0xFF, 0xFF, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01,
  0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01,
  0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01,
  0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xFF, 0xFF
};

void setup() {
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) for (;;) delay(1000);
  display.clearDisplay();
  // Image centrée sur l'écran
  display.drawBitmap((SCREEN_W - LOGO_W) / 2, (SCREEN_H - LOGO_H) / 2,
                     logo, LOGO_W, LOGO_H, SSD1306_WHITE);
  display.display();
}

void loop() {}

Dessiner à la main (128×64)

Dépannage

SymptômeCause probableSolution
Écran noirMauvaise adresse, câblage ou alimentationLance le scanner I²C, vérifie VCC et GND, puis SDA et SCL. Sur ESP32, vérifie les broches passées à Wire.begin.
display.begin() renvoie falseAdresse incorrecte, ou RAM insuffisanteReprends l’adresse trouvée par le scanner. Sur Uno, un tampon de 1 Ko est lourd : passe à U8g2 en mode page.
Rien ne s’affiche après avoir dessinédisplay.display() oubliéLes fonctions de dessin écrivent en mémoire : display() envoie l’image à l’écran.
Pixels aléatoires au démarrageMémoire de l’écran non effacéeAppelle clearDisplay() puis display() juste après begin().
Image décalée de 2 pixels ou colonne parasiteModule SH1106 vendu comme SSD1306 (132 colonnes)Utilise Adafruit_SH110X ou U8g2 avec le constructeur SH1106.
Seule une partie de l’écran s’afficheRésolution déclarée fausse (128×32 contre 128×64)Corrige SCREEN_H pour qu’elle corresponde au module.
Accents illisiblesLa police de base ne contient que l’ASCIIÉcris sans accents, ou utilise U8g2 qui propose des polices avec accents.
Animation saccadéeI²C à 100 kHz par défautAjoute Wire.setClock(400000) après begin(), ou passe en SPI.

Choisir une bibliothèque

Adafruit SSD1306 + GFXU8g2
Prise en mainTrès simple, énormément d’exemplesPlus de notions (un constructeur par écran et par mode), très bien documentée
Mémoire en 128×64Tampon complet de 1 024 octetsMode page de 128 octets (_1_), 256 octets (_2_) ou complet de 1 024 octets (_F_) ; le mode texte u8x8 n’utilise pas de tampon
PolicesPolice de base ASCII, polices GFX en optionTrès grand choix, avec accents
ContrôleursSSD1306 (le SH1106 passe par Adafruit_SH110X)SSD1306, SH1106 et de nombreux autres
À choisir sivous débutez ou vous utilisez déjà Adafruit GFXLa RAM est limitée, votre module est un SH1106, ou vous voulez des polices variées

Les projets Kaihatsu Lab et les codes de cette page utilisent la bibliothèque Adafruit.

Dans les projets

Ces projets utilisent un SSD1306 en I²C à l'adresse 0x3C :

Un autre composant à documenter ?

Recevoir les prochaines sorties

Un email quand un projet, un guide, un PCB ou un kit est publié. Pas de spam, désinscription à tout moment.

Être prévenu

Recevez un email quand une nouveauté est publiée.