Affichage et rafraîchissement de plusieurs infos sur un écran oled

Bonjour à tous,

Je suis en train de remettre en état une vieille moto, et à l'époque on ne se prenait pas trop la tete avec des problématiques de sécurité routière..

En gros, il y avait un feu avant, un feu arrière, un compteur de vitesse mécanique et un moteur poussif... je ne parle pas des freins...

Bref, comme j'ambitionne de remettre sur la route cette machine, j'aimerai lui apporter qqs modifications par rapport à l'origine.

Je souhaite intégrer un Arduino pour piloter certaines fonctions. (j'ai une version fonctionnelle en relayage pur, mais je souhaite un système plus intégré).

j'ai donc un Arduino (mega) et Arduino est relié à un écran oled (com en I2C).

Il reçoit les entrées suivantes:

  • Clignotant gauche
  • Clignotant droit
  • Plein phares
  • Un switch (capteur effet hall) qui pilote un relay de puissance
  • Un DHT22 qui mesure la température dans un caisson et active si besoin un ventilateur
  • Un module GPS qui me permet de mesurer la vitesse et les km

Je dois encore rajouter

  • Un pont diviseur de tension pour afficher le % de batterie restant (j'en profite pour mettre un moteur électrique)
  • Une entrée qui va piloter la lumière du feu stop.

Sur l’écran, je souhaite pouvoir afficher les infos suivantes

  • Clignotant gauche/droit
  • Plein phare
  • %bat
  • Km trip
  • Km total
  • Vitesse

j'ai réussi à pondre des bouts de codes fonctionnels pour les clignotants, les pleins phares et la vitesse.

Le reste je n'ai pas encore attaqué mais j'ai trouvé des sources.

pour chacune de ces fonctions je sais (je devrais plutôt dire, j'ai réussi à) communiquer avec l'écran oled et afficher les infos dont j'ai besoin.

Mon problème, vous devez vous en douter, survient quand je commence à tout assembler.

J'ai des informations qui remontent alors qu'elles ne le devraient pas. (par exemple clignotant gauche alors que je pilote pas de cligno)

Le programme qui se bloque après plusieurs itérations.../...

Bref, je pense qu'il y a de grosses lacunes en programmation...

Voici le code, je suis de bonne constitution, vous pouvez y aller franchement dans vos commentaires...

/* a intégrer
pont diviseur de tension
compteur km
stop
*/



//déclaration des librairies
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include <DHT.h>  //librairie capteur T-H DHT22
#include <TinyGPS++.h>

U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);  // création objet écran

void u8g2_prepare(void) {  //fct qui décrit la config et l'orientation de l'écran
  //u8g2.setFont(u8g2_font_6x12_tf);
  u8g2.setFontRefHeightExtendedText();
  u8g2.setDrawColor(1);
  u8g2.setFontPosTop();
  u8g2.setFontDirection(0);
}

TinyGPSPlus gps;

// Initialisation capteur effet Hall
#define DetectStart 2  // pin 2 détection de champ magnétique
#define Start 12       // pin 12 pilotage relay
#define StandBy 13     // pin 13 stand-by

// Initialisation clignotants
#define InClignoG 6   // pin 6 entrée cligno gauche
#define InClignoD 7   // pin 7 entrée cligno droit
#define OutClignoG 8  // pin 8 sortie cligno G
#define OutclignoD 9  // pin 9 sortie cligno D

//Initialisation plein phare
#define InPleinPhare 10   // pin 10 entrée plein phare
#define OutPleinPhare 11  // pin 11 sortie plein phare

// Initialisation DHT22
#define brocheDHT 3           //pin 3 broche data du DHT22
#define typeDHT DHT22         // capteur utilisé =DHT22
#define Startvent 4           // pin 4 pilotage relay (4 pour mega)
DHT dht(brocheDHT, typeDHT);  // Instanciation de la librairie DHT


void startScreen() {  // fonction écran de démarrage

  u8g2.setFont(u8g2_font_crox1h_tf);
  u8g2.setCursor(30, 23);
  u8g2.print("Hall-Impulse");
  u8g2.setCursor(15, 40);
  u8g2.print("On va faire un tour?");
}

/*void print_speed() {  //affichage vitesse

  u8g2.clearBuffer();

  u8g2.setCursor(70, 37);
  u8g2.setFont(u8g2_font_crox5h_tf);
  u8g2.print("00");

  u8g2.setCursor(105, 45);
  u8g2.setFont(u8g2_font_crox1h_tf);
  u8g2.print("km/h");
}*/

void clignoG() {  //affichage clignoG

  u8g2.drawTriangle(3, 13, 22, 4, 22, 22);
}
void clignoD() {  //affichage clignoD

  u8g2.drawTriangle(126, 13, 107, 4, 107, 22);
}

void Phare() {  //affichage plein phare

  u8g2.drawCircle(80, 14, 12);
}

void pleinPhare() {  //affichage plein phare

  u8g2.drawDisc(80, 14, 10);
  u8g2.drawCircle(80, 14, 12);
}

void Bat() {  //affichage batterie

  u8g2.setCursor(33, 8);
  u8g2.setFont(u8g2_font_crox2h_tf);
  u8g2.print("XX");

  u8g2.setCursor(50, 8);
  u8g2.setFont(u8g2_font_crox2h_tf);
  u8g2.print("%");
}

void distTot() {  //affichage km tot
  u8g2.setCursor(0, 30);
  u8g2.setFont(u8g2_font_crox1h_tf);
  u8g2.print("XXXX");

  u8g2.setCursor(30, 30);
  u8g2.setFont(u8g2_font_crox1h_tf);
  u8g2.print("km");
}

void distTrip() {  //affichage km trip

  u8g2.setCursor(12, 45);
  u8g2.setFont(u8g2_font_crox1h_tf);
  u8g2.print("xxx");

  u8g2.setCursor(30, 45);
  u8g2.setFont(u8g2_font_crox1h_tf);
  u8g2.print("km");
}


void setup(void) {
  Serial.begin(57600);  //Init com avec arduino (Serial monitor)

  Serial3.begin(9600);  //Begin serial communication Neo6mGPS

  u8g2.begin();

  u8g2_prepare();

  // setup capteur effet Hall

  pinMode(DetectStart, INPUT);
  pinMode(Start, OUTPUT);
  pinMode(StandBy, OUTPUT);
  Serial.println("init capteur effet hall");

  //setup clignotants
  pinMode(InClignoG, INPUT);
  pinMode(InClignoD, INPUT);
  pinMode(OutClignoG, OUTPUT);
  pinMode(OutclignoD, OUTPUT);
  Serial.println("init clignotants");

  //setup plein phare
  pinMode(InPleinPhare, INPUT);
  pinMode(OutPleinPhare, OUTPUT);
  Serial.println("init plein phare");

  // Initialisation du DHT22;
  dht.begin();
  Serial.println("initpilotage ventilateurs");
  // déclartion startvent = sortie
  pinMode(Startvent, OUTPUT);
}

void loop(void) {
  u8g2.clearBuffer();
  u8g2_prepare();


  // routine Start_Hall
  int detectedSH = digitalRead(DetectStart);  // lecture capteur effet hall --- attention capteur en NF

  boolean startStatus = false;

  if (detectedSH == HIGH) {
    digitalWrite(Start, HIGH);   //25 pour mega
    digitalWrite(StandBy, LOW);  //24 pour mega
    Serial.println("Stand-By");
    startScreen();  // affiche l'écran de démarrage
    u8g2.sendBuffer();
    delay(3000);

  } else {
    digitalWrite(Start, LOW);  //relay pilotage antiSpark
    digitalWrite(StandBy, HIGH);
    Serial.println("Accroche toi mon petit !");
    startStatus = true;
  }
  
  u8g2.clearBuffer();
  u8g2_prepare();

  if (startStatus == true) {
    //on commence par check température pour ventillo -> ok, à vérifier prod ok
    //on déclanche le GPS -> ok, à vérifier prod ok
    // on active le pont diviseur de tenstion -> à tester avant
    //on déclanche les km -> à tester avant
    //on active les clignos
    //on active les pleins phares

    //routine DHT22
    // Lecture des données
    float tauxHumidite = dht.readHumidity();             // Lecture du taux d'humidité (en %)
    float temperatureEnCelsius = dht.readTemperature();  // Lecture de la température, exprimée en degrés Celsius

    // Vérification si données bien reçues
    if (/*(isnan(tauxHumidite) ||*/ isnan(temperatureEnCelsius)) {  //isnan détermine si la valeur et NAN
      Serial.println("Aucune valeur retournée par le DHT22. Est-il bien branché ?");
      delay(2000);
      return;  // Si aucune valeur n'a été reçue par l'Arduino, on attend 2 secondes, puis on redémarre la fonction loop()
    }

    // Calcul de la température ressentie
    float temperatureRessentieEnCelsius = dht.computeHeatIndex(temperatureEnCelsius, tauxHumidite, false);  // Le "false" est là pour dire qu'on travaille en °C, et non en °F

    // Affichage des valeurs
    Serial.print("Température ressentie = ");
    Serial.print(temperatureRessentieEnCelsius);
    Serial.println(" °C");
    Serial.println();
    if (temperatureRessentieEnCelsius >= 26) {
      digitalWrite(Startvent, LOW);  //low car ce relay se pilote à l'envers
      Serial.println("ca ventille...");
    } else {
      digitalWrite(Startvent, HIGH);
      Serial.println("ca ne ventille pas...");
    }

    // Temporisation de 60 secondes (pour rappel : il ne faut pas essayer de faire plus d'1 lecture toutes les 2 secondes, avec le DHT22, selon le fabricant)
    delay(60000);
  }

  // routine GPS calcul de vitesse
  boolean newData = false;
  for (unsigned long start = millis(); millis() - start < 1000;) {
    while (Serial3.available()) {
      if (gps.encode(Serial3.read())) {
        newData = true;
      }
    }
  }

  //If newData is true
  if (newData == true) {
    newData = false;
    print_speed();
  } else {
    u8g2.clearBuffer();
    u8g2.setCursor(0, 0);
    //u8g2.setScale(3);
    u8g2.print("No Data");
    u8g2.sendBuffer();
  }
}

void print_speed() {
  //u8g2.clearBuffer();

  /*
  // affichage heure
      // Hour (0-23) (u8)
      Serial.print("Hour = "); 
      Serial.println(gps.time.hour()); 
      // Minute (0-59) (u8)
      Serial.print("Minute = "); 
      Serial.println(gps.time.minute()); 
      // Second (0-59) (u8)
      Serial.print("Second = "); 
      Serial.println(gps.time.second()); 
  */

  //affichage nde sat
  if (gps.location.isValid() == 1) {
    u8g2.setCursor(0, 0);
    u8g2.setFont(u8g2_font_crox1h_tf);
    u8g2.print("SAT:");
    u8g2.setCursor(25, 0);
    u8g2.setFont(u8g2_font_crox1h_tf);
    u8g2.print(gps.satellites.value());
  }
  //affichage vitesse
  if (gps.location.isValid() == 1) {
    String gps_speed = String(gps.speed.kmph());
    (gps.speed.kmph() <= 4);  // on n'affiche pas en dessous de 4km/h
    u8g2.setCursor(70, 37);
    u8g2.setFont(u8g2_font_crox5h_tf);
    u8g2.print("00");

    u8g2.setCursor(105, 45);
    u8g2.setFont(u8g2_font_crox1h_tf);
    u8g2.print("km/h");

  } else {
    String gps_speed = String(gps.speed.kmph());

    u8g2.setCursor(70, 37);
    u8g2.setFont(u8g2_font_crox5h_tf);
    u8g2.print(round(gps.speed.kmph()));  // affichage de la vitesse avec un arrondi

    u8g2.setCursor(105, 45);
    u8g2.setFont(u8g2_font_crox1h_tf);
    u8g2.print("km/h");
  }

  u8g2.sendBuffer();

  // routine clignotants
  int detectedClG = digitalRead(InClignoG);  //lecture cligno G
  if (detectedClG == HIGH) {
    digitalWrite(OutClignoG, HIGH);  // clignotement à gauche
    digitalWrite(OutclignoD, LOW);
    delay(500);
    digitalWrite(OutClignoG, LOW);
    digitalWrite(OutclignoD, LOW);
    delay(500);
    Serial.println("Gauche");
    clignoG();  //definir la position de cette portion de code dans le prog, idem pour clignoD
  } else {
    int detectedClD = digitalRead(InClignoD);  // lecture cligno D
    if (detectedClD == HIGH) {
      digitalWrite(OutClignoG, LOW);
      digitalWrite(OutclignoD, HIGH);  //Clignotement à droite
      delay(500);
      digitalWrite(OutClignoG, LOW);
      digitalWrite(OutclignoD, LOW);
      delay(500);
      Serial.println("Droite");
      clignoD();
    } else {
      digitalWrite(OutClignoG, LOW);
      digitalWrite(OutclignoD, LOW);
      delay(500);
    }

    // routine plein phare
    int detectedPH = digitalRead(InPleinPhare);  // lecture entrée plein phare

    if (detectedPH == HIGH) {
      digitalWrite(OutPleinPhare, HIGH);  // allumage plein phare
      Serial.println("AAHHH mes yeux !!!");
      pleinPhare();
    } else {
      digitalWrite(OutPleinPhare, LOW);
      Serial.println("j y vois rien :(");
      Phare();
    }
  }

  Bat();
  distTot();
  distTrip();


  //u8g2.sendBuffer();
}

merci pour votre temps et vos conseils

si vous voulez avoir une chance que ça fonctionne correctement il faut absolument virer tout ce qui arrête le code, ceci par exemple bloque le code tout une minute...

le clignotant ne peut pas arrêter le code avec des délais sinon rien d'autre ne sera géré...

c'est typiquement une définition de programme qui se prête bien à la programmation par machine à états ➜ cf mon tuto éventuellement. Il faudra dessiner le diagramme d'états et les évènements que vous souhaitez gérer

merci pour le tutoriel, il y a pas mal d'infos, je vais le potasser.

Une autre personne a qui j'en ai parlé (un de mes voisins) ma conseillé de sortir tous les delay() possible et de les changer par des millis().

Je pars du principe que fonctionner par états de m'absous pas de ce prerequis. est-ce que je me trompe?

l'approche par machine à état va justement vous conduire à une structure de code où ces délais ne sont plus nécessaires et effectivement l'usage de millis() vous permet de conditionner les actions et déclencher des comportements : le temps qui s'écoule est un évènement comme un autre (par exemple comme une t° trop élevée) qui déclenche des actions ou changement d'état.

Exactement, pour avoir un affichage fluide et un programme réactif il faut proscrire delay().

Je ne sais pas ou j'ai mis les pieds...

ok pas de problème, je vais commencer par tout mettre par écrit, c'est je pense ce qui sera le plus utile pour reprendre le sujet :smiley:

Oui, c'est ce qu'il faut chercher à faire, par contre il ne suffit pas de changer l'appel de la fonction delay par millis, il faut changer aussi la structure de ton programme.
C'est pour ça que l'on conseille de partir des le début avec une machine à état, mais cela réclame souvent pas mal de temps de changer la structuration de ton code.
surtout lorsque l'on a déjà écris pas mal de code.

le début d'une grande aventure :slight_smile:

oui mais c'est quand même tres souvent plus court (surtout quand on débute) de faire table rase et de partir direct sur des bonnes bases...

Oui et non,
Quand tu commence les machines à état ne sont pas toujours simple à appréhender et très peu souvent utiliser des les bouts d'exemple ou tutoriel que tu trouves.
Donc en plus de l'apprentissage du langage de programmation, tu dois appréhender une notion de programmation pas si intuitive.

Salut !
Si tu ne sais pas comment utiliser la bibliothèque OneButton utilisée dans le tuto sur les machines à état de @J-M-L , tu peux regarder ici : Tuto bibliothèque OneButton

PS : j'ai mis près d'un mois et demi à comprendre les machines à état, mais maintenant j'en mets partout ! :grin:

Bonne bidouille

Amitiés
R-P7

merci à tous pour ces infos.

je n'ai pas encore fini de tout comprendre mais bon...

si je cherche à décrire le programme en machine à états, je constate que j'ai certaines fonctions qui vont fonctionner en trame de fond, comme le GPS ou le capteur de température et d'autre qui seront potentiellement imbriquées, les clignotants, le feu stop et les pleins phares.

sans trop rentrer dans le détail des actions je tombe sur un diagramme de transition de ce type:
(je n'ai pas nommé mes transitions, plus pour faire simple que par fainéantise)

est-ce que çà vous semble cohérent ou est que je n'ai pas saisi le propos?

@pandaroux007 je n'ai pas bien encore compris comment je peux utiliser la bibliothèque OneButton, peux-tu m'éclairer sur ce point stp?

Pourquoi ne pas avoir plusieurs machines à états?
Tu vois bien que les clignotant, les stops et les phares peuvent fonctionner en même temps mais indépendamment les uns des autres.
D'ailleurs entre-nous, les phares et les stops tu crois vraiment qu'il y a besoin d'une machine à états pour les faire fonctionner?
Pourquoi ajouter de la complication à des choses simples?

Une machine à états a du sens si une suite d'actions doit s'enchaîner automatiquement ou si l'enchaînement entre les différentes actions et séquencée par des événements extérieurs. Mais au centre de tout ça il y a une logique, les événements sont liés ou bien le (ou les) actionneur(s) travaillent de concert.
Si les événements sont dé-corrélés et les actionneurs indépendants, il est préférable d'avoir plusieurs machines distinctes.
L'idée après c'est que loop() balaye toutes les machines à états en séquence.

Nous en venons au fond du pb, mon manque de compréhension de l'ensemble :smiley:

En toute franchise je suis ok avec toi, et mon programme initial fait semblant de fonctionner si je n'utilise pas d'écran.

C'est parce que j'utilise un écran que je me suis rendu compte que mon programme était mal fichu.

Sur le principe, je n'ai pas de pbs pour avoir plusieurs machines à états, mais plus parce que je ne perçois pas réellement les implications que cela va entrainer...

Pour chaque fonctions, indépendamment les unes des autres, mes systèmes n'ont que 3 états maxi.

J'ai supposé qu'il fallait décrire l'ensemble des combinaisons (et heureusement qu'on parle d'une meule et pas d'une fusée), c'est pour cela que je n'ai pas inclus les trames de fond.

Manifestement, j'ai mal supposé :slight_smile:

Oui et non, tu ne dois pas décrire l'ensemble des combinaison, mais l'ensemble des états.
Si on veut caricaturer, à chaque fois que tu utilise un délais, c'est que tu as un état.
le clignotant peut être considéré comme une machine, certes simpliste, mais qui possède les états inactif, actif_off, actif_on, les traits entre tes états étant alors ce qui agit sur ton clignotant, actionneur ou "timer".

en fait il faut définir qu'est-ce que le système. Parfois vous avez des systèmes indépendants et donc ils ont chacun leur machine à état.

si les clignotants ne dépendent de rien d'autre que leur bouton, aucune raison qu'ils soient gérés dans le même système que le GPS.

Salut !
tu peux l'utiliser si tu as des boutons dans ton projet, pour simplifier leur utilisation, mais aussi si tu veux seulement tester les exemples du tutos de @J-M-L pour bien comprendre les machines à état (tu peux utiliser Wokwi :wink:)

Cordialement
R-P7

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.