Appui sur un bouton long à la détente

Bonjour,

Dans mon programme, pour étalonner mon capteur j'utilise 3 boutons poussoir (branchés en pull up interne) : l'appui sur le 1er bouton (ENTER) pour lancer la procédure est parfois long pour que le programme réagisse alors que les appuis suivants (sur CAL puis EXIT) se fait très rapidement.

void etalonnage(byte valtampon) //fonction qui lance la procédure d'étalonnage du capteur
{

  // on teste si le bouton ENTER est pressé
  etatBoutonEnter = digitalRead(pinBoutonEnter);  //si enter est presse etatBoutonEnter=LOW=0 car pin en mode pull up interne
  if (etatBoutonEnter == LOW )  //si le bouton était précédemment non appuyé alors lastBoutonEnter=1=HIGH
  {
    testBoutonEnter = 1;
    float resultatvoltage = 0.0;
    for (int i = 0; i < cycles; i++)
    {
      voltage = analogRead(PH_PIN) / 1024.0 * 5000; // read the voltage
      resultatvoltage += voltage;
    }
    resultatvoltage /= cycles;
    phValue = ph.readPH(resultatvoltage, temperature); // convert voltage to pH with temperature compensation
    ph.calibration(resultatvoltage, temperature, Enter); // on envoie la commande ENTEREC au module pour commencer la calibration
    lcd.backlight();
    lcd.clear();
    
    affiche_texte_lcd(5, 0, "ETALONNAGE");
    affiche_texte_lcd(0, 2, "CAL pour valider    ");

    if (lastBoutonCal == HIGH)   // on  teste si Cal n'est pas appuyé après que le bouton Enter ait été appuyé
      while (testBoutonEnter == 1)
      { // on teste si le bouton CAL est pressé --> si oui, on valide l'étalonnage
        etatBoutonCal = digitalRead(pinBoutonCal);  //on lit l'état du bouton Cal
        if (etatBoutonCal == 0) //si le bouton Cal est pressé alors etatBoutonCal=LOW=0 car pin en mode pull up interne
        {
          //lcd.backlight();
          lcd.clear();
          ph.calibration(voltage, temperature, Cal); // calibration process by Serail CMD
          //lcd.backlight();
          affiche_texte_lcd(0, 0, "->Etalonnage reussi ");
          affiche_texte_lcd(0, 1, "EXIT pour sauvegarde");
          affiche_texte_lcd(0, 2, "et pour quitter     ");
          testBoutonCal = 1;
        }
        if (lastBoutonExit == HIGH)   // Appui sur Exit après appui sur Enter
          while (testBoutonCal == 1)
          {
            etatBoutonExit = digitalRead(pinBoutonExit);
            if (etatBoutonExit == 0)
            {
              //lcd.backlight();
              lcd.clear();
              ph.calibration(voltage, temperature, Exit); // calibration process by Serail CMD
              //lcd.backlight();
              affiche_texte_lcd(0, 0, "->Etalonnage termine");
              delay(1000);
              lcd.clear();
              testBoutonEnter = 0;
              testBoutonCal = 0;
              testEtalonnage = 1;
              break;
            }
          }
      }
  }
  else
  {
    etatBoutonCal = digitalRead(pinBoutonCal);
    etatBoutonExit = digitalRead(pinBoutonExit);
    if (etatBoutonExit > lastBoutonExit && etatBoutonCal == 0)
    {
      ph.calibration(voltage, temperature, Exit); // calibration process by Serail CMD
      //lcd.backlight();
      lcd.clear();
      affiche_texte_lcd(0, 0, "->Etalonnage arrete");
      delay(2000);
      lcd.clear();
    }
  }
  lastBoutonEnter = etatBoutonEnter;
  lastBoutonCal  = etatBoutonCal;
  lastBoutonExit = etatBoutonExit;
}

Existe-t-il un moyen pour améliorer la prise en compte de ce premier appui sur le bouton ENTER ?

Merci d'avance

Pour que les boutons soient pris en compte rapidement il ne faut aucun délai significatif dans la loop().

La programmation par machine à états (cf mon tuto éventuellement) permet de structurer le code pour prendre cela en compte. là c'est un peu spaghetti...

Pour simplifier votre gestion des boutons, éventuellement utilisez la librairie Button dans easyRun de @bricoleau

ça pourrait donner un truc comme cela:

#include <easyRun.h>      // bibliothèque de @bricoleau https://github.com/bricoleau/easyRun

const byte pinEnter = 2;
const byte pinOK = 3;
const byte pinCancel = 4;
const byte phPin = A0;

float phValue;

button boutonEnter(pinEnter);
button boutonOK(pinOK);
button boutonCancel(pinCancel);

enum : uint8_t {DEBUT, ATTENTE, CONFIRMATION, PRET} etatCalibration = DEBUT;

void calibrage() {
  easyRun(); // met à jour l'état des boutons
  switch (etatCalibration) {
    case DEBUT:
      Serial.println(F("Appuyez Enter pour calibrer"));
      etatCalibration = ATTENTE;
      break;

    case ATTENTE:
      if (boutonEnter) {
        Serial.println(F("DEBUT DE CALIBRATION."));
        phValue = random(-110, 1501) / 100.0; // une valeur entre -1.1 (Acide chlorhydrique à 37 %m) et 15,0 (Soude saturée)
        Serial.print(F("Valeur du pH = ")); Serial.println(phValue);
        Serial.println(F("Appuyez OK pour valider, CANCEL pour annuler."));
        etatCalibration = CONFIRMATION;
      }
      break;

    case CONFIRMATION:
      if (boutonOK) {
        Serial.println(F("Calibration terminée."));
        Serial.print(F("pH Retenu = ")); Serial.println(phValue);
        etatCalibration = PRET;
      } else if (boutonCancel) {
        Serial.println(F("Calibration abandonnée."));
        etatCalibration = DEBUT;
      }
      break;
    case PRET: break;
  }
}

void setup() {
  Serial.begin(115200); Serial.println();

}

void loop() {
  if (etatCalibration != PRET) calibrage();
  else {
    //... le code une fois calibré
  }
}

avec des boutons en 2, 3, et 4

J'ai commencé à regarder la proposition : je comprends le principe. En pratique, ça coince un peu, en fait dès le début car l'appui sur un bouton ne me renvoie rien.

La commande if (boutonEnter) test l'appui du bouton ENTER je suppose sauf que dans mon cas, le bouton est branché en pull-up interne : comment on le spécifie ? J'ai cherché dans la bibliothèque, je ne trouve pas.
Merci

La commande standard est
pinMode (pin, INPUT_PULLUP);

Il semble que ce soit le choix par défaut dans la méthode init de button.h, qui est incluse dans easyRun.h :

//Buttons
#include "utility/button.h"

et
inline void init() const {pinMode(pin, INPUT_PULLUP);}

La déclaration de l’instance met la pin en INPUT PULLUP

je vais donc me repencher sur les erreurs que j'ai pu commettre !

Bonjour,

J'ai réussi à trouver mon erreur : désormais, j'arrive bien à naviguer entre mes menus à l'aide des boutons. Par contre j'ai un souci de boucle que je n'arrive pas à comprendre.

Dans ma boucle loop, la fonction cinetique n'est, selon moi, lancée que lorsque la variable etatInitialisation vaut PRET.
Quand je choisis le "parcours" de la variable etatInitialisation suivant : DEBUT -> MENU-> CINETIQUE -> ATTENTE -> ETALONNAGE -> CONFIRMATION -> ENREGISTREMENT -> ETALONNAGE -> CONFIRMATION -> ENREGISTREMENT -> VALIDATION, il me démarre la fonction cinetique en même temps que l'affichage lié à l'état VALIDATION sans pourtant être passé au préalable à l'état PRET ce qui me laisse perplexe.

Quelqu'un a-t-il une aide à m'apporter ?

Merci d'avance

Mon programme (j'ai oté toute la partie d'affichage de texte pour ne pas trop alourdir !) :

//IMPORT DES BIBLIOTHEQUES UTILISEES
#include "DFRobot_PH.h"         //module conductimétrique
#include <EEPROM.h>             //module de la mémoire pour stocker les mesures du pH à court terme
#include <Wire.h>               //module wire pour la communication avec le protocole I2C 
#include<LiquidCrystal_I2C.h>   //bibliothèque pour l'écran lcd connecté en I2C
#include <OneWire.h>            //module one wire pour la mesure de température avec sonde DS18B0 
#include <DallasTemperature.h>  //
#include <easyRun.h>      // bibliothèque de @bricoleau https://github.com/bricoleau/easyRun

//Définition des connexions utilisées sur la carte Arduino
#define PH_PIN A2           //sonde conductimétrique
#define pinBoutonEnter  2   //bouton ENTER
#define pinBoutonCal  4     //bouton CAL
#define pinBoutonExit  7    //bouton EXIT
#define ONE_WIRE_BUS  12    //broche de connexion du capteurs température DS18B20


//DEFINITION DES VARIABLES UTILISEES PAR LE CAPTEUR DE CONDUCTIVITE (voir bibliothèque associée)
float voltage, phValue, temperature = 25;

//SIMPLIFICATION DES APPELS AU MODULE DE CONDUCTIVITE ET A L'ECRAN LCD
DFRobot_PH ph;
LiquidCrystal_I2C lcd(0x27, 20, 4);

boolean testEtalonnage = 0; //variable pour savoir si l'etalonnage est fini

button boutonEnter(pinBoutonEnter, true);
button boutonCal(pinBoutonCal, true);
button boutonExit(pinBoutonExit, true);



byte choixMode; //variable pour connaitre le mode de mesure (mesure simple, dosage, suivi cinétique)

// GESTION DES ETATS
enum : uint8_t {DEBUT, MENU, SIMPLE, DOSAGE, CINETIQUE, PREPA,  ATTENTE, ETALONNAGE, ANNULATION, CONFIRMATION, ENREGISTREMENT, VALIDATION, PRET} etatInitialisation = DEBUT; //pour le choix du mode
enum t_etat : byte {PERIODE, DUREE, GO} etat = PERIODE; //pour le parametrage de l'acquisition

int nbCar ; //nombre de caractere de la variable affichée sur le lcd


unsigned long tempsZero;
unsigned long tempsCourant;
unsigned long tempsPrec;

float resultatph;

void setup()
{
  //initialisation de la liaison série, du module pH et du lcd et du capteur de température
  Serial.begin(250000);
  ph.begin();
  lcd.init(); // initialisation de l'afficheur
  temp.begin();

  lcd.backlight();
}

void loop()
{
  if (etatInitialisation != PRET)
  {
    initialisation();
  }

  else
  {
    switch (choixMode)
    {
      case 1: //mode de mesure simple
        mesure_simple();
        break;

      case 2: //mode de mesure dosage
        dosage();
        break;

      case 3:
        cinetique();
        break;
    }
  }
}


void initialisation() {
  easyRun();
  switch (etatInitialisation) {
    case DEBUT:
      etatInitialisation = MENU;
      break;

    case MENU:
      if (boutonExit)
      {
        etatInitialisation = SIMPLE;
        lcd.clear();
      }
      else if (boutonCal)
      {
        etatInitialisation = DOSAGE;
        lcd.clear();
      }
      else if (boutonEnter)
      {
        etatInitialisation = CINETIQUE;
        lcd.clear();
      }
      break;

    case SIMPLE:
      choixMode = 1; //mode simple
      if (boutonEnter)
      {
        etatInitialisation = ATTENTE;
        lcd.clear();
      }
      else if (boutonExit)
      {
        etatInitialisation = DEBUT;
        lcd.clear();
      }
      break;

    case DOSAGE:
      choixMode = 2; //mode dosage
      if (boutonEnter)
      {
        etatInitialisation = ATTENTE;
        lcd.clear();
        ouverture_moniteur();
        lcd.clear();
      }
      else if (boutonExit)
      {
        etatInitialisation = DEBUT;
        lcd.clear();
      }
      break;


    case CINETIQUE:
      choixMode = 3; //mode cinétique
      if (boutonEnter)
      {
        etatInitialisation = ATTENTE;
        lcd.clear();
        ouverture_moniteur();
        lcd.clear();
      }
      else if (boutonExit)
      {
        etatInitialisation = DEBUT;
        lcd.clear();
      }
      break;

    case ATTENTE:
      if (boutonCal)
      {
        etatInitialisation = ETALONNAGE;
        lcd.clear();
      }
      else if (boutonExit)
      {
        etatInitialisation = PREPA;
        lcd.clear();
      }
      break;


    case ETALONNAGE:
      if (testEtalonnage == 0)
        affiche_texte_lcd(15, 2, "7");
      else
        affiche_texte_lcd(15, 2, "4");
      affiche_texte_lcd(0, 3, "Appuyer CAL si OK");
      if (boutonCal)
      {
        etalonnage(7);
        if (testEtalonnage == 1)
          testEtalonnage = 0;
        else testEtalonnage = 1;
        etatInitialisation = CONFIRMATION;
        lcd.clear();
      }
      break;


    case CONFIRMATION:
      ph.calibration(voltage, temperature, Cal); // calibration process by Serail CMD
      if (boutonExit)
      {
        etatInitialisation = ENREGISTREMENT;
        lcd.clear();
      }
      break;

    case ENREGISTREMENT:
      ph.calibration(voltage, temperature, Exit); // calibration process by Serail CMD
      //lcd.backlight();
      affiche_texte_lcd(0, 0, "->Etalonnage termine");
      delay(1000);
      if (testEtalonnage)
      {
        etatInitialisation = ETALONNAGE;
        lcd.clear();
      }
      else
      {
        etatInitialisation = VALIDATION;
        lcd.clear();
      }
      break;

    case VALIDATION:
      mesure_simple();
      affiche_texte_lcd(0, 0, "Etalonnage bon ?");
      affiche_texte_lcd(0, 1, "oui --> ENTER");
      affiche_texte_lcd(0, 2, "non --> CAL");
      if (boutonCal)
      {
        etatInitialisation = ETALONNAGE;
        testEtalonnage = 0;
        lcd.clear();
      }
      else if (boutonEnter)
      {
        etatInitialisation = PREPA;
        lcd.clear();
      }
      break;

    case PREPA:
      switch (choixMode)
      {
        case 1: //mode de mesure simple
          etatInitialisation = PRET;
          break;

        case 3:
          lcd.clear();
          menuPeriode();

          while (true) {
            if (parametrage()) {
              Serial.print(F("t"));  // donne la date en seconde
              Serial.print('\t');
              Serial.println(F("pH")); // donne le pH
              Serial.print(F("s"));  // donne la date en seconde
              Serial.print('\t');
              Serial.println(F("")); // donne la valeur recalculée de la tension
              break;
            }
          }
          lcd.clear();
          tempsZero = millis();
          tempsPrec = 0;
          etatInitialisation = PRET;
          break;
      }

    case PRET:
      break;
  }

}


void etalonnage(byte valtampon)
{
  float resultatvoltage = 0.0;
  for (int i = 0; i < cycles; i++)
  {
    voltage = analogRead(PH_PIN) / 1024.0 * 5000; // read the voltage
    resultatvoltage += voltage;
  }
  resultatvoltage /= cycles;
  phValue = ph.readPH(resultatvoltage, temperature); // convert voltage to pH with temperature compensation
  ph.calibration(resultatvoltage, temperature, Enter); // on envoie la commande ENTEREC au module pour commencer la calibration
  affiche_texte_lcd(0, 2, "CAL pour valider    ");
}



void menuPeriode()
{
  Serial.println(F("Saisir la période d'échantillonnage (en s) : "));
  etat = PERIODE;
}

void menuDuree()
{
  Serial.println(F("Saisir la durée totale d'acquisition (en s) : "));
  Serial.println(F("Saisir 0 pour revenir au menu précédent (choix période d'échantillonnage)\n"));
  etat = DUREE;
}

void menuGo()
{
  Serial.println(F("Saisir S pour démarrer l'acquisition : \n"));
  etat = GO;
}

bool parametrage()
{
  bool gestionTerminee = false;
  switch (etat) {
    case PERIODE:
      if (messageDisponible()) { // si l'utilisteur a validé
        if (!strcmp(message, "0")) { // a-t-il tapé 0 ?
          //menuCalibre();
        } else {
          Serial.print(F("Période d'échantillonnage saisie : ")); Serial.print(message); Serial.println(F(" s\n"));
          timeRes = 1000UL * atoi(message);
          delay(2000);
          menuDuree();
        }
      }
      break;

    case DUREE:
      if (messageDisponible()) { // si l'utilisteur a validé
        if (!strcmp(message, "0")) { // a-t-il tapé 0 ?
          menuPeriode();
        } else {
          Serial.print(F("Durée d'acquisition saisie : ")); Serial.print(message); Serial.println(F(" s\n"));
          dureeAcquisition = 1000UL * atoi(message);
          nbrPoints = dureeAcquisition / timeRes;
          delay(2000);
          menuGo();
        }
      }
      break;

    case GO:
      if (messageDisponible()) { // si l'utilisteur a validé

        gestionTerminee = true;
        //menuCalibre();
      }
      break;
  }
  return gestionTerminee;
}


void affiche_echantillonnage()
{
  Serial.println(F("Saisir la période d'échantillonnage (en s) : "));
  Serial.println(F("Saisir 0 pour revenir au menu précédent (choix calibre)\n"));
}


void affiche_duree()
{
  Serial.println("Saisir la durée totale d'acquisition (en s) : ");
  Serial.println(F("Saisir 0 pour revenir au menu précédent (choix période d'échantillonnage)\n"));
}

void mesure_simple()
{
  resultatph = lecture();
  affichage_ph();
}


void cinetique()
{

  tempsCourant = millis() - tempsZero;
  affiche_texte_lcd(0, 0, "Chrono : ");
  lcd.setCursor(9, 0);
  int longueur = lcd.print(tempsCourant / 1000);
  affiche_texte_lcd(10 + longueur, 0, " s");

  if ((tempsCourant >= tempsPrec) && (cpt <= nbrPoints + 1))
  {
    resultatph = lecture();
    affichage_ph();
    cpt++;
  }

  if (cpt == (nbrPoints + 2))
  {
    testFinAcquisition = 1; //l'acquisition est finie
    resultatph = lecture();
    affichage_ph();
  }
}

float lecture() //fonction de lecture des données par le capteur
{
  resultatph = 0.0;
  for (int i = 0; i < cycles; i++) //on boucle pour prendre plusieurs valeurs et améliorer la précision
  {
    voltage = analogRead(PH_PIN) / 1024.0 * 5000; // lecture de la tension sur la borne de la cellule conductimétrique
    phValue = ph.readPH(voltage, temperature); // conversion de la tension en conductivité avec prise en compte de la température
    resultatph += phValue;  //on cumule les valeurs pour le calcul de la moyenne
  }

  resultatph /= cycles;  //on divise la somme des conductivités par le nombre de cycles pour obtenir la valeur moyenne
  return resultatph;
}

void affichage_ph()
{
  switch (choixMode)
  {
    case 1:
      affiche_texte_lcd(0, 3, "pH = ");
      lcd.setCursor(5, 3);  //on se place sur la 3ème ligne, 8ème caractère de l'afficheur lcd
      lcd.print(resultatph, 2); //donne le pH avec 2 chiffres après la virgule
      break;


    case 3:
      if (testFinAcquisition == 0)
      {
        // Envoie les données sur moniteur série si pas la fin d'acquisition
        Serial.print(tempsCourant / 1000); // donne la date en seconde
        Serial.print('\t');
        Serial.println(resultatph, 2); // donne le pH avec 2 chiffres après la virgule
        affiche_texte_lcd(0, 3, "pH = ");
        lcd.setCursor(5, 3);  //on se place sur la 3ème ligne, 8ème caractère de l'afficheur lcd
        lcd.print(resultatph, 2); //donne le pH avec 2 chiffres après la virgule
      }

      tempsPrec += timeRes;
      Serial.println("dernierrem esure");
      affiche_texte_lcd(0, 1, "Derniere mesure :");
      affiche_texte_lcd(0, 2, "t = ");
      lcd.print(tempsCourant / 1000);
      affiche_texte_lcd(8, 2, "s");
      affiche_texte_lcd(0, 3, "pH = ");
      lcd.setCursor(5, 3);  //on se place sur la 3ème ligne, 8ème caractère de l'afficheur lcd
      lcd.print(resultatph, 2); //on affiche la moyenne avec 2 chiffre après la virgule
      break;
  }
}



void affiche_texte_lcd(byte caractere, byte ligne, const char* texte)
{
  lcd.setCursor(caractere, ligne);
  lcd.print(texte);
}

void ouverture_moniteur()
{
  //Affichage des manipulations préalable à faire avant toute mesure
  while (testBoutonEnter == 0)
  {
    affiche_texte_lcd(0, 0, "Ouvrir le moniteur");
    affiche_texte_lcd(0, 1, "serie dans Arduino");
    affiche_texte_lcd(0, 2, "Appuyer sur bouton");
    affiche_texte_lcd(0, 3, "ENTER si c'est ok");
    etatBoutonEnter = digitalRead(pinBoutonEnter);  //si enter est presse etatBoutonEnter=LOW=0 car pin en mode pull up interne
    if (etatBoutonEnter == LOW)  //si le bouton était précédemment non appuyé alors lastBoutonEnter=1=HIGH
    {
      testBoutonEnter = 1;
    }
  }

  testBoutonEnter = 0;
  lcd.clear();


  while (testBoutonEnter == 0)
  {
    affiche_texte_lcd(0, 0, "Verifier le flux de");
    affiche_texte_lcd(0, 1, "la liaison serie :");
    lcd.setCursor(0, 2);
    lcd.print(flux);
    affiche_texte_lcd(7, 2, "bauds");
    affiche_texte_lcd(0, 3, "ENTER si c'est ok");
    delay(100);
    etatBoutonEnter = digitalRead(pinBoutonEnter);  //si enter est presse etatBoutonEnter=LOW=0 car pin en mode pull up interne
    if (etatBoutonEnter == LOW)  //si le bouton était précédemment non appuyé alors lastBoutonEnter=1=HIGH
    {
      testBoutonEnter = 1;
    }
  }
  testBoutonEnter = 0;
  lcd.clear();

  while (testBoutonEnter == 0)
  {
    affiche_texte_lcd(0, 0, "Verifier que la list");
    affiche_texte_lcd(0, 1, "deroulante est sur");
    affiche_texte_lcd(0, 2, "'les deux, NL et CR'");
    affiche_texte_lcd(0, 3, "ENTER si c'est ok");
    delay(100);
    etatBoutonEnter = digitalRead(pinBoutonEnter);  //si enter est presse etatBoutonEnter=LOW=0 car pin en mode pull up interne
    if (etatBoutonEnter == LOW)  //si le bouton était précédemment non appuyé alors lastBoutonEnter=1=HIGH
    {
      testBoutonEnter = 1;
    }
  }

}