Publié le 24 février 2023 - par

Un chronomètre au 1/1000 de seconde avec Arduino UNO – Ecran LCD1602

Pour un projet que j’ai présenté à Tech Inn’Vitré j’ai préparé un chronomètre à base d’Arduino pour mesurer le temps de parcours d’un robot CrowBot Bolt sur une piste. La piste est entourée de cornières en aluminium qui ont servi à clipser les switch de départ et d’arrivée. J’avais un écran LCD de chez SainSmart que j’ai utilisé pour l’occasion.

Un chronomètre au 1/1000 de seconde avec Arduino

Le cahier des charges

L’idée c’était de créer une piste pour un Rover CrowBot Bolt, que je vous ai présenté il y a quelque temps. La piste comporte des obstacles et une trace qui guide le déplacement du rover. Le joueur utilise la manette de jeu connectée en Bluetooth au rover. Pour connaitre le temps réalisé par le joueur, un chronomètre au 1/1000 de seconde enregistre la performance.

Au départ et à l’arrivée, un switch déclenche et arrête le chronométrage. Au départ on appuie le rover sur le switch et quand il démarre il libère le switch. A l’arrivée, le rover tape sur la plaque et ferme le switch.

L’écran affiche « Positionner le rover », le joueur pose le rover de façon à appuyer sur le switch. L’écran affiche demande alors à l’arbitre de valider. L’arbitre bascule l’interrupteur.Le joueur démarre quand il veut, ce qui démarre le chronométrage, jusqu’à l’arrivée sur la plaque finale.

Si le temps est meilleur que le temps enregistré, il remplace celui-ci dans la mémoire de l’Arduino. Même si on débranche le chronomètre, le meilleur temps est conservé.

L’arbitre remet son interrupteur en position d’attente. On attend le joueur suivant.

Il est possible de remettre le temps au maximum 999999 en appuyant sur une des touches du clavier.

Le matériel utilisé

J’ai utilisé un Arduino UNO que j’avais en stock, avec un écran SainSmart LCD1602 également disponible dans une de mes boîtes (écran 2 lignes de 16 caractères) :

Malheureusement cet écran ne semble plus disponible chez SainSmart mais on doit pouvoir le remplacer par un modèle DFROBOT disponible chez AliExpress pour 4€ environ. Il semble bien identique.

Les switch sont des modèles étanches trouvés sur Amazon, mais tous les switch apparentés fonctionneront. Pourquoi étanche me direz-vous ?

C’est un souvenir de Nantes Maker Campus où le stand framboise314 était en bordure des Nefs des Machines de l’île. Lors d’un gros orage de l’eau avait coulé dans le boîtier des Bouncing Leds… et avait détruit la carte. Du coup maintenant quand je vois marqué « étanche » j’ai tendance à préférer 😀 …

Les impressions 3D

Pour habille tout ce joli monde, j’ai dessiné des systèmes qui se posent sur la bordure du plateau, une cornière en aluminium de près de 2mm d’épaisseur. Pour bloquer les switch j’ai prévu une vis de blocage sur le support de switch.

Voilà le système que j’ai dessiné. Il est démontable car dans le transport le plateau peut recevoir des chocs ou des pressions. On range tout dans une boite et c’est rapidement remonté à l’arrivée.

Ici le chronomètre terminé avec la plaque de l’arrivée.

Les deux ensembles sont identiques, un au départ, l’autre à l’arrivée

Vue de dessus d’un déclencheur

Les supports de switch. Comme les fils sont directement reliés à l’Arduino, ils sont repérés Départ et Arrivée. On voit la vis qui sert à bloquer le support en position sur la cornière.

J’avais déjà le support pour l’Arduino et son écran. J’ai imprimé un support pour les LEDs et le bouton de validation

Un coup d’araldite et les deux parties se sont retrouvées rassemblées…

Il restait juste à imprimer un pied pour supporter l’ensemble et le poser sur la table. Ici le rover est positionné sur le switch de départ. La LED du bas est allumée. L’afficheur demande que l’arbitre valide avec son interrupteur

L’arbitre a validé son interrupteur et a autorisé le départ. La LED haute est allumée et l’écran affiche « PARTEZ ». Lorsque le joueur démarrera, le chrono se déclenchera.

Les fichiers 3D sont disponibles au format  .STL en cliquant sur ce lien. Le boîtier Arduino + écran LCD1602 vient de Thingiverse.

Le programme Arduino

Je vous le livre tel quel… Vous y trouverez comment enregistrer une valeur dans l’EEPROM de l’Arduino (ce qui permet de garder une valeur même si on éteint la carte – C’est ce qui conserve le meilleur score). Attention, l’écran LCD est très gourmand en ressources (il y a de nombreux fils connectés et j’ai du choisir soigneusement les fils utilisés pour mes switchs et interrupteurs pour ne pas perturber le programme. On retrouve ces infos en cherchant un peu sur Internet.

// Programme Arduino Uno pour mesure le temps entre Appui sur switch (départ)
// et switch(arrivée) avec écran LCD Keypad Shield de Sainsmart
// La touche RIGHT du clavier remet le temps mémorisé au maxi 999999

// Objectif : mesurer le temps mis pour effectuer un parcours avec un robot.

/*
  NE PAS UTILISER CES PINS utilisées par l'écran LCD
  The circuit:
 * LCD RS pin to digital pin 8
 * LCD Enable pin to digital pin 9
 * LCD D4 pin to digital pin 4
 * LCD D5 pin to digital pin 5
 * LCD D6 pin to digital pin 6
 * LCD D7 pin to digital pin 7
 * LCD BL pin to digital pin 10
 * KEY pin to analogl pin 0
 */
// Bibliothèque pour gérer l'écran LCD
#include 
// Bibliothèque pour gérer l'EEPROM
#include 
// Adresse de la data dans l'EEPROM
int adresse = 0;

// Connexion de l'écran sur les pins de l'Arduino
LiquidCrystal lcd(8, 13, 9, 4, 5, 6, 7);


// Lecture des boutons poussoirs via une entrée analogique
// Niveaux correspondant aux touches
int adc_key_val[5] ={50, 200, 400, 600, 800 };
int NUM_KEYS = 5;
int adc_key_in;
int key=-1;
int oldkey=-1;
int flag = 0;
int fini = 1; // indique que le parcours est fini


// Quelques variables qui serviront (ou pas)
int start = 1;
int stop = 0;
unsigned long debounce ;
unsigned long tempo ;
unsigned long MS;
unsigned long MS1;
unsigned long MS2;
unsigned long meilleurTemps = 123456;
unsigned long MAXI = 9999999;
int valid = 0; // Validation de la remise au temps maxi
int jeu = 0; 

// Pour convertir les chiffres en chaine de caractère
char buf[16];


// Partie du programme qui se charge d'initialiser 
// Ce qui doit l'être
void setup()
{
  //Initialise la communication série
  Serial.begin(9600);
  lcd.clear();       // Efface l'écran
  lcd.begin(16, 2);  // Déclare une instance de l'écran


  pinMode(3, OUTPUT);  // Déclarer la LED indiquant que le jeu est prêt
  pinMode(12, OUTPUT);  // Déclarer la LED indiquant que l'animateur a autorisé le départ
  
  digitalWrite(3, HIGH); // Allumer la LED
  digitalWrite(12, HIGH); // Allumer la LED
    delay(1000);
  digitalWrite(3, LOW);  // Eteindre la LED pour montrer le démarrage du programme
  digitalWrite(12, LOW);  // Eteindre la LED pour montrer le démarrage du programme
 

  

  pinMode(A5, INPUT_PULLUP);  // Entrée D0 : START
  pinMode(11, INPUT_PULLUP);  // Entrée D1 : STOP
  pinMode(2, INPUT_PULLUP);  // Entrée D2 : Validation par l'animateur (valid quand est à LOW
  

  // Relire contenu de l'EEPROM et le ramener en mémoire
  // Meilleur temps
  EEPROM_readAnything(0, meilleurTemps);
  Serial.print("Meilleur temps: "); 
  Serial.println(meilleurTemps);           // Afficher le meilleur temps sur la console pour vérification

// Effacer deuxième ligne
lcd.setCursor(0,1); 
lcd.print("                "); 

}



// Boucle principale du programme
//======================================================================================
void loop()
{

// Positionner le curseur au début de la ligne 1
lcd.setCursor(0,0); 
lcd.print("A battre        "); 
// Positionner le curseur après le texte
lcd.setCursor(9,0);
//lcd.print("       "); 
// afficher le meilleur temps
lcd.print(meilleurTemps); 


// Allumer la LED PRET si les switch sont bien positionnés)
// c'est une simple indication pour que l'animateur valide le début du jeu 
// Switch de Départ appuyé, switch Arrivée relaché
if ((digitalRead(A5) == LOW) && (digitalRead(11) == HIGH))
{
//    Serial.println("Switches prêts");
    digitalWrite(3, HIGH); // Allumer la LED  PRET
}
else
{
    digitalWrite(3, LOW); // Eteindre la LED  PRET
}

// Validation du jeu par l'animateur
if (digitalRead(2) == LOW)
{
    digitalWrite(12, HIGH); // Allumer la LED  JEU
}
else
{
    digitalWrite(12, LOW); // Eteindre la LED  JEU
}

  
adc_key_in = analogRead(0);    // read the value from the sensor  
 
// Fonctionnement manuel avec les boutons du clavier
// SELECT  démarre le comptage
// LEFT    arrête le comptage
// RIGHT suivi de UP  remet le temps à 9999999 s

key = get_key(adc_key_in);    // convert into key press
  if (key != oldkey)  // if keypress is detected
  {
      //    delay(50);    // wait for debounce time Ne pas utiliser car arrete le programme
      debounce = millis();
      while ((millis() -debounce) <50) { ; } adc_key_in = analogRead(0); // read the value from the sensor key = get_key(adc_key_in); // convert into key press if (key != oldkey) { oldkey = key; Serial.println("Key détectée"); Serial.println(key); delay(1000); } } // On a récupéré la touche appuyée // Si c'est right on remet le temps au maxi // Remettre le Meilleur temps au maxi // On ne doit pas être en train de compter if (key == 0 && flag == 0) { Serial.println("Key à l'entrée de la boucle"); Serial.println(key); delay(1000); Serial.println("Reset du Maxi"); EEPROM_writeAnything(0, MAXI); lcd.setCursor(0,0); lcd.print("A battre "); // Positionner le curseur après le texte lcd.setCursor(9,0); // afficher le meilleur temps lcd.print(meilleurTemps); lcd.setCursor(0,1); lcd.print(" "); // Relire contenu de l'EEPROM et le ramener en mémoire // Temps MAXI EEPROM_readAnything(0, meilleurTemps); Serial.print("Temps MAxi : "); Serial.println(meilleurTemps); // Afficher le meilleur temps sur la console pour vérification valid = 0; // On remet valid à 0 } // Si le flag est à 0 on n'est pas en train de compter => on attend le départ
// =============================================================================
if (flag == 0)
{
      // Attendre que le robot soit en position sur le départ
      // Start appuyé - Stop relâché
      if ((digitalRead(A5) == LOW && digitalRead(11) == HIGH)) 
      {
            // Ecrire ATTENDEZ sur la deuxième ligne
            lcd.setCursor(0,1); 
            lcd.print(" ATTENDEZ VALID ");
       }
       else
       {
            // Ecrire POSITIOONEZ ROVER sur la deuxième ligne
            lcd.setCursor(0,1); 
            lcd.print("PLACEZ LE ROVER ");
       }

      // Attendre la validation
      // Start appuyé Stop relâché  Bouton validé OK
      if ((digitalRead(A5) == LOW  &&  digitalRead(11) == HIGH && digitalRead(2) == LOW) )
          {
            // Ecrire PARTEZ sur la deuxième ligne
            lcd.setCursor(0,1); 
            lcd.print("     PARTEZ     ");
            fini = 0;
          }

// Démarrage du jeu 
// Start relâché  Stop  relâché    Validé = 0K
//================================================================================
if ( digitalRead(A5) == HIGH && digitalRead(11) == HIGH && digitalRead(2) == LOW && fini == 0 ) 
    {
      // Déclencher le chrono
      MS1 = millis();
      Serial.println("Debut comptage MS1");
      Serial.println(MS1);
      flag = 1; // Indique qu'on compte
      // Temporisation antirebond
      while ((millis() - tempo) <100) {
        ;
        }
    }

// Fin du if (flag = 0)
}

// S'exécute si le flag est à 1 : Comptage en cours
// Start relâché  Stop  appuyé    Validé = 0K
// Arrêt du jeu
// ==================================================================================
if (flag == 1)
{
    if ( (digitalRead(A5) == HIGH && digitalRead (11) == LOW && digitalRead (2) == LOW) ) 
        { 
          MS2 = millis();
          // On a fini le parcours tant que le bouton valid est on on bloque
          fini = 1;
                    
          flag = 0; // Indique qu'on arrête le comptage
    
          MS = MS2 - MS1;
          Serial.println("MS a l'arret du jeu");
          Serial.println(MS);
          Serial.println("FLAG a l'arret du jeu");
          Serial.println(flag);
    
          ltoa(MS, buf, 10);
          lcd.setCursor(0, 1);  //Position 0, ligne 2
          lcd.print(MS);
          delay (5000);
          if (MS < meilleurTemps)
          {
             meilleurTemps = MS;
             lcd.setCursor(0, 1);  //Position 0, ligne 2
             lcd.print("NOUVEAU RECORD !");
             delay (5000);
             // Ecrire la nouvelle valeur en EEPROM
             EEPROM_writeAnything(0, MS); 
             delay(3000);
    
          }
        }
    else
        {
        // On est entrain de compter : flag à 1
        // Mais on n'arrête pas le comptage
        // Afficher le temps en cours
        lcd.setCursor(0, 1);  //Position 0, ligne 2
        lcd.print("                ");
        lcd.setCursor(0, 1);  //Position 0, ligne 2
        lcd.print(millis()-MS1);
        }
    
    
    // Si on compte et que le bouton validation n'est plus OK
    // Par exemple car triche ou abandon    
    if (digitalRead(2) == HIGH)
        { 
            Serial.println("Validation enlevée");
            // On arrête le comptage
            flag = 0;
        }
}

// Temporisation d'affichage
while ((millis() - tempo) <10) {
  ;
  }

}


// FONCTIONS
// ============================================================

// Convert ADC value to key number
int get_key(unsigned int input)
{
    int k;
   
    for (k = 0; k < NUM_KEYS; k++)
    {
      if (input < adc_key_val[k]) { return k; } } if (k >= NUM_KEYS)k = -1;  // No valid key pressed
    return k;

}


//We create two fucntions for writing and reading data from the EEPROM
template  int EEPROM_writeAnything(int ee, const T& value)
{
  const byte* p = (const byte*)(const void*)&value;
  unsigned int i;
  for (i = 0; i < sizeof(value); i++)
    EEPROM.write(ee++, *p++);
    return i;
}
template  int EEPROM_readAnything(int ee, T& value)
{
    byte* p = (byte*)(void*)&value;
    unsigned int i;
    for (i = 0; i < sizeof(value); i++)
          *p++ = EEPROM.read(ee++);
    return i;
}

Vous pouvez aussi télécharger le programme de chronomètre en cliquant sur ce lien.

Comme je dis toujours, je suis un maker, pas un développeur. L’objectif de mes programmes, c’est de faire fonctionner le projet. Après si vous y regardez de près vous trouverez des trucs pas jolis, des choses qu’on ne fait pas… Moi, si ça fonctionne ça me va. Le programme est disponible, brut de fonderie et il ne tient qu’à vous de l’améliorer et de le redistribuer (j’ajouterai le lien avec plaisir !)

Il y a du debuggage de maker, oui, des print() ! pour voir par où passe le programme et m’aider à trouver mes conneries erreurs… Libre à vous de les enlever également. Mais pour ceux qui sont passés sur le stand à Vitré vous avez p tester le programme 😉

Conclusion

Un projet développé dans la semaine avant le rendez vous de Vitré. Quelques qoucis de saturation Bluetooth avec le robot, mais le chrono a bien fonctionné. Si cet article peut vous servir, ce sera avec plaisir, et n’hésitez pas à laisser votre avis dans les commentaires ci-dessous.

À propos François MOCQ

Électronicien d'origine, devenu informaticien, et passionné de nouvelles technologies, formateur en maintenance informatique puis en Réseau et Télécommunications. Dès son arrivée sur le marché, le potentiel offert par Raspberry Pi m’a enthousiasmé j'ai rapidement créé un blog dédié à ce nano-ordinateur (www.framboise314.fr) pour partager cette passion. Auteur de plusieurs livres sur le Raspberry Pi publiés aux Editions ENI.

Une réflexion au sujet de « Un chronomètre au 1/1000 de seconde avec Arduino UNO – Ecran LCD1602 »

  1. Ping : Un chronomètre au 1/1000 de seconde avec Arduino UNO – Ecran LCD1602

Laisser un commentaire

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

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.