/*
***********************************************
* Chronomètre / compteur de vitesse *
* version 20260204 *
* détection dans les deux sens de marche *
* Arduino Nano et écran LCD 20 X 4 en I2c *
* ou Arduino Nano et écran OLED 1306 en I2C *
***********************************************
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! LICENCE D'UTILISATION – Philippe Guenet !
! !
! Ce code est librement téléchargeable pour un usage personnel et non commercial. !
! !
! Toute utilisation commerciale, professionnelle ou rémunérée est interdite. !
! !
! La redistribution, la copie publique ou l’intégration dans un autre projet !
! sont interdites. !
! !
! Aucune modification ou adaptation n’est autorisée sans l’accord écrit préalable !
! de l’auteur. !
! !
! L’auteur conserve tous les droits de propriété intellectuelle sur ce code. !
! !
! L’auteur ne saurait être tenu responsable d’aucun dommage résultant de !
! l’utilisation de ce code. !
! !
! L’utilisation du code vaut acceptation de ces conditions. !
! !
! © Philippe Guenet – Tous droits réservés - [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)
On choisit l'écran d'affichage (LCD ou OLED) en commentant ou dé-commentatnt la ligne 63
*/
#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
// A1 Réservé signal analogique barrière IR A
// A2 Réservé signal analogique barrière IR B
const int pinBP_On = A2; // On peut utiliser d'autres pins
const int pinBP_Raz = A3;
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 pinLedVerte = 8; // chronomètre en attente (etat = ATTENTE)
const int pinLedRouge = 7; // chronomètre stoppé (etat = ARRET)
const int pinLedJaune = 6; // chronomètre démarré (etat = DEMARRAGE)
/* 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;
/* Déclaration des sens de circulation */
enum Sens {
A_VERS_B,
B_VERS_A
};
volatile Sens sensCirculation = A_VERS_B;
/* 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("SPEEDOMETRE >> ");
display.setCursor(30, 20); display.print("by PG / 20260204");
display.display();
#else
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0); lcd.print("SPEEDOMETRE >> ");
lcd.setCursor(0, 2); lcd.print("by PG / 20260204");
#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);
delay(2000);
#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 => WAIT");
#endif
} // 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(0, 0); lcd.print("CHRONOMETRE => WAIT");
lcd.setCursor(4, 1);lcd.print("00:00:00.000");
lcd.setCursor(3, 2);lcd.print(" ");
#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() && etat == ATTENTE){
chronoDepart = millis();
sensCirculation = A_VERS_B;
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");
display.setCursor(20, 30);
display.print("Sens : ");
display.print(sensCirculation == A_VERS_B ? "A => B " : "B => A ");
#else
lcd.setCursor(0, 0); lcd.print("CHRONOMETRE => ON ");
lcd.setCursor(3, 2); lcd.print("Sens : ");
lcd.print(sensCirculation == A_VERS_B ? "A => B " : "B => A ");
#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() && etat == DEMARRAGE && sensCirculation == A_VERS_B){
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(0, 0); lcd.print("CHRONOMETRE => 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(0, 0); lcd.print("CHRONOMETRE => 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) {
// On démarre sur l'entrée A donc le sens est A => B
sensCirculation = A_VERS_B;
etat = DEMARRAGE;
chronoDepart = millis();
} else if (etat == DEMARRAGE && sensCirculation == B_VERS_A) {
// on a déjà démarré et on est dans le sens B => donc on arrête
chronoFin = chronoDecompte;
tempsDebutArret = millis();
etat = ARRET;
}// Fin de if (etat == ATTENTE)
} // Fin de procédure detecterPassageIR_A()
void detecterPassageIR_B() {
// ISR sur l'interruption INT1
if (etat == ATTENTE) {
// On démarre sur l'entrée A donc le sens est A => B
sensCirculation = B_VERS_A;
chronoDepart = millis();
etat = DEMARRAGE;
} else if (etat == DEMARRAGE && sensCirculation == A_VERS_B) {
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(20, 30);
display.print("Sens : ");
display.print(sensCirculation == A_VERS_B ? "A => B " : "B => A ");
display.setCursor(0, 50);
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(20, 30);
display.print("Sens : ");
display.print(sensCirculation == A_VERS_B ? "A => B " : "B => A ");
display.setCursor(0, 50);
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)BP Test
Bascule
RAZ
IR-A
IR-B
Wait
RAZ
ON