Multi affichage avec un seul écran type oled 128x64

Bonjour à tous,

Je suis actuellement en train de travailler sur un projet. Il s'agit d'un petit "ordinateur de bord".

J'arrive à mesurer et à afficher sur mon écran les données que je souhaite, tels que la tension de ma batterie, la température ainsi que divers information provenant d'un module GPS.

Le problème auquel je suis confronter est que je souhaite avoir différent "écrans" et pouvoir passer de l'un à l'autre d'un "simple clic" par bouton poussoir et qu'une fois le dernier écran est afficher s'il y a clic, retourner au premier et ainsi de suite à chaque clic.

Un petit schéma parlera mieux (cf photo jointe)

Je n'ai malheureusement pas les connaissance pour réaliser ce système d'écrans/pages, c'est pourquoi je sollicite votre aide.

J'ai tout de même effectué de multiples recherches mais je retombe sans cesse sur un système de menu sous forme de liste par exemple, ce qui ne correspond pas du tout à mes attentes.

Si vous avez besoin de plus d'information, n'hésitez pas !

Bonsoir,

si tu incrémentes une variable (que j’appellerai screenNum) à chaque 'clic' (avec retour à zéro) alors après tu peux utiliser l'instruction switch comme cela :

switch(screenNum) {
   case 0 : drawScreen0(); break;
   case 1 : drawScreen1(); break;
   case 2 : drawScreen2(); break;
   case 3 : drawScreen3(); break;
}

Avec drawScreen0() la fonction affichant l'écran 0, drawScreen1() la fonction affichant l'écran 1, etc ...

On peut faire encore plus compact et performant avec un tableau de fonctions mais avec 4 possibilités cela vaut le coup de commencer avec un 'switch'.

La version avec tableau de fonctions (la définition du tableau est un peu ... spéciale) :

int screenNum;

void drawScreen0() {
    // ...
}

// pareil pour drawScreen1, drawScreen2 et drawScreen3

void (*myArrayOfFunctions[])() = {
    drawScreen0, drawScreen1, drawScreen2, drawScreen3 };

void setup() {
    // ...
}

void loop() {

    // ...

    // quand tu veux afficher l'écran dont le numéro est dans screenNum

    myArrayOfFunctions[screenNum](); // Simple et efficace (pas de tests).

}

Bonjour et merci beaucoup supercc !
J'avais tenter d'incrementer à chaque clic puis de retourner à 0, mais je ne m'y était pas du tout pris de la bonne façon et mes écran défilaient en boucle ( par moment sans même appuyer sur le poussoir en plus ) pour se figer sur le dernier.

Ce soir en rentrant du travail, je teste tes propositions puis je te fais un retour.

Merci encore.

Ah c'est marrant j'aurais pas fait comme ça.
J'aurais fait une fonction par mode d'affichage, et une fonction qui "sert à attendre" le clic

loop() {
menu1();
clic();
menu2();
clic();
...
}

Histoire de faire simple

@kammo, le problème avec ta solution c'est que tes écrans ne se rafraîchiront pas (si la température évolue elle ne sera pas mise à jour à l'écran) car le programme sera en train d'attendre un clic.

@Dsmx83, quelques infos qui pourraient t'être utiles si tu ne les connais pas déjà :

  • il faut gérer l'anti rebond du bouton poussoir. Regardes par exemple ici.

  • il est possible de se passer de la résistance externe en série pour le bouton poussoir (en activant la résistance interne dite de pull up du microcontrôleur). Voir par exemple ici.

  • incrémenter une variable en la forçant à repasser à 0 lorsqu'elle atteint la valeur 4 se fait en 1 ligne : screenNum=++screenNum % 4;

Je vous remercie tout les deux pour toutes ces informations, je vous tiens au courant dès que possible.

Eventuellement lisez mon petit tuto Programmation Automate fini / Machine à état

--> au lieu d'allumer des LEDs vous aurez vos écrans mais le principe de la machine à état reste valable (et un bon concept à maitriser)

Bonjour J-M-L et merci, je vais regarder et essayer de comprendre tout ça !

Bonsoir,
@J-M-L :
Alors, j'ai essayer d'utiliser l'une des méthodes de votre tutoriel, plus précisément, celle utilisée pour l'exercice n°1.

J'ai effectivement mes différents écrans qui "fonctionnent".
Cependant, j'ai deux problèmes:

problème 1 : mes mesures (tension batterie 36v par exemple) ne s'actualisent plus, sauf lors du passage d'un écran à l'autre.

problème 2 : le changement d'écran ne s'effectue pas lors d'un simple clic sur le poussoir, mais aléatoirement (1, 2, ..., voire 4 clics)

Je vous met mon code en pièce jointe si ça peut aider :

// Le numéro de broche analogique pour la mesure de la tension en VIN
const byte BROCHE_CAPTEUR_VIN = A0;

// Coefficient diviseur du pont de résistance
const float COEFF_PONT_DIVISEUR_VIN = 9.1910847267667;

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <OneButton.h>
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#include "RunningAverage.h"

RunningAverage MoyGliss(60);  // objet "Moyenne Glissante" avec nombre d'échantillons pour le calcul
int compteur = 0;             // un compteur d'échantillons
float moyenne = 0;            // la moyenne glissante des échantillons
 
int inputPin = A0;

const byte buttonPin = 3; // on définit un nom pour la pin associée au bouton
OneButton button(buttonPin, true); // true pour dire qu'on est en INPUT_PULLUP, donc actif LOW, connecté à GND

// la liste des états possible de notre système
// ainsi qu'une variable etatCourant prenant une de ces valeurs
enum {HOME, ECRAN1, ECRAN2} etatCourant;

// verification que nous sommes bien configure en 128X64
#if (SSD1306_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif

void mettreAuRepos()
{
  display.clearDisplay();
  etatCourant = HOME;
}

// ------------------------------------------------------
// La fonction de call back, appellée automatiquement quand on clique
// ------------------------------------------------------
void simpleclick()
{
  switch (etatCourant) {
    case HOME: // on était au HOME et on a un appui, on allume la verte 
    {
    
// Mesure la tension en VIN et la référence interne à 1.1 volts
  unsigned int raw_vin = analogRead(BROCHE_CAPTEUR_VIN);
  unsigned int raw_ref = analogReadReference();
  
  // Calcul de la tension réel avec un produit en croix
  float real_vin = ((raw_vin * 1.1) / raw_ref) * COEFF_PONT_DIVISEUR_VIN;
//    float real_vin = 42 ;




  // Lecture du capteur
  
  // Ajout de l'échantillon à l'objet "Moyenne Glissante"
  MoyGliss.addValue(real_vin);
  
  // Récupération de la moyenne glissante
  moyenne = MoyGliss.getAverage();
  
  
  // Incrémentation du compteur
  compteur++;
  // Effacement de l'historique pour éviter les débordements
  if (compteur == 300)
  {
    compteur = 0;
    MoyGliss.clear();
  }

  /* Calcul pourcentage batterie */
  float pourcentage = 100 * ((moyenne - 33) / (42-33));
//    float pourcentage = 100 ;
     
     //Affichage

 
  display.setTextSize(1);
  display.setTextColor(WHITE);
  
  // Tableau
  display.setCursor(0,28);
  display.println("---------");

  display.setCursor(68,28);
  display.println("----------");

  display.setCursor(58,0);
  display.println("|");  
  display.setCursor(58,16);
  display.println("|"); 
  display.setCursor(58,32);
  display.println("|"); 
  display.setCursor(58,48);
  display.println("|");  

  
    
  // Tension de la batterie
  display.setCursor(0,16);
  display.println(moyenne);
  display.setCursor(35,16);
  display.println("V");

  // Pourcentage batterie restante
  display.setCursor(80,16);
  display.println(pourcentage);
  display.setCursor(120,16);
  display.println("%");

  // Température
  display.setCursor(0,40);
  display.println("30.00");
  display.setCursor(35,40);
  display.println((char)247);
  display.setCursor(40,40);
  display.println("C");
  
  
  display.display();
  //delay(750);
  display.clearDisplay();
  }
      etatCourant = ECRAN1; // on note le nouvel état de notre système
      break;

    case ECRAN1: // on était led verte allumée et on a un appui, on allume la jaune 
    {
     
 // Mesure la tension en VIN et la référence interne à 1.1 volts
  unsigned int raw_vin = analogRead(BROCHE_CAPTEUR_VIN);
  unsigned int raw_ref = analogReadReference();
  
  // Calcul de la tension réel avec un produit en croix 
  float real_vin = ((raw_vin * 1.1) / raw_ref) * COEFF_PONT_DIVISEUR_VIN;
  //    float real_vin = 42 ;




  // Lecture du capteur
  
  // Ajout de l'échantillon à l'objet "Moyenne Glissante"
  MoyGliss.addValue(real_vin);
  
  // Récupération de la moyenne glissante
  moyenne = MoyGliss.getAverage();
  
  
  // Incrémentation du compteur
  compteur++;
  // Effacement de l'historique pour éviter les débordements
  if (compteur == 300)
  {
    compteur = 0;
    MoyGliss.clear();
  }

  // Calcul pourcentage batterie
  float pourcentage = 100 * ((moyenne - 33) / (42-33));
//    float pourcentage = 100 ;
//Affichage

 
  display.setTextSize(1);
  display.setTextColor(WHITE);
  
  // Tableau
  display.setCursor(0,28);
  display.println("---------");

  display.setCursor(68,28);
  display.println("----------");

  display.setCursor(58,0);
  display.println("|");  
  display.setCursor(58,16);
  display.println("|"); 
  display.setCursor(58,32);
  display.println("|"); 
  display.setCursor(58,48);
  display.println("|");  

  // Titre
  display.setCursor(0,0);
  display.println("Tension"); 

  display.setCursor(80,0);
  display.println("Batterie"); 
    
  // Tension de la batterie
  display.setCursor(0,16);
  display.println(moyenne);
  display.setCursor(35,16);
  display.println("V");

  // Pourcentage batterie restante
  display.setCursor(80,16);
  display.println(pourcentage);
  display.setCursor(120,16);
  display.println("%");

  // Température
  display.setCursor(0,40);
  display.println("30.00");
  display.setCursor(35,40);
  display.println((char)247);
  display.setCursor(40,40);
  display.println("C");
  
  
  display.display();
  //delay(750);
  display.clearDisplay();
}
      etatCourant = ECRAN2;// on note le nouvel état de notre système
      break;

    case ECRAN2: // tout était allumé, on a un appui, on retourne au HOME
      mettreAuRepos(); // on retourne à l'état initial
      break;
  }
}

// Fonction setup()
void setup(void) {
 
  // initialisation de la vitesse de la liaison serie
  Serial.begin(9600);
  // par defaut nous allons generer une tension de 3.3v 
  // initialisation de la liaison ID2C avec l'adresse 0x3D
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C); 
  // fin de l'initialisation
  
  display.display();
  delay(6000);
  display.clearDisplay();


  
  // Initialisation de l'objet "Moyenne Glissante"
  MoyGliss.clear();

  //conditions Initiales
  mettreAuRepos();

  // On attache la fonction simpleClick() comme callBack
  button.attachClick(simpleclick);

}

// Mesure la référence interne à 1.1 volts
unsigned int analogReadReference(void) {
  
  // Elimine toutes charges résiduelles
#if defined(__AVR_ATmega328P__)
  ADMUX = 0x4F;
#elif defined(__AVR_ATmega2560__)
  ADCSRB &= ~(1 << MUX5);
  ADMUX = 0x5F;
#elif defined(__AVR_ATmega32U4__)
  ADCSRB &= ~(1 << MUX5);
  ADMUX = 0x5F;
#endif
  delayMicroseconds(5);
  
  // Sélectionne la référence interne à 1.1 volts comme point de mesure, avec comme limite haute VCC
#if defined(__AVR_ATmega328P__)
  ADMUX = 0x4E;
#elif defined(__AVR_ATmega2560__)
  ADCSRB &= ~(1 << MUX5);
  ADMUX = 0x5E;
#elif defined(__AVR_ATmega32U4__)
  ADCSRB &= ~(1 << MUX5);
  ADMUX = 0x5E;
#endif
  delayMicroseconds(200);

  // Active le convertisseur analogique -> numérique 
  ADCSRA |= (1 << ADEN);
  
  // Lance une conversion analogique -> numérique 
  ADCSRA |= (1 << ADSC);
  
  // Attend la fin de la conversion 
  while(ADCSRA & (1 << ADSC));
  
  // Récupère le résultat de la conversion 
  return ADCL | (ADCH << 8);
}

// Fonction loop() 
void loop(void) {

    // On vérifie l'état des boutons, ce qui déclenche l'appel de la fonction callBack si nécessaire
  button.tick();
 
}

PS : soyez indulgent, car mon code doit être sacrément mal conçu pour des connaisseurs !

oui puisque vous ne faites que les mise à jour que lors des changements d'états. Si vous lisez un peu plus loin dans le tuto, j'y introduis une notion de temps (qui est aussi un événement que l'on peut recevoir) --> vous pourriez prendre cette approche pour envoyer un évènement toutes les secondes par exemple et dans l'exécution vous testez dans quel état vous êtes, allez lire la bonne valeur à afficher et faites l'affichage. (idéalement vous auriez des fonctions pour chaque écran)

Bon, j'essaie désespérément depuis votre réponse ! (j'ai pas encore fait à manger, je crois que ma femme va me tuer).

Je commence un peu (beaucoup) à me perdre ...

Je souhaiterai vraiment le faire par moi-même, c'est pourquoi je ne veux pas un code tout fait, sinon je n'apprendrais jamais. Par contre, s'il est possible d'avoir quelques indications afin que je puisse (re)partir dans le bon sens ?

Trop de caractères d'où le deuxième post pour le code mais en pièce jointe.

J'ai également du attendre 5 min avant de reposter.

TEST_MULTI_ECRAN.ino (8.68 KB)

Quand vous bidouillez, l'idéal est de simplifier au maximum pour avoir une structure de code aussi claire que possible qui sert de squelette à votre programme. Ensuite vous remplissez ce squelette avec ce dont vous avez besoin

voici un exemple de squelette à étudier

// La librairie de gestion des boutons
#include <OneButton.h>
const byte buttonPin = 3; // notre bouton est sur la pin 3
OneButton leBouton(buttonPin, true); // true pour le mettre en INPUT_PULLUP

// On introduit le temps comme évènement supplémentaire
unsigned long chrono; // attention, type unsigned long comme millis()
const unsigned long periodeRafraichissement = 1000ul; // 1 seconde (le ul à la fin pour unsigned long au cas où)

// la liste des états possible de notre système
// ainsi qu'une variable etatCourant prenant une de ces valeurs
enum {ETAT_1, ETAT_2, ETAT_3} etatCourant;

// ------------------------------------------------------
// Cette fonction installe l'état initial
// ------------------------------------------------------
void initialistionSysteme()
{
  Serial.println(F("-------"));
  Serial.println(F("ETAT #1"));
  Serial.println(F("-------"));

  etatCourant = ETAT_1;
}

// ------------------------------------------------------
// La fonction de call back, appellée automatiquement quand on clique
// ------------------------------------------------------
void clickSurLeBouton()
{
  switch (etatCourant) {
    case ETAT_1: // on était ETAT_1, on a cliqué le bouton donc on passe en ETAT_2
      etatCourant = ETAT_2;// on note le nouvel état de notre système
      Serial.println(F("\nBOUTON: ON PASSE EN ETAT #2"));
      dessineEcran2();

      break;

    case ETAT_2: // on était ETAT_2, on a cliqué le bouton donc on passe en ETAT_3
      etatCourant = ETAT_3;// on note le nouvel état de notre système
      Serial.println(F("\nBOUTON: ON PASSE EN ETAT #3"));
      dessineEcran3();
      break;

    case ETAT_3: // on était ETAT_3, on a cliqué le bouton donc on retourne en ETAT_1
      etatCourant = ETAT_1;// on note le nouvel état de notre système
      Serial.println(F("\nBOUTON: ON PASSE EN ETAT #1"));
      dessineEcran1();
      break;
  }

  chrono = millis(); // on vient d'avoir une action donc on ré-arme notre chronomètre
}

// ------------------------------------------------------
// La gestion des affichages
// ------------------------------------------------------

void dessineEcran1()
{
  Serial.println(F("MISE A JOUR ECRAN1 (ETAT #1)"));
}

void dessineEcran2()
{
  Serial.println(F("MISE A JOUR ECRAN2 (ETAT #2)"));
}

void dessineEcran3()
{
  Serial.println(F("MISE A JOUR ECRAN3 (ETAT #3)"));
}

// ------------------------------------------------------
// La fonction de appellée quand l'évenement temps arrive
// ------------------------------------------------------
void miseAJourEcran()
{
  switch (etatCourant) {
    case ETAT_1:
      dessineEcran1();
      break;

    case ETAT_2:
      dessineEcran2();
      break;

    case ETAT_3:
      dessineEcran3();
      break;
  }
  chrono = millis(); // on ré-arme notre chronomètre
}


// ------------------------------------------------------
// On initialise notre système dans le setup
// ------------------------------------------------------
void setup() {

  Serial.begin(115200);

  //conditions Initiales
  initialistionSysteme();

  // On attache la fonction clickSurLeBouton() comme callBack en cas de simple click
  leBouton.attachClick(clickSurLeBouton);

}

void loop() {

  // dans la loop on commence par vérifier tous les évènements

  // On vérifie l'état du bouton, ce qui déclenche l'appel du callBack si nécessaire
  leBouton.tick();

  // On vérifie le timer et on déclenche l'évènement si nécéssaire
  if (millis() - chrono >= periodeRafraichissement) {
    miseAJourEcran();
  }

  // ici on peut faire autre chose du moment que ça ne prend pas trop longtemps

}

vous verrez que j'ai viré tout ce qui concerne votre système, mais c'est la structure qui compte:

si vous conservez votre bouton connecté en pin 3 et que vous lancez le programme vous verrez dans la console (réglée à 115200 bauds)

[sub][color=purple]-------
ETAT #1                  [color=blue]       <<--- le setup() s'exécute[/color]
-------
MISE A JOUR ECRAN1 (ETAT #1)[color=blue]    <<--- mise à jour toutes les secondes[/color]
MISE A JOUR ECRAN1 (ETAT #1)[color=blue]    <<--- mise à jour toutes les secondes[/color]
MISE A JOUR ECRAN1 (ETAT #1)[color=blue]    <<--- mise à jour toutes les secondes[/color]
MISE A JOUR ECRAN1 (ETAT #1)
MISE A JOUR ECRAN1 (ETAT #1)
MISE A JOUR ECRAN1 (ETAT #1)
MISE A JOUR ECRAN1 (ETAT #1)
MISE A JOUR ECRAN1 (ETAT #1)
MISE A JOUR ECRAN1 (ETAT #1)
MISE A JOUR ECRAN1 (ETAT #1)

[color=red]BOUTON: ON PASSE EN ETAT #2[/color][color=green]    <<--- ici on appuie sur le bouton[/color]
MISE A JOUR ECRAN2 (ETAT #2)[color=blue]    <<--- mise à jour toutes les secondes[/color]
MISE A JOUR ECRAN2 (ETAT #2)[color=blue]    <<--- mise à jour toutes les secondes[/color]
MISE A JOUR ECRAN2 (ETAT #2)[color=blue]    <<--- mise à jour toutes les secondes[/color]
MISE A JOUR ECRAN2 (ETAT #2)

[color=red]BOUTON: ON PASSE EN ETAT #3[/color][color=green]    <<--- ici on appuie sur le bouton[/color]
MISE A JOUR ECRAN3 (ETAT #3)[color=blue]    <<--- mise à jour toutes les secondes[/color]

[color=red]BOUTON: ON PASSE EN ETAT #1[/color][color=green]    <<--- ici on appuie sur le bouton[/color]
MISE A JOUR ECRAN1 (ETAT #1)[color=blue]    <<--- mise à jour toutes les secondes[/color]
...

[/color][/sub]

vous voyez donc que l'on gère le bouton et qu'il y a aussi un évènement temps qui défile et qui sert - en fonction de l'état du système - à afficher un message approprié (donc pour vous à peindre le bon écran)

--> ça aide ?


Pour ce qui concerne votre soucis suivant

problème 2 : le changement d'écran ne s'effectue pas lors d'un simple clic sur le poussoir, mais aléatoirement (1, 2, ..., voire 4 clics)

il se peut que ça provienne du fait que la lecture des données et l'affichage sur votre OLED soit lent et donc pendant que vous faites l'affichage, vous ratez l'appui sur le bouton puisque le code n'est pas en train de passer dans la boucle pour tester l'appui du bouton...

Si c'est le cas alors il faut minimiser ce que vous raffraichissez sur l'écran, ne pas tout repeindre mais uniquement ce qui change par exemple.

Bonjour,
Je pense y voir beaucoup plus clair et je vous en remercie !
Je vais travailler là-dessus de manière plus organisée et avec simplement une valeur à mettre à jour pour vérifier que je ne fasse pas de bêtise.

Après avoir bien compris, j’essayerai d’implanter « mon système » au fur et à mesure, jusqu’au (je l’espère) bon fonctionnement.

Je reviendrais vers vous par la suite. (Quel que soit le résultat bien sûr)

Oui une fois compris la structure de ce code (assez simple puisque la boucle attend soit un appui bouton, soit la fin de la période d’attente pour faire une action qui est déterminée par l’état courant du système) vous pouvez rajouter le code qui initialise votre écran dans le setup, puis utiliser les fonctions où l’affichage doit se faire pour afficher quelque chose sur l’écran. Commencez par quelque chose de statique genre écran1, écran2, écran3 pour vérifier que votre écran est bien configuré et réactif.une fois que ça fonctionne, passez à du dynamique et lisez vos valeurs à afficher;

Il faudra sans doute ensuite réfléchir à une stratégie d’optimisation Si l’affichage de l’écran est trop lent car dans cecas vous ne reviendrez pas assez souvent dans la loop() pour tester les évènements et votre système semblera pas réactif

PS: profitez en pour corriger le nom de ma fonction initialistionSysteme() et la changer pour un truc qui se lit bien - initialisationSysteme() serait mieux - il était un peu tard :slight_smile:

Bonjour,
Je pense avoir compris votre code.
J'ai réussi à y implanter mon système et cela marche parfaitement !

Maintenant que tout fonctionne à merveille (manque juste une petite optimisation), je vais me renseigner pour allumer et éteindre l'arduino en restant appuyer pendant 3 secondes et tout ceci se fera avec le même bouton que pour le changement d'écran. Mais il s'agit d'un autre sujet.

Je vous remercie encore pour votre aide et le temps que vous avez pris pour cela :wink:
Je pense également que cela pourra répondre aux questions de certaines personnes.

PS: J'ai corrigé le initialistionSysteme() en initialisationSysteme()

Bravo!

Éteindre ou mettre en veille ?

Merci :wink:

Je pense à une mise en veille (écran éteint, module gps également et capteur température aussi) vu que l'arduino sera alimenté en permanence par ma batterie 36v et que je souhaite le réveillé après un pression de 3 sec sur le poussoir.

Il faudrait donc également que la consommation soit très faible pour ne pas trop vider ma batterie. Même si un arduino nano ne doit pas consommer beaucoup je pense.

Je n'ai pas encore fait de vrai recherche, mais j'ai trouvé ceci :

http://playground.arduino.cc/Learning/ArduinoSleepCode

Il faudra que je recherche également dans le forum car j'imagine que le sujet a été traité bon nombre de fois.

Dsmx83:
je souhaite le réveiller après un pression de 3 sec sur le poussoir

et qui va compter les 3 secondes si l'arduino dort? :slight_smile:

oui, jetez un oeil sur ce que fait rocketscream et la la Low Power Library for Arduino (qui fonctionne avec votre arduino, pas forcément besoin de leurs produits)