Mesure de la vitesse d'une roue avec capteur effet Hall

Bonjour, j'ai réaliser un code pour calculer la vitesse d'une roue de chariot à l'aide d'un capteur effet hall, j'ai 3 aimant fixé sur la roue à distance, j'ai du calculer le temps entre les 3 aimants et par la suite il faudra que j'y convertisse en vitesse linéaire (km/h).
Voici le problème lorsque je regarde dans le moniteur de série il m'affiche des valeurs en plus lorsque je passe sur un aimant et je voudrais qu'il me compte qu'une fois à partir du passage de chaque aimant.

*Voici mon code :

int dt;
const int ledPin = 13; 
const int digitalPin = 2; //pin pour le capteur

int sensorValue; 
unsigned long ot = 0;
bool reset = true;

void setup()
{
pinMode( ledPin, OUTPUT ); 
pinMode( digitalPin, INPUT );

Serial.begin(9600);
}

void loop() 
{
// lecture du capteur a Effet Hall
sensorValue = digitalRead( digitalPin );

// senseurValue = HIGH sans aimant
// senseurValue = LOW  quand POLE SUD aimant
sensorValue = not( sensorValue );

if (sensorValue == LOW && reset)
{
  if (dt<3000)
  digitalWrite(3,HIGH);
  unsigned long dt = millis() - ot;
  ot = millis();
  
  Serial.print("Temps entre deux impulsions:      ");
  Serial.print(dt);
  Serial.print(" ms\n");
  reset = false;

  
}
else if (sensorValue != LOW && !reset)
  reset = true;

// Allumer eteindre la LED
digitalWrite( ledPin, sensorValue );
}

*Voici ce qu'affiche le moniteur de série :

Merci pour votre aide.

:warning:

Post mis dans la mauvaise section, on parle anglais dans les forums généraux. ➜ déplacé vers le forum francophone.

Merci de prendre en compte les recommandations listées dans "Les bonnes pratiques du Forum Francophone”


ce qui est important c'est le changement d'état, pas juste l'état. donc vous devez mémoriser l'état précédent et quand le nouvel état n'est plus le même c'est qu'il s'est passé un truc

Un seul aimant devrait suffire et cela apporterais de la stabilité.
Pour le calcul de la distance c'est le périmètre de la roue divisé par le nombre d'aimants.

Si les aimants sont équidistants (pour la précision en portion de tour de roue - le modulo)

D’accord merci pour votre réponse, auriez-vous un exemple de code que je pourrais faire ?
Merci

Vous avez essayé ?

Oui mais ça m'affiche de mauvaise valeur pour la vitesse, ma roue à une diamètre de 15 cm j'ai fais : 15*3.14/1 et je trouve 47,1

c'est typiquement une définition de programme qui se prête bien à la programmation par machine à états (cf mon tuto éventuellement)

en supposant que vous avez un PULLUP externe sur votre pin (vous obtenez LOW lorsque la détection est effectuée) et qu'il n'y a aucun rebond, et que la roue ne tourne pas trop vite par rapport à la vitesse d'exécution du code dans la loop(), vous pourriez envisager un petit code comme cela:

const int digitalPin = 2; //pin pour le capteur
enum {ATTENTE, DETECTION} etat = ATTENTE;
bool premierPassage = true;
unsigned long topPrecedent;

void setup() {
  pinMode( digitalPin, INPUT);
  Serial.begin(115200);
}

void loop() {
  switch (etat) {
    case ATTENTE:
      if (digitalRead(digitalPin) == LOW) {
        unsigned long maintenant = millis();
        if (premierPassage) {
          premierPassage = false;
        } else {
          unsigned long deltaT = maintenant - topPrecedent;
          Serial.print("Temps entre deux impulsions: "); Serial.println(deltaT);
        }
        topPrecedent = maintenant;
        etat = DETECTION;
      }
      break;

    case DETECTION:
      if (digitalRead(digitalPin) == HIGH)  etat = ATTENTE;
      break;
  }
}

ce code détecte le changement d'état.

Si vous avez des rebonds et que ça ne tourne pas trop vite, la solution "du pauvre" est de juste rajouter un delay() après la détection d'un front pour ne pas voir les oscillations. Généralement 15ms suffisent. Une solution matérielle est aussi envisageable (simple résistance-capacité ou plus élaboré cf https://www.digikey.fr/fr/articles/how-to-implement-hardware-debounce-for-switches-and-relays)

Bonsoir thomasfr24

Vouloir chronométrer des impulsions dans la loop(), ne mène a rien de bien régulier comme résultat, c'est mieux de passer par les interruptions, ça rend la mesure "indépendante" et moins tributaire du reste du programme.
Dans ton programme, tu as déjà la bonne pin pour ce faire, la pin D2 a les interruptions.
Si tu veux un exemple...

Cordialement
jpbbricole

le code est déterministe, donc on connait la régularité et on peut même calculer le temps d'un tour de loop.

Si le code ne fait que ça et que la roue ne tourne pas trop vite alors ça fonctionnera.

Si le code devient plus important ou que la roue tourne vite par rapport au temps d'exécution de la loop() alors oui les interruptions sont le meilleur moyen de gérer cela (mais ça apporte de la complexité dans le développement et des choses plus avancées à comprendre comme volatile, section critique etc)

Bonjour thomasfr24

J'ai essayé ton programme avec un générateur, je n'ai pas actuellement de roue avec aimants.
Ton programme mesure très bien et régulièrement, ce qui tend à dire que tu as des rebonds sur ton contact ou tout autres perturbations.
Tu devrait changer l'initialisation de la pin d'entrée de
pinMode( digitalPin, INPUT );
à
pinMode( digitalPin, INPUT_PULLUP );
ça devrait atténuer.

Comme je l'ai dit plus haut, mesurer ainsi dans la loop(), il suffit qu'un événement du programme dure plus que l'impulsion du passage de l'aimant, cette impulsion est perdue, ce qui diminue ce que l'on peut faire dans loop(),

Il faut démarrer et arrêter la mesure du temps sur des transitions et non sur des états.
Transition FALLING de HIGH à LOW
Transition RISING de LOW à HIGH
et ça c'est réalisable en travaillant avec les interruptions. Ca "libère" la loop() et on est moins tributaire de ce que fait le reste du programme.
Do bonnes info, également ici.

Voilà un exemple avec interruption:

//------------------------------------- Roue
struct RoueDef
{//               Mesures en cm.
	const float ciconferance = 15 * 3.14;     // Circonférence de la roue
	volatile float vitesseKmH = 0.0;
	const int aimantsNombre = 3;      // Nombre d'aimants
	unsigned long hallTimer = millis();   // Chronomètre
	volatile unsigned long hallImpulsion;   // Temps entre 2 aimants
};
RoueDef roue;
const int reedPin = 2;

unsigned long affichageTimer = millis();

void setup()
{
	Serial.begin(115200);
	
	pinMode(reedPin, INPUT_PULLUP);
	attachInterrupt(digitalPinToInterrupt(reedPin), capteurImpulsion, RISING);     // Au flanc descendant du signal
}

void loop()
{
	if (millis() - affichageTimer >= 1000)     // Toutes les secondes
	{
		Serial.println("Vitesse " + String(roue.vitesseKmH) + " km/h");
		affichageTimer = millis();
	}

}

void capteurImpulsion()
{
	roue.hallImpulsion = millis() - roue.hallTimer;
	//                      Nombre impusions/heure          multiplié par distamce entre les aiamnts    de cm en km
	roue.vitesseKmH = ((3600000.0 / roue.hallImpulsion) * (roue.ciconferance/roue.aimantsNombre))     /100000.0;
	
	roue.hallTimer = millis();

	// Eventuellement un "petit" anti-rebonds
	//delay(10)	;
}

Tu vois qu'il, n'y a plus rien dans loop() concernant la mesure de vitesse si ce n'est son affichage toutes les secondes.

Cordialement
jpbbricole

Sincèrement ce n'est pas un code à recommander...

  • Le code que vous proposez met la section critique au mauvais endroit : dans l'interruption, les interruptions sont déjà désactivées, mais par contre dans la loop() quand on veut afficher la vitesse, il faut une section critique et copier la vitesse...

  • Quand je vois qu'il propose un delay anti-rebond dans l'ISR, on voit bien qu'il y a un souci de compréhension des interruptions et ce que l'on peut/doit y faire...

  • Si la roue tourne vite ou qu'il y a un rebond, hallImpulsion peut valoir 0 et on aura une division par 0 dans l'interruption.

➜ comme je le disais, ça apporte de la complexité dans le développement et des choses plus avancées à comprendre...

PS/ est-ce que RISING est le plus adapté à un PULLUP ? on mesure la fin du passage de l'aimant, pourquoi pas... intuitivement j'aurais mis FALLING plutôt (come le commentaire le laisse entendre d'ailleurs dans le code)

Bonsoir J-M-L

!!! C'est un miracle, il fonctionne à merveille, j'y ai même adjoint un écran LCD, sans problème.
Je concède "l'anti rebond" est un peu léger.

Si tu pouvais t'expliquer afin que je comprenne. J'ai presque toujours vu fait des compte-tours, ce qui est l'équivalent de ce montage, faits ainsi et rarement dans loop().

Cordialement
jpbbricole

bien sûr il fonctionne jusqu'à ce qu'un cas non prévu - mais possible - intervienne

Si tu pouvais t'expliquer afin que je comprenne

  • tournez super vite (avec 3 aimants, en gros 334 tours / secondes - pas le moteur de tous les jours - sauf rebonds) pour que capteurImpulsion() soit appelé deux fois dans la même milliseconde. On aura alors
    roue.hallImpulsion = millis() - roue.hallTimer;
    qui vaudra 0 et donc une division par 0 dans l'expression
    (3600000.0 / roue.hallImpulsion)

  • pour la section critique, dans une interruption les interruptions sont déjà interrompues, donc pas la peine de le refaire

  • je suppose que je n'ai pas besoin de vous faire un dessin pour la recommendation du delay() qui dépend de timer0 et de l'évolution de micros(). C'est pour cela qu'il réactive les interruptions avant la fin, mais dans ce cas on est à la merci d'une nouvelle interruption (par un rebond justement) pendant le delay qui re-déclenche l'ISR... Donc on ne traite pas les rebonds et on aura une vitesse "foireuse"

  • si vous recevez une interruption pendant que la loop() est en plein String(roue.vitesseKmH) - appel de fonction qui prend du temps puisque vous travaillez sur un float (4 octets) ➜ vous allez modifier les octets sous jacents à la variable vitesseKmH et revenir au calcul... ça va fausser l'affichage. ➜ c'est là qu'il faudrait une section critique

ma recommendation serait de juste compter les impulsions dans l'ISR

volatile unsigned long nbImpulsions = 0;
void capteurImpulsion() {
  nbImpulsions++;
}

et d'avoir dans la loop() une section critique

void loop() {
  static unsigned long affichagePrecedent = millis();

  if (millis() - affichagePrecedent >= 1000) { // Toutes les secondes
   
    // ------------ SECTION CRITIQUE -----------
    noInterrupts();
    unsigned long nbImpulsionsCopie = nbImpulsions;
    unsigned long maintenant = millis();
    nbImpulsions = 0;
    interrupts();
    // ------------------------------------------

    // calcul et affichage de la vitesse ici

    affichagePrecedent = maintenant;
  }

 ...
}

et traiter les rebonds (s'il y en a) en hardware.

Bonsoir J-M-L

Quoi, par exemple?
Il en va de même que ta version "tout dans loop()" ?

Soit un pourtour de 0,471 m

Soit 0,471 * 334 = 157 m/sec.
soit 0,157 km/sec * 3600 = 566 km/h

Super chariot!
Il faut quand même prendre des chiffres réalistes.

Je corrige, dans mon programme, ce qui me semble être plausible.

Comme quoi il y a plusieurs façons de faire la même chose, ce ce qui fait le charme de la programmation :wink:

Cordialement
jpbbricole

La division par zéro n’est qu’un des soucis…

La non protection en zone critique dans la loop est une erreur

Oui, mais quand et avec quelle conséquence(s)?

Calcul erroné de la vitesse :slight_smile:
Vous ne pouvez pas prévoir quand l’interruption va se produire
Ça fait partie des obligations quand on utilise les ISR, comme l’usage de volatile

code_vitesse_rotation_lcd.ino (3,2 Ko)
Bonjour je tiens à vous remercier de vos réponse cela fait un moment et je ne vous avait pas répondu, du coup j'avais ajouter au code qui permet de calculer la vitesse de rotation de la roue le code pour afficher la vitesse sur un écran lcd, mais je trouve que le temps des millis est trop long la vitesse change environ toutes les 2 secondes.
Selon vous pourrais-je optimiser mon code pour que cela soit plus rapide ?
Merci

Je note quand même que calculer une vitesse en utilisant millis() n'est pas judicieux si on utilise une nano par exemple. millis() s'incrémente alors toutes les 1,024ms (si le quartz est parfait à 16,000MHz) le plus souvent de 1 et parfois de 2 pour rattraper le retard.
Il vaut mieux dans ce cas utiliser micros(). Il faut peut être veiller au problème si micros() repasse à 0.

Le problème de mettre delay(10) dans une interruption "normale" est que delay ne s'incrémente plus. utiliser delaymicrosecond(100000) qui fait des boucles et qui donc fonctionne encore est que l'on va perdre 9 incrémentations de l'horloge système. En gros si on tourne lentement, ce la ne se voit pas, mais si on tourne vite, l'horloge système va ralentir et la vitesse calculée sera plus grande que la vitesse réelle.

Réglé si on utilise micros() sauf si la vitesse est supersonique.

2 écoles:
− ceux qui traitent les rebonds par logiciel (moi)
− ceux qui traitent les rebonds par matériel
On peut toujours traiter les rebonds dans une interruption, exactement comme on le ferait dans une boucle loop trop rapide. Cela complexifie un peu le code de l'interruption.

cela ne fonctionne pas parce que roue.vitesseKmH est multioctets et est incrémenté par une interruption. On peut lire le premier octet, que la variable soit remise à jour, et lire ensuite la fin de la variable. C'est pour cela que millis() désactive éventuellement les interruptions.

Si c'est moi qui écrit le programme, je passerai par les interruptions, parce que j'en ai l'habitude. Mais je ne le conseillerai pas si ce n'est pas très utile, tout le monde ne sait pas forcément les utiliser (preuve: mettre un delay(10) dans une inter normale!).