Use o Watchrog AVR como ISR normal

17

Estou tentando entender o cronômetro do watchdog da série ATTinyX5. Então, as coisas que li fizeram parecer que você poderia usá-lo para fazer o programa fazer algo específico a cada N segundos, mas nunca realmente mostrou como. Outros fizeram parecer que ele só redefiniria o chip, a menos que algo no código redefinisse sua contagem enquanto isso (que parece ser o uso "normal").

Existe alguma maneira de usar o WDT como você faria com TIMER1_COMPA_vect ou similar. Percebi que ele tem um modo de tempo limite de 1 segundo e eu adoraria poder usá-lo para fazer algo acontecer a cada 1 segundo no meu código (e, de preferência, durma no meio).

Pensamentos?

* Atualização: * Desde que foi solicitado, estou me referindo à seção 8.4 da folha de dados do ATTinyX5 . Não que eu o entenda completamente, que é o meu problema ...

Adam Haile
fonte
1
+1 por pensar fora da caixa. Polegares extras, se você adicionar um link à folha de dados do AVR.
jippie

Respostas:

23

Você certamente pode. De acordo com a folha de dados, o timer do watchdog pode ser configurado para redefinir o MCU ou causar uma interrupção quando acionado. Parece que você está mais interessado na possibilidade de interrupção.

O WDT é realmente mais fácil de configurar do que um temporizador normal, pelo mesmo motivo que é menos útil: menos opções. Ele roda em um relógio de 128kHz calibrado internamente, o que significa que seu tempo não é afetado pela velocidade do relógio principal do MCU. Ele também pode continuar sendo executado nos modos de sono mais profundo para fornecer uma fonte de ativação.

Analisarei alguns exemplos de dados, bem como algum código que usei (em C).

Arquivos e definições incluídos

Para começar, você provavelmente desejará incluir os dois arquivos de cabeçalho a seguir para que as coisas funcionem:

#include <avr/wdt.h>        // Supplied Watch Dog Timer Macros 
#include <avr/sleep.h>      // Supplied AVR Sleep Macros

Além disso, eu uso a macro <_BV (BIT)> que é definida em um dos cabeçalhos padrão do AVR como a seguir (que pode ser mais familiar para você):

#define _BV(BIT)   (1<<BIT)

Início do Código

Quando o MCU é iniciado pela primeira vez, você normalmente inicializa a E / S, configura os temporizadores, etc. Em algum lugar aqui é um bom momento para garantir que o WDT não tenha causado uma redefinição porque poderia fazê-lo novamente, mantendo o programa em um loop instável.

if(MCUSR & _BV(WDRF)){            // If a reset was caused by the Watchdog Timer...
    MCUSR &= ~_BV(WDRF);                 // Clear the WDT reset flag
    WDTCSR |= (_BV(WDCE) | _BV(WDE));   // Enable the WD Change Bit
    WDTCSR = 0x00;                      // Disable the WDT
}

Configuração WDT

Depois de configurar o restante do chip, refaça o WDT. A configuração do WDT requer uma "sequência programada", mas é realmente fácil de fazer ...

// Set up Watch Dog Timer for Inactivity
WDTCSR |= (_BV(WDCE) | _BV(WDE));   // Enable the WD Change Bit
WDTCSR =   _BV(WDIE) |              // Enable WDT Interrupt
           _BV(WDP2) | _BV(WDP1);   // Set Timeout to ~1 seconds

Obviamente, suas interrupções devem ser desativadas durante esse código. Certifique-se de reativá-los depois!

cli();    // Disable the Interrupts
sei();    // Enable the Interrupts

Rotina de serviço de interrupção WDT A próxima coisa a se preocupar é lidar com o WDT ISR. Isso é feito da seguinte maneira:

ISR(WDT_vect)
{
  sleep_disable();          // Disable Sleep on Wakeup
  // Your code goes here...
  // Whatever needs to happen every 1 second
  sleep_enable();           // Enable Sleep Mode
}

MCU Sleep

Em vez de colocar o MCU em repouso dentro do WDT ISR, recomendo simplesmente ativar o modo de suspensão no final do ISR e pedir ao programa PRINCIPAL que coloque o MCU em repouso. Dessa forma, o programa está realmente saindo do ISR antes que ele entre no modo de suspensão, e ele será ativado e retornará diretamente ao ISR do WDT.

// Enable Sleep Mode for Power Down
set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // Set Sleep Mode: Power Down
sleep_enable();                     // Enable Sleep Mode  
sei();                              // Enable Interrupts 

/****************************
 *  Enter Main Program Loop  *
 ****************************/
 for(;;)
 {
   if (MCUCR & _BV(SE)){    // If Sleep is Enabled...
     cli();                 // Disable Interrupts
     sleep_bod_disable();   // Disable BOD
     sei();                 // Enable Interrupts
     sleep_cpu();           // Go to Sleep

 /****************************
  *   Sleep Until WDT Times Out  
  *   -> Go to WDT ISR   
  ****************************/

   }
 }
Kurt E. Clothier
fonte
WOW ... muito detalhado. Obrigado! Estou um pouco confuso com a seção sleep, onde você mostra o loop principal (se (MCUCR & _BV (SE)) {// Se o sono estiver ativado ... etc.) Estou confuso por que, na aparência principal, você desativar continuamente e ativar interrupções. E a parte na parte superior dessa seção (set_sleep_mode (SLEEP_MODE_PWR_DOWN);) Onde é que isso deve ser executado?
21713 Adam Haile
OK, a parte "set_sleep_mode (MODE)" deve estar na parte principal ANTES do loop principal, idealmente no outro código de inicialização em que você configura as portas de E / S, temporizadores, etc. Você realmente não precisa habilitar a opção_sleep (); nesse ponto, pois isso será feito após o seu primeiro gatilho WDT. DENTRO do loop principal, esse código de suspensão será executado apenas se a suspensão estiver ativada e não for totalmente necessário desativar / reativar as interrupções, apenas se você estiver executando o sleep_bod_disable (); Essa instrução IF inteira pode estar na parte inferior (mas ainda dentro) do loop MAIN após qualquer outro código que você estiver executando lá.
Kurt E. Clothier
Ok ... isso faz mais sentido agora. Apenas última coisa que eu estou confuso sobre o que é essa "sequência temporizada" é ...
Adam Haile
Nota lateral: Eu sei por que você gostaria de dormir, mas presumo que você não precise usar o WDT?
9118 Adam Haile #
Dê uma olhada nesta pequena seção da folha de dados: 8.4.1. Basicamente, para alterar o registro WDT, é necessário definir o bit de alteração e, em seguida, definir os bits WDTCR apropriados em tantos ciclos de clock. O código fornecido na seção Configuração do WDT faz isso ativando primeiro o bit de alteração do WD. E não, você não precisa usar a funcionalidade de suspensão com o WDT. Pode ser uma fonte de interrupção programada padrão por qualquer motivo que você desejar.
Kurt E. Clothier
1

De acordo com a folha de dados, é possível. Você pode até ativar uma interrupção e a redefinição. Se os dois estiverem ativados, o primeiro tempo limite do watchdog acionará a interrupção, o que fará com que o bit Interrupt Enable seja desabilitado (interrupção desabilitada). O próximo tempo limite reiniciará sua CPU. Se você ativar a interrupção diretamente após a execução, o próximo tempo limite (novamente) acionará apenas uma interrupção.

Você também pode ativar a interrupção e não ativar a redefinição. Você precisará definir o bit WDIE toda vez que a interrupção for acionada.

Tom L.
fonte
Hmmm .... acho que faz sentido. Vou tentar. Eu sempre gosto de usar coisas para que eles não tinham a intenção :)
Adam Haile
Na verdade, acho que é um design inteligente. Economiza um temporizador, mantendo a funcionalidade watchdog.
Tom
2
Esse tempo limite inicial do WDT e a interrupção subsequente também podem ser usados ​​com vantagem em alguns aplicativos que estão habilitando o WDT para a recuperação de interrupção real. Pode-se observar o endereço de retorno empilhado no WDT ISR para inferir o que o código estava tentando fazer quando ocorreu o "tempo limite inesperado".
Michael Karas
1

Isso é muito mais fácil do que o sugerido acima e em outros lugares.

Desde que o WDTONfusível não esteja programado (não está programado por padrão), você precisará apenas ...

  1. Defina o bit de ativação da interrupção do watchdog e o tempo limite no registro de controle do watchdog.
  2. Ativar interrupções.

Aqui está um exemplo de código que executará um ISR uma vez a cada 16ms ...

ISR(WDT_vect) {
   // Any code here will get called each time the watchdog expires
}

void main(void) {
   WDTCR =  _BV(WDIE);    // Enable WDT interrupt, leave existing timeout (default 16ms) 
   sei();                                           // Turn on global interrupts
   // Put any code you want after here.
   // You can also go into deep sleep here and as long as 
   // global interrupts are eneabled, you will get woken 
   // up when the watchdog timer expires
   while (1);
}

É isso mesmo. Como nunca habilitamos a redefinição do watchdog, nunca precisamos mexer nas seqüências programadas para desativá-la. O sinalizador de interrupção do watchdog é limpo automaticamente quando o ISR é chamado.

Se você deseja um período diferente a cada 1 segundo, pode usar esses valores aqui para definir os bits apropriados em WDTCR...

insira a descrição da imagem aqui

Observe que você precisa executar a sequência cronometrada para alterar o tempo limite. Aqui está o código que define o tempo limite para 1 segundo ...

   WDTCR = _BV(WDCE) | _BV(WDE);                   // Enable changes
   WDTCR = _BV(WDIE) | _BV( WDP2) | _BV( WDP1);    // Enable WDT interrupt, change timeout to 1 second
bigjosh
fonte
Não executar a sequência cronometrada durante a instalação salva uma linha de código - uma operação de leitura-modificação-gravação. A sequência inicial é recomendada na folha de dados "Se o Watchdog for ativado acidentalmente, por exemplo, por um ponteiro descontrolado ou condição de escurecimento" e o restante do meu código é específico para usar o WDT em combinação com um modo de suspensão, conforme solicitado por o OP. Sua solução não é mais simples, você apenas negligenciou o código recomendado / necessário da placa da caldeira.
Kurt E. Clothier
@ KurtE.Clothier Desculpe, apenas tentando dar o exemplo de trabalho mais simples!
bigjosh