Relógio digital no Arduino, com interrupções e uso do maior modo de economia de energia

Um relógio digital baseado em interrupções do Arduino, cuja CPU acorda só na hora de atualizar o display. Somos todos Ahmed!

O Arduino possui modos de economia de energia que paralisam a maior parte das funcionalidades do seu microcontrolador principal, até que um evento externo ocorra. Neste projeto, usei um módulo RTC (relógio de tempo real com bateria) para não apenas manter com precisão a hora certa enquanto o Arduino dorme, mas também para – uma vez por minuto – acordar o Arduino para que este atualize a hora exibida em um display digital.

Em termos de hardware, o meu relógio digital é bem simples, pois usa só módulos pré-prontos: um Arduino, um RTC e um display digital, além de um resistor de 10KΩ e 11 jumpers.

Em termos de software, ele é mais complexo do que precisaria ser: para atualizar a hora certa em um display, certamente não é necessário usar interrupções e modos de economia de energia. Mas aí entra meu interesse de aprendizado, a intenção de fazer um projeto futuro cuja funcionalidade exigirá essas técnicas, e o interesse de compartilhar esse aprendizado com vocês ;-)

Leia também: Data, hora e memória extra no Arduino com o módulo Tiny RTC e o chip DS1307.

Somos todos Ahmed.

Inúmeros chamados desta semana na Internet convidaram a uma forma bem acessível de ativismo: fazer e postar nossas versões de relógio digital, em solidariedade ao menino Ahmed, que montou um relógio com Arduino e o levou para a escola, onde foi denunciado à polícia, detido, revistado e teve foto divulgada ao público enquanto submetido a essa violência humilhante – tudo porque os incultos de lá acharam que era uma bomba, já que em filmes as bombas sempre parecem assim.

No mesmo dia, muita gente menos inculta mandou seu recado de apoio para o Ahmed, incluindo o presidente dos EUA, o criador do Facebook, a administração do Google e do Twitter, várias entidades de hobbistas de eletrônica, e mais.

Assim que eu soube, publiquei meu propósito de fazer e postar o meu próprio relógio, e ao longo do dia vários sites e revistas também convidaram seus públicos a fazerem o mesmo.

O meu recado é simples: somos todos Ahmed! Nossos protótipos têm números luminosos e fios coloridos, e os instalamos nos mais variados estojos. Recebemos e enviamos pelo correio, levamos para a escola e na bagagem do avião, escrevemos, lemos e aprendemos sobre o assunto, e não queremos ser presos por isso, independentemente do nosso sobrenome ou do tom da nossa pele.

Economia de energia no Arduino: os modos Sleep

Já vimos anteriormente, no artigo “Arduino e bateria: providências simples para reduzir o consumo”, uma das formas mais simples de colocar o Arduino em um modo de baixo consumo: a biblioteca Narcoleptic, que usa um timer interno para acordar o Arduino após um período de suspensão.

Hoje veremos o uso de um modo mais complexo, porque coloca o Arduino para dormir mas só o acordará quando ocorrer um evento provocado externamente – no nosso caso, o alarme a ser programado no módulo RTC.

Vale observar: tudo o que falarmos sobre economia de energia diz respeito apenas ao microcontrolador principal do Arduino – no nosso caso, um ATmega328. Se você estiver usando um ATmega standalone, ou um modelo de Arduino sem muitos circuitos externos (como um Pro Mini, por exemplo), isso corresponderá a um grande percentual de redução; se estiver usando um Uno ou similar, o percentual será menor, porque os circuitos de apoio (regulador de tensão, conversor USB/Serial, etc.) continuam consumindo o mesmo que antes.

Meu projeto de hoje é o embrião de uma ideia na qual pretendo deixar um Arduino Pro Mini rodando com pilhas por meses a fio.

Para um projeto futuro eu tenho a intenção de deixar um Arduino Pro Mini (com RTC, mas sem display) rodando por meses a fio usando pilhas, portanto hoje meu experimento foi com o mais extremo dos modos de economia de energia: o SLEEP_MODE_PWR_DOWN, que desliga quase todos os recursos do microcontrolador até ser acordado por um evento externo detectado em um de seus pinos programáveis para isso.

"Desligar quase todos os recursos do microcontrolador" inclui desligar até os timers internos que podem ser usados para acordar a CPU em modos de economia menos ousados, como o SLEEP_MODE_STANDBY e o SLEEP_MODE_PWR_SAVE.

A tabela acima vem da seção 9 da datasheet do ATmega328. Alguns destaques sobre os modos:

  • PWR_DOWN – Mantém ativo só o watchdog timer, e pode ser acordado pelas interrupções (INT0, INT1 e pin change), pelo match de I2C e pelo watchdog.
  • PWR_SAVE – Mantém ativos o watchdog timer, a fonte de clock assíncrona e seu oscilador de timer (timer2); pode ser acordado pelas interrupções (INT0, INT1 e pin change), o match de I2C, o timer2, e o watchdog.
  • STANDBY – Mantêm ativos o clock principal e o watchdog timer, e pode ser acordado pelas interrupções (INT0, INT1 e pin change), pelo match de I2C e pelo watchdog.
  • IDLE – Mantêm ativos o clock principal, o clock do ADC (entradas analógicas), a fonte de clock assíncrona e seu oscilador de timer (timer2), e o watchdog timer; pode ser acordado pelas interrupções (INT0, INT1 e pin change), pelo match de I2C, pelo watchdog, pelo Timer2, pelo comparador analógico, por outras entradas e saídas, pelo estado das memórias, e mais.

Em todos os modos acima, exceto o IDLE, é possível ainda desligar o BOD, recurso interno de detecção de queda de tensão, e economizar um pouquinho mais de energia.

A diferença de consumo entre o IDLE (menos economia) e o PWR_DOWN (mais economia) é grande: no IDLE, as medições no microcontrolador ficam por volta de 15mA, e no PWR_DOWN por volta de 0,4mA. Mas antes de tirar conclusões a respeito, releia o que eu escrevi acima sobre o consumo dos circuitos externos dos Arduinos mais comuns: regulador, USB, led de power, etc...

No que diz respeito ao software, ativar esse modo é bem simples, como veremos no programa, mais abaixo.

A montagem física

Nosso circuito de hoje usa módulos prontos, e assim envolve muito pouca montagem. Seus elementos centrais são:

  • Um Arduino Uno (usei um Uno Plus, mas poderia ser qualquer Arduino baseado no ATmega328 – Uno, Nano, etc.).
  • Um módulo RTC (relógio de tempo real). As técnicas que usei funcionarão em qualquer módulo baseado nos chips DS3231 ou DS3232, desde que eles exponham o pino SQW (e a maioria expõe). Se o seu módulo RTC for baseado no DS1307, acredito que não haverá como compatibilizar.
  • Um módulo display. Usei o "4-digit display v. 1.0", com projeto originalmente da Catalex, mas poderia ser qualquer um baseado no driver TM1637. Este modelo, em particular, é de 4 dígitos e possui o ":" (controlável separadamente) separando as horas dos minutos.

As conexões entre eles ficam assim:

a) Conexões do módulo RTC ao Arduino:

  1. pino VCC do RTC é conectado ao pino 5V do Arduino;
  2. pino GND do RTC é conectado a um pino GND do Arduino;
  3. pino SDA do RTC é conectado ao pino A4 do Arduino;
  4. pino SCL do RTC é conectado ao pino A5 do Arduino;
  5. pino SQW do RTC é conectado ao pino 2 (digital) do Arduino e também, por meio de um resistor de 10KΩ, ao pino 5V do Arduino.
  6. os demais pinos do RTC ficam desconectados.

A conexão por meio do resistor, no caso do pino SQW, é para fazer o papel de um pull-up. No programa eu também configurei o pino 2 do Arduino como INPUT_PULLUP mas, nos testes práticos, não bastou – o resistor pull-up externo foi necessário.

b) Conexões do módulo display ao Arduino:

  1. pino GND do módulo display é conectado a um pino GND do Arduino;
  2. pino VCC do módulo display é conectado ao pino 5V do Arduino;
  3. pino CLK do módulo display é conectado ao pino 6 (digital) do Arduino;
  4. pino DIO do módulo display é conectado ao pino 7 (digital) do Arduino;

Os alarmes do módulo RTC

Os módulos RTC baseados nos chips DS3231 ou DS3232, da Maxim, fazem bem mais do que informar a data e hora certas e facilitar preservá-las com uma bateria; entre outras funções adicionais, eles possuem 2 alarmes, que podem ser definidos para acionamento em um horário de um determinado dia (do mês ou da semana), ou em vários modos repetitivos: mesmo horário a cada dia, mesmo segundo a cada minuto, etc.

O alarme do RTC não é visual nem sonoro, apenas um pino indo para LOW para indicar ao sistema que o evento acaba de ocorrer.

Do ponto de vista da interface do módulo, a notificação desse alarme nada mais é do que o pino SQW (que, nesse modo, normalmente fica em HIGH) ir brevemente para LOW. Além disso, mesmo que não quiséssemos monitorar esse pino, é possível consultar (via função alarm()) se os alarmes foram acionados ou não.

O mesmo pino SQW também serve para gerar uma onda quadrada1 de sincronização, com frequência selecionável. Para podermos usar a funcionalidade de notificação do alarme, é necessário desativar essa onda quadrada.

A programação

A essa altura você já deve ter notado que, se programarmos um desses alarmes para o momento em que o Arduino deve acordar do modo de economia de energia, e ligarmos o pino SQW a um dos pinos nos quais o Arduino pode receber uma interrupção, é possível usar o alarme como o elemento que tira o Arduino do já mencionado modo SLEEP_MODE_PWR_DOWN. Como o RTC gasta bem pouca energia, isso permitirá (nas condições e restrições já mencionadas) alcançar um ponto de equilíbrio bem interessante no consumo de bateria.

Em síntese, é isso mesmo que o nosso programa fará, programando o alarme 1 do RTC para ser acionado uma vez por minuto e em seguida executando ciclicamente os seguintes passos essenciais:

  • Colocar o Arduino em SLEEP_MODE_PWR_DOWN
  • (aguardar ser acordado pelo alarme do RTC)
  • Consultar no RTC a hora certa
  • Atualizar a hora mostrada no display
  • Colocar o Arduino em SLEEP_MODE_PWR_DOWN
  • (aguardar ser acordado pelo alarme do RTC)
  • (...) e assim sucessivamente

Simular um relógio digital é um exercício clássico de programação, porque precisa de vários elementos interessantes: definir a hora inicial, permitir ajustá-la, mantê-la atualizada, e exibi-la.

Meu programa de hoje só se preocupa a sério com os 2 últimos elementos: ter a hora certa atualizada, e exibi-la. Para ter um mínimo de funcionalidade, é oferecido um método de ajuste de horas e minutos por meio do monitor serial (mais detalhes a seguir), e a minha expectativa é que você já tenha ajustado a hora certa por meio do programa de exemplo que acompanha as bibliotecas do RTC antes de rodar o meu programa pela primeira vez2 ;-)

Um mínimo de informações para debug é exibido no Monitor Serial do Arduino, incluindo uma rotina básica que permite ajustar as horas e minutos do horário do RTC, como você pode ver na tela a seguir:

As instruções acima são exibidas uma vez por minuto. Se quiser ajustar a hora, digite H, h, M ou m no campo no alto da janela (como o h no exemplo acima), e pressione o botão Send.

Para usar o programa, você precisa ter instalado previamente as seguintes bibliotecas:

  • DS3232RTC, para os RTCs baseados no DS3231 ou DS3232 - http://github.com/JChristensen/DS3232RTC
  • Time, para gerenciamento de data e hora - http://www.arduino.cc/playground/Code/Time
  • TM1637, para o display - https://github.com/chamie/TM1637

Quanto à TM1637, note que existem outras versões com o mesmo nome, mas a que eu apontei tem o recurso de controlar independentemente o sinal de ":" da tela – nem todas têm.

O programa usa ainda as bibliotecas Wire, sleep e power, mas essas vêm instaladas junto com a IDE do Arduino.

O programa

Vamos agora ao programa, com trechos destacados em cores para facilitar a explicação posterior.

// Augusto Campos 2015 - BR-Arduino.org
// Somos todos Ahmed

#include <DS3232RTC.h>
#include <Time.h>  
#include <Wire.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <TM1637Display.h>

// definicoes do RTC
#define SQW 2
#define CLK 6
#define DIO 7
TM1637Display display(CLK, DIO);

volatile boolean interrupcaoRTCfoiChamada = false;

void setup() {  
  Serial.begin(9600);
  Serial.setTimeout(5000);    
  setSyncProvider(RTC.get);
  if (timeStatus() != timeSet) 
    Serial.println("Erro: Nao recebi horario do RTC");
  else Serial.println("Horario foi obtido do RTC normalmente");      
  // desativa a frequencia no pino SQW para podermos usa-lo como alarme:
  RTC.squareWave(SQWAVE_NONE);
  pinMode(SQW, INPUT_PULLUP);
  RTC.setAlarm(ALM1_MATCH_SECONDS, 10, 0, 0, 1);
  RTC.alarm(ALARM_1);
  RTC.alarmInterrupt(ALARM_1, true);
  display.setBrightness(0x0f);
}

void interrupcaoRTC() {
    interrupcaoRTCfoiChamada = true;
    detachInterrupt(INT0);
}

void adormecer() {  
  attachInterrupt(INT0, interrupcaoRTC, FALLING);
  delay(100);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_mode();
  // o programa para aqui, indefinidamente  

  // quando alguma interrupcao acordar o Arduino, o programa
  // continua daqui, e a primeira coisa a fazer eh desativar
  // o modo sleep...
  sleep_disable(); 
  Serial.println("Acordando porque o RTC me chamou...");
}

void loop () {
  boolean teclou;
  atualizaDisplay();
  do {
    teclou=false;
    if (RTC.alarm(ALARM_1)) 
      Serial.println(" --> O RTC confirma que o alarme 1 disparou.");
    Serial.println("SE DESEJAR ajustar o horario, voce tem 5 segundos para");
    Serial.println("entrar H ou h para adiantar/retroceder a hora, ou M/m para minutos.");
    String S=Serial.readStringUntil(10);
    if (S!="") {
      teclou=true;
      if (S.startsWith("H")) ajustaHora(+1);
      else if (S.startsWith("h")) ajustaHora(-1);
      else if (S.startsWith("M")) ajustaMinuto(+1);
      else if (S.startsWith("m")) ajustaMinuto(-1);
      Serial.println(S);
      atualizaDisplay();
    }
  } while(teclou);  
  Serial.println("Entrando em economia de energia ate o RTC me acordar...");
  delay(500);
  adormecer();
}

void ajustaHora(short ajuste) {
  short hora=hour()+ajuste;
  if (hora<0) hora=23;
  if (hora>23) hora=0;
  setTime(hora, minute(), second(), day(), month(), year());
  RTC.set(now());
}  

void ajustaMinuto(short ajuste) {
  short minuto=minute()+ajuste;
  if (minuto<0) minuto=59;
  if (minuto>59) minuto=0;
  setTime(hour(), minuto, 0, day(), month(), year());
  RTC.set(now());
}  

void atualizaDisplay() {
    setSyncProvider(RTC.get);   // re-atualiza hora a partir do RTC
    display.setColon(true);
    display.showNumberDec(hour()*100+minute(),true,4,0);
    Serial.print(hour());
    duasCasas(minute());
    duasCasas(second());
    Serial.print(' ');
    Serial.print(day());
    Serial.print(' ');
    Serial.print(month());
    Serial.print(' ');
    Serial.print(year()); 
    Serial.println(); 
}

void duasCasas(int digitos) {
  Serial.print(':');
  if(digitos < 10) Serial.print('0');
  Serial.print(digitos);
}

O programa começa, como de hábito, com a inclusão de todas as bibliotecas (já mencionadas, inclusive com URL, no texto acima) necessárias para gerenciar o RTC, o display, a manipulação de datas e horas e os modos de economia de energia, bem como com a definição dos pinos lógicos relevantes do display e do RTC e a instanciação do objeto TM1637Display, correspondente ao display.

No setup(), após a usual inicialização da Serial, temos 2 trechos em tons de verde, sendo que no primeiro ocorre a configuração básica do RTC (defini-lo como a fonte de data e hora para uso pelas funções da biblioteca Time, desativar a onda quadrada para poder usar a interrupção no pino SQW, e colocar o pino do Arduino que está conectado ao pino SQW em modo de entrada), e no segundo ocorre a ativação do alarme a cada minuto, sempre que a contagem de segundos chega a 10.

Essa ativação do alarme tem 3 passos, todos eles necessários: o RTC.setAlarm indica o tipo de alarme e em qual segundo ele deve ser acionado, o RTC.Alarm "zera" a flag do alarme para ter certeza de que ele poderá ser acionado (enquanto a flag estiver ativa devido a algum alarme anterior, novos alarmes não acontecerão), e o RTC.alarmInterrupt instrui o RTC a sinalizar no pino SQW o momento em que o alarme acontecer – é possível ter alarmes sem sinalização, e aí eles serão verificáveis apenas via consulta à função RTC.Alarm. Veja detalhes sobre todos esses métodos na documentação da biblioteca DS3232.

Antes do final do setup(), ainda aproveitamos para definir o nível de brilho do display LCD.

A função de tratamento da interrupção é importante, ainda que no nosso caso ela não faça nada de muito útil.

A seguir, em rosa, está a função interrupcaoRTC() que – embora necessária – não cumpre nenhum propósito visivelmente prático neste programa3. Ela é o manipulador de interrupção, que será chamado (veremos detalhes depois) a cada vez que o pino SQW do nosso módulo RTC passar de HIGH para LOW. Na prática, e nesta versão, tudo que ela faz é definir uma variável global que poderia ser usada em outras partes do programa para saber se uma interrupção ocorreu, e desativar (via detachInterrupt()) a sua própria interrupção, para evitar ser chamada novamente antes de o programa terminar de tratar uma chamada anterior.

Depois dela, em vermelho, temos uma função com funcionalidade mais visível: a adormecer(), responsável por colocar o microcontrolador para dormir (no SLEEP_MODE_POWER_DOWN) e para gerenciar o momento em que ele acordar.

Quando chamada, ela começa por definir (via attachInterrupt()) a interrupção INT0, que é a associada ao pino 2 do Arduino. Note que os parâmetros indicam que a função a ser chamada no momento da interrupção é a nossa já conhecida interrupcaoRTC(), e que a interrupção deve ser gerada no caso FALLING, ou seja, quando o nível do pino 2 cair de HIGH para LOW.

Como referência para o uso do AttachInterrupt(): as interrupções deste tipo presentes no Uno são a INT0 (pino 2) e INT1 (pino 3). Os casos de interrupção no Uno são CHANGE, FALLING, RISING e LOW, correspondendo respectivamente a: qualquer mudança, mudança de HIGH para LOW, mudança de LOW para HIGH, estar em LOW.

A seguir, ainda na função adormecer(), vem a trinca de comandos necessários para colocar o Arduino para dormir: set_sleep_mode, sleep_enable e sleep_mode. O primeiro seleciona qual dos modos de economia de energia será ativado, o segundo permite que o Arduino entre em modos de economia de energia, e o terceiro define que ele deve entrar imediatamente neste modo.

O processamento para imediatamente após executar o sleep_mode(), e retorna a partir da linha seguinte quando a interrupção acontecer.

Os comentários que estão logo a seguir, no meio da função, expressam o que ocorre ali: o processamento para naquele exato ponto. Como o modo que escolhemos é o SLEEP_MODE_POWER_DOWN, o processamento só vai recomeçar quando ocorrer a interrupção que definimos no nosso pino 2 e, ao final dela, o programa continua exatamente do ponto em que havia parado. Neste momento, a função usa o sleep_disable()4 para não permitir que o Arduino volte a dormir até que haja alguma nova definição a respeito (que só acontecerão em uma futura chamada à mesma função adormecer()), e avisa no Monitor Serial o que aconteceu.

Como já cuidamos de toda a complexidade, a nossa função loop() ficou bem simples. Destaquei em negrito o que ela tem de mais relevante: uma chamada à função atualizaDisplay() para colocar a hora certa no display, um loop do...while() para cuidar das teclas que o usuário pode pressionar no Monitir Serial caso queira ajustar as horas, e uma chamada à função adormecer(), que fará o microcontrolador dormir em modo de baixa energia até ser despertado pelo próximo alarme, no minuto seguinte.

A mesma função que confirma se um alarme do RTC ocorreu serve para indicar a ele que os próximos alarmes estão liberados para ocorrer.

Também em negrito está a chamada à função RTC.Alarm, que é importantíssima: além de verificar se o alarme aconteceu (que é algo que neste programa nem precisaríamos fazer), ela "zera" o flag desse alarme, para que ele possa acontecer de novo. Se você não chamá-la, um alarme repetitivo jamais voltará a acontecer.

As funções ajustaHora() e ajustaMinuto(), a seguir, lidam apenas com aritmética, exceto os 2 trechos em negrito, que ilustram uma realidade desse tipo de operação: além de ajustar o horário do Arduino (com setTime()), é necessário instrui-lo a gravar esse mesmo horário no RTC (com RTC.set(now())).

Completando os trechos relevantes, temos as 3 linhas em laranja, que são parte da já mencionada função atualizaDisplay(). A primeira força uma nova sincronização do horário do Arduino com o do RTC, a segunda define que na próxima impressão de números no display o ":" deve ficar aceso, e a terceira imprime a data e hora no display.

Experimente remover a primeira das linhas em laranja. Após alguns minutos de funcionamento com ela removida, você perceberá que o relógio está atrasando, como se a cada minuto só 5 segundos fossem contados. Você consegue identificar a razão?

O Ahmed certamente conseguiria ;-)

 
  1.  Daí vem seu nome: SQuare Wave.

  2.  É simples programar o ajuste automático a partir da hora da compilação, veja os detalhes no meu artigo anterior “Data, hora e memória extra no Arduino com o módulo Tiny RTC e o chip DS1307”.

  3.  Mas será muito importante no próximo programa que eu irei desenvolver a partir deste

  4.  Veja neste tutorial a importância de evitar race conditions entre a definição da interrupção e a permissão para entrar em modos de economia de energia.

Comentar

Dos leds ao Arduino, ESP8266 e mais

Aprenda eletrônica com as experiências de um geek veterano dos bits e bytes que nunca tinha soldado um led na vida, e resolveu narrar para você o que descobre enquanto explora esse universo – a partir da eletrônica básica, até chegar aos circuitos modernos.

Por Augusto Campos, autor do BR-Linux e Efetividade.net.

Recomendados

Livro recomendado


Artigos já disponíveis

Comunidade Arduino

O BR-Arduino é integrante da comunidade internacional de entusiastas do Arduino, mas não tem relação com os criadores e distribuidores do produto, nem com os detentores das marcas registradas.

Livros recomendados