Demande d'inscription
* Login:
* Nom:
* Prénom:
* Date de Naissance:
* E-mail:
* Motivations d'inscription:

Mr Coper

Top
PROJET OSCILLOSCOPE OP72
By Boboss
Infos:

Projet Oscilloscope OP72 - Code Arduino ESP32 - Simulation CRT sur écran OLED rond GC9A01 240x240 avec commandes physiques

1. PRÉSENTATION

Fin juillet, lors d'enchères en ligne, j’ai mis la main sur plusieurs armoires de contrôle de télévision cathodique et radio parmi lesquelles se trouvait un oscilloscope des années 50–60. Ses condensateurs, câbles et autres composants sont désormais hors d’usage, et la lampe est grillée —> le brancher serait trop risqué. =>02/08/2025 J’ai donc décidé de lui donner une seconde vie, en modernisant ses entrailles tout en préservant son look vintage. Le projet est donc de remplacer la lampe par un écran OLED piloté par un ESP32. Les six curseurs à droite de l'écran devront faire évoluer la courbe de l'oscilloscope animé indirectement par un capteur audio. La premièere étape est de vider tout les composants inutiles : transformateur HT, lampes, condensateurs, fils, résistances. Tout ça afin de l'aléger et de laisser la place pour le nouveau montage. =>23/08/2025 Après analyse et reflexion j'ai listé les composants nécessaires. ✅ Pour ce projet nous aurons besoin : ▷ ESP32-VROOM-32D (le cerveau du montage) ▷ Écran TFT rond 1,28″ GC9A01 (240×240 SPI) (~3,25 cm) ▷ Filtre vert pour harmoniser l’affichage ▷ Lentille grossissante K9 70 mm (focus 100) pour atteindre le diamètre original ▷ Support 3D imprimé pour fixer écran et lentille ▷ Alimentation 5 V régulée ▷ 5 potentiomètres 10 kΩ (compatibles ESP32), plus un 6ⁿᵉ spécifique pour un réglage vintage ▷ 2 interrupteurs pour des commandes spéciales ▷ Câblage coloré pour une installation propre ▷ Programme Arduino (ESP32) générant la courbe

Voici l'ancienne lampe avec laquelle l'oscilloscope fonctionnait.

Nous allons l'émuler en la remplacant par cet écran OLED rond GC9A01 240x240 que nous allons grossir avec une loupe afin d'arriver au diamètre d'origine (environ 70mm).

Voici le rendu recherché une fois le projet terminé.

2. REMPLACEMENT DES POTENTIOMÈTRES

=>27/08/2025 Afin de gagner du temps, je vais m'occuper de remplacer les potentiomètres vétustes et non appropriés au projet. Ayant justement 5 potentiomètres neuf dans mon bric à brac. Les six boutons de l'oscilloscope devront être fonctionnels pour l'ESP afin de d'intéragir sur la courbe : ✅ Contrôle fin avec les 6 potentiomètres : ▷ Lumière = intensité verte de la courbe ▷ Concentration = effet de flou radial ▷ Synchro = intensité du cadrillage en fond ▷ Cadrage V = déplacement de la courbe verticalement ▷ Cadrage H = déplacement de la courbe horizontalement ▷ Vernier balayage = vitesse de phase ✅ Variation de la courbe suivant GAIN micro avec un 7ème potentiomètre : ▷ GAIN = gain du micro (directement branché sur le micro qui renvoi le signal calibré)

Nous allons remplacer les 5 potentiomètres qui ont une résistance trop élevée pour l'arduino (jusqu'a 1 Mega Ohm). Nous conserverons uniquement le 6ème (en bas à droite de gros diamètre sur la photo ci-dessous) qui est un 10 Kilo Ohm parfait pour le "Cadrage V".

Les boutons d'origine sont récupérables, en bon état et s'adaptent parfaitement sur les nouveaux potentiomètres de 10 Kilo Ohm classique WL.

3. SCHÉMA CABLAGE ESP32

=>28/08/2025 Alors le temps de recevoir l'ESP ainsi que les composants, je vais me pencher sur le schéma de câblage. ✅ Commençons par lister les éléments et leurs associations aux broches : ▷ Alimentations de l'écran et de l'ESP32 ▷ Alimentations depuis l'arduino des potentiomètre en 3.3V et ground ▷ Signaux des potientiomètres ▷ Ground et signaux des deux boutons en PULL-UP ▷ Signal capteur audio (mic)

// --- Attribution des broches ---

// Écran GC9A01 :
// VCC - Alimentation                   →  5V      (VCC)
// GND - Mise à la masse                →  NEGATIF (GND)  - broche GND
// SCL - Horloge SPI                    →  GPIO18  (SCK)  - broche 18
// SDA - Données SPI                    →  GPIO23  (MOSI) - broche 23
// DC  - Data/Command                   →  GPIO2   (A2_2) - broche 2
// CS  - Chip select                    →  GPIO5   (SS)   - broche 5
// RES - Reset écran                    →  GPIO4   (A2_0) - broche 4

// Potentiomètres (3 broches) :
// Lumière          - Intensité         →  GPIO32  (A1_4) - broche 32
// Concentration    - Filtre flou       →  GPIO33  (A1_5) - broche 33
// Cadrage H (X)    - Dépl. horizon     →  GPIO39  (A1_3) - broche VN
// Cadrage V (Y)    - Dépl vertical     →  GPIO36  (A1_0) - broche VP       -     10kΩA vintage
// Vernier Balayage - Vitesse phase     →  GPIO34  (A1_6) - broche 34
// Synchro          - Intensité grille  →  GPIO35  (A1_7) - broche 35

//Boutons pull-up :
//SYNC - Animation courbe               →  GPIO26 (A2_9) - broche 26
//TEST - Mode TEST                      →  GPIO27 (A2_7) - broche 27

// Capteur audio (analogique) :
// Audio - Entrée micro                 →  GPIO25 (A2_8) - broche 25

Maintenant on va faire le schéma de cablage complet en essayant de faire des couleurs de fil différentes afin de pas se mélanger les pinçeaux lors du branchement.

4. PROGRAMME ESP32

=>28/08/2025 Le programme devra gérer les valeurs des potentio et adapter la courbe en conséquence, tout en dessinant la courbe suivant les infos du capteur audio. Un message d'accueil s'affichera également au démarrage. Le code est disponible ci-dessous.

https://mrcoper.fr/index.php?part=downloads&spart=search-file&file=oscilloop72&x=0&y=0#topP

Commençons par définir le broches suivant nos entrées / sorties du schéma. N'oublions pas avant tout d'inclure les bibliothèques.

#include SPI.h
#include Adafruit_GFX.h
#include Adafruit_GC9A01A.h

// === Attribution des broches ===
#define TFT_CS    5
#define TFT_DC    2
#define TFT_RST   4

// Potentiomètres analogiques (3 broches)
#define LUMIERE_PIN 32 // Luminosité courbe
#define CONC_PIN 33 // Netteté / focus
#define CAL_H_PIN 39 // Cadrage horizontal
#define CAL_V_PIN 36 // Cadrage vertical (10kA vintage)
#define VERNIER_PIN 34 // Vitesse balayage
#define GRID_FADE_PIN 35 // Intensité de la grille

// --- Boutons digitaux ---
#define SYNC_BUTTON_PIN  26  // Bouton sur synchro ON
#define TEST_BUTTON_PIN  27  // Bouton sur mode test signal

// Micro analogique à gain réglable
#define PIN_MICRO 25 // GPIO25 (A2_8) pour OUT micro

Adafruit_GC9A01A tft(TFT_CS, TFT_DC, TFT_RST);

// 🔧 Variables MICRO
int valeurMicro = 0;         // Valeur brute du micro
float signalFiltre = 0;      // Signal lissé avec filtre passe-bas

Ensuite nous allons définir les fonctions appélées par void SETUP & void LOOP, ces deux fonctions sont indispasable à n'importe quel programme ARDUINO. SETUP étant la séquence d'initialisation du programme. LOOP étant la boucle prinicpale sur laquelle le programme tourne tant qu'il est allumé et après avoir était initialisé.

Première fonction "drawSplash" appelée par le void SETUP, celle-ci à pour but d'afficher un message d'accueil.

// === Splash screen ===
void drawSplash() {
  tft.setTextColor(GC9A01A_GREEN);
  tft.setTextSize(2);
  tft.setCursor(20, 90);
  tft.println("OSCILLOSCOPE");
  tft.setTextSize(3);
  tft.setCursor(50, 120);
  tft.println("OP72");
  //ajouter "created by Boboss"
  tft.setTextSize(1);
  tft.setCursor(70, 80);
  tft.println("created by Boboss");
}

Seconde fonction "lireSignalMicro", comme indiqué dans son nom, son but est de lire le signal du micro puis le filtrer.

// === Lecture micro + filtre passe-bas ===
void lireSignalMicro() {
  valeurMicro = analogRead(PIN_MICRO);
  float alpha = 0.1;
  signalFiltre = alpha * valeurMicro + (1 - alpha) * signalFiltre;
}

Troisième fonction "drawGrid" qui affiche la grille de fond avec l'intensité souhaitée.

// === Grille verte circulaire avec intensité réglable ===
void drawGrid() {
  int gridFade = analogRead(GRID_FADE_PIN);
  int g = map(gridFade, 0, 4095, 0, 180);

  tft.drawCircle(120, 120, 119, tft.color565(0, g, 0));
  for (int i = 0; i < 240; i += 20) {
    tft.drawFastHLine(0, i, 240, tft.color565(0, g, 0));
    tft.drawFastVLine(i, 0, 240, tft.color565(0, g, 0));
  }
}

Quatrième fonction "drawTextOverlay" qui attend les paramètres calH et calV afin d'afficher le texte de la position et div pour les informations de l'écran.

// === Affichage texte : position H/V et div ===
void drawTextOverlay(int calH, int calV) {
  tft.setTextColor(GC9A01A_GREEN);
  tft.setTextSize(1);

  char buffer[32];
  snprintf(buffer, sizeof(buffer), "H: %d   V: %d", (calH - 2048)/20, (calV - 2048)/20);
  tft.setCursor(10, 10);
  tft.print(buffer);

  tft.setCursor(10, 225);
  tft.print("H/div  V/div");
}

Cinquième fonction "drawGrid" qui génère le signal carré lorsque le bouton TEST est appuyé.

// === Génère un signal de test (carré) si activé ===
int getSignalValue(bool testSignal, int audioRaw, int x) {
  if (testSignal) {
    return (x / 20) % 2 == 0 ? 1500 : 2500;  // signal carré
  } else {
    return audioRaw;
  }
}

Sixième fonction "drawFromInputs", la principale qui génère le signal de l'oscilloscope CRT avec bruit, en lisant les valeurs des 6 potentiomètres et des deux boutons tout en générant le signal capté par le capteur audio.

// === Simulation CRT : affichage sinusoïde + bruit + réglages ===
void drawFromInputs() {
  // Lecture entrées
  int lumiere = analogRead(LUMIERE_PIN);
  int calH = analogRead(CAL_H_PIN);
  int calV = analogRead(CAL_V_PIN);
  int vernier = analogRead(VERNIER_PIN);
  int focus = analogRead(CONC_PIN);
  int grid = analogRead(GRID_FADE_PIN);
  bool syncLocked = !digitalRead(SYNC_BUTTON_PIN);
  bool testSignal = !digitalRead(TEST_BUTTON_PIN);

  lireSignalMicro(); // [RAJOUT MICRO]
  int audioRaw = signalFiltre;

  // Mise à jour phase (déplacement)
  static float phase = 0.0;
  if (!syncLocked) {
    phase += 0.05 + vernier / 8192.0;
    if (phase > TWO_PI) phase -= TWO_PI;
  }

  // Effacement de l écran avec fond noir circulaire
  tft.fillCircle(120, 120, 119, GC9A01A_BLACK);
  drawGrid();                     // Grille
  drawTextOverlay(calH, calV);   // Texte dynamique

  int brightness = map(lumiere, 0, 4095, 0, 255);

  // Affichage de la courbe
  for (int x = 0; x < 240; x++) {
    float angle = 2 * PI * x / 240.0 + phase;

    int signal = getSignalValue(testSignal, audioRaw, x);
    float ampli = signal / 64.0;

    int y = 120 + (calV - 2048) / 50 + ampli * sin(angle);
    int xOff = x + (calH - 2048) / 50;

    // Effet radial = flou = moins intense si éloigné
    int dx = abs(xOff - 120);
    int dy = abs(y - 120);
    float d = sqrt(dx * dx + dy * dy);
    int fade = constrain(map(d * (focus / 1024.0), 0, 120, brightness, 0), 0, 255);

    // Effet de bruit CRT : pixels parasites
    if (random(100) < 2) {
      int rx = random(240);
      int ry = random(240);
      tft.drawPixel(rx, ry, tft.color565(0, random(50, 150), 0));
    }

    if (xOff >= 0 && xOff < 240 && y >= 0 && y < 240) {
      tft.drawPixel(xOff, y, tft.color565(0, fade, 0));
    }
  }
}

Enfin, la fonction d'initialisation "setup", qui paramètre les entrées des deux boutons en PULL-UP et appelle la fonction "drawSplash", affin d'afficher le texte de présentation à l'allumage.

/ === Initialisation ===
void setup() {
  pinMode(SYNC_BUTTON_PIN, INPUT_PULLUP);
  pinMode(TEST_BUTTON_PIN, INPUT_PULLUP);
  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(GC9A01A_BLACK);
  drawSplash();
  delay(3000);
  tft.fillScreen(GC9A01A_BLACK);
}

Et pour finir la boucle principale "loop", celle qui appellera la fonction la plus complexe. La où tout se passe. Elle appelle la fonction drawFromInputs avec un rafraichhissement delay(30), c'est à dire que toute les 30 millisecondes les paramètres des boutons sont repris en compte et la courbes est recalibrée (la fonction drawFromInputs est relancée toute les 30 millisecondes).

// === Boucle principale ===
void loop() {
  drawFromInputs();
  delay(30);  // rafraîchissement fluide
}

5. ALIMENTATION 220V -> 5V

=>30/08/2025 Aujourd'hui j'ai reçu l'alimentation 220v / 5v DC. Nous allons la mettre en place proprement avec une prise 220v IEC 320 C13 / C14.

J'ai dû percer deux trous pour adapter la prise femelle. Elle rentre parfaitement dans le trou de l'ancien sélecteur de voltage au dos.

Voilà, l'alimentation installée, trois autres trous ont dû être percés pour pouvoir la fixer correctement. Le câblage a été posé avec du câble gainé 220v. Et mise à la terre sur toute la partie métallique.

Il restera maintenant à câbler la sortie 5v DC. Nous le ferons lors du branchement de l'ESP et de l'écran.

6. SUPPORTS 3D POUR L'ESP ET POUR L'ÉCRAN

=>01/09/2025 J'ai reçu l'ESP et l'écran, passons aux supports 3D ! ✅ Les logiciels et le matériel que j'utilise pour ce chapitre : ▷ Modélisation => "FreeCAD" sous linux (gratuit et complet, mais mise en main difficile) ▷ Préparation et configuration d'impression => "PruscaSlicer" toujours sous linux (gratuit, simple et très complet) ▷ Impression => Imprimante "Neptune 3 Plus" pas très cher et efficace pour ce que j'ai à faire, le plateau d'impression est plutôt généreux (Volume d'impression 320 x 320 x 400 mm³) Le but de ce chapitre est de créer via une imprimante 3D un support pour l'ESP32 et un autre pour l'écran OLED. J'avais trouvé un fichier STL sur internet mais malheureusement, après impression il ne convient pas. Donc on part sur un modélisation 3D sur mesure ! Commençons par le support de l'ESP WROOM 32D. Je vais essayer de faire un coffrage pour accueil lez l'ESP sur un support avec 4 trous pour fixation. De plus je vais faire en sorte de faire les passage de câbles pour les GPIO qui se trouvent à l'arrière de l'ESP. ✅ Ci-dessous vous verrez l'avancement : ▷ Conception sur outil de modélisation => FreeCAD ▷ Préparation et configuration d’impression => PuriscaSlicer ▷ Et enfin le lien du fichier STL à télécharger

Bon, je me suis trompé dans les mesures de longueur 😂. Je recommence, rassurez-vous les images et le code sont à jour, pas d'erreur sur les fichiers mais c'est reparti pour 3h34 d'impression (sans parler de la modélisation à corriger) 😭

https://mrcoper.fr/index.php?part=downloads&spart=search-file&file=supportespwroom32-body&x=0&y=0#topP

Le support de l'écran est bien trop particulier pour pouvoir le trouver en ligne, ça ne va pas être une mince affaire, car il faut calculer le recul pour la lentille (focus / netteté). Je vais donc créer un support circulaire conique pour l'écran TFT rond qui se fixe à l'arrière de la lentille dans le conne qui recevait auparavant la lampe. Une entretoise cylindrique qui se loge dans le conne et qui accueille l’écran. Deux trous pour fixer l'écran au support, il faudra découper le cône pour faire passer les nombreux fils qui le relie à l'ESP. On utilisera encore une fois FreeCAD pour la modélisation, même combat pour le reste...

Un peu d'ajustage au cuter sur celui-ci, mais le résultat n'est pas trop mal.

https://mrcoper.fr/index.php?part=downloads&spart=search-file&file=supportecran-body.stl&x=26&y=36#topP

Plusieurs difficultés rencontrées pour la mise en place du support : ▷ Diamètre du cône en respectant la distance pour le focus donc le diamètre change suivant la distance (c'est le principe d'un cône contrairement à un cylindre 😉). ▷ Il faut découper ce cône pour faire une fenêtre afin d'y passer les câbles. ▷ Il faut réussir à tout fixer proprement pour éviter que l'écran bouge car son réglage est important. Les photos qui suivent montrent les étapes pour la mise en place de ce support.

Voici le tout assemblé, ça ne me semble pas trop mal, en plus la distance est impeccable. Du travail de précision ! 💪

Maintenant il reste à faire la fenêtre dans le cône pour le passage de câble et puis fixer ce support.

Bon j'ai envi de vous dire que je suis satisfait, d'ailleurs ça aurait été certainement le cas si je n'avais pas cassé l'écran en le mettant en place 😭😭😭 C'est reparti pour une commande d'écran, je serai plus vigilant à l'avenir... Dessous le résultat avant le massacre et puis après... NOTE : Sur la première photo, je tenais l'écran à la main, c'est pour cela que ça ne rend pas bien non plus.