Comptage impulsion fournit par générateur de fonction

Bonjour à tous,

Afin de m'entrainer à manipuler la fonction millis(); j'ai réalisé un petit montage qui à l'objectif suivant :

  • Compter les impulsions de mon générateur de fonction en 1 seconde et en faisant varier la fréquence.
  • Faire clignoter une Led Néopixel à la vitesse des impulsions.

Pour l'équipement :

  • Carte Méga 2560
  • Un ruban Néopixel
  • Un générateur de fonction
  • Un oscilo pour la visualisation du signal

Voici le schéma :

Voici les réglages du générateur et le visuel oscilo :

Voici le code :

const byte pinInterruption = 19;

volatile int compteurImpulsion = 0;

const unsigned long tempoClignotementLed = 25; 
unsigned long tempsEcouler = millis();

unsigned long tempoAffichageFrequence = 1000;
unsigned long tempsEcoulerFrequence = 0;

// __________________________  Bibliothèque Néopixel _________________________________

#include <Adafruit_NeoPixel.h>

#define ledPin 9
#define ledCount 17

Adafruit_NeoPixel strip(ledCount, ledPin, NEO_GRB + NEO_KHZ800);

void setup() {

Serial.begin(115200);

strip.begin();
strip.show();
strip.setBrightness(60);

pinMode(pinInterruption, INPUT);
attachInterrupt(digitalPinToInterrupt(pinInterruption), detectionInterruption, FALLING);

}

void loop(){

 if( millis() - tempsEcoulerFrequence >= tempoAffichageFrequence){
    Serial.println(compteurImpulsion);
   compteurImpulsion = 0;
    tempsEcoulerFrequence = millis();  
  }
}

void detectionInterruption(){

static boolean ledOn; // Pour garder l'état de la LED
	
	if (millis() - tempsEcouler >= tempoClignotementLed) // Si période
	{
		ledOn = !ledOn; // Inverser l'état de la LED
		
		if (ledOn) // Allumer la LED
		{
      
          strip.setPixelColor(2, 0, 0, 255);
          strip.show();
          compteurImpulsion++;
		}
		else // Eteindre la LED
		{
          strip.setPixelColor(2, 0, 0, 0);
          strip.show();
		
		}
		tempsEcouler = millis(); 
	}
}

Après essai, le programme fonctionne bien, c'est à dire qu'à 0Hz, je n'ai pas de clignotement et le compteur reste à 0 toutes les secondes, à 1000Hz, j'ai un clignotement lent et mon compteur passe à 4 et ainsi de suite jusqu'à un certains point ( le compteur bloque autour des 17 à 19 impulsions max et ne monte pas plus haut ).
Une idée sur ce problème ?

Bonjour

Attention : le réglage actuel du générateur de signaux produit un signal carré alternatif entre -2,5 V et +2,5 V
C'est confirmé par l'oscilloscope.

...Il n'est pas certain que ça fonctionne longtemps en envoyant en entrée une tension inférieure à -0,5V :rage:

solution : pour obtenir un signal carré entre 0 V et 5 V régler à +2,5V l'offset sur le générateur, cela semble être fait dans la simulation.

Dans detectionInterruption() il faut se contenter d'incrémenter compteurImpulsion car sinon les autres actions peuvent de faire rater des interruptions. Tous les autres traitements doivent se faire dans loop() pour cela, dans detectionInterruption() tu peux positionner un booléen qui signalera à loop() qu'une IT a été générée.

Autre chose, la gestion des smartLEDs repose sur des timings assez critiques et en conséquence lors du transfert des consignes vers les LEDs le programme masque les interruptions donc là aussi tu va rater des interruptions.

vous avez au moins deux problèmes

  • vous modifiez compteurImpulsion dans la loop sans faire de section critique. L'opération n'est pas atomique et si une interruption intervient pendant ce temps, votre variable peut se retrouver modifiée.

  • vous utilisez strip.show() avec un bandeau qui comporte 17 neopixels vous bloquez les interruptions pendant 50µs + 17 x 30µs = 560 µs ➜ pendant ce temps là vous pouvez rater une impulsion.

Merci pour tous ces conseils, je vais retenter avec une seul led pour voir et améliorer le code.

essayez déjà avec la LED de la carte (pin 13) sans utiliser la bibliothèque neopixels

Ok, je vais y aller par étape pour voir l'amélioration. Merci

Désolé, j'ai pris des photos à titre d'illustration et je n'ai pas réglé le générateur, j'ai été un peu trop vite dans la fabrication du post.

J'ai réussi à créer un code qui fonctionne bien, le comptage des impulsions ne beug pas, l' affichage en Hz est égal au retour sur moniteur série et les flashs led ( la led 13 + led Néopixel ) réagissent bien aux variations d'impulsions.

const byte pinInterruption = 19;
bool etatInterruption = false;

volatile int compteurImpulsion = 0;
const unsigned long tempoClignotementLed = 25; 
unsigned long tempsEcouler = millis();

unsigned long tempoAffichageFrequence = 1000;
unsigned long tempsEcoulerFrequence = 0;

// __________________________  Bibliothèque Néopixel _________________________________

#include <Adafruit_NeoPixel.h>

#define ledPin 9
#define ledCount 17

Adafruit_NeoPixel strip(ledCount, ledPin, NEO_GRB + NEO_KHZ800);

const byte led = 13;


void setup() {

Serial.begin(115200);

strip.begin();
strip.show();
strip.setBrightness(60);

pinMode(led, OUTPUT);
digitalWrite(led, LOW);

pinMode(pinInterruption, INPUT);
attachInterrupt(digitalPinToInterrupt(pinInterruption), detectionInterruption, FALLING);

}

void loop(){

  if( millis() - tempsEcoulerFrequence >= tempoAffichageFrequence){
    Serial.println(compteurImpulsion);
    compteurImpulsion = 0;
    tempsEcoulerFrequence = millis();  
  }
  
  
  if( etatInterruption == true ){
    clignotementLed();
  }
}

void clignotementLed(){

static boolean ledOn; // Pour garder l'état de la LED
	
	if (millis() - tempsEcouler >= tempoClignotementLed) // Si période
	{
		ledOn = !ledOn; // Inverser l'état de la LED
		
		if (ledOn) // Allumer la LED
		{
          digitalWrite(led, HIGH);
        
          strip.setPixelColor(2, 0, 0, 255);
          strip.show();
		}
		else // Eteindre la LED
		{
          digitalWrite(led, LOW);
          strip.setPixelColor(2, 0, 0, 0);
          strip.show();
		
		}
		tempsEcouler = millis(); 
    etatInterruption = false;
	}
}

void detectionInterruption(){
  compteurImpulsion++;
  etatInterruption = true;
}

Merci pour vos conseils

il faudrait quand même une section critique pour compteurImpulsion (et pour faire propre etatInterruption).

Commet peut on lier une variableetatInterruption; à une interruption sans la faire passer dans la fonction d'interruption ( detectionInterruption() ) elle même ?

Dans la loop quand vous faites

vous prenez un risque qu'une interruption intervienne pile au moment ou le compilateur traite l'instruction compteurImpulsion = 0; ou lors de l'impression. Comme il y a plusieurs octets à mettre à 0, l'opération n'est pas atomique et si l'interruption se déclenche, vous pourriez vous retrouver avec une valeur bizarre dans le compteur

une approche classique sur petit AVR est de bloquer les interruptions pour quelques microsecondes quand on touche à ces valeurs partagées pour faire une copie avant remise à 0 et ensuite on travaille sur la copie.

  if( millis() - tempsEcoulerFrequence >= tempoAffichageFrequence){
    unsigned int copieCompteurImpulsion;
    // --------- SECTION CRITIQUE ---------
    noInterrupts();
    copieCompteurImpulsion = compteurImpulsion;
    compteurImpulsion = 0;
    interrupts();
    // ------------------------------------
    Serial.println(copieCompteurImpulsion);
    tempsEcoulerFrequence = millis();  
  }

si une interruption arrive pendant qu'on les a bloqué, on ne crée pas ainsi de souci de double accès à la variable et cette interruption sera prise en compte juste à la sortie après le interrupts() donc vous ne la ratez pas. (vous en rateriez si plusieurs interruption avaient eu lieu pendant l'exécution de la section critique, c'est pour cela qu'on la restreint aux minimum nécessaire)

Super, merci pour l'explication, ça me parait très clair :+1: et pour le coup logique !

Par contre, prenons le cas ou je surveilles deux capteurs de compte tours à effet hall, qui auraient deux interruptions :

pinMode(pinInterruptionCapteurUn, INPUT);
attachInterrupt(digitalPinToInterrupt(pinInterruptionCapteurUn), detectionInterruptionUn, FALLING);

pinMode(pinInterruptionCapteurDeux, INPUT);
attachInterrupt(digitalPinToInterrupt(pinInterruptionCapteurDeux), detectionInterruptionDeux, FALLING);

La section critique

noInterrupts();
// ------------------
// ------------------
interrupts();

Ça bloque les deux interruptions ou on peut sélectionner l'interruption que l'on souhaite manipuler même si j'ai bien compris qu'en laissant une interruption en route et l'autre bloquée ne change pas le risque de louper des interruptions :sweat_smile:

oui

Si vous voulez afficher les infos de vos capteurs à des moments différents et que vous avez pour chacun un test

if( millis() - tempsEcoulerFrequenceCapteur1  >= tempoAffichageFrequenceCapteur1){
  ...
  tempsEcoulerFrequenceCapteur1 = millis();
}

if( millis() - tempsEcoulerFrequenceCapteur2  >= tempoAffichageFrequenceCapteur2){
  ...
  tempsEcoulerFrequenceCapteur2 = millis();
}

rien ne vous empêche d'avoir dans chaque if la section critique et de ne faire la copie et remise à 0 que du compteur qui vous intéresse

si vous avez un seul if pour les deux, vous n'avez qu'une seule section critique où vous faites la copie et remise à zéro des deux capteurs et comme chaque interruption a son drapeau, même si les 2 venaient à être déclenchées pendant que vous copiez leur valeur, les ISR seraient ensuite appelées dans l'ordre de priorité déterminé par la puce (sur 328P d'un UNO par exemple, le vecteur d'interruption INT0 (pin 2) est prioritaire par rapport à INT1 (pin 3), ce qui signifie que si les deux interruptions se produisent en même temps, l'interruption sur la pin 2 (INT0) sera traitée en premier).