Watchdog e EEPROM do Arduino

Comparador, Watchdog e EEPROM do Arduino: Três recursos pouco utilizados 8

Todos sabem que o Arduino conta com diversos recursos, portas digitais, analógicas, timers, PWM entre outros. Comparador analógico, Watchdog e EEPROM do Arduino são alguns recursos muito úteis porém pouco utilizados. São fáceis de serem utilizados e podem facilitar bastante a vida em alguns projetos. Este post descreve esses recursos e apresenta um exemplo de aplicação. Um projeto é montado para demonstrar o Watchdog e EEPROM do Arduino e outro para exemplificar o comparador analógico.

Watchdog e EEPROM do Arduino

Para exemplificar o Watchdog e EEPROM do Arduino, vamos precisar de:

Watchdog

O watchdog é um recurso simples mas com um objetivo vital para sistemas embarcados: garantir que o software volte para uma condição conhecida em caso de algum erro ou situação inesperada.

Trata-se de um timer que, uma vez ligado, precisa ser rearmado periodicamente. Se ele não for rearmado dentro de um tempo limite, o microcontrolador é automaticamente reiniciado.

Para usar o watchdog precisamos descer um nível abaixo da biblioteca do Arduino, usando a biblioteca do compilador C. Por este motivo, as informações abaixo se aplicam somente aos modelos baseados nos microprocessadores AVR (ATmega e ATtinny). No seu programa você precisa incluir o header wdt.h:

#include <avr/wdt.h>

Para ligar o watchdog, chame a função wdt_enable, informando o tempo limite:

wdt_enable(WDTO_2S);

O valor no exemplo acima corresponde a 2 segundos; estão também disponíveis WDTO_8S, WDTO_4S, WDTO_1S, WDTO_500MS, WDTO_250MS,WDTO_120MS, WDTO_60MS, WDTO_30MS e WDTO_15MS. A temporização do watchdog é fornecida por um oscilador interno ao microcontrolador e independe do clock de execução das instruções.

Para rearmar o watchdog, basta chamar wdt_reset(). Normalmente você irá colocar chamadas nos pontos normais de execução, para garantir que não ocorra o reset durante o funcionamento correto.

O exemplo abaixo permite ver o watchdog em funcionamento, para rodá-lo conecte um botão entre o pino 3 e GND. Se você mantiver o botão apertado por mais de 2 segundos o Arduino reiniciará.

Watchdog e EEPROM do Arduino exemplo 1

#include <avr/wdt.h>

const int pinBotao = 3;
const int pinLED = 13;

void setupt() {
  wdt_enable(WDTO_2S);
  pinMode (pinBotao, INPUT_PULLUP);
  pinMode (pinLED, OUTPUT);
  Serial.begin(9600);
  Serial.println("Reset");
  digitalWrite (pinLED, HIGH);  // para indicar o reset
  delay (500);
  digitalWrite (pinLED, LOW);
}

void loop() {
  wdt_reset();
  while (digitalRead(pinBotao) == LOW) {
    delay (10);
  }
}

EEPROM

Vamos olhar o terceiro tipo de memória no microcontrolador: a EEProm. Já estamos acostumados com a Flash (onde o programa é gravado conectando o Arduino a um micro e aí permanece mesmo sem alimentação) e a Ram (onde estão as nossas variáveis e cujo conteúdo é perdido quando desligamos o Arduino). A EEProm combina a não volatilidade da Flash (manutenção dos dados sem alimentação) com algo próximo à flexibilidade de leitura e escrita da Ram.

A EEProm deve ser usada para armazenar informações que queremos que sobrevivam a um desligamento, como parâmetros ou variáveis importantes. O ATmega328 possui generosos 1K de EEProm (generosos considerando que temos 2K de Ram!). Ao planejar o uso da EEProm, é preciso levar em conta que existe um limite de 100.000 escritas em cada posição (se você escrever a mesma posição a cada segundo ela começaria a falhar em pouco mais de um dia) e que a escrita e leitura são bem mais demoradas que na Ram (principalmente a escrita).

A EEProm é suportada pela IDE do Arduino através da biblioteca EEProm (https://www.arduino.cc/en/Reference/EEPROM).

No exemplo abaixo, vamos salvar na EEProm o número de vezes que foi apertado o botão conectado entre o Digital 3 e GND. Para evitar um número excessivo de escritas, vamos limitar o contador a 1000 e usar o método update() para atualizar a EEProm (este método só faz a gravação se o valor for diferente do já existentes). Uma preocupação no uso da EEProm é ter certeza que o conteúdo dela é válido. Neste exemplo a informação é gravada duas vezes, a segunda dela negada. No início do programa é conferido se as duas gravações são compatíveis, se não forem a contagem é iniciada com zero. Ao armazenar um conjunto mais complexo de dados você pode usar um checksum ou CRC para verificar a integridade dos dados.

#include <EEPROM.h>

const int pinBotao = 3;
const int endCont = 10;

int contador = 0;

void setup() {
  pinMode (pinBotao, INPUT_PULLUP);
  Serial.begin(9600);
  leContador();
  mostraContador();
}

void loop() {
  delay(100);
  if (digitalRead(pinBotao) == LOW) {
    if (contador < 1000) {
      contador++;
    }
    gravaContador();
    mostraContador();
    while (digitalRead(pinBotao) == LOW) {
      delay(100);
    }
  }
}

// Mostra valor atual do contador
void mostraContador() {
  Serial.print("Contador: ");
  Serial.println(contador);
}

// Le o contador da EEProm
void leContador() {
  uint16_t val1, val2;

  val1 = EEPROM.read(endCont) + (EEPROM.read(endCont+1) << 8);
  val2 = EEPROM.read(endCont+2) + (EEPROM.read(endCont+3) << 8);
  if (val1 != ~val2) {
    Serial.println("Nao encontrou valor valido, zerando contador");
    contador = 0;
  } else {
    contador = val1;
  }
}

// Grava o contador na EEProm
void gravaContador() {
  uint16_t val1, val2;

  val1 = (uint16_t) contador;
  val2 = ~val1;
  EEPROM.update(endCont, val1 & 0xFF);
  EEPROM.update(endCont+1, val1 >> 8);
  EEPROM.update(endCont+2, val2 & 0xFF);
  EEPROM.update(endCont+3, val2 >> 8);
}

Comparador Analógico

É bastante comum o uso do Conversor Analógico Digital (ADC) para analisar uma entrada analógica, mas poucos sabem (ou lembram) que o ATmega possui também um Comparador Analógico que permite executar algumas tarefas de uma forma mais simples e rápida que o ADC.

Para exemplificar o comparador analógico, vamos precisar de:

O coração deste periférico é um comparador cuja saída é “1” quando uma de suas entradas analógicas (AIN0) for maior que a outra (AIN1). O sinal de saída pode gerar uma interrupção ou disparar o timer/contador1.

A entrada AIN0 pode ser o pino “Digital 6” do Arduino ou uma referência interna de 1.1V. Já a entrada AIN1 pode ser selecionada entre o pino “Digital 7” ou uma  entradas analógicas do Arduino (A0 a A5). Atenção que para usar as entradas analógicas é necessário desligar o ADC.

A biblioteca do Arduino não possui rotinas de suporte ao comparador analógico, portanto é necessário interagir diretamente com os registradores do microcontrolador (o código a seguir é para o ATmega328 usado no Arduino UNO, Nano e Pro Mini). Para facilitar, criei uma classe para encapsular estes acessos aos registradores.

Como exemplo, vamos ligar um potenciômetro no pino A0 (que corresponderá a AIN1) e comparar a tensão obtida com a referência interna. O LED do Arduino sinaliza quando a tensão em A0 for menor que a referência. Repare que a comparação é feita toda pelo hardware, o software só é acionado quando a tensão cruza o limite. Um esquema parecido poderia ser usado, por exemplo, para detectar que uma bateria está baixa.

Watchdog e EEPROM do Arduino exemplo 2

const int pinLED = 13;

class C_COMPARADOR
{
  public:
  
  typedef enum { 
    SEM = bit(ACIS0), 
    MUDANCA = 0, 
    SUBIDA = bit(ACIS1) | bit(ACIS0), 
    DESCIDA  = bit(ACIS1)
  } COMP_INT ;

  // Inicia o comparador analógico
  // refInterna: true   usa referência interna na entrada + do comparador
  //             false  usa AIN0 (Digital 6)   na entrada + do comparador
  // entrada:    -1     usa AIN1 (Digital 7)   na entrada - do comparador
  //             0 a 5  usa o pino A0 a A5     na entrada - do comparador
  // interrup    seleciona o modo de interrupcao
  void inicia(bool refInterna, int entrada, COMP_INT interrup) {
    byte val;
    
    cli();  // sem interrupções
  
    if (entrada == -1) {
      ADCSRB &= ~bit(ACME);   // Não usar MUX
    } else {
      ADCSRA &= ~bit(ADEN);  // ADC desligado para usar o MUX
      ADCSRB |= bit(ACME);   // Usar MUX
      ADMUX = entrada;
    }
  
    val = bit(ACI);        // Limpar interrupção
    if (refInterna) {
      val |= bit(ACBG);    // Usar referência interna
    }
    if (interrup != SEM) {
      val |= bit(ACIE);    // Habilitar interrupção
      val += interrup;
    }
    ACSR = val;            // Programa o comparador
    
    sei(); // habilita interrupções
  }

  inline bool saida() {
    return (ACSR & bit(ACO)) == 0;
  }
};
static C_COMPARADOR COMPARADOR;

void setup() {
  pinMode (pinLED, OUTPUT);
  digitalWrite (pinLED, LOW);
  COMPARADOR.inicia (true, 0, COMPARADOR.MUDANCA);
}

void loop() {
  // nada a ser feito
  delay(100);
}

// Interrupção do comparador
ISR (ANALOG_COMP_vect) {
  digitalWrite (pinLED, COMPARADOR.saida()? HIGH : LOW);
}

Curtiu ver aprender sobre o Comparador analógico, Watchdog e EEPROM do Arduino? Ajude-nos a melhorar o blog comentando abaixo sobre este tutorial.

Faça seu comentário

Acesse sua conta e participe

8 Comentários

  1. Parabens pelo ‘tutorial”. Ele me ajudou muitona compreensão e futuro uso destes recursos.

  2. Olá, obrigado pelos exemplos. Eu tenho uma dúvida quanto ao watchdog: entendi aqui, com meus botões, que o acionamento do watchdog é hardware, não software. No entanto, você tem como confirmar isso para mim? Eu gostaria de saber, de fato, caso o processador trave (não alguma sub-rotina mal escrita), por exemplo se houver uma falha de alimentação ou estouro de memória, o watchdog atua também? Ou tudo para de funcionar?

    1. Sim, é hardware. Ele vai ressetar o Arduino se o seu programa não chamar wdt_reset() no tempo configurado, qualquer que seja o motivo. Note que nem todos estouros de memória podem fazer o seu programa se perder a ponto de não chamar mais wdt_reset()… No caso da alimentação, existe um outro recurso de hardware, o Brown-out Detector, que reinicia o microcontrolador se a tensão de alimentação cair abaixo de um certo valor.

  3. Olá DQ e Rosana da Equipe MakerHero. A função Watchdog é uma aplicação muito boa para resetar o sistema quando o loop trava. Estou com uma problema que até você poderiam criar um poste de como resolver. Eu não consigo remover ou desabilitar a macro (avr/wdt.h) após utiliza-lá. Você poderiam me ajudar?

    1. Luciano, a macro wdt_disable() desliga o watchdog, desde que ele não esteja forçado através dos fuses.

  4. Bom artigo! Obrigado.

    1. Olá Fábio!

      É muito bom saber que você gostou!

      Abraço!
      Rosana – Equipe MakerHero

  5. Muuuuuito útil.
    Simples e objetivo.
    Obrigado.