Questão:
O `millis ()` é afetado por longos ISRs?
Anonymous Penguin
2014-09-29 07:49:44 UTC
view on stackexchange narkive permalink

Tenho um projeto que usa timers e interrompe com frequência. Muito tempo de CPU é gasto lidando com ISRs por um longo período de tempo.

Isso afetaria o código dentro do loop principal que depende da função millis (), uma vez que uma quantidade significativa de tempo é gasta processando os ISRs ?

Além disso, qualquer coisa baseada em millis () também irá falhar? Eu acho que a biblioteca servo (baseada no novo software) e talvez até mesmo o software serial (?) Também pode ter problemas de tempo. Embora eu não tenha certeza se ambos são baseados nisso.
Trzy respostas:
Anonymous Penguin
2014-09-29 07:49:44 UTC
view on stackexchange narkive permalink

Depois de vasculhar o núcleo, parece que o Arduino atualiza millis () com um temporizador de 8 bits: ele usa overflow com um valor de prescaler de 64. Em termos mais simples, ele está definido de modo que um determinado trecho de código (o ISR) é executado aproximadamente uma vez por milissegundo em um sistema de 16 MHz (e proporcionalmente com menos frequência em sistemas de clock mais lento).

Quando esse ISR é executado, ele incrementa uma variável que é usado pela função millis () .

Então, mesmo se algum código ininterrupto estiver sendo executado por uma fração de milissegundo, não importa, pois o bit para esse vetor de interrupção ainda permanecerá definido.

No entanto, há um problema quando o ISR é executado com menos frequência do que deveria ser acionado. Isso pode ser o resultado do uso de noInterrupts () por um longo período de tempo ou quando muitas outras interrupções estão sendo executadas e não há CPU suficiente para todas as tarefas. Se muitos dos outros ISRs têm prioridades de interrupção mais altas, é possível que o código para millis () nunca seja executado. Nesse caso, você teria problemas mais sérios do que isso, mas é algo para se ter em mente ao escrever o código.

A regra geral dos ISRs ainda se aplica: mantenha-o curto !!!

Nota: se você precisar de extrema precisão, a função integrada não é a melhor escolha. Um RTC é uma boa ideia para manter o tempo a longo prazo.

Eu acho que você pode ter interrupções, interrompendo outras rotinas de interrupções (chamando `sei ()` dentro do `ISR`). Mas é uma ladeira escorregadia. Por exemplo. overflows de memória podem ocorrer muito mais rápido.
`micros ()` parece ter o mesmo problema e, em meu teste, deu a mesma hora errada que `millis ()`.
O problema não se limita aos ISRs. A [função write ()] (https://github.com/adafruit/Adafruit_TLC59711/blob/master/Adafruit_TLC59711.cpp#L63) na biblioteca TLC59711 de Adafruit desativa interrupções por possivelmente muitos milissegundos, fazendo com que minha tentativa inicial de cronometrar esta função usando `micros ()` falhar.
@UlrichStern, Tehee, adafruit faz você pensar que sua biblioteca é super rápida, pausando seu "benchmark de tempo"; D
@Paul, ao cronometrar a biblioteca de Adafruit, o valor de retorno de `micros ()` ainda aumentou o suficiente para que o tempo de execução não estivesse obviamente errado e eu fui enganado por um tempo. :) (Adafruit não desabilitou interrupções para me enganar, eu acho, veja o [Wiki] da minha biblioteca (https://github.com/ulrichstern/Tlc59711/wiki#how-does-the-tlc59711-know-when-a- transferência está concluída).)
Nick Gammon
2016-01-18 11:40:32 UTC
view on stackexchange narkive permalink

Isso afetaria o código dentro do loop principal que depende da função millis (), uma vez que uma quantidade significativa de tempo é gasta processando os ISRs?

Para colocar uma figura para você ...


Frequência do ISR de estouro do Timer 0 sendo chamado

O código chamado pelo (padrão) vetor de interrupção de estouro do Timer 0 (TIM0_OVF_vect) é usado por millis e micros para ajudar a retornar seus resultados. Seu objetivo é contar estouros do Timer 0.

Para obter resultados precisos, este ISR não deve perder um estouro. O cronômetro está configurado para tick a cada 4 µs em um sistema de 16 MHz (por causa do prescaler de 64: 64 * 62,5 ns = 4000 ns ) e overflow a cada 1,024 ms (1024 µs) - porque transborda após 256 tiques ( 4 µs * 256 = 1024 µs ).

Visto que há apenas um sinalizador de estouro, se o ISR perde um estouro, então ambos os milis e micros estarão fora de 1,024 ms (ou mais, se ele perder vários estouros).

Para ter certeza de capturar esse estouro, o ISR, portanto, deve ser chamado dentro 1,024 ms (provavelmente um pouco menos por causa do tempo necessário para entrar no ISR, então diga: 1 ms).


Prioridade de interrupção

No Atmega328P (como usado em o Arduino Uno) estas são as prioridades do vetor de interrupção:

  1 Reinicializar 2 Solicitação de interrupção externa 0 (pino D2) (INT0_vect) 3 Solicitação de interrupção externa 1 (pino D3) (INT1_vect) 4 Alteração de pino Solicitação de interrupção 0 (pinos D8 a D13) (PCINT0_vect) 5 pinos de troca Solicitação de interrupção 1 (pinos A0 a A5) (PCINT1_vect) 6 Solicitação de interrupção de alteração de pino 2 (pinos D0 a D7) (PCINT2_vect) 7 Interrupção de tempo limite do watchdog (WDT_vect) 8 Timer / Contador2 Compare a correspondência A (TIMER2_COMPA_vect) 9 Timer / Counter2 Compare Match B (TIMER2_COMPB_vect) 10 Timer / Counter2 Overflow (TIMER2_OVF_vect)
11 Temporizador / counter1 Captura de Eventos (TIMER1_CAPT_vect) 12 Temporizador / counter1 Comparar Combinar A (TIMER1_COMPA_vect) 13 Temporizador / counter1 Comparar Combinar B (TIMER1_COMPB_vect) 14 Temporizador / counter1 Overflow (TIMER1_OVF_vect) 15 Temporizador / Counter0 Comparar Combinar A (TIMER0_COMPA_vect) 16 Temporizador / Counter0 Compare Match B (TIMER0_COMPB_vect) 17 Timer / Counter0 Overflow (TIMER0_OVF_vect) 18 Transferência serial SPI completa (SPI_STC_vect) 19 USART Rx Complete (USART_RX_vect) 20 USART, Registro de dados vazio (USART_UDRE_vect) 21 USART, Tx ADVect_Tx) Conversão completa 21 USART, Tx ADX Completo (ADC_vect) 23 EEPROM Pronto (EE_READY_vect) 24 Comparador Analógico (ANALOG_COMP_vect) 25 Interface Serial de 2 fios (I2C) (TWI_vect) 26 Armazenar memória de programa pronta (SPM_READY_vect)  

Você pode ver a partir dessa lista que TIMER0_OVF_vect é o número 17 dessa lista, então qualquer interrupção de prioridade anterior teria precedência, por exemplo, interrupções externas , interrupções de mudança de pino, os outros temporizadores (porém não SPI / Serial / ADC / I2C).

Se um estouro acabou de acontecer , você teria praticamente 2 ms de graça ( porque você tem 1 ms antes do próximo e mais 1 ms antes de precisar notá-lo). No entanto, se o estouro está prestes a acontecer , então você só tem o período de carência de 1 ms.

Menciono isso porque se você tiver um evento de interrupção externa 0 (INT0_vect) e o ISR leva 500 µs, e então o evento de interrupção externa 1 (INT1_vect) durante esse tempo (então outro ISR será atendido), então a interrupção do temporizador pode ser bloqueada por um tempo.

É por isso que todos os ISRs devem ser curtos. Não é bom o suficiente que alguns deles sejam.


Reativando interrupções

Eu recomendo fortemente contra isso. As bibliotecas não são projetadas para serem reentrantes e, uma vez que você comece a habilitar interrupções em um ISR, você pode descobrir que ele próprio é chamado novamente quando estiver no meio do caminho sendo chamado pela primeira vez. Você também pode interromper uma função de biblioteca (por exemplo, memcpy) que não foi projetada para ela.

E, claro, se você estiver reativando interrupções dentro de um ISR porque o ISR leva muito tempo: bem, essa é a situação exata em que você pode disparar este re -entrada.


Mais informações:

mpflaga
2014-09-29 18:39:58 UTC
view on stackexchange narkive permalink

ABSOLUTAMENTE

qualquer ISR longo impedirá o incremento de millis (). Pode ser uma distorção duvidosamente lenta deste relógio.

A função ISR sempre começa com interrupções de bloqueio com o comando cli () no início e termina habilitando-as com o comando sei (). Bloquear outros ISRs junto com a interrupção OverFlow do Timer0 que é usada para aumentar o contador de milis.

Qualquer uso prolongado de noInterrupts () ou cli () ou uso implícito com ISRs deve ter uma duração cuidadosamente curta. Não há nenhum dano geral em executar interrupts () ou sei () com no ISR, para permitir interrupções novamente.

Exemplo:

na biblioteca VS1053 Eu tenho dados Solicitação em um PIN INT. Que o ISR pode ler do SdCard, o que pode levar muito tempo. Então eu reativo as interrupções () antes de ler o SdCard, mas ainda no ISR.

No seu exemplo, o loop principal nunca é retomado durante aquela longa rotina de interrupção, certo? Então, isso é uma desvantagem. É bom saber que habilitar interrupções dentro de outras interrupções não é tão "perigoso" quanto eu pensava.
Acho que apenas um ISR (ou cadeia de ISRs) com mais de um milissegundo impedirá a interrupção da forma. Você pode bloqueá-lo por 0,5 ms e ele será atualizado na próxima coisa a fazer (interromper).


Estas perguntas e respostas foram traduzidas automaticamente do idioma inglês.O conteúdo original está disponível em stackexchange, que agradecemos pela licença cc by-sa 3.0 sob a qual é distribuído.
Loading...