Calculer un débit à partir d'interruptions

L’auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonjour,

Je travaille avec une Arduino (une Controllino pour être exacte) et j’ai besoin de calculer régulièrement un débit d’eau. Le débimètre est relié à un pin d’interruption et envoie un signal tous les litres.

A plusieurs reprises, je suis tombé sur le code suivant (traduit d'ici):

byte sensorPin = 2;

volatile byte pulseCount = 0;  
float flowRate;
unsigned long oldTime = 0;

void pulseCounter()
{
  pulseCount++;
}

void setup()
{
  
  pinMode(sensorPin, INPUT);
  digitalWrite(sensorPin, HIGH);
  attachInterrupt(digitalPinToInterrupt(sensorPin), pulseCounter, FALLING);
}

void loop()
{
   
   if((millis() - oldTime) > 10000) // Calcule le débit toutes les 10 secondes environ
   { 
    // Désactive les interruptions
    detachInterrupt(digitalPinToInterrupt(sensorPin));
        
    // 1 pulse = 1L
    // L/min
    flowRate = 60000.0 / (millis() - oldTime) * pulseCount;

    oldTime = millis();
    pulseCount = 0;
    
    // Réactive les interruptions
    attachInterrupt(digitalPinToInterrupt(sensorPin), pulseCounter, FALLING);
  }
}

J’ai fait des tests et ça fonctionne bien. Seulement, désactiver les interruptions rend possibles ce genre de cas, avec un débit réel d’environ 1L/s :

  1. t=8.00qqch : pulseCount vaut 8
  2. t=9.00qqch : pulseCount vaut 9
  3. t=10 : désactiver les interruptions
  4. t=10.00qqch : le débitmètre envoie un signal non détecté
  5. Calculer le débit
  6. Réactiver les interruptions

Si bien que des fois, un litre passe à la trappe, selon si on calcule le débit pendant que le débitmètre nous contacte.

Or je ne comprends pas tellement pourquoi on désactive les interruptions. J’ai l’intuition que ce n’est ps génial de potentiellement changer la valeur des variables pendant notre calcul de débit, mais je ne parviens pas à trouver d’exemple problématique concret.

Merci d’avance. :)

Merci. :)

+0 -0

Cette réponse a aidé l’auteur du sujet

Salut,

On désactive les interruptions pour éviter qu’une interruption ne tombe pendant qu’on manipule pulseCount. Sans ça, une interruption peut tomber entre le calcul du flowRate et l’effacement de pulseCount, au quel cas on a raté une impulsion et on fausse le compte. Le problème est encore plus marqué quand on lit des variables sur plusieurs octets: La variable vaut 0x11FF On lit 0x11 l’interruption passe la valeur à 0x1200 On lit 0x00 On a lu 0x1100 alors que la valeur passe de 0x11FF à 0x1200.

Pourtant, ici, le remède a l’air pire que le mal. C’est un défaut d’implémentation: le fait de détacher et rattacher l’interruption efface les flags d’interruption. La bonne méthode consiste à masquer l’interruption (la désactiver, ou désactiver toutes les interruptions, ce qui se fait probablement en une instruction sur ce microcontrôleur), récupérer la valeur de pulse, réactiver les interruptions (sans effacer les flags d’interruption) puis faire le calcul avec la valeur qu’on a récupéré et sauvegardée localement. Ainsi, on bloque les interruptions pendant le moins de temps possible (ce qui est important si le temps de calcul dépasse la période d’interruption), et, quand on rétablit les interruptions, celles qui auraient du se produire pendant qu’on les avait désactivées arrivent (maximum une par source).

Le code devient alors:

byte sensorPin = 2;

volatile byte pulseCount = 0;  
float flowRate;
unsigned long oldTime = 0;

void pulseCounter()
{
  pulseCount++;
}

void setup()
{
  
  pinMode(sensorPin, INPUT);
  digitalWrite(sensorPin, HIGH);
  attachInterrupt(digitalPinToInterrupt(sensorPin), pulseCounter, FALLING);
}

void loop()
{
   
  if((millis() - oldTime) > 10000) // Calcule le débit toutes les 10 secondes environ
  {
    byte pulses;
    unsigned long time;
    // Désactive les interruptions
    noInterrupts();
    // Récupère les valeurs pour le calcul
    pulses = pulseCount;
    pulseCount = 0;
    time = millis();
    // Réactive les interruptions
    interrupts();
        
    // 1 pulse = 1L
    // L/min
    flowRate = 60000.0 / (time - oldTime) * pulses;

    oldTime = time;
  }
}

De cette façon, même si le calcul est lent, il se fait régulièrement, et on attrape toutes les interruptions.

+0 -0
Auteur du sujet

Merci pour la réponse. :)

Je ne comprends pas ce que tu entends par les flags d’interruption et donc ce que serait tes fonctions noInterrupts() et interrupts().

Couper les interruptions le moins longtemps possible est effectivement une très bonne idée.

+0 -0

Cette réponse a aidé l’auteur du sujet

Les GPIO de ton arduino sont un bloc logique (ou plusieurs), relativement indépendant du processeur, qui expose des données sur le bus mémoire du processeur, mais qui dispose aussi d’un signal pour indiquer au processeur que quelque chose est arrivé, qu’on nomme flag d’interruption. Quand tu fais attachInterrupt, beaucoup de choses se passent. Parmi elles, la lib arduino va écrire dans les registres du bloc GPIO pour lui dire de lever le flag quand il voit un front descendant passer sur la pin sensorPin. A partir de là, quand le bloc GPIO voit ce front descendant, il lève le flag, le processeur passe en mode d’interruption, le code arduino regarde quels flags sont levés, il voit le flag du GPIO, se souvient qu’on lui avait dit d’appeler pulseCounter dans ce cas, le fait, puis repasse le flag à 0 (ou peut être qu’il passe le flag à 0 avant d’appeler pulseCounter, pour éviter de rater 2 fronts très proches).

noInterrupts dit au processeur d’ignorer les flags d’interruption, ce qui évite que les données changent pendant qu’on y accède. Il n’a aucun effet sur le bloc GPIO, qui va donc lever le flag si le front arrive. Quand on dit interrupts, le flag est donc déjà levé, et le processeur vient traiter l’interruption (et toutes les interruptions différentes survenues pendant le temps où on a masqué les interruptions).

detachInterrupt a un comportement différent, il vient dire au bloc GPIO de ne plus générer d’interruptions, donc si un front survient pendant le calcul, on le rate.

note: pourquoi je dis flag et pas drapeau ? Parce que le bit du registre pour le consulter s’appelle généralement IF, pour interruption flag, et en plus c’est plus court à écrire.

Édité par Jacen

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte