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.
Au sommaire :
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.
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
// 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.
Ping : Un chronomètre au 1/1000 de seconde avec Arduino UNO – Ecran LCD1602