Chronomètre a la microseconde près

Bonsoir à tous,

voilà une semaine que je galère pour faire un chronomètre, j'ai épluché tous tutos, libraries en rapport avec ça mais aucun résultat.. =( bref !

Donc mon projet, un chrony pour ceux qui connaissent en airsoft, appareil qui permet de mesurer la vélocité d'une bille ( 100m/s de moyenne ), le moyen, très simple : deux photo-transistors et deux diodes IR, le projectile traverse le premier laser qui lance le chrono et puis coupe le second qui arrête celui-là. J'ai déjà fais plusieurs codes, en vain...

const int APPUI=1; 
const int PAS_APPUI=0; 

const int BEGIN=A1; 
const int END=A4;

int timedebut = 0;
int timefin = 0;
int time;
double FPS = 0.3078 ;
double distance = 0.005;
double vitesse;
double Joules;
double FPS1;

int ETAT_BP1;
int ETAT_BP2;

void setup() {
  
Serial.begin(9600);
  
pinMode(BEGIN, INPUT);
pinMode(END, INPUT);


Serial.print("Okay");

}

void loop() {

  
do {  
  
  ETAT_BP1=digitalRead(BEGIN);
}

while(ETAT_BP1 == PAS_APPUI);

timedebut=micros();

ETAT_BP1 == PAS_APPUI;

do {
  
ETAT_BP2=digitalRead(END);

}
while (ETAT_BP2 == PAS_APPUI);
  
  timefin=micros();

  time = timefin-timedebut;
  vitesse = distance/(time*0.000001);
 
  FPS1 = vitesse / FPS;
  Joules = 0.5*sq(vitesse)*0.2*0.001;
  Serial.print(vitesse);
  Serial.print("\n");
  
  ETAT_BP2 == PAS_APPUI;

}

Le code était à la base au lieu des photo-transistors, des boutons poussoirs, de là les noms.

Si j'ai bien compris, il faut faire des interruptions, perso j'y comprends rien j'en ai fais ça marchait toujours pas !
Donc si je pourrais avoir une aide sur le programme, j'en serais reconnaissant :slight_smile:

bon bon ^^ alors ton histoire n'est pas si mal barré il manque pas grand chose, voila ce qu'il faudrait changer:

const int APPUI=1; 
const int PAS_APPUI=0; 

const int BEGIN=A1; // pour des capteur laser je sais pas si c'est pas plutot 1 quelque chose 0 rien donc pas une sortie analogique 
const int END=A4;

int timedebut = 0;
int timefin = 0;
int time;
double FPS = 0.3078 ;
double distance = 0.005;
double vitesse;
double Joules;
double FPS1;

int ETAT_BP1;
int ETAT_BP2;

void setup() {
  
Serial.begin(9600);
  
pinMode(BEGIN, INPUT);
pinMode(END, INPUT);

Serial.print("Okay");

}

void loop() {

  
  
ETAT_BP1=digitalRead(BEGIN);

while(ETAT_BP1 == 0); //rien si il y a passage => sortie while
ETAT_BP1=digitalRead(BEGIN);
timedebut=micros();
}

  


while (ETAT_BP2 == 0){
ETAT_BP2=digitalRead(END);  
timefin=micros();
}

  time = timefin-timedebut;
  vitesse = distance/(time*0.000001);
 
  FPS1 = vitesse / FPS;
  Joules = 0.5*sq(vitesse)*0.2*0.001;
  Serial.print(vitesse);
  Serial.print("\n");
 
}

voila, tant que bp1 n'est pas la on part pas, une fois BP1 détecté on prend le temps, on attend un appui sur BP2 et a l'appui on prend le deuxième temps et passe au calcule !

Voila monsieur !

reste a remplacer les sortie Analogique par celle ou tu câblera ton capteur et adapter le while ==0 si tes capteur t'envoie des valeurs différentes de 0 et 1.

ps: toujours original de voir des do{ }while{ } lol ^^

Skizo !

Mais la première question a se poser c'est : quelle est la durée moyenne que tu cherche à mesurer avec cette précision de 1 µs ?
Est-ce 100 µs, 1000 µs, 1 secondes ?

Skizoh, la durée de ta boucle est trop longue : copier la valeur de la lecture dans une variable globale et lire le temps dans la boucle ne sert à rien.

  • Si l'impulsion est trop courte tu ne la vois pas...
  • La durée d'exécution de la boucle introduit une incertitude sur la lecture du temps qui est supérieure à la microseconde.

Ça s'est mieux, et çà serait encore mieux en utilisant digitalReadFast ou la lecture direct des ports.

while( digitalRead(BEGIN) );
timedebut = micros();
while( digitalRead(END) );
timefin = micros();

C'est déjà mieux. Et c'est encore mieux en utilisant la lib digitalReadFast.

Avec des interruptions c'est pas mal non plus :

  • Impulsion de début sur pin D2 (obligatoire)
  • Impulsion de fin sur pin D3 (idem)

On suppose que le délai de traitement de l'interruption est le même donc le délai de prise en compte de l'interruption jusqu'à la lecture du temps est supposé constant - ce qui n'est pas prouvé...
Ainsi la différence des dates élimine le délai identique dans les 2 cas.

unsigned long tDebut, tFin;

volatile boolean Fait = false;

void itDebut()
{
  tDebut = micros();
}

void itFin()
{
  tFin = micros();
  Fait = true;  
}


void setup()
{
  attachInterrupt( 0, itDebut, RISING );  // Sur pin D2, RAISING ou FALLING au choix
  attachInterrupt( 1, itFin, RISING ); // Sur pin D3
}

void loop()
{
  while( !Fait );
  Serial.print(" temps en us=" );
  Serial.println( tFin - tDebut );
}

Cela devrait être raisonnablement.

Sinon, il faut utiliser le mode capture du timer mais cela impose de rentrer les 2 impulsions sur la même PIN.
Dans le mode capture, l'impulsion sur la broche déclenche en hardware simultanément un copie de la valeur du timer dans le registre de capture et la génération d'une interruption.
Dans l'interruption tu viens lire la valeur capturée.
C'est beaucoup plus précis.
Si besoin je regarderais cette solution en détail.

oui effectivement, c'est bien mieux, j'avais pas fait gaff à la contrainte de temps pointilleuse ^^

bref je te laisse entre les mains d'un pro ça vaut mieux pour toi ^^

Skizo !

Bon, j'avais envoie alors j'ai joué un peu avec le mode de capture du TIMER1.

Voici comment utiliser le TIMER1 pour capturer des temps.
L'entrée capture du TIMER1 est D8 (obligé, pas le choix).
Quand une impulsion (montante ou descendante suivant config de TCCR1B) arrive, cela déclenche :

  • la capture de la valeur du compteur du TIMER1 dans le registre de capture ICR1 et
  • une interruption TIMER1_CAPT.
    Dans la routine d'interruption, on sauve la valeur du registre dans un tableau.
    Dans l'exemple je ne garde que 2 valeurs.

Dans loop je génère une impulsion aléatoire sur D10 qui est rebouclée par un fil sur D8.
Je peux ainsi mesurer la durée réelle de l'impulsion générée.

Pour info, le code qui généère l'impulsion (digitalWrite HIGH, LOW, HIGH, LOW) prend du temps.
Même sans delayMicroseconds() cela prend 14.5 microseconds.
On voit que même si on applique cette correction, la fonction delayMicroseconds() est loin d'être parfaite.

J'ai vérifié de nombreuses mesures à l'analyseur logique : la mesure est bien exacte à 0,5µs près.

#include <avr/io.h>
#include <avr/interrupt.h>

#define MAX_CAPTURE  2
uint16_t Capture[MAX_CAPTURE];
int NbCapture = 0;

ISR(TIMER1_CAPT_vect)
{
  uint16_t capture = ICR1;
  if ( NbCapture < MAX_CAPTURE )
    Capture[NbCapture++] = capture;
}

// Choix d'activer (conseillé) ou pas le débruiteur sur l'entrée de capture
#define CAPTURE_DEBRUITEUR_ON    0x80
#define CAPTURE_DEBRUITEUR_OFF   0x00
// Choix du front montant ou desscendant
#define CAPTURE_FRONT_MONTANT    0x40
#define CAPTURE_FRONT_DESCENDANT 0x00
// Le choix du prediviseur influe sur la précision de la mesure 
// mais aussi sur la durée maximale entre 2 impulsions
#define PREDIVISEUR_OFF        0x01  /* Horloge 16MHz, precision 0.0625us, max 4ms */
#define PREDIVISEUR_8          0x02  /* Horloge 2MHz,  precision 0.5us, max 32ms  */
#define PREDIVISEUR_64         0x03  /* Horloge 250 kHz, precision 4us, max 260ms */
#define PREDIVISEUR_256        0x04  /* Horloge 62.5kHz, precision 16us, max 1sec */
#define PREDIVISEUR_1024       0x05  /* Horloge 15.6kHz, precision 64us, max 4.1sec */

double precision;

void setup()
{
  Serial.begin( 9600 );

  // Configuration du TIMER1 pour la capture
  TCCR1A = 0x00;
  TCCR1B = CAPTURE_DEBRUITEUR_ON + CAPTURE_FRONT_MONTANT + PREDIVISEUR_8;
  precision = 0.5; // a mettre a jour en fonction du prediviseur choisit
  TIFR1  = 0x20;
  TIMSK1 = 0x20;

  // on s'assure que la broche D8 (entrée capture) est bien en entrée
  pinMode( 8, INPUT );

  // Configuration de la broche 10 pour générer des impulsions de test  
  digitalWrite( 10, LOW );
  pinMode( 10, OUTPUT );
}

unsigned int test;

void loop()
{
  if ( NbCapture == 2 )
  {
    Serial.print( "Mesure: " );
    Serial.print( precision*(Capture[1] - Capture[0]) );
    Serial.println( "us" );
    delay( 1000 );
    NbCapture = 0;
  }
  else
  {
    // génération d'une impulsion de test sur la pin 10 reliée à la pin 8
    test = rand()>>2 ; // 0..16383 - limite supportée par delayMicroseconds()
    Serial.print( "Genere: " );
    Serial.print( test );
    Serial.println( "us" );
    digitalWrite( 10, HIGH );
    digitalWrite( 10, LOW );
    delayMicroseconds( test );
    digitalWrite( 10, HIGH );
    digitalWrite( 10, LOW );
  }
}

barbudor:
J'ai vérifié de nombreuses mesures à l'analyseur logique : la mesure est bien exacte à 0,5µs près.

Bonjour barbudor
un peu HS mais pas trop
Je ne sais plus où sur le forum tu a annocé que tu avais reçu un petit analyseur logique.
J'ai promis à un de mes neveux de lui en offrir un
Pour mes petites bidouilles perso, j'utilise un petit ikalogic,
http://www.ikalogic.com/ikalogic-products/scanalogic-2/
j'en suis bien satisfait et ça a été développé par un petit jeune bien reactif.
le mode replay/generateur est aussi bien sympa
mais si le nombre de voies est suffisants pour les protocoles les plus courants, 4 voies est quand même quelquefois limite.
Perso si j'ai besoin de plus de voies je tape dans mon materiel pro, mais cela ne sera pas le cas pour mon neveu.
Tu peux faire un petit feedback de tes premières utilisations sur le tien ?

Fait : [Test] L'Analyseur Logique Open Logic Sniffer - Tutoriels et cours - Arduino Forum

Bonsoir,
Dans le domaine du HS, il y a également 2 petits oscillos, snifer de protocoles, multimètres, etc..de la socité Gabotronics : http://www.gabotronics.com/
Les deux sont distribués en France par Lextronic :
Xprotolab : http://www.lextronic.fr/P22737-mini-oscilloscope-oem-xmega-xprotolab.html
Xminilab : http://www.lextronic.fr/P26429-mini-oscilloscope-oem-xminilab.html

Déroutant par le prix !!!

Salut à tous,

merci amplement d'avoir répondu, vous m'avez apporté une aide précieuse. Je n'ai pas eu encore le temps de tester mais d'après la lecture des codes je pense que c'est tout à fait bon.

Cependant par simplicité, j'ai songé à remplace le "capteur2" par un bouton poussoir sur lequel j'ai collé un disque rond simple : la bille passe dans le premier laser et active le timer, ensuite pour le couper elle appuie sur le bouton poussoir, la mise en oeuvre paraît beaucoup plus simple mais j'ai peur pour le BP au bout d'un moment XD.

Je teste vos réponses d'ici peu !!

Ah oui juste :

C'est déjà mieux. Et c'est encore mieux en utilisant la lib digitalReadFast.

Avec des interruptions c'est pas mal non plus :

  • Impulsion de début sur pin D2 (obligatoire)
  • Impulsion de fin sur pin D3 (idem)

Je n'ai pas trop compris sur le fait de la librairie que je n'ai pas trouvé, pour les pin D2 et D3 : j'ai une duemilanove j'ai que pin 2 et 3 je pense que ces ceux-là que tu sous-entendais.

Désolé de mes questions bêtes ... :~

Oui D2 et D3 sont les pins Digital2 et Digital 3
Pour ne pas confondre avec A2 et A3 les pins Analog In 2 et Analog In 3 :slight_smile:

La lib digitalwritefast est ici : Google Code Archive - Long-term storage for Google Code Project Hosting.

C'est une lib qui fournit des fonction digitalWriteFast et digitalReadFast() beaucoup plus rapide que celles d'origine
Surtout si tu définit les numéros de pin par un #define au lieu d'une variable

Donc dans le cas de la boucle

while ( digitalReadFast( 2 ) );

La boucle s'exécute beaucoup plus rapidement et tu diminue le risque de rater une impulsion très brève (je suppose qu'une bille qui passe à 100m/s devant un capteur laser ca fait une impulsion très très courte.
Si ta bille fait 15mm de diamètre, l'impulsion devrait faire en gros (15.E-3 / 100) * 1 = 150 microsec.
Bon, d'accord, pas si court que ca :wink:

Merci de ta réponse rapide, pour le 100 m/s dis toi que c'est un bon grosso modo parce-qu'en réalité une bille de 6mm en sortie de canon est dans les environs de 120m/s mais comme tu peux supposer il y en a pour 50µs.
Je vais essayer avec cette librairie et le code que tu m'as donné, je te tiens au jus !

Salut barbudor,

alors j'ai essayé ce code :

#include <digitalWriteFast.h>
#define BEGIN 2
#define END 3

unsigned long tDebut, tFin;

volatile boolean Fait = false;

void itDebut()
{
  tDebut = micros();
}

void itFin()
{
  tFin = micros();
  Fait = true;  
}


void setup()
{
  attachInterrupt( 0, itDebut, RISING );  // Sur pin D2, RAISING ou FALLING au choix
  attachInterrupt( 1, itFin, RISING ); // Sur pin D3
  Serial.begin(9600);
}

void loop()
{
  while( !Fait );
  Serial.print(" temps en us=" );
  Serial.println( tFin - tDebut );
}

et j'obtiens :

 temps en us=4294946788
 temps en us=4294946788
 temps en us=8428204
 temps en us=4294932244
 temps en us=4294932244
 temps en us=4294932244
 temps en us=4294932244
 temps en us=4294932244
 temps en us=4294932244
 temps en us=4294932244
 temps en us=4294932244
 temps en us=4294932244
 temps en us=4294932244
 temps en us=4294932244
 temps en us=4294932244
 temps en us=4294932244
 temps en us=4294932244
 temps en us=1766960
 temps en us=4294940924
 temps en us=4294940924
 temps en us=4294940924

j'y comprends plus rien ...

Déjà, si le sketch affiche des valeurs en continue c'est pas ce que tu ne remet pas à zéro la variable fait.
Donc une fois que çà a été fait une fois, çà continue...
De plus tu as peut être des rebonds sur les entrées.
je te suggère la modif suivante pour vérifier cela :

#include <digitalWriteFast.h>
#define BEGIN 2
#define END 3

#define LED_DEBUT      12
#define LED_FIN           13

unsigned long tDebut, tFin;

int compteurDebut = 0, compteurFin = 0;

void itDebut()
{
  int t = millis();
  if ( !compteurDebut )
      tDebut = t;
  ++compteurDebut;
  digitalWriteFast( LED_DEBUT, HIGH );
}

void itFin()
{
  int t = millis();
  if ( !compteurFin )
    tFin = t;
  ++compteurFin;
  digitalWriteFast( LED_FIN, HIGH );
}


void setup()
{
  digitalWrite( LED_DEBUT, LOW );
  digitalWrite( LED_FIN, LOW );
  pinMode( LED_DEBUT, OUTPUT );
  pinMode( LED_FIN, OUTPUT );

  attachInterrupt( 0, itDebut, RISING );  // Sur pin D2, RAISING ou FALLING au choix
  attachInterrupt( 1, itFin, RISING ); // Sur pin D3
  Serial.begin(9600);
}

void loop()
{
  Serial.println( "Pret..." );

  while( !compteurFin );
  Serial.print(" temps en us=" );
  Serial.println( tFin - tDebut );

  // Debug : vérification des rebonds
  // On attend un peu pour être sur
  delay( 1000 );
  Serial.print( "Compteur debut = " ) ; Serial.println(  compteurDebut );
  Serial.print( "Compteur fin    = " ); Serial.println( compteurFin );
  compteurDebut = 0;
  compteurFin = 0;
  digitalWrite( LED_DEBUT, LOW );
  digitalWrite( LED_FIN, LOW );
  Serial.println( "\n----------------------------" );

}

Si tu n'as pas de rebond sur les entrées, les 2 compteurs, debut et fin, doivent valoir respectivement 1.
S'ils valent plus, tu as un problème de rebond.

Mais j'ai rajouté un filtrage soft qui devrait être suffisant sauf si tu as des parasites. Dans ce cas les mesures se font n'importe quand.
J'ai aussi ajouté 2 leds qui debraient s'llumer respectivement sur le passage à debut et a fin.
Si elles s'allument avant, c'est que tu as des parasites et que les mures sont donc fausses.

Comment captes tu le passage de la bille ?
Optique : ne devrait pas faire de rebonds
Microrupteur : risque de rebonds.

par rayon IR donc je pense que c'est du domaine de l'optique, je vérifie !

EDIT : ça ne marche pas, ça reste sur prêt et puis voilà aucune détection juste led de fin qui s'allume...
Je pense que c'est un probleme car quand un obstacle survient il envoie plusieurs 1, peut-être de là vient le problème :frowning:

Si la led de fin s'allume, c'est que ca va bien dans la routine d'interruption

Essaye cette modif :

volatile int compteurDebut = 0, compteurFin = 0;

Si la led de début ne s'allume pas c'est que tu as un problème sur le capteur de début.

Comment fais tu la détection et connectes tu le tout à l'Arduino -> schéma STP.

EDIT : je suis un bléro, j'avais pas vu que tu avais mis en millis() ...

Sur ta première série on voit que le compteur de début reste à 0 et que seul celui de fin s'incrémente.
Ca veut dire que tu ton interruption sur le capteur de début ne marche pas.
Donc seul le temps de fin est mis à jour et la différence est calculé avec une valeur constante.
Donc normal que ca augment a chaque fois.

Et c'est fou le nombre de doublons que tu récupère. Ce n'est pas normale avec un capteur optique. Ca devrait être franc.

Dans la 2eme série, on a des nombres entre 44 et 48 us. Sur 5cm çà donne une vitesse de l'ordre de 1100m/s !!!!
Y'a un blème quelque part.

tu avais mis en millis() donc c'est tout à fait raisonnable je pense

Autre série après débogage :

Pret...
 temps en us=42812
Compteur debut = 1
Compteur fin    = 1

----------------------------
Pret...
 temps en us=4294941824
Compteur debut = 1
Compteur fin    = 1

----------------------------
Pret...
 temps en us=41388
Compteur debut = 1
Compteur fin    = 1

----------------------------
Pret...

Soit 1m/s ce qui est tout à fait normal

EDIT : je suis un bléro, j'avais pas vu que tu avais mis en millis() ...

Blaireau moi-même, je savais que tu cherchais à mesurer des micros() :roll_eyes:

Il y a quand même un problème sur la mesure du milieu.
la grande valeur signifie que la mesure est fausse et qu'on soustrait un nombre plus grand.
En gros soit tu a inversé la position des capteurs :wink: soit il y a eu un parasite qui a fait prendre une mesure de FIN avant que la bille passe devant le capteur DEBUT.
Mais étrange car tu n'as pas eu de vrai mesure de FIN sinon le compteur de fin aurait été double.

Bon sinon je pense que tu as compris le principe et que ca roule (ta bille) maintenant :wink: