Développement de pilote pour écran OLED : des principes fondamentaux à l'implémentation pratique

Les écrans OLED (diodes électroluminescentes organiques) sont devenus l'un des périphériques d'affichage les plus populaires dans le développement embarqué grâce à leur auto-éclairage, fort contraste, faible consommation et large angle de vision. Cet article se concentre sur le modèle classique d'écran OLED de 0,96 pouces avec interface I2C/SPI (puise à la pucee SSD1306) pour expliquer le processus complet de développement de pilote, du principe matériel à l'extension des fonctionnalités. Les exemples de code sont adaptés pour les microcontrôleurs STM32 et peuvent être directement移植és dans l'environnement Keil MDK.

I. Compréhension des bases des écrans OLED

1.1 Spécifications principales (écran OLED de 0,96 pouces)

  • Puce pilote : SSD1306 (solution standard de l'industrie) ;
  • Résolution : 128×64 (128 colonnes, 64 lignes) ;
  • Type d'interface : I2C (2 fils) ou SPI (4/6 fils), l'interface I2C étant plus simple pour les débutants ;
  • Tension d'alimentation : 3,3V (certains modèles compatibles 5V, attention au niveau logique) ;
  • Couleur d'affichage : monochrome (bleu/blanc) ou bicolore (bleu-jaune/blanc-bleu), nous prendrons l'exemple d'un écran bleu avec texte blanc.

1.2 Principe de fonctionnement du SSD1306

Le SSD1306 est une puce conçue spécifiquement pour les écrans OLED. Son rôle principal est de recevoir les commandes et données du microcontrôleur pour contrôler l'allumage/extinction des pixels :

  1. Mode commande : reception des instructions de contrôle (initialisation, positionnement du curseur, activation de l'affichage) ;
  2. Mode données : reception des données d'affichage des pixels, la résolution 128×64 correspondant à 1024 octets de mémoire vidéo (chaque octet contrôle 8 pixels) ;
  3. Mappage mémoire : les colonnes de l'écran correspondent à l'axe X (0127) de la mémoire, les lignes sont groupées par 8 pour correspondre à l'axe Y (07, chaque groupe représentant 8 lignes).

II. Préparation matérielle et câblage

2.1 Matériel requis

  • Contrôleur principal : carte minimale STM32F103C8T6 (noyau Cortex-M3, adapté aux débutants) ;
  • Écran OLED : modèle de 0,96 pouces avec interface I2C SSD1306 ;
  • Accessoires : câbles dupont, alimentation 3,3V (ou alimentation directe par le microcontrôleur), module USB-série pour le débogage.

2.2 Câblage de l'interface I2C (configuration minimale)

L'interface I2C de l'écran OLED de 0,96 pouces n'utilise que 4 broches principales, comme indiqué ci-desous (exemple pour STM32) :

Broche OLED Fonction Broche STM32 (exemple) Remarques
VCC Alimentation 3,3V Ne pas connecter en 5V pour éviter d'endommager l'écran
GND Masse GND Une masse commune assure la stabilité des niveaux logiques
SCL Horloge I2C PB6 Peut être assignée à d'autres GPIO
SDA Données I2C PB7 Peut être assignée à d'autres GPIO

Note : Les écrans OLED avec interface SPI nécessitent des connexions supplémentaires pour les broches DC (sélecteur données/commandes), RES (reset) et CS (sélection de puce), rendant le câblage plus complexe. Pour les débutants, privilégiez la version I2C.

III. Configuration de l'environnement de développement

3.1 Environnement de base

  • Outil de développement : Keil MDK-ARM V5.x ;
  • Bibliothèque de firmware : bibliothèque standard STM32F10x (ou bibliothèque HAL, nous utiliserons la bibliothèque standard) ;
  • Essentiel : implémentation d'un pilote de bas niveau pour I2C (I2C logiciel, sans dépendre du matériel I2C pour une meilleure compatibilité).

3.2 Implémentation du pilote de bas niveau I2C logiciel

Les débutants n'ont pas besoin de comprendre en détail le protocole I2C, ils peuvent directement réutiliser le code I2C logiciel suivant, en modifiant simplement les définitions de broches :

#include "stm32f10x.h"
#include "delais.h"  // Fonction de délai à implémenter (niveau us/ms)

// ************************* Définition des broches *************************
#define BROCHE_SCL     GPIO_Pin_6
#define PORT_SCL       GPIOB
#define BROCHE_SDA     GPIO_Pin_7
#define PORT_SDA       GPIOB

// Macros de contrôle des niveaux logiques
#define SCL_H()  GPIO_SetBits(PORT_SCL, BROCHE_SCL)
#define SCL_L()  GPIO_ResetBits(PORT_SCL, BROCHE_SCL)
#define SDA_H()  GPIO_SetBits(PORT_SDA, BROCHE_SDA)
#define SDA_L()  GPIO_ResetBits(PORT_SDA, BROCHE_SDA)

/**
 * @brief  Initialisation des broches I2C pour l'écran OLED
 * @param  Aucun
 * @retval Aucun
 */
void Init_I2C_OLED(void)
{
    GPIO_InitTypeDef structureGPIO;
    
    // Activation de l'horloge GPIOB
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    // Configuration des broches SCL/SDA en sortie push-pull
    structureGPIO.GPIO_Pin = BROCHE_SCL | BROCHE_SDA;
    structureGPIO.GPIO_Mode = GPIO_Mode_Out_PP;
    structureGPIO.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(PORT_SCL, &structureGPIO);
    
    // Niveaux logiques initiaux à haut
    SCL_H();
    SDA_H();
}

/**
 * @brief  Signal de départ I2C logiciel
 * @param  Aucun
 * @retval Aucun
 */
void Signal_Debut_I2C(void)
{
    SDA_H();
    SCL_H();
    delais_us(2);
    SDA_L();  // SDA passe à bas niveau pendant que SCL est haut
    delais_us(2);
    SCL_L();
}

/**
 * @brief  Signal d'arrêt I2C logiciel
 * @param  Aucun
 * @retval Aucun
 */
void Signal_Arret_I2C(void)
{
    SDA_L();
    SCL_H();
    delais_us(2);
    SDA_H();  // SDA repasse à haut pendant que SCL est haut
    delais_us(2);
    SCL_L();
}

/**
 * @brief  Envoi d'un octet via I2C logiciel
 * @param  donnee : octet à envoyer
 * @retval Aucun
 */
void Envoyer_Octet_I2C(uint8_t donnee)
{
    uint8_t i;
    for(i=0; i<8; i++)
    {
        SCL_L();
        delais_us(2);
        // Envoi bit par bit (bit de poids fort en premier)
        if(donnee & 0x80) SDA_H();
        else SDA_L();
        donnee <<= 1;
        delais_us(2);
        SCL_H();
        delais_us(2);
    }
    // Attente de l'accusé de réception du esclave (simplifié pour les débutants)
    SCL_L();
    delais_us(2);
    SDA_H();
    delais_us(2);
    SCL_H();
    delais_us(2);
    SCL_L();
}

/**
 * @brief  Envoi d'une commande à l'écran OLED
 * @param  commande : octet de commande
 * @retval Aucun
 */
void Envoyer_Commande_OLED(uint8_t commande)
{
    Signal_Debut_I2C();
    Envoyer_Octet_I2C(0x78);  // Adresse I2C du périphérique (0x3C << 1, adresse par défaut du SSD1306)
    Envoyer_Octet_I2C(0x00);  // Mode commande (0x00)
    Envoyer_Octet_I2C(commande);
    Signal_Arret_I2C();
}

/**
 * @brief  Envoi de données à l'écran OLED
 * @param  donnee : octet de données d'affichage
 * @retval Aucun
 */
void Envoyer_Donnee_OLED(uint8_t donnee)
{
    Signal_Debut_I2C();
    Envoyer_Octet_I2C(0x78);  // Adresse I2C
    Envoyer_Octet_I2C(0x40);  // Mode données (0x40)
    Envoyer_Octet_I2C(donnee);
    Signal_Arret_I2C();
}

Points clés :

  • L'I2C logiciel n'a pas besoin de configurer le matériel I2C du microcontrôleur, il simule simplement le timing via GPIO, ce qui est plus simple pour les débutants ;
  • La fonction delais_us() doit être implémentée (basée sur SysTick ou un timer), elle est cruciale pour le timing I2C ;
  • L'adresse I2C par défaut du SSD1306 est 0x3C (certains écrans utilisent 0x78, à ajuster selon le matériel).

IV. Initialisation de l'écran OLED et fonctions d'affichage de base

4.1 Initialisation de l'écran OLED

Le SSD1306 nécessite l'envoi d'une série de commandes d'initialisation pour configurer ses paramètres d'affichage. Voici la fonction d'initialisation générique :

/**
 * @brief  Initialisation de l'écran OLED
 * @param  Aucun
 * @retval Aucun
 */
void Initialiser_OLED(void)
{
    delais_ms(100);  // Délai après mise sous tension pour stabilisation de la puce
    
    Init_I2C_OLED();  // Initialisation des broches I2C
    
    // Commandes d'initialisation du SSD1306
    Envoyer_Commande_OLED(0xAE);  // Désactiver l'affichage
    Envoyer_Commande_OLED(0x00);  // Définir l'adresse de départ de colonne (4 bits bas)
    Envoyer_Commande_OLED(0x10);  // Définir l'adresse de départ de colonne (4 bits haut)
    Envoyer_Commande_OLED(0x40);  // Définir la ligne de départ d'affichage
    Envoyer_Commande_OLED(0xB0);  // Définir l'adresse de page (axe Y)
    Envoyer_Commande_OLED(0x81);  // Régler le contraste
    Envoyer_Commande_OLED(0xFF);  // Valeur de contraste (0~255, plus grand = plus lumineux)
    Envoyer_Commande_OLED(0xA1);  // Remappage des segments (0xA0 = inversion gauche/droite, 0xA1 = normal)
    Envoyer_Commande_OLED(0xA6);  // Affichage normal (0xA6 = normal, 0xA7 = inversé)
    Envoyer_Commande_OLED(0xA8);  // Régler le taux de multiplexage
    Envoyer_Commande_OLED(0x3F);  // Affichage en 64 lignes
    Envoyer_Commande_OLED(0xC8);  // Direction de balayage (0xC0 = inversion haut/bas, 0xC8 = normal)
    Envoyer_Commande_OLED(0xD3);  // Définir le décalage d'affichage
    Envoyer_Commande_OLED(0x00);  // Décalage de 0
    Envoyer_Commande_OLED(0xD5);  // Régler la division d'horloge
    Envoyer_Commande_OLED(0x80);  // Facteur de division
    Envoyer_Commande_OLED(0xD9);  // Régler la période de précharge
    Envoyer_Commande_OLED(0xF1);
    Envoyer_Commande_OLED(0xDA);  // Configuration des broches COM
    Envoyer_Commande_OLED(0x12);
    Envoyer_Commande_OLED(0xDB);  // Régler la tension VCOMH
    Envoyer_Commande_OLED(0x40);
    Envoyer_Commande_OLED(0x8D);  // Activer la pompe de charge
    Envoyer_Commande_OLED(0x14);
    Envoyer_Commande_OLED(0xAF);  // Activer l'affichage
}

/**
 * @brief  Définir la position d'affichage sur l'écran OLED (X: colonnes 0~127, Y: pages 0~7)
 * @param  x : coordonnée de colonne
 * @param  y : coordonnée de page (chaque page = 8 lignes)
 * @retval Aucun
 */
void Definir_Position(uint8_t x, uint8_t y)
{
    Envoyer_Commande_OLED(0xB0 + y);                // Définir l'adresse de page
    Envoyer_Commande_OLED(((x & 0xF0) >> 4) | 0x10); // Définir l'adresse de colonne (4 bits haut)
    Envoyer_Commande_OLED(x & 0x0F);                // Définir l'adresse de colonne (4 bits bas)
}

/**
 * @brief  Fonction d'effacement de l'écran
 * @param  Aucun
 * @retval Aucun
 */
void Effacer_Ecran(void)
{
    uint8_t x, y;
    for(y=0; y<8; y++)
    {
        Definir_Position(0, y);
        for(x=0; x<128; x++)
        {
            Envoyer_Donnee_OLED(0x00);  // Écriture de 0 pour éteindre tous les pixels
        }
    }
}

4.2 Fonctions d'affichage de caractères/chiffres

Pour afficher des caractères, il faut d'abord préparer les données de matrice de points (nous utiliserons une matrice ASCII 8×16 dans cet exemple). Voici les fonctions d'affichage principales :

// Tableau de matrice ASCII 8×16 (partiel, tableau complet téléchargeable en ligne)
const unsigned char MATRICE_ASCII[] = {
    0x00,0x00,0x7C,0x12,0x11,0x12,0x7C,0x00,0x00,0x00,0x7C,0x12,0x11,0x12,0x7C,0x00, // 0
    0x00,0x00,0x00,0x70,0x08,0x08,0x08,0x70,0x00,0x00,0x00,0x70,0x08,0x08,0x08,0x70, // 1
    // Plus de caractères omis, tableau ASCII complet nécessaire
};

/**
 * @brief  Afficher un caractère unique (matrice 8×16)
 * @param  x : colonne de départ (0~127)
 * @param  y : page de départ (0~7, chaque caractère occupe 2 pages)
 * @param  c : caractère à afficher (code ASCII)
 * @retval Aucun
 */
void Afficher_Caractere(uint8_t x, uint8_t y, uint8_t c)
{
    uint8_t i;
    c -= '0';  // Basé sur le chiffre 0 (pour les lettres, ajuster le décalage)
    Definir_Position(x, y);
    // Afficher la moitié supérieure (page 1)
    for(i=0; i<8; i++)
    {
        Envoyer_Donnee_OLED(MATRICE_ASCII[c*16 + i]);
    }
    Definir_Position(x, y+1);
    // Afficher la moitié inférieure (page 2)
    for(i=8; i<16; i++)
    {
        Envoyer_Donnee_OLED(MATRICE_ASCII[c*16 + i]);
    }
}

/**
 * @brief  Afficher une chaîne de chiffres
 * @param  x : colonne de départ
 * @param  y : page de départ
 * @param  nombre : nombre à afficher (0~99999999)
 * @param  longueur : nombre de chiffres à afficher
 * @retval Aucun
 */
void Afficher_Nombre(uint8_t x, uint8_t y, uint32_t nombre, uint8_t longueur)
{
    uint8_t i;
    for(i=0; i<longueur; i++)
    {
        Afficher_Caractere(x + 8*i, y, (nombre / (uint32_t)puissance(10, longueur-i-1)) % 10 + '0');
    }
}

/**
 * @brief  Afficher une chaîne de caractères
 * @param  x : colonne de départ
 * @param  y : page de départ
 * @param  chaine : pointeur vers la chaîne de caractères
 * @retval Aucun
 */
void Afficher_Chaine(uint8_t x, uint8_t y, uint8_t *chaine)
{
    while(*chaine != '\0')
    {
        Afficher_Caractere(x, y, *chaine);
        x += 8;
        if(x > 120)  // Dépassement de la largeur de l'écran, passage à la ligne suivante
        {
            x = 0;
            y += 2;
        }
        chaine++;
    }
}

V. Exemple pratique : affichage sur l'écran OLED

5.1 Code de test complet

Intégrons les fonctions précédentes pour implémenter des fonctionnalités d'affichage de base :

#include "stm32f10x.h"
#include "oled.h"  // Encapsuler les fonctions de pilote OLED dans oled.h/oled.c
#include "delais.h"

int main(void)
{
    uint32_t compteur = 0;
    
    // Initialisation du système
    SystemInit();  // Configuration de la fréquence principale STM32 (72MHz)
    delais_init();  // Initialisation de la fonction de délai
    Initialiser_OLED();   // Initialisation de l'écran OLED
    Effacer_Ecran();  // Effacer l'écran
    
    while(1)
    {
        // Affichage d'une chaîne de caractères fixe
        Afficher_Chaine(0, 0, "Demonstration OLED");
        Afficher_Chaine(0, 2, "Compteur:");
        // Affichage d'un nombre incrémenté
        Afficher_Nombre(60, 2, compteur, 5);
        compteur++;
        if(compteur > 99999) compteur = 0;
        
        delais_ms(100);  // Rafraîchissement toutes les 100ms
    }
}

5.2 Vérification du résultat

  1. Compiler le code et le télécharger dans le microcontrôleur STM32 ;
  2. Après mise sous tension, l'écran OLED s'efface d'abord, puis affiche :
  • Première ligne : Demonstration OLED ;
  • Troisième ligne : Compteur: 00000 (le nombre incrmente toutes les secondes) ;
  1. Si l'écran n'affiche rien, vérifier le câblage (notamment que VCC est bien à 3,3V), l'adresse I2C et les commandes d'initialisation.

VI. Problèmes courants et solutions

6.1 Pas d'affichage

  • Cause 1 : tension d'alimentation incorrecte (5V endommage l'écran ou tension insuffisante) ; Solution : connecter impérativement en 3,3V, vérifier la tension au niveau de la broche VCC de l'écran OLED ;
  • Cause 2 : adresse I2C incorrecte ; Solution : changer Envoyer_Octet_I2C(0x78) en 0x38 (0x1C<<1) pour tester ;
  • Cause 3 : imprécision de la fonction de délai ; Solution : optimiser la fonction delais_us() pour s'assurer que le timing I2C correspond aux exigences du SSD1306.

6.2 Affichage corrompu/décalé

  • Cause 1 : incompatibilité entre la matrice de points et la fonction d'affichage ; Solution : s'assurer que la matrice est bien en 8×16 et que le bit de poids fort est envoyé en premier ;
  • Cause 2 : définition incorrecte des coordonnées ; Solution : vérifier la fonction Definir_Position(), les adresses de page doivent être comprises entre 0 et 7, les adresses de colonne entre 0 et 127.

6.3 Scintillement de l'écran

  • Cause : fréquence d'effacement/rafraîchissement trop élevée ; Solution : réduire les opérations d'effacement inutiles, ne rafraîchir que les zones modifiées.

VII. Suggestions d'extension des fonctionnalités

  1. Affichage de caractères chinois : ajouter une matrice de points de 16×16 pour les caractères chinois, modifier la fonction d'affichage pour adapter la largeur de 16 colonnes ;
  2. Affichage graphique : implémenter l'affichage de points, lignes, cercles et images bitmap en écrivant dans la mémoire vidéo ;
  3. Défilement d'affichage : utiliser les commandes de défilement du SSD1306 (0x26~0x29) pour un défilement de texte ;
  4. Mode basse consommation : désactiver l'affichage via Envoyer_Commande_OLED(0xAE) pour réduire la consommation.

Étiquettes: STM32 OLED SSD1306 I2C écrans embarqués

Publié le 29 juin à 07h30