Usando millis () e micros () dentro de uma rotina de interrupção

13

A documentação para attachInterrupt()diz:

... millis()depende de interrupções para contar, para nunca aumentar dentro de um ISR. Como delay()requer interrupções para funcionar, ele não funcionará se for chamado dentro de um ISR. micros()funciona inicialmente, mas começará a se comportar de maneira irregular após 1-2 ms. ...

Em que micros()difere millis()(exceto, é claro, por sua precisão)? O aviso acima significa que usar micros()dentro de uma rotina de interrupção é sempre uma má idéia?

Contexto - Quero medir a baixa ocupação de pulsos , por isso preciso disparar minha rotina quando meu sinal de entrada mudar e registrar a hora atual.

Petr Pudlák
fonte

Respostas:

16

As outras respostas são muito boas, mas quero elaborar como micros()funciona. Ele sempre lê o cronômetro atual do hardware (possivelmente TCNT0) que é constantemente atualizado pelo hardware (de fato, a cada 4 µs por causa do pré-calibrador de 64). Em seguida, ele adiciona a contagem de estouro do timer 0, que é atualizada por uma interrupção de estouro do timer (multiplicada por 256).

Assim, mesmo dentro de um ISR, você pode confiar na micros()atualização. No entanto, se você esperar demais , perderá a atualização do estouro e o resultado retornado será reduzido (ou seja, você receberá 253, 254, 255, 0, 1, 2, 3 etc.)

Isso é micros()um pouco simplificado para remover definições para outros processadores:

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

O código acima permite o estouro (ele verifica o bit TOV0) para que ele possa lidar com o estouro enquanto as interrupções estão desativadas, mas apenas uma vez - não há previsão para lidar com dois estouros.


TLDR;

  • Não faça atrasos dentro de um ISR
  • Se você precisar fazê-las, poderá cronometrar com micros()mas não millis(). Também delayMicroseconds()é uma possibilidade.
  • Não demore mais de 500 µs ou mais, ou você perderá o estouro do temporizador.
  • Mesmo pequenos atrasos podem fazer com que você perca os dados seriais recebidos (em 115200 baud, você obterá um novo caractere a cada 87 µs).
Nick Gammon
fonte
Não é possível entender a declaração fornecida abaixo que é usada em micros (). Você poderia por favor elaborar? Como o ISR é gravado, o sinalizador TOV0 será apagado assim que o ISR for inserido e, portanto, a condição abaixo pode não se tornar verdadeira !. if ((TIFR0 & _BV (TOV0)) && (t <255)) m ++;
Rajesh
micros()não é um ISR. É uma função normal. O sinalizador TOV0 permite testar o hardware para verificar se o estouro do temporizador ocorreu (mas ainda não foi processado).
Nick Gammon
Como o teste é concluído com interrupções, você sabe que o sinalizador não será alterado durante o teste.
Nick Gammon
Então você quer dizer que após a função cli () dentro de micros (), se ocorrer um estouro, isso precisa ser verificado? Isso faz sentido. Caso contrário, mesmo que o micros não seja uma função, o TIMER0_OVF_vect ISR limpará TOV0 em TIFR0 é o que minha dúvida era.
quer
Também porque o TCNT0 é comparado com 255 para ver se é menor que 255? Após cli () se o TCNT0 atingir 255, o que acontecerá?
Rajesh
8

Não é errado usar millis()ou micros()dentro de uma rotina de interrupção.

Ele é errado usá-los incorretamente.

O principal aqui é que, enquanto você está em uma rotina de interrupção, "o relógio não está correndo". millis()e micros()não mudará (bem, micros()mudará inicialmente, mas assim que ultrapassar o ponto mágico de milissegundo em que é necessário um tique de milissegundo, tudo desmorona).

Portanto, você certamente pode ligar millis()ou micros()descobrir o horário atual no seu ISR, mas não espere que esse horário mude.

É essa falta de mudança no tempo que está sendo avisado na cotação que você fornece. delay()depende de millis()mudar para saber quanto tempo se passou. Uma vez que não muda, delay()nunca pode terminar.

Então, basicamente millis()e micros()irá dizer-lhe o tempo quando seu ISR foi chamado , não importa quando, em seu ISR você usá-los.

Majenko
fonte
3
Não, micros()atualizações. Ele sempre lê o registro do temporizador de hardware.
Nick Gammon
4

A frase citada não é um aviso, é apenas uma declaração sobre como as coisas funcionam.

Não há nada intrinsecamente errado em usar millis()ou micros()em uma rotina de interrupção escrita corretamente.

Por outro lado, fazer qualquer coisa dentro de uma rotina de interrupção incorretamente escrita é, por definição, errado.

Uma rotina de interrupção que leva mais do que alguns microssegundos para fazer seu trabalho é, com toda a probabilidade, gravada incorretamente.

Em resumo: uma rotina de interrupção escrita corretamente não causará ou encontrará problemas com millis()ou micros().

Edit: Quanto a "por que micros ()" começa a se comportar de maneira irregular "", conforme explicado em uma página da Web " exame da função micros do Arduino ", o micros()código em um Uno comum é funcionalmente equivalente a

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

Isso retorna um comprimento não assinado de quatro bytes, composto pelos três bytes mais baixos timer0_overflow_counte um byte do registro de contagem do timer-0.

Ele timer0_overflow_counté incrementado cerca de uma vez por milissegundo pelo TIMER0_OVF_vectmanipulador de interrupção, conforme explicado em um exame da página da função arduino millis .

Antes de um manipulador de interrupção começar, o hardware do AVR desativa as interrupções. Se (por exemplo) um manipulador de interrupções fosse executado por cinco milissegundos com as interrupções ainda desativadas, pelo menos quatro estouros de timer 0 seriam perdidos. [Interrupções escritas no código C no sistema Arduino não são reentrantes (capazes de manipular corretamente várias execuções sobrepostas no mesmo manipulador), mas é possível escrever um manipulador reentrante de linguagem assembly que reativa as interrupções antes de iniciar um processo demorado.]

Em outras palavras, os estouros de temporizador não "empilham"; sempre que um estouro ocorre antes que a interrupção do estouro anterior tenha sido tratada, o millis()contador perde um milissegundo e a discrepância, timer0_overflow_countpor sua vez, também é micros()equivocada por um milissegundo.

Em relação a "menor que 500 μs" como limite de tempo superior para o processamento de interrupções, "para evitar o bloqueio da interrupção do timer por muito tempo", você poderia subir até 1024 μs (por exemplo, 1020 μs) e millis()ainda assim funcionaria, a maioria das Tempo. No entanto, considero um manipulador de interrupções que leva mais de 5 μs como preguiçoso, mais de 10 μs como preguiçoso, mais de 20 μs como caracol.

James Waldby - jwpat7
fonte
Você poderia explicar como "as coisas funcionam", em particular por que micros()"começar a se comportar de maneira irregular"? E o que você quer dizer com "rotina de interrupção escrita corretamente"? Eu suponho que significa "menor que 500us" (para evitar o bloqueio da interrupção do timer por muito tempo), "usando variáveis ​​voláteis para comunicação" e "não chamando o código da biblioteca" o máximo possível, existe mais alguma coisa?
Petr Pudlák 24/03
@ PetrPudlák, ver edição
James Waldby - jwpat7