/*
***********************************************
* Chronomètre / compteur de vitesse *
* version 20240525 *
* Arduino Nano et écran LCD 20 X 4 en I2c *
* ou Arduino Nano et écran OLED 1306 en I2C *
***********************************************
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Ce code est distribué gratuitement sous licence !
! Creativ Commons CC-BY-NC-ND 3.0 !
! !
! Cette licence permet à d'autres personnes d'utiliser ce code à des fins !
! non commerciales, dans la mesure où le nom de l'auteur est mentionné. !
! Toute modification du code devra être communiquée à l'auteur dans le but!
! de parfaire son apprentissage en la matière. lol !
! !
! auteur Philippe GUENET - [email protected] - https://wgnt-train.fr !
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*/
/*
Les barrières IR sont simulées ici par les deux BP de droite (verts)
le montage réel utilise des barrières Infra-Rouge de ce type
https://fr.aliexpress.com/item/32656286188.html
Le bouton de gauche (bleu) sert à démarrer / arrêter le déclanchement du chrono.
Sur ce présent montage il est donc en doublon avec les poussoirs verts mais est utile
sur la version de production pour tester le fonctionnement sans avoir à brancher
les détecteurs infra-rouge.
Le bouton de gauche (rouge) sert à réinitialiser manuellement le chrono.
Une réinitialisation automatique est prévue 5 secondes (paramétrable) après
l'arrêt du chrono.
On utilise les interruptions INT0 (D2) et INT1 (D3) pour tester les changements
d'état des signaux infra rouge.
3 leds indiquent l'état du système.
- vert = chronomètre en attente (etat = ATTENTE)
- jaune = chronomètre démarré (etat = DEMARRAGE)
- rouge = chronomètre stoppé (etat = ARRET)
*/
#include <Bounce2.h> // pour l'anti-rebond des boutons poussoirs et des détecteurs
#include <Wire.h>
//#define ECRAN_OLED // Définir ECRAN_OLED pour utiliser l'écran OLED, commenter pour utiliser l'écran LCD
#ifdef ECRAN_OLED
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#else
#include <LiquidCrystal_I2C.h> // pour gérer un écran LCD 20 x 4 en I2C
LiquidCrystal_I2C lcd(0x27, 20, 4); // construction de l'objet lcd
#endif
/* les pins définis ci-dessous l'ont été pour pouvoir utiliser un PCB existant de
système de détection de présence par infra-rouge. Ils peuvent être adaptés en respectant
les pins d'interruption INT0 et INT1 et de communication I2C pour l'écran.
*/
const int pinIR_A = 2; // connection de signal du détecteur IR d'entrée
const int pinIR_B = 3; // connection de signal du détecteur IR de sortie
const int pinBP_On = A1; // On peut utiliser d'autres pins
const int pin_SDA = A4; // pin réservé pour l'écran en I2C
const int pin_SCL = A5; // pin réservé pour l'écran en I2C
const int pinBP_Raz = A0; // On peut utiliser d'autres pins
const int pinLedVerte = 8; // chronomètre en attente (etat = ATTENTE)
const int pinLedJaune = 7; // chronomètre démarré (etat = DEMARRAGE)
const int pinLedRouge = 6; // chronomètre stoppé (etat = ARRET)
/* Les constantes qui peuvent être modifiées */
const long distance = 1000; // Distance en millimètres entre les deux détecteurs
const float echelle = 87; // Echelle du réseau
const unsigned long delaiRAZAutomatique = 5000;
/* Les variables */
unsigned long chronoDepart = 0;
unsigned long chronoFin = 0;
unsigned long chronoDecompte = 0;
unsigned long tempsDebutArret = 0;
float vitesseEchelle;
bool flagAffiche = false;
/* Déclaration des états possibles */
enum Etat {
ATTENTE,
DEMARRAGE,
ARRET,
RAZ
};
Etat etat = ATTENTE;
/* Les objets pour l'anti-rebond */
Bounce BP_Raz = Bounce(); // Construction d'un objet BP_Raz pour l'anti-rebond
Bounce BP_On = Bounce(); // Construction d'un objet IR_A pour l'anti-rebond
Bounce IR_A = Bounce(); // Construction d'un objet IR_B pour l'anti-rebond
Bounce IR_B = Bounce(); //
#define tempoDebounce 40
void setup() {
#ifdef ECRAN_OLED
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Address 0x3C for 128x32
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0); display.print("CHRONOMETRE => ");
display.display();
#else
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0); lcd.print("CHRONOMETRE => ");
#endif
/* Paramétrage des pins */
pinMode(pinLedVerte, OUTPUT);
pinMode(pinLedJaune, OUTPUT);
pinMode(pinLedRouge, OUTPUT);
/* Paramétrage des objets anti-rebond */
BP_Raz.attach(pinBP_Raz, INPUT_PULLUP);
BP_Raz.interval(tempoDebounce);
BP_On.attach(pinBP_On, INPUT_PULLUP);
BP_On.interval(tempoDebounce);
IR_A.attach(pinIR_A, INPUT_PULLUP);
IR_A.interval(tempoDebounce);
IR_B.attach(pinIR_B, INPUT_PULLUP);
IR_B.interval(tempoDebounce);
/* paramétrage des interruption sur front descendant
puisque les détecteurs sont à HIGH au repos et à LOW quand
un objet est détecté.*/
attachInterrupt(digitalPinToInterrupt(pinIR_A), detecterPassageIR_A, FALLING);
attachInterrupt(digitalPinToInterrupt(pinIR_B), detecterPassageIR_B, FALLING);
/* On teste les connexions des leds puis on les initialise */
digitalWrite(pinLedVerte, HIGH);
digitalWrite(pinLedJaune, HIGH);
digitalWrite(pinLedRouge, HIGH);
delay(600);
digitalWrite(pinLedVerte, HIGH);
digitalWrite(pinLedJaune, LOW);
digitalWrite(pinLedRouge, LOW);
} // Fin de setup()
void loop() {
BP_Raz.update();
BP_On.update();
switch (etat){
case ATTENTE:
// On met à jour l'état des leds
digitalWrite(pinLedVerte, HIGH);
digitalWrite(pinLedJaune, LOW);
digitalWrite(pinLedRouge, LOW);
// On affiche l'état sur l'écran
#ifdef ECRAN_OLED
display.clearDisplay();
display.setCursor(0, 0); display.print("CHRONOMETRE => WAIT");
display.setCursor(30, 10); display.print("00:00:00.000");
display.display();
#else
lcd.setCursor(16, 0); lcd.print("WAIT");
lcd.setCursor(4, 1);lcd.print("00:00:00.000");
#endif
/* On vérifie si on appuie sur le BP de démarrage.
Le démarrage par l'infra-rouge est géré par l'ISR sur INT0.*/
if (BP_On.rose()){
chronoDepart = millis();
etat = DEMARRAGE;
} // Fin de if (BP_On.rose())
break;
case DEMARRAGE:
// On met à jour l'état des leds
digitalWrite(pinLedVerte, LOW);
digitalWrite(pinLedJaune, HIGH);
digitalWrite(pinLedRouge, LOW);
// On affiche l'état sur l'écran
#ifdef ECRAN_OLED
display.clearDisplay();
display.setCursor(0, 0);
display.print("CHRONOMETRE => ON");
#else
lcd.setCursor(16, 0); lcd.print("ON ");
#endif
// On compte le temps qui s'écoule et on l'affiche sur l'écran
chronoDecompte = millis() - chronoDepart;
afficheDecompte(chronoDecompte);
/* On vérifie si on appuie à nouveau sur le BP de démarrage.
L'arrêt par l'infra-rouge est géré par l'ISR sur INT1.*/
if (BP_On.rose()){
chronoFin = chronoDecompte;
tempsDebutArret = millis();
etat = ARRET;
} // Fin de if (BP_On.fell())
break;
case ARRET:
// On met à jour l'état des leds
digitalWrite(pinLedVerte, LOW);
digitalWrite(pinLedJaune, LOW);
digitalWrite(pinLedRouge, HIGH);
// On affiche l'état sur l'écran
#ifdef ECRAN_OLED
display.clearDisplay();
display.setCursor(0, 0);
display.print("CHRONOMETRE => OFF");
#else
lcd.setCursor(16, 0); lcd.print("OFF ");
#endif
// On calcule et affiche la vitesse une seule fois
if (!flagAffiche){
flagAffiche = true;
afficheDecompte(chronoFin);
calculeVitesse(chronoFin);
} // if (!flagAffiche)
/* On vérifie si on appuie sur le BP de remise à zéro.
La remise à zéro peut également être déclanchée après
5 secondes (paramétrable)*/
if (BP_Raz.fell() || (millis() - tempsDebutArret >= delaiRAZAutomatique)) {
etat = RAZ;
} // Fin de if (BP_Raz.fell() || (millis() - tempsDebutArret >= delaiRAZAutomatique))
break;
case RAZ:
// On met à jour l'état des leds
digitalWrite(pinLedVerte, HIGH);
digitalWrite(pinLedJaune, HIGH);
digitalWrite(pinLedRouge, HIGH);
// On affiche l'état sur l'écran
#ifdef ECRAN_OLED
#else
lcd.setCursor(16, 0); lcd.print("RAZ ");
// On efface les anciennes valeurs de l'écran
lcd.setCursor(12, 1); lcd.print(" ");
lcd.setCursor(0, 3); lcd.print(" ");
#endif
// On re-initialise les variables
chronoDepart = 0;
chronoDecompte = 0;
chronoFin = 0;
tempsDebutArret = 0;
afficheDecompte(chronoDecompte);
flagAffiche = false;
vitesseEchelle = 0;
etat = ATTENTE;
break;
} // Fin de switch(etat)
} // Fin de loop()
void detecterPassageIR_A() {
// ISR sur l'interruption INT0
if (etat == ATTENTE) {
chronoDepart = millis();
etat = DEMARRAGE;
} // Fin de if (etat == ATTENTE)
} // Fin de procédure detecterPassageIR_A()
void detecterPassageIR_B() {
// ISR sur l'interruption INT1
if (etat == DEMARRAGE){
chronoFin = chronoDecompte;
tempsDebutArret = millis();
etat = ARRET;
} // Fin de if (etat == DEMARRAGE)
} // Fin de procédure detecterPassageIR_B()
void afficheDecompte(unsigned long chrono) {
unsigned long heures = chrono / 3600000;
unsigned long minutes = (chrono % 3600000) / 60000;
unsigned long secondes = ((chrono % 3600000) % 60000) / 1000;
unsigned long millisecondes = ((chrono % 3600000) % 60000) % 1000;
#ifdef ECRAN_OLED
display.setCursor(30, 10);
if (heures < 10) {display.print("0");}
display.print(heures);
display.print(":");
if (minutes < 10) {display.print("0");}
display.print(minutes);
display.print(":");
if (secondes < 10) {display.print("0");}
display.print(secondes);
display.print(".");
if (millisecondes < 10) {display.print("0");}
display.print(millisecondes);
display.display();
#else
lcd.setCursor(4, 1);
if (heures < 10) {lcd.print("0");}
lcd.print(heures);
lcd.print(":");
if (minutes < 10) {lcd.print("0");}
lcd.print(minutes);
lcd.print(":");
if (secondes < 10) {lcd.print("0");}
lcd.print(secondes);
lcd.print(".");
if (millisecondes < 10) {lcd.print("0");}
lcd.print(millisecondes);
#endif
} // Fin de procédure afficheDecompte(unsigned long chrono)
void calculeVitesse(unsigned long chrono) {
vitesseEchelle = (distance / (float)chrono) * 3.6 * echelle;
String vitesseFormatee = String(vitesseEchelle, 3);
while (vitesseFormatee.length() < 7) {
vitesseFormatee = "0" + vitesseFormatee;
} // Fin de while (vitesseFormatee.length() < 7)
if (vitesseEchelle <= 0) {
#ifdef ECRAN_OLED
display.setCursor(0, 30);
display.print("Vitesse: ");
display.print("000.000");
display.print(" Km/H");
display.display();
#else
lcd.setCursor(0, 3); lcd.print(F("Speed : "));
lcd.print("000.000");
lcd.print(F(" Km/H"));
#endif
} else {
#ifdef ECRAN_OLED
display.setCursor(0, 30);
display.print("Vitesse: ");
display.print(vitesseFormatee);
display.print(" Km/H");
display.display();
#else
lcd.setCursor(0, 3); lcd.print(F("Speed : "));
lcd.print(vitesseFormatee);
lcd.print(F(" Km/H"));
#endif
} // Fin de if (vitesseEchelle <= 0)
} // Fin de procédure calculeVitesse(unsigned long chrono)