Sélecteur d'amplificateur avec VU mètres Affichage oscilloscope

Sélecteur d’amplificateur et VU mètres analogiques

Le voici en action

J’ai deux amplificateurs dans mon bureau :

Je souhaitais pouvoir basculer de l’un à l’autre sans devoir manipuler les câbles.
Mon premier prototype était constitué de deux relais alimentés par USB et d’un interrupteur inverseur.
Pourquoi ne pas l’agrémenter de deux VUmètres analogiques qui trainaient dans un tiroir et gérer les relais par un microcontrôleur ?
J’ai donc réalisé un cahier des charges pour y voir plus clair !

Cahier des charges

  • L’appareil doit tenir dans un boîtier imprimé en 3D placé sous mon écran avec une face inclinée pour bien visualiser les VU mètres;
  • La luminosité des VU mètres est réglable;
  • La sensibilité des VU mètres est réglable;
  • Un écran OLED affichera en fonctionnement soit :
    • Une horloge numérique;
    • Un oscilloscope;
    • Un analyseur de spectre;
  • Les choix du fonctionnement se feront par le bief de menus et d’un encodeur rotatif;
  • Une paire de résistances de 8 Ω protègera l’amplificateur à tubes le cas échéant;
  • Le port USB du microcontrôleur sera facilement accessible.
  • L’horloge sera synchronisée sur un serveur NTP via WiFi et passera automatiquement aux heures d’été et d’hiver;
  • Le tout sera alimenté par un port USB

Cela me paraît être un bon début.

Schéma

Schéma sélecteur d'amplificateur avec VU mètres

L’alimentation 5V DC obtenue par l’USB est utilisée pour alimenter le microcontrôleur, l’écran OLED, les relais et les résistances pull-up de l’encodeur.
Pour adapter le niveau du signal audio pour une déviation des VU mètres à faible puissance, j’ai ajouté un préamplificateur à amplificateur opérationnel.
De plus, les galvanomètres étant des modèles à courant continu ): Il est nécessaire de redresser le signal. Pour compenser la perte de tension dans la diode, j’injecte une composante continue à l’aide du diviseur de tension constitué des résistances R3, R43 et RV3. RV3 permet de faire un réglage fin du seuil.
Un petit circuit composé de D9, D10, R6, R44, R45, R46, C14 et C15 fait la somme des deux canaux, ajoute une composante continue et limite la tension à 3.3V sur la pin ADC du microcontrôleur.
Enfin, les ampoules des VU mètres sont pilotées par un signal PWM transmis à la base du transistor Q1 et les relais sont commandés par des niveaux logiques amplifiés par Q2 et Q3.

Composants

  • 1 Wemos Lolin32;
  • 1 Ecran OLED 1,3″ I2C;
  • 1 encodeur rotatif équipé de résistances pull-up et condensateurs anti-rebond;
  • 1 potentiomètre double 22 KΩ;
  • 1 LM381 ou équivalent;
  • 1 convertisseur DC 5V – DC 12V
  • 2 Vu mètres analogiques 600 Ω 500 µA;
  • 2 relais 5 V DC DPDT;
  • 2 transistors 2N2328 ou équivalents;
  • 1 transistor H945 ou équivalent;
  • 6 diodes 1N4148 ou équivalentes;
  • 2 résistances de 8 Ω 5W;
  • 2 résistances de 330 Ω;
  • 2 résistances de 1,2 KΩ;
  • 1 résistance de 3,3 KΩ;
  • 4 résistances de 6,8 KΩ;
  • 2 résistances de 27 KΩ;
  • 2 résistances de 120 KΩ;
  • 2 résistances de 220 KΩ;
  • 2 résistances de 470 KΩ;
  • 2 résistances variables de 2,2 KΩ;
  • 4 condensateurs de 1 µF;
  • 2 condensateurs de 3,3 µF;
  • 2 condensateurs de 22 µF;
  • 2 condensateurs de 33 µF;
  • 1 condensateurs de 220 µF;
  • 3 connecteur de haut-parleurs doubles rouge et noir;
  • 1 circuit imprimé double face de 50 x 70 mm au pas de 1,25 mm;
  • 1 morceau de câble USB avec fiche type A;
  • 1 boîtier imprimé en 3D.

Boîtier

Les fichiers 3D pourront bientôt être téléchargés en cliquant sur le bouton ci-dessous

Pour plus d'informations, cliquez le bouton

Si vous souhaitez les obtenir avant, laissez-moi un message.
Vous pouvez adapter les dimensions et modifier les formes à partir du fichier FreeCAD.

Le voilà en place sur le bureau

Sélecteur d'amplificateur VU mètres en place

Code

Pour la programmation, j’utilise Platformio mais l’IDE Arduino convient également.

Platformio.ini

[env:lolin32]
platform = espressif32
board = lolin32
framework = arduino
lib_deps = 
	olikraus/U8g2@^2.36.17
	sstaub/TickTwo@^4.4.0
	thomasfredericks/Bounce2@^2.72
	madhephaestus/ESP32Encoder@^5.0.0
	kosme/arduinoFFT@^2.0.4
monitor_speed = 115200
jagprod.h :
#ifndef jagprod_h
#define jagprod_h

// WiFi
#define sid "votre SSID"
#define wifi_sesame "Votre mot de passe WiFi"

// IP (Si utilisation en DHCP, supprimer cette partie)
const uint8_t ip[4] = {Adresse IP de l'hôte dans votre réseau};
const uint8_t mask[4] = {Masque de sous-réseaux};
const uint8_t ns[4] = {Adresse IP du serveur DNS};
const uint8_t gw[4] = {Adresse de la passerelle};
const char *ntpServerName = "...";

endif

main.cpp

30.74 KB
#include <Arduino.h>
#include <Preferences.h>
Preferences stockage;
// WiFi
#include <jagprod.h>
#include <WiFi.h>
#include <WiFiUdp.h>
WiFiUDP udp;
bool wifi_statut = false;
void WiFiEvent(WiFiEvent_t event)
{
  switch (event)
  {
  case SYSTEM_EVENT_STA_CONNECTED:
    Serial.println("Assoscié");
    break;
  case SYSTEM_EVENT_STA_GOT_IP:
    Serial.println("Connecté");
    wifi_statut = true;
    break;
  case SYSTEM_EVENT_STA_DISCONNECTED:
    Serial.println("Wifi perdu");
    wifi_statut = false;
    break;
  default:
    break;
  }
}

// Routines
void affichage_horloge();
void affichage_oscilloscope();
void affichage_spectre();
void affichage_infos();
void initialisation_I2S();
void echantillonage();
void calcul_par_bandes();
void calibration_bruit();
void affichage();
void menu_principal();
void menu_ampli();
void menu_visualisation();
void commutation_ampli();

// Encodeur
#include <ESP32Encoder.h>
#define CLK_PIN 16
#define DATA_PIN 17
ESP32Encoder encodeur;
long encodeur_position_precedente = -999;
long encodeur_position;

// Affichage
#include <U8g2lib.h>
U8G2_SH1106_128X64_NONAME_F_HW_I2C ecran(U8G2_R0, U8X8_PIN_NONE);
int largeur = 128;
int hauteur = 64;

// Analogique
#define entree_audio 34
#define adc_canal ADC1_CHANNEL_6
#define I2S_port I2S_NUM_0

// +++ Oscilloscope +++
#define echantillons 1024
#define frequence_echantillonage 40000.0
#define SAMPLE_POINTS 128
int samples[SAMPLE_POINTS];
int sampleDelay = 15;     // µs
float sensitivity = 36.0; // Sensibilité verticale
int offset = -180;        // Position verticale du zéro ADC
#define SCOPE_DIVISION 12
#include <arduinoFFT.h>
#include <driver/i2s.h>
#include <driver/adc.h>

// +++ Spectrum +++

uint16_t i2sBuffer[echantillons];
double vReal[echantillons];
double vImag[echantillons];
ArduinoFFT<double> FFT(vReal, vImag, echantillons, frequence_echantillonage);
const int bandes = 7;
const float bandLow[bandes] = {
    20, 300, 600, 1200, 2400, 4800, 9600};
const float bandHigh[bandes] = {
    300, 600, 1200, 2400, 4800, 9600, 14000};
float niveau[bandes] = {0};
float bruit[bandes] = {0};
const int calibration_duree = 60;
const float bruit_marge = 1.5;
const float gain = 15.0;

// Menus
bool menu_principal_etat = false;
int menu_principal_point = 0;
bool menu_ampli_etat = false;
int menu_ampli_point = 0;
bool menu_visualisation_etat = false;
int menu_visualisation_point = 0;
bool couleur_fond = false;
bool couleur_texte = true;
int mode_affichage = 0;
int ampli_id = 0;
String ampli;

// Boutons
#include <Bounce2.h>
const int Encodeur_poussoir_pin = 18;
Bounce2::Button encodeur_poussoir = Bounce2::Button();

// Relais
const int relais_Xtors = 27;
const int relais_tubes = 26;

// VU mètres
int vu_luminosite_pin = 13;
int vu_luminosite_niveau = 32;

// Horloge
String heure;
unsigned int localPort = 2390;
const int NTP_PACKET_SIZE = 48;
byte packetBuffer[NTP_PACKET_SIZE];
IPAddress timeServerIP;
int s;
int ds;
int m;
int dm;
int h;
int dh;
int gmtbe;
// Variables DSTAuto
int dst = 1;
unsigned long epoch;
unsigned long epochmars;
unsigned long epochoctobre;
unsigned long epochannee;
int seconde;
int minute;
int Heure;
int bis = 0;
int annee;
unsigned long epoch01012025 = 1735689600;
unsigned long nombredejours;
// int mois;
int nombredejoursannee;
int jour;
String joursemaine;
int queljoursemaine;
String mois;
String epoch2string;
bool premierefois = true;
long secondes;
bool temoinntp = true;
void quelleheure(IPAddress &address);

// Timers
#include <TickTwo.h>
void plus1s() // Action à réaliser lorsque le timer uneseconde est écoulé
{
  s = s + 1;
  secondes = dh * 36000 + h * 3600 + dm * 600 + m * 60 + ds * 10 + s;
  if (secondes == 32400 or secondes == 75600)
  { // Synchro NTP à 9h et 21h
    temoinntp = true;
  }
  if (temoinntp == true) // il faut faire une synchro NTP
  {
    quelleheure(timeServerIP);
    temoinntp = false;
  }
  if (s > 9)
  {
    s = 0;
    ds = ds + 1;
    if (ds > 5)
    {
      ds = 0;
      m = m + 1;
      if (m > 9)
      {
        m = 0;
        dm = dm + 1;
        if (dm > 5)
        {
          dm = 0;
          h = h + 1;
          if (dh == 2 and h == 4)
          {
            dh = 0;
            h = 0;
          }
          if (h > 9)
          {
            h = 0;
            dh = dh + 1;
          }
        }
      }
      heure = String(dh) + String(h) + ":" + String(dm) + String(m);
      Serial.println(heure);
      if (menu_principal_etat == false and menu_ampli_etat == false and menu_visualisation_etat == false)
      {
        affichage_infos();
      }
    }
  }
}
TickTwo uneseconde(plus1s, 1000);

void setup()
{
  Serial.begin(115200);
  delay(500);
  stockage.begin("config", true);
  for (int b = 0; b < bandes; b++)
  {
    String cle = "bruit" + String(b);
    bruit[b] = stockage.getFloat(cle.c_str(), 0.0); // Restauration des résultats de calibration ADC précédents et 0 si pas de précédents
  }
  ampli_id = stockage.getInt("amp", 0);        // Restauration de l'ampli précédent et tubes si pas de précédent
  mode_affichage = stockage.getInt("mode", 0); // Restauration du mode d'affichage précédent et horloge si pas de précédent
  stockage.end();
  pinMode(entree_audio, INPUT);

  // WiFI
  WiFi.setSleep(false);
  WiFi.setAutoReconnect(true);
  WiFi.config(ip, gw, mask, ns);
  WiFi.onEvent(WiFiEvent);
  WiFi.begin(sid, wifi_sesame);

  // Affichage
  ecran.begin();
  ecran.setFlipMode(1);
  ecran.enableUTF8Print();
  ecran.setContrast(1);
  ecran.clearBuffer();
  ecran.sendBuffer();

  // VU mètres
  pinMode(vu_luminosite_pin, OUTPUT);
  analogWrite(vu_luminosite_pin, vu_luminosite_niveau);

  // Spectrum
  initialisation_I2S();
  delay(200);

  // Encodeur
  encodeur.attachFullQuad(DATA_PIN, CLK_PIN);
  encodeur.clearCount();
  encodeur.setCount(vu_luminosite_niveau * 4);

  // Boutons
  encodeur_poussoir.attach(Encodeur_poussoir_pin, INPUT_PULLUP);
  encodeur_poussoir.setPressedState(LOW);
  encodeur_poussoir.interval(25);

  // Relais
  pinMode(relais_tubes, OUTPUT);
  pinMode(relais_Xtors, OUTPUT);
  commutation_ampli();
  if (mode_affichage == 0)
  {
    uneseconde.start();
  }
}

void loop()
{
  uneseconde.update();
  encodeur_poussoir.update();
  if (menu_principal_etat == false and menu_ampli_etat == false and menu_visualisation_etat == false) // Fonctionnement normal hors menus
  {
    if (encodeur_poussoir.pressed())
    {
      encodeur.setCount(menu_principal_point * 4);
      encodeur_position_precedente = menu_principal_point;
      menu_principal_etat = true;
      menu_principal();
    }
    else
    {
      long encodeur_position = encodeur.getCount() / 4;
      if (encodeur_position != encodeur_position_precedente) // Si l'encodeur est tourné
      {
        if (encodeur_position > 254)
        {
          encodeur_position = 254;
          encodeur.setCount(254 * 4);
        }
        if (encodeur_position < 0)
        {
          encodeur_position = 0;
          encodeur.setCount(0);
        }
        vu_luminosite_niveau = encodeur_position;
        Serial.print("Luminosité VU : ");
        Serial.println(vu_luminosite_niveau);
        analogWrite(vu_luminosite_pin, vu_luminosite_niveau);
        encodeur_position_precedente = encodeur_position;
        if (mode_affichage != 2)
        {
          affichage_infos();
        }
      }
      if (mode_affichage == 0)
      {
        affichage_horloge();
      }
      if (mode_affichage == 1)
      {
        affichage_oscilloscope();
      }
      if (mode_affichage == 2)
      {
        affichage_spectre();
      }
    }
  }
  else if (menu_principal_etat == true)
  {
    if (encodeur_poussoir.pressed())
    {
      if (menu_principal_point == 0)
      {
        // Vers sous-menu Ampli
        encodeur.setCount(menu_ampli_point * 4);
        encodeur_position_precedente = menu_ampli_point;
        menu_ampli();
        menu_ampli_etat = true;
        menu_principal_etat = false;
      }
      if (menu_principal_point == 1)
      {
        // Vers sous-menu Visualisation
        encodeur.setCount(menu_visualisation_point * 4);
        encodeur_position_precedente = menu_visualisation_point;
        menu_visualisation();
        menu_visualisation_etat = true;
        menu_principal_etat = false;
      }
      if (menu_principal_point == 2)
      {
        // Vers sous-menu Calibration
        calibration_bruit();
        for (int b = 0; b < bandes; b++)
        {
          niveau[b] = 0;
        }
        encodeur.setCount(vu_luminosite_niveau * 4);
        encodeur_position_precedente = vu_luminosite_niveau;
        ecran.clearBuffer();
        ecran.setDrawColor(true);
        menu_principal_etat = false;
        menu_ampli_etat = false;
        menu_visualisation_etat = false;
        affichage_infos();
      }
      if (menu_principal_point == 3)
      {
        // Sortie du menu
        encodeur.setCount(vu_luminosite_niveau * 4);
        encodeur_position_precedente = vu_luminosite_niveau;
        ecran.clearBuffer();
        ecran.setDrawColor(true);
        menu_principal_etat = false;
        menu_ampli_etat = false;
        menu_visualisation_etat = false;
        affichage_infos();
      }
    }
    else
    {
      long encodeur_position = encodeur.getCount() / 4;
      if (encodeur_position != encodeur_position_precedente) // Si l'encodeur est tourné
      {
        if (encodeur_position > 3)
        {
          encodeur_position = 3;
          encodeur.setCount(3 * 4);
        }
        if (encodeur_position < 0)
        {
          encodeur_position = 0;
          encodeur.setCount(0);
        }
        menu_principal_point = encodeur_position;
        Serial.print("Menu principal point : ");
        Serial.println(menu_principal_point);
        encodeur_position_precedente = encodeur_position;
        menu_principal();
      }
    }
  }
  else if (menu_ampli_etat == true)
  {
    if (encodeur_poussoir.pressed())
    {
      if (menu_ampli_point == 0)
      {
        ampli_id = 1;
        stockage.begin("config", false);
        stockage.putInt("amp", ampli_id);
        stockage.end();
        commutation_ampli();
        encodeur.setCount(vu_luminosite_niveau * 4);
        encodeur_position_precedente = vu_luminosite_niveau;
        ecran.clearBuffer();
        ecran.setDrawColor(true);
        menu_principal_etat = false;
        menu_ampli_etat = false;
        menu_visualisation_etat = false;
        affichage_infos();
      }
      if (menu_ampli_point == 1)
      {
        ampli_id = 0;
        stockage.begin("config", false);
        stockage.putInt("amp", ampli_id);
        stockage.end();
        commutation_ampli();
        encodeur.setCount(vu_luminosite_niveau * 4);
        encodeur_position_precedente = vu_luminosite_niveau;
        ecran.clearBuffer();
        ecran.setDrawColor(true);
        menu_principal_etat = false;
        menu_ampli_etat = false;
        menu_visualisation_etat = false;
        affichage_infos();
      }
      if (menu_ampli_point == 2)
      {
        // retour au menu parent
        encodeur.setCount(menu_principal_point * 4);
        encodeur_position_precedente = menu_principal_point;
        menu_ampli_etat = false;
        menu_principal_etat = true;
        menu_principal();
      }
    }
    else
    {
      long encodeur_position = encodeur.getCount() / 4;
      if (encodeur_position != encodeur_position_precedente) // Si l'encodeur est tourné
      {
        if (encodeur_position > 2)
        {
          encodeur_position = 2;
          encodeur.setCount(2 * 4);
        }
        if (encodeur_position < 0)
        {
          encodeur_position = 0;
          encodeur.setCount(0);
        }
        menu_ampli_point = encodeur_position;
        Serial.print("Menu ampli point : ");
        Serial.println(menu_ampli_point);
        encodeur_position_precedente = encodeur_position;
        menu_ampli();
      }
    }
  }
  else if (menu_visualisation_etat == true)
  {
    if (encodeur_poussoir.pressed())
    {
      if (menu_visualisation_point == 0)
      {
        // Définir mode Horloge
        mode_affichage = 0;
        stockage.begin("config", false);
        stockage.putInt("mode", mode_affichage);
        stockage.end();
        premierefois = true;
        temoinntp = true;
        uneseconde.start();
        encodeur.setCount(vu_luminosite_niveau * 4);
        encodeur_position_precedente = vu_luminosite_niveau;
        ecran.clearBuffer();
        ecran.setDrawColor(true);
        menu_principal_etat = false;
        menu_ampli_etat = false;
        menu_visualisation_etat = false;
        affichage_infos();
      }
      else if (menu_visualisation_point == 1)
      {
        // Définir mode Oscilloscope
        uneseconde.stop();
        mode_affichage = 1;
        stockage.begin("config", false);
        stockage.putInt("mode", mode_affichage);
        stockage.end();
        encodeur.setCount(vu_luminosite_niveau * 4);
        encodeur_position_precedente = vu_luminosite_niveau;
        ecran.clearBuffer();
        ecran.setDrawColor(true);
        menu_principal_etat = false;
        menu_ampli_etat = false;
        menu_visualisation_etat = false;
        affichage_infos();
      }
      else if (menu_visualisation_point == 2)
      {
        // Définir mode Spectre
        uneseconde.stop();
        mode_affichage = 2;
        stockage.begin("config", false);
        stockage.putInt("mode", mode_affichage);
        stockage.end();
        encodeur.setCount(vu_luminosite_niveau * 4);
        encodeur_position_precedente = vu_luminosite_niveau;
        ecran.clearBuffer();
        ecran.setDrawColor(true);
        menu_principal_etat = false;
        menu_ampli_etat = false;
        menu_visualisation_etat = false;
      }
      else if (menu_visualisation_point == 3)
      {
        // Retour menu parent
        encodeur.setCount(menu_principal_point * 4);
        encodeur_position_precedente = menu_principal_point;
        menu_visualisation_etat = false;
        menu_principal_etat = true;
        menu_principal();
      }
    }
    else
    {
      long encodeur_position = encodeur.getCount() / 4;
      if (encodeur_position != encodeur_position_precedente) // Si l'encodeur est tourné
      {
        if (encodeur_position > 3)
        {
          encodeur_position = 3;
          encodeur.setCount(3 * 4);
        }
        if (encodeur_position < 0)
        {
          encodeur_position = 0;
          encodeur.setCount(0);
        }
        menu_visualisation_point = encodeur_position;
        Serial.print("Menu visualisation point : ");
        Serial.println(menu_visualisation_point);
        encodeur_position_precedente = encodeur_position;
        menu_visualisation();
      }
    }
  }
}

void commutation_ampli()
{
  if (ampli_id == 0)
  {
    ampli = "Tubes";
    digitalWrite(relais_Xtors, false);
    delay(100);
    digitalWrite(relais_tubes, true);
  }
  else if (ampli_id == 1)
  {
    ampli = "Pioneer";
    digitalWrite(relais_tubes, false);
    delay(100);
    digitalWrite(relais_Xtors, true);
  }
}

void menu_principal()
{
  ecran.setFont(u8g2_font_courR08_tf);
  ecran.clearBuffer();
  if (menu_principal_point == 0)
  {
    couleur_fond = true;
    couleur_texte = false;
  }
  else
  {
    couleur_fond = false;
    couleur_texte = true;
  }
  ecran.setDrawColor(couleur_fond);
  ecran.drawRBox(0, 6, 128, 12, 5);
  ecran.setDrawColor(couleur_texte);
  ecran.drawUTF8(5, 15, "Choix Ampli");
  if (menu_principal_point == 1)
  {
    couleur_fond = true;
    couleur_texte = false;
  }
  else
  {
    couleur_fond = false;
    couleur_texte = true;
  }
  ecran.setDrawColor(couleur_fond);
  ecran.drawRBox(0, 21, 128, 12, 5);
  ecran.setDrawColor(couleur_texte);
  ecran.drawUTF8(5, 30, "Choix Visualisation");
  if (menu_principal_point == 2)
  {
    couleur_fond = true;
    couleur_texte = false;
  }
  else
  {
    couleur_fond = false;
    couleur_texte = true;
  }
  ecran.setDrawColor(couleur_fond);
  ecran.drawRBox(0, 36, 128, 12, 5);
  ecran.setDrawColor(couleur_texte);
  ecran.drawUTF8(5, 45, "Calibration ADC");
  ecran.sendBuffer();
  if (menu_principal_point == 3)
  {
    couleur_fond = true;
    couleur_texte = false;
  }
  else
  {
    couleur_fond = false;
    couleur_texte = true;
  }
  ecran.setDrawColor(couleur_fond);
  ecran.drawRBox(0, 51, 128, 12, 5);
  ecran.setDrawColor(couleur_texte);
  ecran.drawUTF8(5, 60, "<< Retour");
  ecran.sendBuffer();
}

void menu_ampli()
{
  ecran.clearBuffer();
  ecran.setFont(u8g2_font_courR08_tf);
  if (menu_ampli_point == 0)
  {
    couleur_fond = true;
    couleur_texte = false;
  }
  else
  {
    couleur_fond = false;
    couleur_texte = true;
  }
  ecran.setDrawColor(couleur_fond);
  ecran.drawRBox(0, 6, 128, 12, 5);
  ecran.setDrawColor(couleur_texte);
  ecran.drawUTF8(5, 15, "Pioneer SA6500");
  if (menu_ampli_point == 1)
  {
    couleur_fond = true;
    couleur_texte = false;
  }
  else
  {
    couleur_fond = false;
    couleur_texte = true;
  }
  ecran.setDrawColor(couleur_fond);
  ecran.drawRBox(0, 21, 128, 12, 5);
  ecran.setDrawColor(couleur_texte);
  ecran.drawUTF8(5, 30, "Tubes");
  if (menu_ampli_point == 2)
  {
    couleur_fond = true;
    couleur_texte = false;
  }
  else
  {
    couleur_fond = false;
    couleur_texte = true;
  }
  ecran.setDrawColor(couleur_fond);
  ecran.drawRBox(0, 36, 128, 12, 5);
  ecran.setDrawColor(couleur_texte);
  ecran.drawUTF8(5, 45, "<< Retour");
  ecran.sendBuffer();
}

void menu_visualisation()
{
  ecran.clearBuffer();
  ecran.setFont(u8g2_font_courR08_tf);
  if (menu_visualisation_point == 0)
  {
    couleur_fond = true;
    couleur_texte = false;
  }
  else
  {
    couleur_fond = false;
    couleur_texte = true;
  }
  ecran.setDrawColor(couleur_fond);

  ecran.drawRBox(0, 6, 128, 12, 5);
  ecran.setDrawColor(couleur_texte);
  ecran.drawUTF8(5, 15, "Horloge");

  if (menu_visualisation_point == 1)
  {
    couleur_fond = true;
    couleur_texte = false;
  }
  else
  {
    couleur_fond = false;
    couleur_texte = true;
  }
  ecran.setDrawColor(couleur_fond);
  ecran.drawRBox(0, 21, 128, 12, 5);
  ecran.setDrawColor(couleur_texte);
  ecran.drawUTF8(5, 30, "Oscilloscope");

  if (menu_visualisation_point == 2)
  {
    couleur_fond = true;
    couleur_texte = false;
  }
  else
  {
    couleur_fond = false;
    couleur_texte = true;
  }
  ecran.setDrawColor(couleur_fond);
  ecran.drawRBox(0, 36, 128, 12, 5);
  ecran.setDrawColor(couleur_texte);
  ecran.drawUTF8(5, 45, "Spectre");
  if (menu_visualisation_point == 3)
  {
    couleur_fond = true;
    couleur_texte = false;
  }
  else
  {
    couleur_fond = false;
    couleur_texte = true;
  }
  ecran.setDrawColor(couleur_fond);
  ecran.drawRBox(0, 51, 128, 12, 5);
  ecran.setDrawColor(couleur_texte);
  ecran.drawUTF8(5, 60, "<< Retour");
  ecran.sendBuffer();
}

void affichage_infos()
{
  ecran.drawFrame(0, 0, 128, 64);
  ecran.setFont(u8g2_font_courR08_tf);
  ecran.drawUTF8(5, 12, "Ampli : ");
  ecran.drawUTF8(75, 12, ampli.c_str());
  ecran.drawUTF8(5, 59, "Luminosité VU : ");
  char vubl[6];
  sprintf(vubl, "%d", vu_luminosite_niveau);
  ecran.setDrawColor(0);
  ecran.drawBox(100, 51, 20, 8);
  ecran.setDrawColor(1);
  ecran.drawUTF8(100, 59, vubl);
  ecran.sendBuffer();
}

void affichage_horloge()
{
  ecran.setDrawColor(0);
  ecran.drawBox(1, 13, 126, 29);
  ecran.setDrawColor(1);
  ecran.setFont(u8g2_font_courR24_tf);
  ecran.drawStr(14, 42, heure.c_str());
  ecran.sendBuffer();
}

void affichage_oscilloscope()
{
  static uint16_t bufferI2S[SAMPLE_POINTS * SCOPE_DIVISION];
  size_t bytesRead = 0;
  i2s_read(
      I2S_port,
      (void *)bufferI2S,
      sizeof(bufferI2S),
      &bytesRead,
      portMAX_DELAY);
  if (bytesRead != sizeof(bufferI2S))
    return;
  for (int i = 0; i < SAMPLE_POINTS; i++)
  {
    samples[i] = (bufferI2S[i * SCOPE_DIVISION] >> 4) & 0x0FFF;
  }
  ecran.clearBuffer();
  for (int i = 0; i < SAMPLE_POINTS - 1; i++)
  {
    int y1 = offset - ((samples[i] - 2048) * sensitivity / 64);
    int y2 = offset - ((samples[i + 1] - 2048) * sensitivity / 64);
    ecran.drawLine(i, y1, i + 1, y2);
  }
  affichage_infos();
}

void affichage_spectre()
{
  echantillonage();
  FFT.dcRemoval();
  FFT.windowing(FFTWindow::Hann, FFTDirection::Forward);
  FFT.compute(FFTDirection::Forward);
  FFT.complexToMagnitude();
  calcul_par_bandes();
  affichage();
}

void affichage()
{
  ecran.clearBuffer();
  ecran.setFont(u8g2_font_courR08_tf);
  ecran.drawUTF8(95, 10, "VU");
  char vubl[6];
  sprintf(vubl, "%d", vu_luminosite_niveau);
  ecran.setDrawColor(0);
  ecran.drawBox(110, 1, 17, 9);
  ecran.setDrawColor(1);
  ecran.drawUTF8(110, 10, vubl);
  const int espacement = 5;
  const int largeur_de_barre = (128 - bandes * espacement) / bandes;
  for (int b = 0; b < bandes; b++)
  {
    int x = b * (largeur_de_barre + espacement);
    int height = (int)niveau[b];
    if (height > 63)
      height = 63;
    if (height < 0)
      height = 0;
    if (height > 0)
    {
      ecran.drawBox(x, 63 - height, largeur_de_barre, height);
    }
  }
  ecran.sendBuffer();
}

void echantillonage()
{
  size_t bytesRead = 0;
  i2s_read(I2S_port, (void *)i2sBuffer, sizeof(i2sBuffer), &bytesRead, portMAX_DELAY);
  for (int i = 0; i < echantillons; i++)
  {
    uint16_t raw = i2sBuffer[i];
    // ADC 12 bits dans les 12 bits hauts
    int adcValue = (raw >> 4) & 0x0FFF;
    vReal[i] = (double)adcValue;
    vImag[i] = 0.0;
  }
}

void calibration_bruit()
{
  ecran.clearBuffer();
  Serial.println();
  ecran.setFont(u8g2_font_courR08_tf);
  Serial.println("Calibration du bruit...");
  ecran.drawUTF8(0, 30, "Calibration ...");
  ecran.sendBuffer();
  float accumulated[bandes] = {0};
  for (int frame = 0; frame < calibration_duree; frame++)
  {
    echantillonage();
    FFT.dcRemoval();
    FFT.windowing(FFTWindow::Hann, FFTDirection::Forward);
    FFT.compute(FFTDirection::Forward);
    FFT.complexToMagnitude();
    for (int b = 0; b < bandes; b++)
    {
      int firstBin = max(1, (int)(bandLow[b] * echantillons / frequence_echantillonage));
      int lastBin = min(echantillons / 2 - 1, (int)(bandHigh[b] * echantillons / frequence_echantillonage));
      double maxMagnitude = 0;
      for (int i = firstBin; i <= lastBin; i++)
      {
        if (vReal[i] > maxMagnitude)
          maxMagnitude = vReal[i];
      }
      float level = (2.0 * maxMagnitude) / echantillons;
      accumulated[b] += level;
    }
  }
  stockage.begin("config", false);
  for (int b = 0; b < bandes; b++)
  {
    bruit[b] = accumulated[b] / calibration_duree;
    Serial.print("Bande ");
    Serial.print(b + 1);
    Serial.print(" bruit = ");
    Serial.println(bruit[b], 2);
    String pointeur = "bruit" + String(b);
    stockage.putFloat(pointeur.c_str(), bruit[b]);
  }
  stockage.end();
  Serial.println("Calibration terminee.");
  Serial.println();
}

void calcul_par_bandes()
{
  for (int b = 0; b < bandes; b++)
  {
    int firstBin =
        max(
            1,
            (int)(bandLow[b] * echantillons / frequence_echantillonage));
    int lastBin =
        min(
            echantillons / 2 - 1,
            (int)(bandHigh[b] * echantillons / frequence_echantillonage));
    double maxMagnitude = 0;
    for (int i = firstBin; i <= lastBin; i++)
    {
      if (vReal[i] > maxMagnitude)
      {
        maxMagnitude = vReal[i];
      }
    }
    // Normalisation FFT
    float level =
        (2.0 * maxMagnitude) / echantillons;
    // Suppression du bruit
    float display =
        //(level - noiseFloor[b] - NOISE_MARGIN) * DISPLAY_GAIN;
        (level - bruit[b]) * gain;
    if (display < 0)
      display = 0;
    if (display > 63)
      display = 63;
    niveau[b] = display;
  }
}

void initialisation_I2S()
{
  i2s_config_t i2s_config = {
      .mode = (i2s_mode_t)(I2S_MODE_MASTER |
                           I2S_MODE_RX |
                           I2S_MODE_ADC_BUILT_IN),
      .sample_rate = (int)frequence_echantillonage,
      .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
      .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
      .communication_format =
          (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S |
                              I2S_COMM_FORMAT_I2S_MSB),
      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
      .dma_buf_count = 4,
      .dma_buf_len = 256,
      .use_apll = false,
      .tx_desc_auto_clear = false,
      .fixed_mclk = 0};
  i2s_driver_install(
      I2S_port,
      &i2s_config,
      0,
      NULL);
  i2s_set_adc_mode(
      ADC_UNIT_1,
      adc_canal);
  // ADC 12 bits
  analogSetWidth(12);
  // Atténuation
  analogSetPinAttenuation(
      entree_audio,
      ADC_0db);
  i2s_adc_enable(I2S_port);
}
void quelleheure(IPAddress &address) // Synchronisation du temps via NTP
{
  uneseconde.pause();
  ecran.clearBuffer();
  ecran.setFont(u8g2_font_courR18_tf);
  ecran.drawUTF8(20, 23, "Mise à");
  ecran.drawUTF8(12, 52, "l'heure");
  ecran.sendBuffer();
  udp.begin(localPort);
  WiFi.hostByName(ntpServerName, timeServerIP);
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  packetBuffer[0] = 0b11100011;
  packetBuffer[1] = 0;
  packetBuffer[2] = 6;
  packetBuffer[3] = 0xEC;
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;
  packetBuffer[15] = 52;
  udp.beginPacket(address, 123);
  udp.write(packetBuffer, NTP_PACKET_SIZE);
  udp.endPacket();
  delay(1000);
  int cb = udp.parsePacket();
  while (!cb)
  {
    cb = udp.parsePacket();
  }
  udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
  unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
  unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
  unsigned long secsSince1900 = highWord << 16 | lowWord;
  const unsigned long seventyYears = 2208988800UL;
  unsigned long epoch = secsSince1900 - seventyYears - 1; // moins délais udp
  annee = 2025;
  bis = 0;
  epochannee = epoch01012025;
  nombredejours = (epoch - epoch01012025) / 86400L + 1; // car le 1 janvier correspond aux secondes 0 à 86400 soit le jour 0
  nombredejoursannee = nombredejours;
  while (nombredejoursannee > 365 + bis)
  {
    nombredejoursannee = nombredejoursannee - (365 + bis);
    annee = annee + 1;
    epochannee = epochannee + (365 + bis) * 86400L;
    bis = 0;
    if (annee % 400 == 0 or (annee % 4 == 0 and annee % 100 != 0))
    {
      bis = 1;
    }
  }
  if (nombredejoursannee <= 31)
  {
    mois = "janvier";
    jour = nombredejoursannee;
  }
  else if (nombredejoursannee <= (59 + bis))
  {
    mois = "fevrier";
    jour = nombredejoursannee - 31;
  }
  else if (nombredejoursannee <= 90 + bis)
  {
    mois = "mars";
    jour = nombredejoursannee - (59 + bis);
  }
  else if (nombredejoursannee <= 120 + bis)
  {
    mois = "avril";
    jour = nombredejoursannee - (90 + bis);
  }
  else if (nombredejoursannee <= 151 + bis)
  {
    mois = "mai";
    jour = nombredejoursannee - (120 + bis);
  }
  else if (nombredejoursannee <= 181 + bis)
  {
    mois = "juin";
    jour = nombredejoursannee - (151 + bis);
  }
  else if (nombredejoursannee <= 212 + bis)
  {
    mois = "juillet";
    jour = nombredejoursannee - (181 + bis);
  }
  else if (nombredejoursannee <= 243 + bis)
  {
    mois = "aout";
    jour = nombredejoursannee - (212 + bis);
  }
  else if (nombredejoursannee <= 273 + bis)
  {
    mois = "septembre";
    jour = nombredejoursannee - (243 + bis);
  }
  else if (nombredejoursannee <= 304 + bis)
  {
    mois = "octobre";
    jour = nombredejoursannee - (273 + bis);
  }
  else if (nombredejoursannee <= 334 + bis)
  {
    mois = "novembre";
    jour = nombredejoursannee - (304 + bis);
  }
  else if (nombredejoursannee <= 365 + bis)
  {
    mois = "decembre";
    jour = nombredejoursannee - (334 + bis);
  }
  queljoursemaine = (epoch / 86400L + 4) % 7; // +4 car 1/1/1970 = jeudi = jour 4 (si dimanche = 0)
  switch (queljoursemaine)
  {
  case 0:
    joursemaine = "Dimanche";
    break;
  case 1:
    joursemaine = "Lundi";
    break;
  case 2:
    joursemaine = "Mardi";
    break;
  case 3:
    joursemaine = "Mercredi";
    break;
  case 4:
    joursemaine = "Jeudi";
    break;
  case 5:
    joursemaine = "Vendredi";
    break;
  case 6:
    joursemaine = "Samedi";
    break;
  }
  epochmars = epochannee + (90 + bis) * 86400UL;   // correspond au dernier jour de de mars
  queljoursemaine = (epochmars / 86400UL + 4) % 7; // +4 car 1/1/1970 = jeudi = jour 4 (si dimanche = 0)
  while (queljoursemaine != 0)
  {
    epochmars = epochmars - 86400UL;
    queljoursemaine = (epochmars / 86400UL + 4) % 7;
  }
  epochmars = epochmars + 1 * 3600UL; // le changement se fait à UTC+1 soit +2H car UTC+1 +DST hiver

  epochoctobre = epochannee + (303 + bis) * 86400UL;  // correspond au dernier jour d'octobre
  queljoursemaine = (epochoctobre / 86400UL + 4) % 7; // +4 car 1/1/1970 = jeudi = jour 4 (si dimanche = 0)
  while (queljoursemaine != 0)
  {
    epochoctobre = epochoctobre - 86400UL;
    queljoursemaine = (epochoctobre / 86400UL + 4) % 7;
  }
  epochoctobre = epochoctobre + 1 * 3600UL; // le changement se fait à UTC+1 soit +3H car UTC+1 +DST été
  if (epoch >= epochmars and epoch < epochoctobre)
  {
    dst = 2;
  }
  else
  {
    dst = 1;
  }

  Heure = ((epoch % 86400UL) / 3600UL) + dst; // +1 = Heure d'hiver et +2 = Heure d'été
  if (Heure >= 24)
  {
    Heure = Heure - 24;
  }
  minute = ((epoch % 3600) / 60);
  seconde = (epoch % 60);

  // Mise en forme pour l'affichage
  dh = Heure / 10;
  h = Heure - (dh * 10);
  dm = minute / 10;
  m = minute - (dm * 10);
  ds = seconde / 10;
  s = seconde - (ds * 10);
  uneseconde.resume();
  heure = String(dh) + String(h) + ":" + String(dm) + String(m);
  if (premierefois == true)
  {
    premierefois = false;
    ecran.clearBuffer();
    affichage_infos();
    affichage_horloge();
  }
}

Laissez un commentaire et posez vos questions ci-dessous

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *