É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ôleur | SSD1306, écran OLED monochrome |
|---|---|
| Résolutions courantes | 128×64 (0,96 pouce) et 128×32 (0,91 pouce) |
| Interface | I²C sur les modules à 4 broches, SPI sur ceux à 7 broches |
| Adresse I²C | 0x3C la plupart du temps, 0x3D sur certains modules |
| Alimentation du module | 3,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’image | 1 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 écran | ESP32 (30 broches) | Arduino Uno / Nano |
|---|---|---|
| GND | GND | GND |
| VCC | 3V3 | 5V (ou 3,3 V) |
| SCL | GPIO 22 | A5 |
| SDA | GPIO 21 | A4 |
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 écran | ESP32 |
|---|---|
| GND | GND |
| VCC | 3V3 |
| D0 (SCK) | GPIO 18 |
| D1 (MOSI) | GPIO 23 |
| RES | GPIO 17 (au choix) |
| DC | GPIO 16 (au choix) |
| CS | GPIO 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).
/*
* 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.
/*
* 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églage | Effet |
|---|---|
| EYE_W, EYE_H | Taille de chaque œil en pixels (36 × 36 par défaut). |
| EYE_R | Arrondi des coins : 4 donne des yeux carrés, 18 des yeux ronds. |
| EYE_GAP | Espace entre les deux yeux. |
| LOOK_MAX_X, LOOK_MAX_Y | Amplitude du regard. |
| AUTO_MOOD | Mets 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.
/*
* 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ôme | Cause probable | Solution |
|---|---|---|
| Écran noir | Mauvaise adresse, câblage ou alimentation | Lance 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 false | Adresse incorrecte, ou RAM insuffisante | Reprends 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émarrage | Mémoire de l’écran non effacée | Appelle clearDisplay() puis display() juste après begin(). |
| Image décalée de 2 pixels ou colonne parasite | Module SH1106 vendu comme SSD1306 (132 colonnes) | Utilise Adafruit_SH110X ou U8g2 avec le constructeur SH1106. |
| Seule une partie de l’écran s’affiche | Résolution déclarée fausse (128×32 contre 128×64) | Corrige SCREEN_H pour qu’elle corresponde au module. |
| Accents illisibles | La police de base ne contient que l’ASCII | Écris sans accents, ou utilise U8g2 qui propose des polices avec accents. |
| Animation saccadée | I²C à 100 kHz par défaut | Ajoute Wire.setClock(400000) après begin(), ou passe en SPI. |
Choisir une bibliothèque
| Adafruit SSD1306 + GFX | U8g2 | |
|---|---|---|
| Prise en main | Très simple, énormément d’exemples | Plus de notions (un constructeur par écran et par mode), très bien documentée |
| Mémoire en 128×64 | Tampon complet de 1 024 octets | Mode page de 128 octets (_1_), 256 octets (_2_) ou complet de 1 024 octets (_F_) ; le mode texte u8x8 n’utilise pas de tampon |
| Polices | Police de base ASCII, polices GFX en option | Très grand choix, avec accents |
| Contrôleurs | SSD1306 (le SH1106 passe par Adafruit_SH110X) | SSD1306, SH1106 et de nombreux autres |
| À choisir si | vous débutez ou vous utilisez déjà Adafruit GFX | La 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 :
PRJ-02Mètre Numérique
Télémètre ultrasonique ESP32 + HC-SR04 avec OLED SSD1306, interface web, surveillance et alertes Gmail.
PRJ-03Mini Arcade
Mini borne d'arcade de poche avec plusieurs mini-jeux embarqués.
Un autre composant à documenter ?