Bouclier PCB d’interface de station météo ESP32

Bouclier PCB d'interface de station météo ESP32

Dans ce projet, vous apprendrez à construire un blindage PCB d’interface de station météo pour la carte de développement ESP32. Le circuit imprimé comprend un capteur de température, d’humidité et de pression BME280, une résistance dépendant de la lumière, un bouton-poussoir, un écran OLED et plusieurs LED RVB adressables WS2812B. L’OLED affiche les lectures du capteur et les LED produisent des effets d’éclairage différents de ce qui est affiché dans l’OLED. Il affiche également la date et l’heure.

Station météo ESP32 Interface PCB Bouclier Température Humidité Pression Date Heure

Regardez le didacticiel vidéo

Ce projet est disponible en format vidéo et en format écrit. Vous pouvez regarder la vidéo ci-dessous ou vous pouvez faire défiler vers le bas pour les instructions écrites.

YouTube video

Ressources

Vous pouvez trouver toutes les ressources nécessaires pour construire ce projet dans les liens ci-dessous (ou vous pouvez visiter le Projet GitHub page):

Aperçu du projet

Présentation du projet d'interface de station météo ESP32

Caractéristiques matérielles du circuit imprimé de la station météo

Le bouclier est conçu avec des broches d’en-tête pour empiler la carte ESP32. Pour cette raison, si vous souhaitez construire et utiliser notre PCB, vous devez vous procurer la même carte de développement ESP32. Nous utilisons le Kit de développement ESP32 DOIT V1 carte (le modèle avec 36 GPIO).

Bouclier d'interface de station météo PCB à carte empilée

Si vous souhaitez suivre ce projet et que vous avez un modèle ESP32 différent, vous pouvez assembler le circuit sur une planche à pain ou modifier la disposition et le câblage du circuit imprimé pour qu’ils correspondent au brochage de votre carte ESP32. Tout au long de ce projet, nous fournissons tous les fichiers nécessaires, si vous avez besoin de modifier le PCB.

Le bouclier est composé de :

  • Capteur de température, d’humidité et de pression BME280 ;
  • LDR (résistance dépendante de la lumière – capteur de luminosité) ;
  • Écran OLED I2C de 0,96 pouce ;
  • Bouton;
  • 12 LED RVB adressables WS2812B ;

Si vous comptez reproduire ce projet sur une planche à pain, au lieu de LED RVB adressables WS2812B individuelles, vous pouvez utiliser un bande LED RVB adressable ou un anneau LED RVB adressable avec le même nombre de LED (12).

Affectation des broches du PCB de la station météo

Le tableau suivant montre l’affectation des broches pour chaque composant sur le blindage :

Composant Affectation des broches ESP32
BME280 GPIO 21 (SDA), GPIO 22 (SCL)
Écran OLED GPIO 21 (SDA), GPIO 22 (SCL)
Résistance dépendante de la lumière (LDR) GPIO 33
Bouton GPIO 18
LED RVB adressables GPIO 27

Fonctionnalités du logiciel PCB de la station météo

Il existe une infinité de façons de programmer le même circuit pour obtenir différents résultats avec différentes fonctionnalités et caractéristiques. Dans ce projet particulier, nous allons programmer le PCB comme suit :

Bouclier PCB dinterface de station meteo ESP32
  • L’OLED affiche cinq écrans différents :
    1. Date et heure actuelles ;
    2. Température
    3. Humidité
    4. Pression
    5. Luminosité
  • Chaque écran est affiché pendant 15 secondes avant de passer au suivant.
  • Alternativement, vous pouvez appuyer sur le bouton-poussoir pour changer d’écran.
  • Sur chaque écran, les LED RVB adressables du WS2812B affichent un schéma différent :
    • Sur l’écran de la date et de l’heure, les LED RVB affichent un effet arc-en-ciel ;
    • Sur les autres écrans, les LED RVB fonctionnent comme une jauge. Par exemple, 100 % d’humidité allume toutes les LED, 50 % d’humidification allume la moitié du nombre de LED.
    • La couleur des LED est différente pour chaque écran : vert pour la température, bleu pour l’humidité, violet pour la pression et jaune pour la luminosité.
1680338358 136 Bouclier PCB dinterface de station meteo ESP32

Tester le circuit sur une planche à pain

Avant de concevoir et de construire le PCB, il est important de tester le circuit sur une planche à pain. Si vous ne voulez pas faire de PCB, vous pouvez toujours suivre ce projet en assemblant le circuit sur une planche à pain.

Pièces de protection d'interface de station météo ESP32 Breadboard requises

Pièces requises

Pour assembler le circuit sur une planche à pain, vous avez besoin des pièces suivantes (les pièces du PCB réel sont présentées dans une section ultérieure) :

Vous pouvez utiliser les liens précédents ou accéder directement à MakerAdvisor.com/tools pour trouver toutes les pièces pour vos projets au meilleur prix !

1680338358 981 Bouclier PCB dinterface de station meteo ESP32

Après avoir rassemblé toutes les pièces, assemblez le circuit en suivant le schéma de principe suivant :

Schéma de l'interface de la station météo ESP32 Fritzing

Conception du PCB

Pour concevoir le circuit et le PCB, nous avons utilisé EasyEDA qui est un logiciel basé sur un navigateur pour concevoir des PCB. Si vous souhaitez personnaliser votre PCB, il vous suffit de télécharger les fichiers suivants :

La conception du circuit fonctionne comme dans n’importe quel autre outil logiciel de circuit, vous placez certains composants et vous les câblez ensemble. Ensuite, vous affectez chaque composant à une empreinte.

Circuit de câblage du schéma de circuit imprimé de l'interface de la station météo ESP32

Après avoir assigné les pièces, placez chaque composant. Lorsque vous êtes satisfait de la disposition, effectuez toutes les connexions et routez votre PCB.

Disposition de la carte PCB de l'interface de la station météo ESP32 PCB

Enregistrez votre projet et exportez les fichiers Gerber.

Remarque : vous pouvez récupérer les fichiers du projet et les modifier pour personnaliser le bouclier selon vos propres besoins.

Commander les PCB chez PCBWay

Ce projet est parrainé par PCBWay. PCBWay est un service complet de fabrication de cartes de circuits imprimés.

Commander les PCB chez PCBWay

Transformez vos circuits de planche à pain de bricolage en circuits imprimés professionnels – obtenez 10 cartes pour environ 5 $ + expédition (qui variera selon votre pays).

Une fois que vous avez vos fichiers Gerber, vous pouvez commander le PCB. Suivez les étapes suivantes.

1. Téléchargez les fichiers Gerber – cliquez ici pour télécharger le fichier .zip

2. Accédez au site Web PCBWay et ouvrez la page PCB Instant Quote.

PCBWay Order PCB ouvre la page de devis instantané

3. PCBWay peut récupérer tous les détails du PCB et les remplit automatiquement pour vous. Utilisez le « PCB de commande rapide (paramètres de remplissage automatique) ».

PCBWay Commander les paramètres de remplissage automatique des PCB

4. Appuyez sur le bouton « + Ajouter un fichier Gerber » pour télécharger les fichiers Gerber fournis.

PCBWay Order PCB ajouter un bouton de fichier gerber

Et c’est tout. Vous pouvez également utiliser OnlineGerberViewer pour vérifier si votre PCB est comme il se doit.

ESP32 PCB Station Météo Interface Gerber Viewer Online

Si vous n’êtes pas pressé, vous pouvez utiliser la méthode d’expédition China Post pour réduire considérablement vos coûts. À notre avis, nous pensons qu’ils surestiment le délai d’expédition de China Post.

PCBWay Order PCB Chine après la méthode d'expédition

Vous pouvez augmenter la quantité de votre commande de PCB et changer la couleur du masque de soudure. J’ai commandé la couleur bleue.

ESP32 PCB Station Météo Interface Finale PCB PCBWay Commande PCB

Une fois que vous êtes prêt, vous pouvez commander les PCB en cliquant sur « Enregistrer dans le panier » et terminer votre commande.

Déballage

Après environ une semaine d’utilisation de la méthode d’expédition DHL, j’ai reçu les PCB à mon bureau.

Bouclier d'interface de station météo ESP32 PCB

Comme d’habitude, tout est bien emballé et les PCB sont vraiment de haute qualité. Les lettres sur la sérigraphie sont vraiment bien imprimées et faciles à lire. De plus, la soudure adhère facilement aux pastilles.

Nous sommes vraiment satisfaits du service PCBWay. Voici quelques autres projets que nous avons construits à l’aide du service PCBWay :

Souder les composants

L’étape suivante consiste à souder les composants au PCB. J’ai utilisé des LED SMD, des résistances SMD et des condensateurs SMD. Ceux-ci peuvent être un peu difficiles à souder, mais le PCB est bien meilleur.

Si vous n’avez jamais soudé de SMD auparavant, nous vous recommandons de regarder quelques vidéos pour savoir comment procéder. Vous pouvez également vous procurer un kit de soudure SMD DIY pour vous entraîner un peu.

Voici une liste de tous les composants nécessaires pour assembler le PCB :

Composants du blindage de l'interface de la station météo ESPCB Pièces requises

Voici les outils de soudure que j’ai utilisés :

Examen du fer à souder TS80 Meilleur fer à souder portable

Lisez notre avis sur le fer à souder TS80 : Examen du fer à souder TS80 – Meilleur fer à souder portable.

Commencez par souder les composants SMD. Ensuite, soudez les broches de l’en-tête. Et enfin, soudez les autres composants ou utilisez des broches d’en-tête si vous ne souhaitez pas connecter les composants de manière permanente.

Voici à quoi ressemble le Shield ESP32 après avoir assemblé toutes les pièces.

Carte PCB finale d'interface de station météo de carte PCB ESP32

La carte ESP32 doit s’empiler parfaitement sur les broches d’en-tête de l’autre côté du PCB.

Bouclier d'interface de station météo PCB à carte empilée

Programmation de la carte d’interface de la station météo

Le code de ce projet affiche les lectures des capteurs sur différents écrans de l’écran OLED ainsi que la date et l’heure. Les LED RVB adressables affichent différentes couleurs et animations en fonction de ce qui est affiché à l’écran.

Vous pouvez programmer le PCB de la manière qui vous convient le mieux.

Nous allons programmer la carte ESP32 à l’aide de l’IDE Arduino. Assurez-vous donc que le module complémentaire de carte ESP32 est installé.

Si vous souhaitez programmer l’ESP32/ESP8266 en utilisant VS Code + PlatformIO, suivez le tutoriel suivant :

Installation de bibliothèques (Arduino IDE)

Pour ce projet, vous devez installer toutes ces bibliothèques dans votre IDE Arduino.

Toutes ces bibliothèques peuvent être installées à l’aide du gestionnaire de bibliothèques Arduino IDE. Accédez simplement à Sketch> Inclure la bibliothèque> Gérer les bibliothèques et recherchez le nom de la bibliothèque.

Installation de bibliothèques (VS Code + PlatformIO)

Si vous programmez l’ESP32 à l’aide de PlatformIO, vous devez inclure les bibliothèques dans le fichier platformio.ini comme ceci :

[env:esp32doit-devkit-v1]
platform = espressif32
board = esp32doit-devkit-v1
framework = arduino
monitor_speed = 115200
lib_deps = adafruit/Adafruit NeoPixel @ ^1.7.0
           adafruit/Adafruit SSD1306 @ ^2.4.1
           adafruit/Adafruit Unified Sensor @ ^1.1.4
           adafruit/Adafruit BME280 Library @ ^2.1.2
           adafruit/Adafruit GFX Library @ ^1.10.3
           adafruit/Adafruit BusIO @ ^1.6.0

Code

Copiez le code suivant dans votre IDE Arduino ou dans le fichier main.cpp si vous utilisez PlatformIO.

/*
  Rui Santos
  Complete project details at https://Raspberryme.com/esp32-weather-station-pcb/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <WiFi.h>
#include <time.h>

// Insert your network credentials
const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// NTP Server Details
const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 0;
const int   daylightOffset_sec = 3600;

// OLED Display
#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels

#define I2Cdisplay_SDA 21
#define I2Cdisplay_SCL 22
TwoWire I2Cdisplay = TwoWire(1);

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &I2Cdisplay, -1);

// WS2812B Addressable RGB LEDs
#define LED_PIN    27  // GPIO the LEDs are connected to
#define LED_COUNT  12  // Number of LEDs
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

// BME280
#define I2C_SDA 21
#define I2C_SCL 22
TwoWire I2CBME = TwoWire(0);
Adafruit_BME280 bme;

// LDR (Light Dependent Resistor)
#define ldr  33          

// Pushbutton
#define buttonPin  18    

int buttonState;              // current reading from the input pin
int lastButtonState = LOW;    // previous reading from the input pin

unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

// Screens
int displayScreenNum = 0;
int displayScreenNumMax = 4;

unsigned long lastTimer = 0;
unsigned long timerDelay = 15000;

unsigned char temperature_icon[] ={
  0b00000001, 0b11000000, //        ###      
  0b00000011, 0b11100000, //       #####     
  0b00000111, 0b00100000, //      ###  #     
  0b00000111, 0b11100000, //      ######     
  0b00000111, 0b00100000, //      ###  #     
  0b00000111, 0b11100000, //      ######     
  0b00000111, 0b00100000, //      ###  #     
  0b00000111, 0b11100000, //      ######     
  0b00000111, 0b00100000, //      ###  #     
  0b00001111, 0b11110000, //     ########    
  0b00011111, 0b11111000, //    ##########   
  0b00011111, 0b11111000, //    ##########   
  0b00011111, 0b11111000, //    ##########   
  0b00011111, 0b11111000, //    ##########   
  0b00001111, 0b11110000, //     ########    
  0b00000111, 0b11100000, //      ######     
};

unsigned char humidity_icon[] ={
  0b00000000, 0b00000000, //                 
  0b00000001, 0b10000000, //        ##       
  0b00000011, 0b11000000, //       ####      
  0b00000111, 0b11100000, //      ######     
  0b00001111, 0b11110000, //     ########    
  0b00001111, 0b11110000, //     ########    
  0b00011111, 0b11111000, //    ##########   
  0b00011111, 0b11011000, //    ####### ##   
  0b00111111, 0b10011100, //   #######  ###  
  0b00111111, 0b10011100, //   #######  ###  
  0b00111111, 0b00011100, //   ######   ###  
  0b00011110, 0b00111000, //    ####   ###   
  0b00011111, 0b11111000, //    ##########   
  0b00001111, 0b11110000, //     ########    
  0b00000011, 0b11000000, //       ####      
  0b00000000, 0b00000000, //                 
};

unsigned char arrow_down_icon[] ={
  0b00001111, 0b11110000, //     ########    
  0b00011111, 0b11111000, //    ##########   
  0b00011111, 0b11111000, //    ##########   
  0b00011100, 0b00111000, //    ###    ###   
  0b00011100, 0b00111000, //    ###    ###   
  0b00011100, 0b00111000, //    ###    ###   
  0b01111100, 0b00111110, //  #####    ##### 
  0b11111100, 0b00111111, // ######    ######
  0b11111100, 0b00111111, // ######    ######
  0b01111000, 0b00011110, //  ####      #### 
  0b00111100, 0b00111100, //   ####    ####  
  0b00011110, 0b01111000, //    ####  ####   
  0b00001111, 0b11110000, //     ########    
  0b00000111, 0b11100000, //      ######     
  0b00000011, 0b11000000, //       ####      
  0b00000001, 0b10000000, //        ##       
};

unsigned char sun_icon[] ={
  0b00000000, 0b00000000, //                 
  0b00100000, 0b10000010, //   #     #     # 
  0b00010000, 0b10000100, //    #    #    #  
  0b00001000, 0b00001000, //     #       #   
  0b00000001, 0b11000000, //        ###      
  0b00000111, 0b11110000, //      #######    
  0b00000111, 0b11110000, //      #######    
  0b00001111, 0b11111000, //     #########   
  0b01101111, 0b11111011, //  ## ######### ##
  0b00001111, 0b11111000, //     #########   
  0b00000111, 0b11110000, //      #######    
  0b00000111, 0b11110000, //      #######    
  0b00010001, 0b11000100, //    #   ###   #  
  0b00100000, 0b00000010, //   #           # 
  0b01000000, 0b10000001, //  #      #      #
  0b00000000, 0b10000000, //         #       
};

// Clear the LEDs
void colorWipe(uint32_t color, int wait, int numNeoPixels) {
  for(int i=0; i<numNeoPixels; i++) { // For each pixel in strip...
    strip.setPixelColor(i, color);         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}

// Rainbow cycle along all LEDs. Pass delay time (in ms) between frames.
void rainbow(int wait) {
  long firstPixelHue = 256;
    for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
      int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
      strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
    }
    strip.show(); // Update strip with new contents
    delay(wait);  // Pause for a moment
}

// Create display marker for each screen
void displayIndicator(int displayNumber) {
  int xCoordinates[5] = {44, 54, 64, 74, 84};
  for (int i =0; i<5; i++) {
    if (i == displayNumber) {
      display.fillCircle(xCoordinates[i], 60, 2, WHITE);
    }
    else {
      display.drawCircle(xCoordinates[i], 60, 2, WHITE);
    }
  }
}

//SCREEN NUMBER 0: DATE AND TIME
void displayLocalTime(){
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
  }
  Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");

  //GET DATE
  //Get full weekday name
  char weekDay[10];
  strftime(weekDay, sizeof(weekDay), "%a", &timeinfo);
  //Get day of month
  char dayMonth[4];
  strftime(dayMonth, sizeof(dayMonth), "%d", &timeinfo);
  //Get abbreviated month name
  char monthName[5];
  strftime(monthName, sizeof(monthName), "%b", &timeinfo);
  //Get year
  char year[6];
  strftime(year, sizeof(year), "%Y", &timeinfo);

  //GET TIME
  //Get hour (12 hour format)
  /*char hour[4];
  strftime(hour, sizeof(hour), "%I", &timeinfo);*/
  
  //Get hour (24 hour format)
  char hour[4];
  strftime(hour, sizeof(hour), "%H", &timeinfo);
  //Get minute
  char minute[4];
  strftime(minute, sizeof(minute), "%M", &timeinfo);

  //Display Date and Time on OLED display
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(3);
  display.setCursor(19,5);
  display.print(hour);
  display.print(":");
  display.print(minute);
  display.setTextSize(1);
  display.setCursor(16,40);
  display.print(weekDay);
  display.print(", ");
  display.print(dayMonth);
  display.print(" ");
  display.print(monthName);
  display.print(" ");
  display.print(year);
  displayIndicator(displayScreenNum);
  display.display();
  rainbow(10);
}

// SCREEN NUMBER 1: TEMPERATURE
void displayTemperature(){
  display.clearDisplay();
  display.setTextSize(2);
  display.drawBitmap(15, 5, temperature_icon, 16, 16 ,1);
  display.setCursor(35, 5);
  float temperature = bme.readTemperature();
  display.print(temperature);
  display.cp437(true);
  display.setTextSize(1);
  display.print(" ");
  display.write(167);
  display.print("C");
  display.setCursor(0, 34);
  display.setTextSize(1);
  display.print("Humidity: ");
  display.print(bme.readHumidity());
  display.print(" %");
  display.setCursor(0, 44);
  display.setTextSize(1);
  display.print("Pressure: ");
  display.print(bme.readPressure()/100.0F);
  display.print(" hpa");
  displayIndicator(displayScreenNum);
  display.display();
  int temperaturePer = map(temperature, -5, 36, 0, LED_COUNT-1);
  colorWipe(strip.Color(0,   255,   0), 50, temperaturePer);
}

// SCREEN NUMBER 2: HUMIDITY
void displayHumidity(){
  display.clearDisplay();
  display.setTextSize(2);
  display.drawBitmap(15, 5, humidity_icon, 16, 16 ,1);
  display.setCursor(35, 5);
  float humidity = bme.readHumidity();
  display.print(humidity);
  display.print(" %");
  display.setCursor(0, 34);
  display.setTextSize(1);
  display.print("Temperature: ");
  display.print(bme.readTemperature());
  display.cp437(true);
  display.print(" ");
  display.write(167);
  display.print("C");
  display.setCursor(0, 44);
  display.setTextSize(1);
  display.print("Pressure: ");
  display.print(bme.readPressure()/100.0F);
  display.print(" hpa");
  displayIndicator(displayScreenNum);
  display.display();
  int humidityPer = map(humidity, 0, 100, 0, LED_COUNT-1);
  colorWipe(strip.Color(0,   0,   255), 50, humidityPer);
}

// SCREEN NUMBER 3: PRESSURE
void displayPressure(){
  display.clearDisplay();
  display.setTextSize(2);
  display.drawBitmap(0, 5, arrow_down_icon, 16, 16 ,1);
  display.setCursor(20, 5);
  display.print(bme.readPressure()/100.0F);
  display.setTextSize(1);
  display.print(" hpa");
  display.setCursor(0, 34);
  display.setTextSize(1);
  display.print("Temperature: ");
  display.print(bme.readTemperature());
  display.cp437(true);
  display.print(" ");
  display.write(167);
  display.print("C");
  display.setCursor(0, 44);
  display.setTextSize(1);
  display.print("Humidity: ");
  display.print(bme.readHumidity());
  display.print(" hpa");
  displayIndicator(displayScreenNum);
  display.display();
  colorWipe(strip.Color(255,   0,   255), 50, 12);
}

// SCREEN NUMBER 4: LUMINOSITY
void displayLDR(){
  display.clearDisplay();
  display.setTextSize(2);
  display.drawBitmap(33, 5, sun_icon, 16, 16 ,1);
  display.setCursor(53, 5);
  int ldrReading = map(analogRead(ldr), 0, 4095, 100, 0);
  display.print(ldrReading);
  display.print(" %");
  display.setTextSize(1);
  display.setCursor(0, 34);
  display.print("Temperature: ");
  display.print(bme.readTemperature());
  display.print(" ");
  display.cp437(true);
  display.write(167);
  display.print("C");
  display.setCursor(0, 44);
  display.setTextSize(1);
  display.print("Humidity: ");
  display.print(bme.readHumidity());
  display.print(" %");
  display.setCursor(0, 44);
  displayIndicator(displayScreenNum);
  display.display();
  int ldrReadingPer = map(ldrReading, 0, 100, 0, LED_COUNT-1);
  colorWipe(strip.Color(255,   255,   0), 50, ldrReadingPer);
}

// Display the right screen accordingly to the displayScreenNum
void updateScreen() {
  colorWipe(strip.Color(0, 0, 0), 1, LED_COUNT);
  if (displayScreenNum == 0){
    displayLocalTime();
  }
  else if (displayScreenNum == 1) {
    displayTemperature();
  }
  else if (displayScreenNum ==2){
    displayHumidity();
  }
  else if (displayScreenNum==3){
    displayPressure();
  }
  else {
    displayLDR();
  }
}

void setup() {
  Serial.begin(115200);
  
  // Initialize the pushbutton pin as an input
  pinMode(buttonPin, INPUT);
  
  I2CBME.begin(I2C_SDA, I2C_SCL, 100000);
  I2Cdisplay.begin(I2Cdisplay_SDA, I2Cdisplay_SCL, 100000); 

  // Initialize OLED Display
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }
  display.clearDisplay();
  display.setTextColor(WHITE);
  
  // Initialize BME280
  bool status = bme.begin(0x76, &I2CBME);  
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
  
  // Initialize WS2812B LEDs
  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();            // Turn OFF all pixels ASAP
  strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255)

  // Connect to Wi-Fi
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected.");
  
  // Init and get the time
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}

void loop() {
  // read the state of the switch into a local variable
  int reading = digitalRead(buttonPin);

  // Change screen when the pushbutton is pressed
  if (reading != lastButtonState) {
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != buttonState) {
      buttonState = reading;
      if (buttonState == HIGH) {
        updateScreen();
        Serial.println(displayScreenNum);
        if(displayScreenNum < displayScreenNumMax) {
          displayScreenNum++;
        }
        else {
          displayScreenNum = 0;
        }
        lastTimer = millis();
      }
    }
  }
  lastButtonState = reading;
  
  // Change screen every 15 seconds (timerDelay variable)
  if ((millis() - lastTimer) > timerDelay) {
    updateScreen();
    Serial.println(displayScreenNum);
    if(displayScreenNum < displayScreenNumMax) {
      displayScreenNum++;
    }
    else {
      displayScreenNum = 0;
    }
    lastTimer = millis();
  }
}

Afficher le code brut

Nous obtenons la date et l’heure d’un serveur NTP. Ainsi, l’ESP32 doit se connecter à Internet. Insérez vos informations d’identification réseau sur les variables suivantes et le code fonctionnera immédiatement.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Comment fonctionne le code

Lisez cette section si vous voulez savoir comment fonctionne le code ou passez à la section suivante.

Ce code est assez long, mais simple. Si vous voulez bien comprendre comment cela fonctionne, vous devrez peut-être jeter un œil aux tutoriels suivants :

Y compris les bibliothèques

Tout d’abord, vous devez inclure les bibliothèques nécessaires.

#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <WiFi.h>
#include <time.h>

Informations d’identification réseau

Insérez vos informations d’identification réseau dans les lignes suivantes afin que l’ESP32 puisse se connecter à votre réseau pour demander la date et l’heure à un serveur NTP.

const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Serveur NTP

Ensuite, vous devez définir les variables suivantes pour configurer et obtenir l’heure d’un serveur NTP : ntpServer, gmtOffset_sec et daylightOffset_sec.

Nous demanderons l’heure à pool.ntp.org, qui est un cluster de serveurs de temps que n’importe qui peut utiliser pour demander l’heure.

const char* ntpServer = "pool.ntp.org";

Décalage GMT

La variable gmtOffset_sec définit le décalage en secondes entre votre fuseau horaire et GMT. Nous vivons au Portugal, donc le décalage horaire est de 0. Modifiez la variable time gmtOffset_sec pour qu’elle corresponde à votre fuseau horaire.

const long gmtOffset_sec = 0;

Décalage lumière du jour

La variable daylightOffset_sec définit le décalage en secondes pour l’heure d’été. Elle est généralement d’une heure, cela correspond à 3600 secondes

const int daylightOffset_sec = 3600;

En savoir plus sur l’obtention de l’heure à partir du serveur NTP : ESP32 NTP Client-Server : Get Date and Time (Arduino IDE)

Écran OLED

Les variables SCREEN_WIDTH et SCREEN_HEIGHT définissent les dimensions de l’écran OLED en pixels. Nous utilisons un écran OLED de 0,96 pouces : 128 x 64 pixels.

#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels

L’écran OLED est connecté au GPIO 22 (SCL) et au GPIO 21 (SDA).

#define I2Cdisplay_SDA 21
#define I2Cdisplay_SCL 22
TwoWire I2Cdisplay = TwoWire(1);

Initialise un objet d’affichage avec la largeur et la hauteur définies précédemment avec le protocole de communication I2C (&I2Cdisplay).

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &I2Cdisplay, -1);

LED RVB adressables WS2812B

Vous devez définir le GPIO auquel les LED RVB sont connectées. Les LED RVB adressables communiquent avec l’ESP32 à l’aide du protocole One Wire. Ainsi, toutes les LED peuvent être contrôlées par le même GPIO. Dans ce cas, ils sont connectés au GPIO 27.

#define LED_PIN 27

La variable LED_COUNT enregistre le nombre de LED RVB adressables que nous voulons contrôler. Dans ce cas, c’est 12.

#define LED_COUNT 12

Créez un objet Adafruit_NeoPixel appelé bande pour contrôler les LED RVB adressables.

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

Capteur BME280

Créez un objet Adafruit_BME280 appelé bme sur les broches ESP32 par défaut.

#define I2C_SDA 21
#define I2C_SCL 22
TwoWire I2CBME = TwoWire(0);
Adafruit_BME280 bme;

LDR

Définissez le GPIO auquel le LDR est connecté.

const int ldr = 33; // LDR (Light Dependent Resistor)

Bouton

Définissez le GPIO auquel le bouton-poussoir est connecté.

#define buttonPin 18

Les variables suivantes permettent de manipuler le bouton poussoir.

int buttonState;              // current reading from the input pin
int lastButtonState = LOW;    // previous reading from the input pin

unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

Écrans OLED

Comme mentionné précédemment, l’OLED affichera cinq écrans différents. Chaque écran est numéroté de 0 à 4. La variable displayScreenNum contient le numéro d’écran qui doit être affiché sur l’OLED – il commence à zéro. Le displayNumMax contient le nombre maximum d’écrans.

int displayScreenNum = 0;
int displayScreenNumMax = 4;

Les variables suivantes seront utilisées pour gérer la minuterie pour afficher chaque écran pendant 15 secondes. Vous pouvez modifier la période d’affichage de l’écran sur la variable timerDelay.

unsigned long lastTimer = 0;
unsigned long timerDelay = 15000;

Icônes

Dans chaque écran, l’OLED affiche une icône liée à la lecture qu’il affiche. Dans l’écran de température, il affiche un thermomètre (temperature_icon), dans l’écran d’humidité une larme (humidity_icon), dans l’écran de pression une flèche (arrow_down_icon) et dans l’écran de luminosité un soleil (sun_icon). Nous devons inclure ces icônes dans le code. Ces icônes font 16×16 pixels.

unsigned char temperature_icon[] ={
  0b00000001, 0b11000000, //        ###      
  0b00000011, 0b11100000, //       #####     
  0b00000111, 0b00100000, //      ###  #     
  0b00000111, 0b11100000, //      ######     
  0b00000111, 0b00100000, //      ###  #     
  0b00000111, 0b11100000, //      ######     
  0b00000111, 0b00100000, //      ###  #     
  0b00000111, 0b11100000, //      ######     
  0b00000111, 0b00100000, //      ###  #     
  0b00001111, 0b11110000, //     ########    
  0b00011111, 0b11111000, //    ##########   
  0b00011111, 0b11111000, //    ##########   
  0b00011111, 0b11111000, //    ##########   
  0b00011111, 0b11111000, //    ##########   
  0b00001111, 0b11110000, //     ########    
  0b00000111, 0b11100000, //      ######     
};

unsigned char humidity_icon[] ={
  0b00000000, 0b00000000, //                 
  0b00000001, 0b10000000, //        ##       
  0b00000011, 0b11000000, //       ####      
  0b00000111, 0b11100000, //      ######     
  0b00001111, 0b11110000, //     ########    
  0b00001111, 0b11110000, //     ########    
  0b00011111, 0b11111000, //    ##########   
  0b00011111, 0b11011000, //    ####### ##   
  0b00111111, 0b10011100, //   #######  ###  
  0b00111111, 0b10011100, //   #######  ###  
  0b00111111, 0b00011100, //   ######   ###  
  0b00011110, 0b00111000, //    ####   ###   
  0b00011111, 0b11111000, //    ##########   
  0b00001111, 0b11110000, //     ########    
  0b00000011, 0b11000000, //       ####      
  0b00000000, 0b00000000, //                 
};

 unsigned char arrow_down_icon[] ={
  0b00001111, 0b11110000, //     ########    
  0b00011111, 0b11111000, //    ##########   
  0b00011111, 0b11111000, //    ##########   
  0b00011100, 0b00111000, //    ###    ###   
  0b00011100, 0b00111000, //    ###    ###   
  0b00011100, 0b00111000, //    ###    ###   
  0b01111100, 0b00111110, //  #####    ##### 
  0b11111100, 0b00111111, // ######    ######
  0b11111100, 0b00111111, // ######    ######
  0b01111000, 0b00011110, //  ####      #### 
  0b00111100, 0b00111100, //   ####    ####  
  0b00011110, 0b01111000, //    ####  ####   
  0b00001111, 0b11110000, //     ########    
  0b00000111, 0b11100000, //      ######     
  0b00000011, 0b11000000, //       ####      
  0b00000001, 0b10000000, //        ##       
};

 unsigned char sun_icon[] ={
  0b00000000, 0b00000000, //                 
  0b00100000, 0b10000010, //   #     #     # 
  0b00010000, 0b10000100, //    #    #    #  
  0b00001000, 0b00001000, //     #       #   
  0b00000001, 0b11000000, //        ###      
  0b00000111, 0b11110000, //      #######    
  0b00000111, 0b11110000, //      #######    
  0b00001111, 0b11111000, //     #########   
  0b01101111, 0b11111011, //  ## ######### ##
  0b00001111, 0b11111000, //     #########   
  0b00000111, 0b11110000, //      #######    
  0b00000111, 0b11110000, //      #######    
  0b00010001, 0b11000100, //    #   ###   #  
  0b00100000, 0b00000010, //   #           # 
  0b01000000, 0b10000001, //  #      #      #
  0b00000000, 0b10000000, //         #       
};

Pour en savoir plus sur l’affichage des icônes, nous vous recommandons de lire notre guide OLED : ESP32 OLED Display with Arduino IDE.

Fonctions colorWipe() et rainbow()

Les fonctions colorWipe() et rainbow() sont utilisées pour contrôler les LED RVB adressables.

Le colorWipe() est utilisé pour allumer ou effacer des LED spécifiques.

void colorWipe(uint32_t color, int wait, int numNeoPixels) {
  for(int i=0; i<numNeoPixels; i++) { // For each pixel in strip...
    strip.setPixelColor(i, color);         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}

La fonction arc-en-ciel(), comme son nom l’indique, affiche un effet arc-en-ciel.

// Rainbow cycle along all LEDs. Pass delay time (in ms) between frames.
void rainbow(int wait) {
  long firstPixelHue = 256;
    for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
      int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
      strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
    }
    strip.show(); // Update strip with new contents
    delay(wait);  // Pause for a moment
}

Vous pouvez en savoir plus sur ces fonctions et d’autres pour contrôler la bande en jetant un coup d’œil au Exemples de la bibliothèque Neopixel.

fonction displayIndicator()

Le displayIndicator() crée cinq petits cercles au bas de l’affichage en fonction de l’écran affiché en ce moment.

// Create display marker for each screen
void displayIndicator(int displayNumber) {
  int xCoordinates[5] = {44, 54, 64, 74, 84};
  for (int i =0; i<5; i++) {
    if (i == displayNumber) {
      display.fillCircle(xCoordinates[i], 60, 2, WHITE);
    }
    else {
      display.drawCircle(xCoordinates[i], 60, 2, WHITE);
    }
  }
}

La fonction drawCircle(x, y, radius, color) crée un cercle. Le fillCircle(x, y, radius, color) crée un cercle rempli.

Nous plaçons le centre des cercles sur les coordonnées x suivantes : 44, 54, 64, 74 et 84.

int xCoordinates[5] = {44, 54, 64, 74, 84};

Nous dessinons un cercle plein pour l’affichage actuel et un cercle « vide » pour les autres affichages :

if (i == displayNumber) {
  display.fillCircle(xCoordinates[i], 60, 2, WHITE);
}
else {
  display.drawCircle(xCoordinates[i], 60, 2, WHITE);
}

Écran numéro 0 : date et heure

Le premier écran qui apparaît sur l’OLED affiche la date et l’heure. C’est ce que fait la fonction displayLocalTime(). Il affiche également un effet arc-en-ciel sur les LED RVB adressables.

//SCREEN NUMBER 0: DATE AND TIME
void displayLocalTime(){
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
  }
  Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");

  //GET DATE
  //Get full weekday name
  char weekDay[10];
  strftime(weekDay, sizeof(weekDay), "%a", &timeinfo);
  //Get day of month
  char dayMonth[4];
  strftime(dayMonth, sizeof(dayMonth), "%d", &timeinfo);
  //Get abbreviated month name
  char monthName[5];
  strftime(monthName, sizeof(monthName), "%b", &timeinfo);
  //Get year
  char year[6];
  strftime(year, sizeof(year), "%Y", &timeinfo);

  //GET TIME
  //Get hour (12 hour format)
  /*char hour[4];
  strftime(hour, sizeof(hour), "%I", &timeinfo);*/
  
  //Get hour (24 hour format)
  char hour[4];
  strftime(hour, sizeof(hour), "%H", &timeinfo);
  //Get minute
  char minute[4];
  strftime(minute, sizeof(minute), "%M", &timeinfo);

  //Display Date and Time on OLED display
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(3);
  display.setCursor(19,5);
  display.print(hour);
  display.print(":");
  display.print(minute);
  display.setTextSize(1);
  display.setCursor(16,40);
  display.print(weekDay);
  display.print(", ");
  display.print(dayMonth);
  display.print(" ");
  display.print(monthName);
  display.print(" ");
  display.print(year);
  displayIndicator(displayScreenNum);
  display.display();
  rainbow(10);
}

En savoir plus sur l’obtention de la date et de l’heure d’un serveur NTP avec l’ESP32 : ESP32 NTP Client-Server : Get Date and Time (Arduino IDE)

Écran numéro 1 : Température

Le deuxième écran affiche la température. Cela se fait en appelant la fonction displayTemperature(). Cette fonction allume également les LED de couleur verte en fonction de la valeur de température.

// SCREEN NUMBER 1: TEMPERATURE
void displayTemperature(){
  display.clearDisplay();
  display.setTextSize(2);
  display.drawBitmap(15, 5, temperature_icon, 16, 16 ,1);
  display.setCursor(35, 5);
  float temperature = bme.readTemperature();
  display.print(temperature);
  display.cp437(true);
  display.setTextSize(1);
  display.print(" ");
  display.write(167);
  display.print("C");
  display.setCursor(0, 34);
  display.setTextSize(1);
  display.print("Humidity: ");
  display.print(bme.readHumidity());
  display.print(" %");
  display.setCursor(0, 44);
  display.setTextSize(1);
  display.print("Pressure: ");
  display.print(bme.readPressure()/100.0F);
  display.print(" hpa");
  displayIndicator(displayScreenNum);
  display.display();
  int temperaturePer = map(temperature, -5, 36, 0, LED_COUNT-1);
  colorWipe(strip.Color(0,   255,   0), 50, temperaturePer);
}

Écran numéro 2 : Humidité

La fonction displayHumidity() est similaire à la fonction displayTemperature() mais affiche la valeur d’humidité.

// SCREEN NUMBER 2: HUMIDITY
void displayHumidity(){
  display.clearDisplay();
  display.setTextSize(2);
  display.drawBitmap(15, 5, humidity_icon, 16, 16 ,1);
  display.setCursor(35, 5);
  float humidity = bme.readHumidity();
  display.print(humidity);
  display.print(" %");
  display.setCursor(0, 34);
  display.setTextSize(1);
  display.print("Temperature: ");
  display.print(bme.readTemperature());
  display.cp437(true);
  display.print(" ");
  display.write(167);
  display.print("C");
  display.setCursor(0, 44);
  display.setTextSize(1);
  display.print("Pressure: ");
  display.print(bme.readPressure()/100.0F);
  display.print(" hpa");
  displayIndicator(displayScreenNum);
  display.display();
  int humidityPer = map(humidity, 0, 100, 0, LED_COUNT-1);
  colorWipe(strip.Color(0,   0,   255), 50, humidityPer);
}

Écran numéro 3 : Pression

La fonction displayPressure() est similaire aux deux fonctions précédentes, mais affiche la valeur de la pression.

// SCREEN NUMBER 3: PRESSURE
void displayPressure(){
  display.clearDisplay();
  display.setTextSize(2);
  display.drawBitmap(0, 5, arrow_down_icon, 16, 16 ,1);
  display.setCursor(20, 5);
  display.print(bme.readPressure()/100.0F);
  display.setTextSize(1);
  display.print(" hpa");
  display.setCursor(0, 34);
  display.setTextSize(1);
  display.print("Temperature: ");
  display.print(bme.readTemperature());
  display.cp437(true);
  display.print(" ");
  display.write(167);
  display.print("C");
  display.setCursor(0, 44);
  display.setTextSize(1);
  display.print("Humidity: ");
  display.print(bme.readHumidity());
  display.print(" hpa");
  displayIndicator(displayScreenNum);
  display.display();
  colorWipe(strip.Color(255,   0,   255), 50, 12);
}

Écran numéro 4 : Luminosité

Enfin, le dernier écran affiche la luminosité (fonction displayLDR()).

void displayLDR(){
  display.clearDisplay();
  display.setTextSize(2);
  display.drawBitmap(33, 5, sun_icon, 16, 16 ,1);
  display.setCursor(53, 5);
  int ldrReading = map(analogRead(ldr), 0, 4095, 100, 0);
  display.print(ldrReading);
  display.print(" %");
  display.setTextSize(1);
  display.setCursor(0, 34);
  display.print("Temperature: ");
  display.print(bme.readTemperature());
  display.print(" ");
  display.cp437(true);
  display.write(167);
  display.print("C");
  display.setCursor(0, 44);
  display.setTextSize(1);
  display.print("Humidity: ");
  display.print(bme.readHumidity());
  display.print(" %");
  display.setCursor(0, 44);
  displayIndicator(displayScreenNum);
  display.display();
  int ldrReadingPer = map(ldrReading, 0, 100, 0, LED_COUNT-1);
  colorWipe(strip.Color(255,   255,   0), 50, ldrReadingPer);
}

fonction updateScreen()

La fonction updateScreen() appelle les bonnes fonctions en fonction de l’écran que nous voulons afficher :

void updateScreen() {
  colorWipe(strip.Color(0, 0, 0), 1, LED_COUNT);
  if (displayScreenNum == 0){
    displayLocalTime();
  }
  else if (displayScreenNum == 1) {
    displayTemperature();
  }
  else if (displayScreenNum ==2){
    displayHumidity();
  }
  else if (displayScreenNum==3){
    displayPressure();
  }
  else {
    displayLDR();
  }
}

installation()

Définissez le bouton comme entrée.

pinMode(buttonPin, INPUT);

Initialiser l’affichage OLED.

if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
  Serial.println(F("SSD1306 allocation failed"));
  for(;;);
}

Initialisez le capteur BME280 :

if (!bme.begin(0x76)) {
  Serial.println("Could not find a valid BME280 sensor, check wiring!");
  while (1);
}  

Lorsque l’ESP32 démarre pour la première fois, nous voulons effacer l’affichage OLED. Nous définissons également la couleur du texte sur blanc.

display.clearDisplay();
display.setTextColor(WHITE);

Initialisez les LED WS2812B et réglez leur luminosité. Vous pouvez modifier la luminosité à toute autre valeur qui convient le mieux à votre environnement.

strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
strip.show();            // Turn OFF all pixels ASAP
strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255)

Initialisez le Wi-Fi et connectez l’ESP32 à votre réseau local, afin que l’ESP32 puisse se connecter au serveur NTP pour obtenir la date et l’heure.

// Connect to Wi-Fi
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");

Configurez le serveur NTP avec les paramètres définis précédemment.

configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
printLocalTime();

boucle()

Dans la boucle(), les lignes suivantes changent l’écran (displayScreenNum) à chaque appui sur le bouton poussoir.

// read the state of the switch into a local variable
int reading = digitalRead(buttonPin);

// Change screen when the pushbutton is pressed
if (reading != lastButtonState) {
  lastDebounceTime = millis();
}

if ((millis() - lastDebounceTime) > debounceDelay) {
  if (reading != buttonState) {
    buttonState = reading;
    if (buttonState == HIGH) {
      updateScreen();
      Serial.println(displayScreenNum);
      if(displayScreenNum < displayScreenNumMax) {
        displayScreenNum++;
      }
      else {
        displayScreenNum = 0;
      }
      lastTimer = millis();
    }
  }
}
lastButtonState = reading;

Les lignes suivantes changent entre les écrans toutes les 15 secondes (timerDelay).

if ((millis() - lastTimer) > timerDelay) {
  updateScreen();
  Serial.println(displayScreenNum);
  if(displayScreenNum < displayScreenNumMax) {
    displayScreenNum++;
  }
  else {
    displayScreenNum = 0;
  }
  lastTimer = millis();
}

Manifestation

Après avoir téléchargé le code sur la carte, appuyez sur le bouton RESET intégré pour que l’ESP32 commence à exécuter le code.

Présentation du projet d'interface de station météo ESP32

Pour une démonstration complète, nous vous recommandons de regarder la vidéo suivante.

YouTube video

Conclusion

Nous espérons que vous avez trouvé ce projet intéressant et que vous êtes capable de le construire vous-même. Vous pouvez utiliser le service PCBWay et vous obtiendrez un PCB de haute qualité pour vos projets.

Vous pouvez programmer l’ESP32 avec un autre code adapté à vos besoins. Vous pouvez également modifier les fichiers gerber et ajouter d’autres fonctionnalités au PCB ou à d’autres capteurs.

Nous avons d’autres projets similaires qui incluent la construction et la conception de circuits imprimés que vous pourriez aimer :

En savoir plus sur l’ESP32 avec nos ressources :

[Update] le cadeau PCB nu est terminé et les gagnants sont : Etienne Bogaert, Wal Taylor, Charles Geiser, JM GIGAN et Kristian C.

Cette vidéo vous emmène dans l’histoire de Raspberry Pi :

YouTube video