Mesure d'une période entre 2 front montant via INT0

Miaou,

Soit le code suivant :

// Mesure période PWM pour UNO-R3 et utilisant INT0 et TIMER2_OVF

// Constantes
#define pINT0 2 // pin 2 (broche INT0)

const unsigned int presc = 1024; // valeur du préscaler

volatile uint8_t chrono = 0; // nb de tick timer2 en plus du nb d'overflow.
volatile unsigned long int cptr = 0; // Nombre d'overflow durant la mesure de la période.
volatile unsigned long int duree = 0;

void setup()
{
  // Ouverture de la liaison série
  Serial.begin(115200);
  while(!Serial);
  Serial.println("Liaison série ouverte à 115200 bauds");
  Serial.println("");
  
  // Configuration des pins
  pinMode(pINT0, INPUT_PULLUP); // Place la pin 2 (broche INT0) en entrée
  pinMode(LED_BUILTIN, OUTPUT); // Répétiteur visuel de la mesure
  digitalWrite(LED_BUILTIN, LOW); // Extinction Led
  
  // Configuration des registres d'interruption
  SREG  &= 0b01111111; // Interdiction générale des interruptions

  EIMSK |= 0b00000001; // Autorise l'interruption INT0 (INT0 = 1 ds EIMSK)
  EIFR |= 0b00000001; // Reset le flag d'interruption INT0
  EICRA |= 0b00000011; // INT0 sera générée sur un front montant (ISC[1:0] = 11 ds EICRA) détecté sur la broche 2 (broche INT0).

  TCCR2A &= 0b00000000; // Assure que la broche INT0 est I/O (non PWM)
  TCCR2B &= 0b00000000; // Reset registre du timer2 (préscalaire)
  TCCR2B |= 0b00000111; // Initialise le timer2 avec un préscalaire de 1024 (f = 15.625kHz T = 64µS avec l'horloge cadencée à 16MHz)

  TIMSK2 |= 0b00000000; // Interdit l'interruption d'overflow du timer2 (sera autorisé par la routine de INT0)
  TIFR2 |= 0b00000001; // Reset le flag d'interruption d'overflow du timer2

  SREG  |= 0b10000000; // Autorisation générale des interruptions

  // Pause
  delay(250);
}

void loop()
{
  static unsigned long int tps = millis();
  // Sortie de Led builtin svt période (durée) mesurée
  if(millis() - tps > 250)
  {
    if(duree > 45 && duree < 55) digitalWrite(LED_BUILTIN, HIGH); // Période basse
    if(duree > 95 && duree < 105) digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // Période intermédiaire
    if(duree > 145 && duree < 155) digitalWrite(LED_BUILTIN, LOW); // Période haute
    tps = millis();
  }
  
  // Détermination et affichage de la durée de la période
  if((SREG & 0b10000000) == 0b00000000) // Si les interruptions sont interdites
  {
    duree = (1000 * (chrono + 256 * cptr) * presc) / (F_CPU * 256);  // F_CPU = Freq du CPU (16MHz), duree en ms
    Serial.print("Durée : ");
    Serial.print(duree, DEC);
    Serial.println(" ms");
    Serial.print("Chrono = ");
    Serial.println(chrono);
    Serial.print("Compteur = ");
    Serial.println(cptr);
    cptr = 0;
    chrono = 0;
    SREG |= 0b10000000; // Autorisation des interruptions
  }
}

ISR(TIMER2_OVF_vect)
{
  cptr+=1; // Compte le nombre d'overflow du timer 2 depuis le top chrono
}

ISR(INT0_vect)
{
  if((TIMSK2 & 0b00000001) == 0b00000000) // Si l'interruption d'overflow du timer 2 est interdite
  {
    Serial.println("1er Front montant détecté");    
    TCNT2 = 0; // Top chrono (reset du timer 2)
    TIMSK2 |= 0b00000001; // Autorisation de l'interruption d'overflow du timer 2
  }
  else // Si l'interruption d'overflow du timer 2 est autorisée
  {
    chrono = TCNT2; // Arrêt chrono
    Serial.println("2nd Front montant détecté");
    TIMSK2 &= 0b11111110; // Interdiction de l'interruption d'overflow du timer 2
    SREG &= 0b01111111; // Interdiction des interruptions
  }
}

Son ambition est de mesurer le temps qui s'écoule entre 2 fronts montant sur la Pin2 (INT0).
A l'essai, j'ai bien les messages "1er et 2nd Front montant" mais pas d'affichage de la durée...
Il semble que le code :

SREG &= 0b01111111; // Interdiction des interruptions

dans le "else" de la routine INT0 ne soit pas opérant, empêchant le calcul de la durée dans la boucle loop()...
Est-ce normal ? Ai-je mal compris quelque chose dans le fonctionnement du registre SREG ?
A vous lire,

Le chat.

salut

je n'ai pas décortiqué le détail de tes écritures dans les registres mais je pense que tu as dû les travailler ? en tout cas c'est bien documenté donc je pense que tu n'as pas fait d'erreur ...
... sauf l'interdiction des interruptions en sortie d'ISR :
tu crois les interdire en faisant ça mais elles sont automatiquement réactivées de façon hard par l'instruction assembleur RETI qui clôt l'ISR.
si tu veux absolument les interdire dans ta boucle afin ne pas être «perturbé» il faut le faire à ce moment-là.

[astuce]
pour remplacer SREG &= 0b01111111; // Interdiction des interruptions il existe cli(); , ainsi que sei(); pour les réactiver, c'est plus court.
[/astuce]

sinon, pourquoi INT0 (PD2) ? ICP1 (PB0) n'est pas libre ?
et pourquoi TIMER2 ? tu te sers ailleurs de TIMER1 ?
tu pourrais utiliser la fonction Capture sur T1 ...

du coup:

est toujours faux et rien ne n'affiche.

c'est vrai que ce n'était que sous-entendu dans ce que j'ai écrit, mais après-tout pourquoi ne pas le préciser ...

La fonction pulseIn() permet de faire cela

https://docs.arduino.cc/language-reference/en/functions/advanced-io/pulseIn/


faire un

dans une ISR et vouloir mesurer le temps précisément, c'est un peu antinomique...

Pas vraiment. pulseIn() mesure entre le front passé en argument et le front de sens opposé.

Ah oui j’ai dit n’importe quoi - pas pour deux fronts montants

@kazhdu

Quelle ordre de durée cherchez vous à mesurer ? Super court, précision à la micro seconde ou de l’ordre la ms ou quelques secondes ?

peut être un code comme celui ci fonctionnerait

volatile uint32_t overflows = 0;
volatile uint32_t ticks = 0;
volatile bool ready = false;

ISR(TIMER1_OVF_vect) {overflows++;}

ISR(INT0_vect) {
  static bool firstEdge = true;
  if (firstEdge) {
    TCNT1 = 0;
    overflows = 0;
    firstEdge = false;
  } else {
    uint16_t t = TCNT1;
    ticks = ((uint32_t)overflows << 16) | t;  // pas besoin de section critique l'autre ISR est suspendue
    ready = true;
    firstEdge = true;
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(2, INPUT_PULLUP);

  TCCR1A = 0;           // Timer1 en mode normal, PWM désactivé
  TCCR1B = _BV(CS10);   // préscaler = 1, timer cadencé à F_CPU
  TIMSK1 = _BV(TOIE1);  // active l’interruption de débordement du Timer1 (TIMER1_OVF_vect)

  EICRA = _BV(ISC01) | _BV(ISC00);  // INT0 déclenchée sur front montant
  EIMSK = _BV(INT0);                // active l’interruption externe INT0 (INT0_vect)
}

void loop() {
  if (ready) {
    noInterrupts();
    uint32_t t = ticks;
    ready = false;
    interrupts();
    uint32_t us = (uint64_t)t * 1000000ULL / F_CPU;

    Serial.print(F("Periode entre 2 fronts = "));
    Serial.print(us);
    Serial.println(F(" µs"));
  }
}

Ça reprend votre principe d'utiliser un timer pour compter le nombre de ticks entre les 2 fronts. J'utilise le timer1 qui est sur 16 bits, il débordera moins souvent. TCNT1 est mis à 0 lors de la réception du premier front ainsi que le compteur de débordements, puis on attend le second front et on peut fabriquer le nombre de ticks d'horloge que l'on a eu en combinant le nombre d'overflow (décalés de 16 bits) et le compteur lors de l'ISR et on met un drapeau qui tient sur un octet (donc la lecture sera atomique) qui dit que le résultat est prêt. Dans la loop on surveille ce drapeau et quand il devient vrai on copie dans une section critique le nombre de ticks, et on calcule la durée en µs.

Notez que pendant l’exécution d’une ISR, par exemple celle du Timer1, aucune autre interruption ne peut se déclencher, y compris celle associée à INT0. L’interruption INT0 sera simplement marquée comme en attente si son drapeau est positionné pendant l’ISR du Timer1. Elle sera exécutée immédiatement après la fin de l’ISR du Timer1 donc le compteur overflows n'a pas besoin d'être protégé dans l'ISR INT0_vect.

Tout d'abord, merci à tous pour toutes vos réponses. :handshake:

Effectivement, je me suis souvenu après le post que RETI figurait à la fin de ISR().
J'ai donc modifié mon code d'une manière assez similaire à ce que propose J-M-L (merci pour votre code que je vais comparer au mien pour apprendre. ^^)

Le but est de mesurer des périodes "longues" (comparées à celle des 16MHz de l'A328P) de l'ordre de 100ms à +/- 5ms (la variable chrono est d'ailleurs dispensable à ce compte là...)

J'utilise le Timer2 parce que je veux ensuite transférer un code semblable dans un ATTiny85 SANS utiliser son Timer0 (laissé dispo pour millis() ) et que son Timer 1 est un Timer 8 bit (l'ATTiny85 n'a que 2 Timer). Même raison pour l'utilisation de INT0...

Voilà, je vous remercie une fois encore pour vos éclairages. :slight_smile:
Le chat.

Dans ce cas une simple attente va faire le job…


const byte pin = 2;
unsigned long t1, t2;
unsigned long dt;

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

void loop() {
  while (digitalRead(pin) == LOW); // attendre front montant
  t1 = millis();
  while (digitalRead(pin) == HIGH); // attendre la fin du front
  while (digitalRead(pin) == LOW);  // attendre le prochain front montant
  t2 = millis();
  dt = t2 - t1;
  Serial.println(dt);
 while (digitalRead(pin) == HIGH); // attendre la fin du front
}

On peut aussi mesurer tous les fronts:

Question subsidiaire: y a-t-il autre chose à faire pendant ces temps?
Si c'est non, cela suffit
Si c'est oui, on peut tout mesurer par l'int0 en utilisant MTobjects qui devrait fonctionner car le ATTiny85 a le même timer 0 que l'Uno, Mega... (publicité personnelle)
Je n'ai pas d'ATTiny85, je ne peut faire l'essai.

Éventuellement, utiliser la librairie digitalWriteFast (qui fait aussi la lecture). Elle réduira l'erreur car elle est plus rapide.

Oui mais s’il n’est pas à 5ms près… (le digitalRead c’est 3/4 micro secondes)

J'ai regardé la doc du Atiny 85, il n'y a pas grand chose à ajouter dans la bibliothèque, il n'y a qu'un seul registre de différent. Je peux l'adapter si on me dit si cela fonctionne. Le programme pourrait ressembler à:

#include <MTobjects.h> // V1.1.0 Voir http://arduino.dansetrad.fr/MTobjects

const uint8_t PIN_FRONTS = 2; // Mesure des fronts sur la broche 2

unsigned long depart; //Temps de départ pour la mesure

void mesure(void) // Appelée sur font montant
{
  Serial.print("Durée : ");
  Serial.print(millis() - depart);
  Serial.println(" ms");
  depart = millis();
}

MTbutton Front(PIN_FRONTS, PAS_D_ACTION, mesure, HIGH); // Mise en place du bouton

void setup()
{
  Serial.begin(115200);
}

void loop(){}

Le seul intérêt est si il y a autre chose à faire pendant les comptages.

En fait, je suis intéressé par voir si MTobjects peut fonctionner sur Atiny85

Oups, j'ai oublié de préciser que je compte recevoir des données avec le protocole UART et que millis() ou micro() sont utilisés (d'où le fait que le Timer0 est réservé)... :face_with_open_eyes_and_hand_over_mouth:
Le chat...

L’ATTiny85 ne dispose pas d’UART matérielle.… vous allez mettre la pression sur les interruptions pour pas grand chose avec votre timer et ses débordements fréquents.

Si vous n’êtes pas à quelques ms près, utilisez simplement millis avec une petite machine à états pour ne pas être bloquant ce sera bcp plus simple…

Bonjour,

... mais offre une interface type Universal Serial Interface facilement implémentable en Langage C ou en Assembleur avec la prise en charge des 2 dernières interruptions les moins prioritaires 14: USI_START et 15: USI_OVF (cf. Simple ATtiny USI UART et la datasheet de l'ATtiny85)

La seule contrainte @ UART effectivement absent sur l'ATtiny85 est un fonctionnement Half Duplex car un unique registre est proposé pour la réception et l'émission

NB: Personnellement testé de 300 bauds à 19200 bauds sans problème (cf. le projet Micro-OS sur ATtiny85)

À suivre...

c'est tout à fait vrai aussi oui

faudra voir s'il y a une répercussion sur la stabilité en bauds avec une interruption toutes les 16 µs sur débordement du timer.

Bonjour,

Effectivement, si c'est pour comptabiliser le nombre de 16µS dans un 32 bits, l'It TIMER1_OVF_vect risque d'être occupée à plus de 50% et donc cela n'est pas acceptable

volatile uint32_t overflows = 0;

ISR(TIMER1_OVF_vect) {overflows++;}

Maintenant, le timer #1 peut être "préscalé" par puissances de 2 jusqu'à 1024 et en fonction de la résolution attendue (16 µS, 32 µS, 64 µS, etc, ...) cela "allègera" grandement ladite It TIMER1_OVF_vect

Remarque: Le "prescaling" ne devant pas atteindre la résolution de 1 mS car alors utiliser comme proposé dans le fil de discussion la fonction millis() qui doit réaliser la même chose sans se prendre la tête

NB: Sur mes projets s'appuyant sur l'uOS/ATtiny85, l'It de cadencement du timer #1 est fixée à 26 µS occupée à 30% (sous-multiple des durées des bits aux vitesses de 300 bauds à 19200 bauds dans le cas d'une gestion UART Logicielle) et je n'ai jamais eu de problème de "répercussion sur la stabilité en bauds" (dixit) - La tolérance du protocole UART étant entre 2% et 3% par expérience voire plus suivant les implémentations - dont l'une en half duplex ;-)

A suivre...