Faire un tachymètre avec une carte Arduino / Genuino pour lire la vitesse de rotation d'un ventilateur de PC

Silence, ça tourne.

Image d'entête

par skywodd | | Licence (voir pied de page)

Catégories : Tutoriels Arduino | Mots clefs : Arduino Genuino Tachymètre Mesure Fréquence


Dans ce tutoriel, nous allons voir ensemble comment fabriquer un tachymètre simpliste avec une carte Arduino / Genuino. Le but : mesurer la vitesse de rotation d'un ventilateur d'ordinateur. En bonus, nous verrons comment mesurer la période d'un signal à rapport cyclique variable avec notre tachymètre maison.

Sommaire

Bonjour à toutes et à tous !

Il y a quelques jours, un lecteur m'a contacté avec une question bien précise : comment faire pour mesurer la vitesse de rotation de deux ventilateurs d'ordinateur avec une carte Arduino / Genuino ? Excellente question, on va voir cela de suite ;)

Avant de commencer, je tiens à préciser que le but de ce tutoriel n'est pas exclusivement de mesurer la vitesse de rotation d'un ventilateur d'ordinateur, mais bien de mesurer la fréquence d'un signal périodique quelconque.

Ce tutoriel est parfaitement valable si vous utilisez un capteur à effet hall, un phototransistor, ou n'importe quel type de capteur générant un signal logique de fréquence proportionnelle à la grandeur mesurée.

Signal en sortie d'un ventilateur de PC

Capture à l'oscilloscope du signal en sortie d'un ventilateur d'ordinateur

Signal en sortie d'un ventilateur d'ordinateur

Les ventilateurs d'ordinateurs intègrent en général un capteur à effet hall dans le corps du ventilateur et un petit aimant dans la partie mobile.

À chaque fois que l'aimant passe devant le capteur à effet hall, l'état du signal en sortie s'inverse. Ainsi, en mesurant la durée de l'état haut (ou bas, peu importe), il est possible de déterminer la vitesse de rotation du ventilateur. C'est de cette façon que les cartes mères d'ordinateurs peuvent connaitre la vitesse de rotation des ventilateurs du PC.

Montage de démonstration

Pour pouvoir tester les extraits de code un peu plus bas, nous allons mettre en oeuvre un petit montage de démonstration avec un ventilateur d'ordinateur "classique".

Photographie du matériel nécessaire à la réalisation du montage de mesure de la vitesse de rotation d'un ventilateur d'ordinateur

Matériel nécessaire

Pour réaliser ce premier montage, il va nous falloir :

  • Une carte Arduino UNO (et son câble USB),

  • Un ventilateur d'ordinateur à trois ou quatre fils,

  • Une résistance de 10K ohms – code couleur marron noir orange,

  • Une diode pour petits signaux type 1N4148,

  • Une plaque d'essai et des fils pour câbler notre montage.

Vue prototypage du montage de mesure de la vitesse de rotation d'un ventilateur d'ordinateur

Vue prototypage du montage

Vue schématique du montage de mesure de la vitesse de rotation d'un ventilateur d'ordinateur

Vue schématique du montage

Câblage d'un ventilateur de PC

Brochage d'un connecteur de ventilateur d'ordinateur

Brochage du connecteur d'un ventilateur d'ordinateur

Les ventilateurs d'ordinateurs sont disponibles en deux versions : 3 broches et 4 broches.

Les versions 3 broches dites "versions DC" sont conçues pour les cartes mères d'ordinateurs avec un contrôleur de tension analogique. En gros, pour contrôler la vitesse du ventilateur, la carte mère augmente ou diminue la tension d'alimentation du ventilateur, entre 5 volts et 12 volts.

Les versions 4 broches dites "versions PWM" sont conçues pour les cartes mères d'ordinateurs avec un contrôleur de tension PWM. Dans ce cas, le ventilateur est toujours alimenté en 12 volts et la carte mère contrôle la vitesse du ventilateur via la quatrième broche du connecteur au moyen d'un signal PWM.

Dans le cadre de notre tutoriel, le type de ventilateur n'a pas d'importance. Les deux connecteurs sont identiques, à l'exception du quatrième fils dans la version 4 broches.

Le premier fil est toujours la masse. Le second fil est l'alimentation. Le troisième fil est le signal en sortie du capteur hall du ventilateur (c'est lui qui nous intéresse). Et pour finir, dans le cas des versions 4 broches, le quatrième fil est le signal de contrôle pour la vitesse.

N.B. L'utilisation du quatrième fil des versions 4 broches n'est pas le sujet de ce tutoriel et ne sera donc pas traité ;)

Commençons le câblage du montage avec le ventilateur. L'alimentation du ventilateur doit être reliée à une alimentation externe délivrant une tension entre 5 volts et 12 volts. Si vous voulez simplement tester le principe, vous pouvez relier l'alimentation du ventilateur à la broche 5V de la carte Arduino.

Ensuite, la masse du ventilateur doit être reliée à la masse de l'alimentation externe ainsi qu'à la masse de la carte Arduino. Les deux masses doivent être reliées. C'est très important.

On continue avec la résistance de 10K ohms qui vient se câbler entre la broche 5V de la carte Arduino et la broche D2 de la carte Arduino.

N.B. J'utilise la broche D2 car celle-ci est utilisable avec la fonction attachInterrupt() que l'on verra plus tard dans le tutoriel. Si vous utilisez le code avec pulseIn(), n'importe quelle broche est utilisable à la place de D2.

Photographie du montage de mesure de la vitesse de rotation d'un ventilateur d'ordinateur

Le montage fini

La dernière étape du montage est le câblage de la diode 1N4148 entre la broche D2 de la carte Arduino et la sortie du capteur hall du ventilateur.

La diode 1N4148 est polarisée. Celle-ci doit être câblée avec sa cathode (bande de couleur blanche ou noir) du côté de la broche du ventilateur. Si vous n'êtes pas sûr de comprendre, regarder l'illustration ci-dessus.

Pourquoi ajouter une diode au montage ?

Cette diode est capitale pour le bon fonctionnement du montage. Elle permet d'éviter que la tension en sortie du ventilateur (qui peut aller jusqu'à 12 volts) ne revienne dans la carte Arduino et la détruise.

Dans un monde idéal où les constructeurs respectent parfaitement les normes, cette diode serait inutile. Seulement voilà, le monde réel n'est pas si parfait et certains constructeurs prennent la liberté d'avoir une sortie de tension directe au lieu d'une sortie à collecteur ouvert au niveau du capteur de rotation.

Illustration d'une sortie à collecteur ouvert

Illustration d'une sortie à collecteur ouvert (source : Wikipedia)

Une sortie à collecteur ouvert se comporte comme un bouton poussoir câblé vers la masse. Quand la sortie est au repos, la sortie n'est pas connectée. Mais quand la sortie est active, celle-ci se connecte à la masse.

En ajoutant une résistance de tirage à la tension d'alimentation de son circuit logique (5 volts dans le cas d'une carte Arduino) on obtient une sortie logique avec des tensions adéquates.

Dans le cas d'une sortie de tension directe, on obtient la tension d'alimentation du ventilateur lorsque la sortie est au repos. Sans la diode pour bloquer cette tension, bye bye la carte Arduino.

Techniquement, avec la cathode de la diode du côté ventilateur, le courant ne peut que sortir de la carte Arduino pour aller vers le ventilateur et pas l'inverse. Si la tension en sortie du ventilateur est supérieure à la tension de la carte Arduino, la diode est bloquante et la carte Arduino est en sécurité.

Mesure avec la fonction pulseIn()

La première façon de mesurer le signal en sortie d'un ventilateur d'ordinateur consiste à utiliser la fonction pulseIn() fournie par le framework Arduino.

C'est une solution naïve, mais tout à fait fonctionnelle qui part du principe que l'état haut et bas du signal sont de même longueur. C'est le cas avec la plupart des ventilateurs d'ordinateur, mais pas tous. Il est donc conseillé de vérifier la forme du signal à l'oscilloscope avant d'utiliser cette méthode.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/* constantes pour la broche de mesure */
const byte PIN_SIGNAL = 2;

/** Fonction setup() */
void setup() {
  
  /* Initialise le port série */
  Serial.begin(115200);
  
  /* Met la broche en entrée */  
  pinMode(PIN_SIGNAL, INPUT_PULLUP);
}

Pour cette méthode de mesure, on commence par déclarer une broche pour la prise de mesure, puis on configure cette broche en entrée avec résistance de tirage dans la fonction setup().

On en profite au passage pour initialiser le port série qui nous permettra d'avoir un retour d'information sur le moniteur série.

1
2
3
4
5
6
7
8
9
void loop() {
 
  /* Mesure la durée de la (demi) période */
  unsigned long periode = pulseIn(PIN_SIGNAL, LOW, 1000000) * 2;
  
  /* Affiche le résultat de la mesure en RPM */
  Serial.println(1000000 / periode * 60);
  delay(1000);
}

Ensuite, dans la fonction loop(), on appelle la fonction pulseIn() pour mesurer la durée de l'état bas (LOW) du signal. On spécifie un timeout d'une seconde (1 000 000 microsecondes) pour éviter de rester bloquer si le ventilateur est éteint ou bloqué.

En retour de la fonction pulseIn(), on obtient la durée de l'état bas en microsecondes que l'on multiplie par deux pour obtenir la durée de la période complète.

On termine le code avec un affichage de la vitesse de rotation en RPM (Rotations Par Minute).

Le code complet avec commentaires :

 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
/**
 * Tachymétre minimaliste avec une carte Arduino
 */

/* constantes pour la broche de mesure */
const byte PIN_SIGNAL = 2;

/** Fonction setup() */
void setup() {
  
  /* Initialise le port série */
  Serial.begin(115200);
  
  /* Met la broche en entrée */  
  pinMode(PIN_SIGNAL, INPUT_PULLUP);
}

/** Fonction loop() */
void loop() {
 
  /* Mesure la durée de la (demi) période */
  unsigned long periode = pulseIn(PIN_SIGNAL, LOW, 1000000) * 2;
  
  /* Affiche le résultat de la mesure en RPM */
  Serial.println(1000000 / periode * 60);
  delay(1000);
}

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

En résumé

Cette solution a l'avantage d'être simple, mais elle reste très naïve. Si le signal n'est pas symétrique (temps haut = temps bas), la mesure est fausse.

Le vrai avantage de cette méthode (outre sa simplicité), c'est qu'il est possible d'utiliser n'importe quelle broche pour faire une mesure. On peut donc câbler plusieurs ventilateurs sur une même carte Arduino est les mesurer les uns après les autres.

Je recommande l'utilisation de cette méthode si le signal mesuré est compatible (signal logique, avec temps haut = temps bas).

Mesure par interruption

La seconde façon de mesurer le signal en sortie du ventilateur consiste à utiliser une interruption. Le principe est simple : quand le signal passe d'un certain niveau logique à un autre, une fonction dans notre code est appelée. Celle-ci peut alors faire des calculs pour déterminer le temps écoulé entre deux interruptions.

C'est une solution fiable, avec l'avantage de s'exécuter en parallèle du programme normal. La mesure est constamment mise à jour au fil des interruptions. Attention cependant, il y a des pièges à éviter.

1
2
/* Variables pour la mesure */
volatile unsigned long periode = 0;

Tout d'abord il faut déclarer une variable globale pour stocker la mesure. Celle-ci doit obligatoirement être volatile pour éviter que le compilateur n'optimise les accès à cette variable. Piège n°1.

N.B Toute variable globale utilisée dans une interruption doit être volatile. Si ce n'est pas le cas, des bugs "fantômes" apparaitront de temps à autre sans explication apparente. Cela fait partie des joies du développement sur microcontrôleurs ;)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/** Fonction setup() */
void setup() {
  
  /* Initialise le port série */
  Serial.begin(115200);
  
  /* Met la broche en entrée */  
  pinMode(PIN_SIGNAL, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PIN_SIGNAL), tick, FALLING);
  
  /* Permet une première màj de "periode" */
  delay(500);
}

Dans la fonction setup(), on ajoute un appel à la fonction attachInterrupt(). Cette fonction permet de connecter une fonction, nommée tick() dans notre cas, à un événement extérieur, ici une transition HIGH vers LOW du signal (FALLING) sur la broche spécifiée.

N.B. Seules quelques broches sont utilisables avec attachInterrupt(). Sur une carte Arduino UNO, seules les broches D2 et D3 sont utilisables en interruption. C'est le piège n°2.

Piège n°3 : il est nécessaire d'attendre qu'une première série d'interruptions se déclenche avant de pouvoir avoir une mesure viable. Un simple delay() est suffisant dans la plupart des cas.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/** Fonction d'interruption pour la mesure entre deux fronts */
void tick() {
  static unsigned long previousMicros = 0;
  unsigned long currentMicros = micros();
  
  /* Calcul le temps écoulé depuis le précédent front */
  periode = currentMicros - previousMicros;
  
  /* Met à jour la variable pour la prochaine interruption */
  previousMicros = currentMicros;
}

La fonction tick() est appelée dans notre cas à chaque fois que le signal sur la broche passe de HIGH à LOW.

En utilisant micros() et une variable statique (qui conserve sa valeur entre deux appels à la fonction), il est possible de calculer le temps entre deux interruptions.

1
2
3
4
5
6
7
/** Fonction loop() */
void loop() {

  /* Affiche le résultat de la mesure en RPM */
  Serial.println(1000000 / periode * 60);
  delay(1000);
}

Le reste du code est similaire à celui du chapitre précédent.

N.B. Pour bien faire, il faudrait copier le contenu de la variable "periode" dans une variable temporaire en entourant l'accès à la variable avec noInterrupts() et interrupts(). Cela est dû au fait que l'interruption peut mettre à jour la variable pendant que le code de la fonction loop() lit cette même variable. On se retrouve alors avec une valeur corrompue. Piège n°3.

PS Le code ci-dessus ne gère pas la déconnexion ou l'arrêt du ventilateur en cours de mesure. Dans l'idéal, il faudrait garder dans une variable le temps de la dernière interruption pour retourner une valeur nulle si aucune mise à jour de la mesure n'a été effectuée après N secondes. Piège n°4.

Le code complet avec commentaires :

 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
/**
 * Tachymétre minimaliste avec une carte Arduino
 */

// Bug-fix pour Arduino 1.0.6
#define NOT_AN_INTERRUPT -1

/* constantes pour la broche de mesure */
const byte PIN_SIGNAL = 2;

/* Variables pour la mesure */
volatile unsigned long periode = 0;

/** Fonction d'interruption pour la mesure entre deux fronts */
void tick() {
  static unsigned long previousMicros = 0;
  unsigned long currentMicros = micros();
  
  /* Calcul le temps écoulé depuis le précédent front */
  periode = currentMicros - previousMicros;
  
  /* Met à jour la variable pour la prochaine interruption */
  previousMicros = currentMicros;
}

/** Fonction setup() */
void setup() {
  
  /* Initialise le port série */
  Serial.begin(115200);
  
  /* Met la broche en entrée */  
  pinMode(PIN_SIGNAL, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PIN_SIGNAL), tick, FALLING);
  
  /* Permet une première màj de "periode" */
  delay(500);
}

/** Fonction loop() */
void loop() {

  /* Affiche le résultat de la mesure en RPM */
  Serial.println(1000000 / periode * 60);
  delay(1000);
}

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

En résumé

C'est une méthode pour les utilisateurs expérimentés avec des besoins techniques très précis. C'est pour cela que je ne rentre pas plus dans les détails.

Cette solution convient à certains types de situations, mais elle a de nombreux défauts qu'il convient de bien comprendre avant de l'utiliser :

  • Nombre de mesures en parallèle limité par le nombre d'interruptions possibles,

  • Beaucoup de précautions à prendre quand on accède à une variable globale volatile,

  • La première série de mesures est toujours fausse,

  • Il faut gérer le cas où le signal s'arrête sinon on garde en mémoire une ancienne mesure indéfiniment (plusieurs solutions possibles à ce problème).

Bonus : Mesure d'une période complète avec pulseInCycle()

Pour conclure ce tutoriel, je vous propose une variante de la méthode n°1 avec pulseIn(), sauce Carnet du Maker.

 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
unsigned long pulseInCycle(byte pin, byte startState, unsigned long timeout) {
 
  /* Compute low-level bitmask and port for direct I/O reading */
  byte bitmask = digitalPinToBitMask(pin);
  volatile byte* port = portInputRegister(digitalPinToPort(pin));
  byte mask = startState ? bitmask : 0;

  /* Get the starting time (for the timeout handling) */
  unsigned long startMicros = micros();

  /* Wait the end of the current starting state (avoid partial measure) */
  while ((*port & bitmask) == mask) {
    if (micros() - startMicros > timeout)
      return 0;
  }
  
  /* Wait for the next starting state */
  while ((*port & bitmask) != mask) {
    if (micros() - startMicros > timeout)
      return 0;
  }
  
  /* Start the counter */
  unsigned long start = micros();

  /* Measure the starting state duration */
  while ((*port & bitmask) == mask) {
    if (micros() - startMicros > timeout)
      return 0;
  }

  /* Measure the ending state duration */
  while ((*port & bitmask) != mask) {
    if (micros() - startMicros > timeout)
      return 0;
  }

  /* Compute the total duration */
  return micros() - start;
}

Le code ci-dessus est particulièrement indigeste et ne fonctionne qu'avec les cartes Arduino classiques. C'est normal, c'est du code "bas niveau" utilisant des fonctions internes du framework Arduino qu'aucun utilisateur ne devrait normalement utiliser.

Cette fonction que j'ai nommée pulseInCycle() fonctionne exactement comme pulseIn(), mais avec une petite différence : en plus de mesurer la durée du front spécifié, elle mesure aussi la durée du front inverse juste après.

Par exemple, pulseInCycle(broche, HIGH, 1000000) permet de mesurer la durée d'un cycle temps haut + temps bas sur la broche spécifiée avec une timeout d'une seconde. En remplaçant HIGH par LOW, on mesure la durée d'un cycle temps bas + temps haut.

C'est la solution la plus simple pour mesurer des signaux ayant un rapport cyclique variable.

Le code avec commentaires :

 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
/**
 * Tachymétre minimaliste avec une carte Arduino
 */

/* constantes pour la broche de mesure */
const byte PIN_SIGNAL = 2;

/** Fonction setup() */
void setup() {
  
  /* Initialise le port série */
  Serial.begin(115200);
  
  /* Met la broche en entrée */  
  pinMode(PIN_SIGNAL, INPUT_PULLUP);
}

/** Fonction loop() */
void loop() {
 
  /* Mesure la durée de la (demi) période */
  unsigned long periode = pulseInCycle(PIN_SIGNAL, HIGH, 1000000);
  
  /* Affiche le résultat de la mesure en RPM */
  Serial.println(1000000 / periode * 60);
  delay(1000);
}

unsigned long pulseInCycle(byte pin, byte startState, unsigned long timeout) {
 
  /* Compute low-level bitmask and port for direct I/O reading */
  byte bitmask = digitalPinToBitMask(pin);
  volatile byte* port = portInputRegister(digitalPinToPort(pin));
  byte mask = startState ? bitmask : 0;

  /* Get the starting time (for the timeout handling) */
  unsigned long startMicros = micros();

  /* Wait the end of the current starting state (avoid partial measure) */
  while ((*port & bitmask) == mask) {
    if (micros() - startMicros > timeout)
      return 0;
  }
  
  /* Wait for the next starting state */
  while ((*port & bitmask) != mask) {
    if (micros() - startMicros > timeout)
      return 0;
  }
  
  /* Start the counter */
  unsigned long start = micros();

  /* Measure the starting state duration */
  while ((*port & bitmask) == mask) {
    if (micros() - startMicros > timeout)
      return 0;
  }

  /* Measure the ending state duration */
  while ((*port & bitmask) != mask) {
    if (micros() - startMicros > timeout)
      return 0;
  }

  /* Compute the total duration */
  return micros() - start;
}

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Annexe : Vérifier la mesure de vitesse

Certains ventilateurs génèrent plus d'une impulsion par rotation. Dans mon cas, le ventilateur que j'utilise génère deux impulsions par rotation.

Stroboscope DIY avec un générateur de signaux et une LED de puissance

Stroboscope DIY

Pour déterminer le nombre d'impulsions par rotation, j'ai utilisé un stroboscope. Dans mon cas, il s'agit d'une led de puissance reliée à un générateur de signaux au lieu d'un vrai stroboscope. Je fais avec les moyens du bord.

Effet de gel avec un stroboscope

C'est magique

En faisant varier la vitesse de clignotement du stroboscope, on finit par arriver à une fréquence de clignotement où le ventilateur semble figé dans le temps. Pour rendre cet effet visuel plus visible, j'ai tracé un point blanc sur le ventilateur. Quand le point blanc ne bouge plus, c'est que le stroboscope clignote à exactement la même vitesse que la rotation du ventilateur.

Dans mon cas, le signal en sortie du ventilateur indiqué 50 rotations par seconde, mais le stroboscope indiqué lui seulement 25 rotations par seconde. 50 / 25 = 2 impulsions par rotation.

PS Un ventilateur classique génère entre une et quatre impulsions par rotation. Il n'y a pas de standard universel.

N.B. Sinon, solution plus simple et moins "bricolage", vous pouvez acheter un tachymètre laser à 25€ sur internet pour vérifier la justesse des mesures.

Conclusion

Ce tutoriel est désormais terminé.

Si ce tutoriel vous a plu, n'hésitez pas à le commenter sur le forum, à le partager sur les réseaux sociaux et à soutenir le site si cela vous fait plaisir.