Usando millis () y micros () dentro de una rutina de interrupción

13

La documentación para attachInterrupt()dice:

... millis()depende de las interrupciones para contar, por lo que nunca se incrementará dentro de un ISR. Como delay()requiere interrupciones para funcionar, no funcionará si se llama dentro de un ISR. micros()funciona inicialmente, pero comenzará a comportarse de manera errática después de 1-2 ms. ...

¿Cómo micros()difiere de millis()(excepto, por supuesto, por su precisión)? ¿La advertencia anterior significa que usar micros()dentro de una rutina de interrupción siempre es una mala idea?

Contexto: quiero medir la ocupación de pulso bajo , así que necesito activar mi rutina cuando mi señal de entrada cambia y registrar la hora actual.

Petr Pudlák
fuente

Respuestas:

16

Las otras respuestas son muy buenas, pero quiero dar más detalles sobre cómo micros()funciona. Es siempre lee el temporizador de hardware actual (posiblemente TCNT0), que se actualiza constantemente por el hardware (de hecho, cada 4 mu s debido a la pre-escalador de 64). Luego agrega el recuento de desbordamiento del temporizador 0, que se actualiza mediante una interrupción de desbordamiento del temporizador (multiplicado por 256).

Por lo tanto, incluso dentro de un ISR, puede confiar en la micros()actualización. Sin embargo, si espera demasiado , pierde la actualización de desbordamiento y el resultado devuelto disminuirá (es decir, obtendrá 253, 254, 255, 0, 1, 2, 3, etc.)

Esto es micros()- ligeramente simplificado para eliminar define para otros procesadores:

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());
}

El código anterior permite el desbordamiento (verifica el bit TOV0) para que pueda hacer frente al desbordamiento mientras las interrupciones están desactivadas, pero solo una vez ; no hay ninguna disposición para manejar dos desbordamientos.


TLDR;

  • No hagas demoras dentro de un ISR
  • Si debe hacerlo, puede cronometrar con micros()pero no millis(). También delayMicroseconds()es una posibilidad.
  • No demore más de 500 µs, o perderá un desbordamiento del temporizador.
  • Incluso los retrasos breves pueden hacer que pierda datos en serie entrantes (a 115200 baudios obtendrá un nuevo carácter cada 87 µs).
Nick Gammon
fuente
No se puede entender la declaración dada a continuación que se usa en micros (). ¿Podría por favor elaborar? Como se escribe ISR, el indicador TOV0 se borrará tan pronto como se ingrese ISR y, por lo tanto, la siguiente condición puede no ser verdadera. if ((TIFR0 & _BV (TOV0)) && (t <255)) m ++;
Rajesh
micros()No es un ISR. Es una función normal. El indicador TOV0 le permite probar el hardware para ver si se ha producido un desbordamiento del temporizador (pero aún no se ha procesado).
Nick Gammon
Como la prueba se realiza con interrupciones desactivadas, sabe que la bandera no cambiará durante la prueba.
Nick Gammon
Entonces, ¿quiere decir que después de cli () dentro de la función micros (), si se produce un desbordamiento, es necesario verificarlo? Eso tiene sentido. De lo contrario, aunque micros no es una función, TIMER0_OVF_vect ISR borrará TOV0 en TIFR0 es lo que era mi duda.
Rajesh
Además, ¿por qué TCNT0 se compara con 255 para ver si es menor que 255? Después de cli () si TCNT0 alcanza 255, ¿qué sucederá?
Rajesh
8

No está mal usar millis()o micros()dentro de una rutina de interrupción.

Que es incorrecto utilizar de forma incorrecta.

Lo principal aquí es que mientras estás en una rutina de interrupción "el reloj no está funcionando". millis()y micros()no cambiará (bueno, micros()inicialmente lo hará, pero una vez que pasa ese punto mágico de milisegundos donde se requiere una marca de milisegundos, todo se desmorona).

Por lo tanto, puede llamar millis()o micros()averiguar la hora actual dentro de su ISR, pero no espere que esa hora cambie.

Es esa falta de cambio en el tiempo que se le advierte en la cotización que proporciona. delay()confía en millis()cambiar para saber cuánto tiempo ha pasado. Como no cambia delay(), nunca puede terminar.

Así que, esencialmente millis(), y micros()le indicará el tiempo cuando el ISR se llama no importa cuando en su ISR se usan.

Majenko
fuente
3
No, micros()actualizaciones. Siempre lee el registro del temporizador de hardware.
Nick Gammon
4

La frase citada no es una advertencia, es simplemente una declaración sobre cómo funcionan las cosas.

No hay nada intrínsecamente malo en usar millis()o micros()dentro de una rutina de interrupción escrita correctamente.

Por otro lado, hacer cualquier cosa dentro de una rutina de interrupción escrita incorrectamente es, por definición, incorrecto.

Una rutina de interrupción que lleva más de unos pocos microsegundos para hacer su trabajo, con toda probabilidad, está escrita incorrectamente.

En resumen: una rutina de interrupción escrita correctamente no causará ni encontrará problemas con millis()o micros().

Editar: con respecto a "por qué micros ()" comienza a comportarse de manera errática "", como se explica en un " examen de la página web de la función de micros Arduino ", el micros()código en un Uno común es funcionalmente equivalente a

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

Esto devuelve un largo sin signo de cuatro bytes compuesto por los tres bytes más bajos timer0_overflow_county un byte del registro de conteo del temporizador-0.

El controlador de interrupciones timer0_overflow_countincrementa aproximadamente una vez por milisegundo TIMER0_OVF_vect, como se explica en un examen de la página web de la función arduino millis .

Antes de que comience un controlador de interrupciones, el hardware AVR desactiva las interrupciones. Si (por ejemplo) un controlador de interrupciones se ejecutara durante cinco milisegundos con las interrupciones aún deshabilitadas, se perderían al menos cuatro desbordamientos del temporizador 0. [Las interrupciones escritas en código C en el sistema Arduino no son reentrantes (capaces de manejar correctamente ejecuciones superpuestas múltiples dentro del mismo controlador) pero se podría escribir un controlador de lenguaje ensamblador reentrante que vuelva a activar las interrupciones antes de que comience un proceso lento.]

En otras palabras, los desbordamientos del temporizador no se "acumulan"; cada vez que ocurre un desbordamiento antes de que se haya manejado la interrupción del desbordamiento anterior, el millis()contador pierde un milisegundo y la discrepancia timer0_overflow_counta su vez también se micros()equivoca en un milisegundo.

Con respecto a "más corto que 500 μs" como límite de tiempo superior para el procesamiento de interrupción, "para evitar bloquear la interrupción del temporizador durante demasiado tiempo", podría subir a poco menos de 1024 μs (por ejemplo, 1020 μs) y millis()seguiría funcionando, la mayoría de las veces hora. Sin embargo, considero un controlador de interrupciones que toma más de 5 μs como un perezoso, más de 10 μs como perezoso, más de 20 μs como un caracol.

James Waldby - jwpat7
fuente
¿Podría por favor explicar "cómo funcionan las cosas", en particular por qué micros()"comenzar a comportarse de manera errática"? ¿Y qué quieres decir con "rutina de interrupción correctamente escrita"? Supongo que significa "menos de 500us" (para evitar el bloqueo de la interrupción del temporizador durante demasiado tiempo), "usar variables volátiles para la comunicación" y "no llamar al código de la biblioteca" tanto como sea posible, ¿hay algo más?
Petr Pudlák
@ PetrPudlák, ver edición
James Waldby - jwpat7