Utilisation de millis () et micros () dans une routine d'interruption

13

La documentation de attachInterrupt()dit:

... millis()compte sur les interruptions pour compter, donc il n'augmentera jamais à l'intérieur d'un ISR. Puisque delay()nécessite des interruptions pour fonctionner, il ne fonctionnera pas s'il est appelé à l'intérieur d'un ISR. micros()fonctionne initialement, mais commencera à se comporter de façon irrégulière après 1-2 ms. ...

En quoi micros()diffère millis()(sauf bien sûr pour leur précision)? L'avertissement ci-dessus signifie-t-il que l'utilisation d' micros()une routine d'interruption est toujours une mauvaise idée?

Contexte - Je veux mesurer une faible occupation des impulsions , je dois donc déclencher ma routine lorsque mon signal d'entrée change et enregistrer l'heure actuelle.

Petr Pudlák
la source

Réponses:

16

Les autres réponses sont très bonnes, mais je veux expliquer comment cela micros()fonctionne. Il lit toujours le temporisateur matériel actuel (éventuellement TCNT0) qui est constamment mis à jour par le matériel (en fait, toutes les 4 µs à cause du prédimensionneur de 64). Il ajoute ensuite le nombre de dépassements de temporisation 0, qui est mis à jour par une interruption de dépassement de temporisation (multiplié par 256).

Ainsi, même à l'intérieur d'un ISR, vous pouvez compter sur la micros()mise à jour. Cependant, si vous attendez trop longtemps, vous manquez la mise à jour du débordement, puis le résultat renvoyé diminuera (c'est-à-dire que vous obtiendrez 253, 254, 255, 0, 1, 2, 3, etc.)

Ceci est micros()- légèrement simplifié pour supprimer les définitions des autres processeurs:

unsigned long micros() {
    unsigned long m;
    uint8_t oldSREG = SREG, t;
    cli();
    m = timer0_overflow_count;
    t = TCNT0;
    if ((TIFR0 & _BV(TOV0)) && (t < 255))
        m++;
    SREG = oldSREG;
    return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}

Le code ci-dessus permet le débordement (il vérifie le bit TOV0) afin qu'il puisse faire face au débordement lorsque les interruptions sont désactivées mais une seule fois - il n'y a aucune disposition pour gérer deux débordements.


TLDR;

  • Ne faites pas de retards à l'intérieur d'un ISR
  • Si vous devez les faire, vous pouvez alors chronométrer avec micros()mais pas millis(). C'est aussi delayMicroseconds()une possibilité.
  • Ne retardez pas plus de 500 µs environ, ou vous manquerez un débordement de minuterie.
  • Même de courts délais peuvent vous faire manquer des données série entrantes (à 115200 bauds, vous obtiendrez un nouveau caractère toutes les 87 µs).
Nick Gammon
la source
Incapable de comprendre la déclaration ci-dessous utilisée dans micros (). Pourriez-vous s'il vous plaît développer? Puisque l'ISR est écrit, l'indicateur TOV0 sera effacé dès que l'ISR est entré et par conséquent, la condition ci-dessous peut ne pas devenir vraie!. si ((TIFR0 & _BV (TOV0)) && (t <255)) m ++;
Rajesh
micros()n'est pas un ISR. C'est une fonction normale. L'indicateur TOV0 vous permet de tester le matériel pour voir si le dépassement de temporisation s'est produit (mais n'a pas encore été traité).
Nick Gammon
Comme le test est effectué avec des interruptions désactivées, vous savez que l'indicateur ne changera pas pendant le test.
Nick Gammon
Donc, vous voulez dire après cli () à l'intérieur de la fonction micros (), si un débordement se produit, cela doit être vérifié? C'est logique. Sinon, même si le micros n'est pas une fonction, TIMER0_OVF_vect ISR effacera TOV0 dans TIFR0, c'est ce que je doutais.
Rajesh
Aussi pourquoi TCNT0 est comparé à 255 pour voir s'il est inférieur à 255? Après cli () si TCNT0 atteint 255, que se passera-t-il?
Rajesh
8

Il n'est pas faux d'utiliser millis()ou micros()dans une routine d'interruption.

Il est faux de les utiliser incorrectement.

L'essentiel ici est que pendant que vous êtes dans une routine d'interruption "l'horloge ne tourne pas". millis()et micros()ne changera pas (enfin, au micros()début, mais une fois qu'il aura dépassé ce point de milliseconde magique où un tick de milliseconde est requis, tout s'effondre.)

Vous pouvez donc certainement appeler millis()ou micros()connaître l'heure actuelle dans votre ISR, mais ne vous attendez pas à ce que l'heure change.

C'est ce manque de changement dans le temps qui est mis en garde dans le devis que vous fournissez. delay()repose sur le millis()changement pour savoir combien de temps s'est écoulé. Puisqu'il ne change pas, il ne delay()peut jamais finir.

Donc, essentiellement millis()et micros()vous dira l'heure à laquelle votre ISR a été appelé, peu importe quand vous l'utilisez dans votre ISR.

Majenko
la source
3
Non, les micros()mises à jour. Il lit toujours le registre du temporisateur matériel.
Nick Gammon
4

La phrase citée n'est pas un avertissement, c'est simplement une déclaration sur la façon dont les choses fonctionnent.

Il n'y a rien de intrinsèquement mauvais à utiliser millis()ou à l' micros()intérieur d'une routine d'interruption correctement écrite.

D'un autre côté, faire quoi que ce soit dans une routine d'interruption mal écrite est par définition mal.

Une routine d'interruption qui prend plus de quelques microsecondes pour faire son travail est, selon toute vraisemblance, mal écrite.

En bref: une routine d'interruption correctement écrite ne causera ni ne rencontrera de problèmes avec millis()ou micros().

Edit: Concernant "pourquoi micros ()" commence à se comporter de façon erratique "", comme expliqué dans une page Web " examen de la fonction micros Arduino ", le micros()code sur un Uno ordinaire est fonctionnellement équivalent à

unsigned long micros() {
  return((timer0_overflow_count << 8) + TCNT0)*(64/16);
}

Cela retourne une longueur non signée de quatre octets composée des trois octets les plus bas de timer0_overflow_countet d'un octet du registre de comptage du temporisateur-0.

Le timer0_overflow_countest incrémenté environ une fois par milliseconde par le TIMER0_OVF_vectgestionnaire d'interruption, comme expliqué dans un examen de la page Web de la fonction arduino millis .

Avant qu'un gestionnaire d'interruption ne commence, le matériel AVR désactive les interruptions. Si (par exemple) un gestionnaire d'interruption devait s'exécuter pendant cinq millisecondes avec des interruptions toujours désactivées, au moins quatre dépassements de temporisation 0 seraient manqués. [Les interruptions écrites en code C dans le système Arduino ne sont pas réentrantes (capables de gérer correctement plusieurs exécutions qui se chevauchent dans le même gestionnaire) mais on pourrait écrire un gestionnaire de langage d'assemblage réentrant qui réactive les interruptions avant qu'il ne commence un processus long.]

En d'autres termes, les débordements de minuterie ne «s'empilent» pas; chaque fois qu'un débordement se produit avant que l'interruption du débordement précédent n'ait été gérée, le millis()compteur perd une milliseconde, et l'écart timer0_overflow_countà son tour se micros()trompe également d'une milliseconde.

En ce qui concerne «moins de 500 μs» comme limite supérieure de temps pour le traitement des interruptions, «pour éviter de bloquer l'interruption du temporisateur trop longtemps», vous pourriez aller jusqu'à un peu moins de 1024 μs (par exemple 1020 μs) et millis()cela fonctionnerait toujours, la plupart des temps. Cependant, je considère un gestionnaire d'interruption qui prend plus de 5 μs comme un paresseux, plus de 10 μs comme paresseux, plus de 20 μs comme un escargot.

James Waldby - jwpat7
la source
Pourriez-vous expliquer comment les choses fonctionnent, en particulier pourquoi micros()«commencer à se comporter de façon irrégulière»? Et que voulez-vous dire par «routine d'interruption correctement écrite»? Je suppose que cela signifie "plus court que 500us" (pour éviter de bloquer trop longtemps l'interruption du minuteur), "utiliser des variables volatiles pour la communication" et "ne pas appeler le code de bibliothèque" autant que possible, y a-t-il autre chose?
Petr Pudlák
@ PetrPudlák, voir l'édition
James Waldby - jwpat7