Um tipo de aplicação comum é aquela em que um dispositivo realiza um procedimento apenas em um determinado horário do dia e não precisa fazer mais nada durante os outros horários. Por conta disso, é interessante deixar o dispositivo dormindo nos horários de ócio para diminui o gasto energético. Sendo assim, neste post, aprenderemos a manter o Arduino em modo sleep e acordá-lo apenas em horários predeterminados com a ajuda de um módulo RTC.

Informações básicas

O que é o projeto

Este projeto consiste na integração de um RTC ao Arduino para permitir a ele utilizar o modo sleep para dormir durante boa parte do dia e acordar apenas em horários predeterminados para executar as ações que queremos. Com isso, o objetivo é minimizar o gasto de energia do projeto. Imaginando que o projeto seja alimentado por uma bateria, o objetivo é também aumentar a duração da bateria consideravelmente.

No caso, mostrarei a proposta utilizando dois módulos RTC diferentes (imagens abaixo): o DS1302 e o DS3231. É mais tranquilo e eficiente fazer isso com o DS3231, pois ele permite a criação de alarmes que podem acionar uma interrupção externa do Arduino. Por outro lado, o DS1302 não possui alarmes, então a implementação é um pouco mais complicada, mas ainda assim bem tranquila.

Módulo RTC DS1302
Módulo RTC DS3231 frente

Antes de iniciar o projeto, recomendo a leitura de três posts fundamenteis para ajudar no entendimento das etapas que serão apresentadas aqui:

Baixando bibliotecas

Tanto o RTC DS1302, quanto o DS3231, usarão as mesmas bibliotecas do Arduino. Serão duas bibliotecas: uma para lidar com o RTC e outra para lidar com o modo sleep.

Elas podem ser baixadas na própria IDE do Arduino em Sketch -> Incluir Biblioteca -> Gerenciar Bibliotecas…

  • Para a biblioteca RTC, pesquise por “DS1302” e baixe a opção que tem como autor “Rtc_by_Makuna”. 
  • Para a biblioteca do modo sleep, pesquise por “lowpower” e baixe a opção que tem como autor “LowPowerLab”. 

Acordando com RTC DS1302

Circuito

A ligação do Arduino com o DS1302 está indicada abaixo. É possível ligar os pinos CLK, DAT e RST em outros pinos digitais do Arduino. O importante é fazer a mudança no código depois.

Ligação RTC DS1302 e Arduino

Lógica do código

Conforme falei, o DS1302 não possui alarmes configuráveis, então teremos que fazer uma “gambiarra” para permitir ao Arduino acordar apenas no horário desejado.

Na realidade, o Arduino não acordará apenas no horário desejado, ele ficará dormindo e acordando a cada 8 segundos. Toda vez que ele acordar, ele verificará se a hora atual é igual a hora de algum horário predeterminado. Se sim, ele irá executar o procedimento desejado. Se não, ele voltará a dormir. São 8 segundos, pois este é o tempo máximo para fazer o Arduino acordar sozinho.

Comentei no início do post que o procedimento com o DS3231 é mais eficiente, pois aqui o Arduino fica acordando para verificar a hora, o que acaba não economizando tanta energia quanto poderia. Entretanto, esse processo é muito rápido (milésimos de segundos ou até menos), então, no fim das contas, podemos considerar que ele fica dormindo praticamente o tempo todo.

Código completo

Observações:

  • Como exemplo, defini que o procedimento que o Arduino executa quando acorda é apenas o acionamento do LED ligado ao pino 13 (o LED que já vem na plaquinha do Arduino UNO).
  • No início do código existem alguns #defines para configurar os pinos do módulo RTC e o pino do LED.
  • Para configurar os horários de acordar, você precisa alterar o #define QUANTIDADE_ALARMES para a quantidade de horários que você deseja. Em seguida, basta alterar a variável “horas_alarmes” para os horários que você quer.

Leia os comentários para entender o código por completo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// ----- Bibliotecas -----
// RTC
#include <ThreeWire.h>
#include <RtcDS1302.h>
// Sleep
#include "LowPower.h"

// ----- Definições -----
// Pinos do RTC
#define DS1302_RST  3
#define DS1302_DAT  4
#define DS1302_CLK  5

// Quantidade de horários para acordar
#define QUANTIDADE_ALARMES    6

// Pino do LED
#define LED_PINO    13


// ----- Variáveis globais -----
// Instância da ligação e do RTC
ThreeWire ligacao(DS1302_DAT, DS1302_CLK, DS1302_RST);
RtcDS1302<ThreeWire> rtc(ligacao);

// Horas que o Arduino sairá do sleep para fazer um procedimento
// Defini os horários de 4 em 4 horas
const uint8_t horas_alarmes[QUANTIDADE_ALARMES] = {0, 4, 8, 12, 16, 20};


// ----- Protótipo das funções -----
bool verificar_alarme();
void procedimento_acordar();


void setup ()
{
  // Inicia a comunicação serial
  Serial.begin(9600);

  // Configura o pino do LED
  pinMode(LED_PINO, OUTPUT);

  // Inicia a comunicação com o RTC
  rtc.Begin();

  // Desabilita a proteção contra escrita
  if (rtc.GetIsWriteProtected())
  {
    rtc.SetIsWriteProtected(false);
  }

  // Se não estava rodando, começa a funcionar
  if (!rtc.GetIsRunning())
  {
    rtc.SetIsRunning(true);
  }

  // Obtém o valor da hora quando o código foi compilado
  RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__);

  // Se a data/hora não é valida
  // Pode ocorrer na primeira vez que o RTC é usado
  // Ou então se o RTC estiver sem a bateria ou ela estiver com tensão baixa
  if (!rtc.IsDateTimeValid())
  {
    Serial.println("Data e hora inválidos!");
    rtc.SetDateTime(compiled);
  }
  else
  {
    // Se a hora/data for válida, mas estiver desatualizada, atualiza
    RtcDateTime now = rtc.GetDateTime();
    if (now < compiled)
    {
      rtc.SetDateTime(compiled);
    }
    Serial.println("Horario e data atualizados!");
  }
}

void loop ()
{
  // Verifica se está a hora atual é igual a algum horário pré-determinado
  if(verificar_alarme())
  {
    Serial.println("Hora de acordar!!");
    procedimento_acordar();
  }

  // Antes de dormir, termina de enviar os dados da serial
  Serial.flush();
  // Coloca o Arduino pra dormir por 8 segundos
  LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}



/**
 * @brief Verifica se está na hora do Arduino acordar e realizar um procedimento
 * @return true se está na hora e false se não
 */

bool verificar_alarme()
{
  bool hora_certa = false;

  // Obtem o horário atual
  RtcDateTime now = rtc.GetDateTime();

  // Verifica se está no começo da hora (minutos = 0 e segundos baixos)
  // Ex: 8:00:2 sim, mas 8:27:0 não
  if (now.Minute() == 0 && now.Second() < 8)
  {
    // Verifica se a hora bate com a hora de algum alarme
    for(uint8_t i = 0; i < QUANTIDADE_ALARMES; i++)
    {
      if (now.Hour() == horas_alarmes[i])
      {
        hora_certa = true;
        break;
      }
    }
  }

  return hora_certa;
}


/**
 * @brief Procedimento realizado pelo Arduino quando ele está
 * em um horário pré-definido
 */

void procedimento_acordar()
{
  // Liga LED por 1 segundo
  digitalWrite(LED_PINO, 1);
  delay(1000);
  digitalWrite(LED_PINO, 0);
}

Acordando com RTC DS3231

Circuito

A ligação do Arduino com o DS3231 está indicada abaixo. É possível ligar o pino SQW em outro pino digital do Arduino. Se você fizer isso, lembre-se que apenas os pinos 2 e 3 aceitam a interrupção por borda de descida/subida. Os demais só aceitam interrupção por mudança de estado. Leia mais neste post.

Ligação RTC DS3231 e Arduino

Lógica do código

A lógica do código é bem direta: o Arduino dorme indefinidamente e só acorda se o pino 2 receber uma borda de descida. E quando acorda, ele executa o procedimento desejado, configura o alarme para o próximo horário previsto e volta a dormir.

Código completo

Observações:

  • Como exemplo, defini que o procedimento que o Arduino executa quando acorda é apenas o acionamento do LED ligado ao pino 13 (o LED que já vem na plaquinha do Arduino UNO).
  • No início do código existem alguns #defines para configurar o pino de interrupção do módulo RTC e o pino do LED.
  • Para configurar os horários de acordar, você precisa alterar o #define QUANTIDADE_ALARMES para a quantidade de horários que você deseja. Em seguida, basta alterar a variável “horas_alarmes” para os horários que você quer.

Leia os comentários para entender o código por completo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// ----- Bibliotecas -----
// RTC
#include <Wire.h>
#include <RtcDS3231.h>
// Sleep
#include "LowPower.h"

// ----- Definições -----
// Pino interrupção
#define DS3231_SQW  2

// Quantidade de horários para acordar
#define QUANTIDADE_ALARMES    3

// Pino do LED
#define LED_PINO    13


// ----- Variáveis globais -----
// Instância do RTC
RtcDS3231<TwoWire> rtc(Wire);

// Horas que o Arduino sairá do sleep para fazer um procedimento
// Defini os horários de 8 em 8 horas
const uint8_t horas_alarmes[QUANTIDADE_ALARMES] = {0, 8, 16};

// Informa se o alarme disparou ou não
bool disparouAlarme = false;


// ----- Protótipo das funções -----
void atualizar_alarme();
void procedimento_acordar();



// Rotina de interrupção do alarme
void ISR_ATTR funcao_alarme()
{
    disparouAlarme = true;
}


void setup ()
{
  // Inicia a comunicação serial
  Serial.begin(9600);

  // Configura o pino de interrupção
  pinMode(DS3231_SQW, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(DS3231_SQW), funcao_alarme, FALLING);

  // Configura o pino do LED
  pinMode(LED_PINO, OUTPUT);

  // Inicia a comunicação com o RTC
  rtc.Begin();

  // Obtém o valor da hora quando o código foi compilado
  RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__);

  // Se a data/hora não é valida
  // Pode ocorrer na primeira vez que o RTC é usado
  // Ou então se o RTC estiver sem a bateria ou ela estiver com tensão baixa
  if (!rtc.IsDateTimeValid())
  {
    Serial.println("Data e hora inválidos!");
    rtc.SetDateTime(compiled);
  }
  else
  {
    // Se a hora/data for válida, mas estiver desatualizada, atualiza
    RtcDateTime now = rtc.GetDateTime();
    if (now < compiled)
    {
      rtc.SetDateTime(compiled);
    }
  }

  // Desabilita o pino de geração da onda quadrada
  rtc.Enable32kHzPin(false);
  rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone);
}

void loop ()
{
  // Obs: O if abaixo é desnecessário, pois se o Arduino está acordado, quer dizer que o alarme disparou
  // deixei ele para o caso de alguém precisar usar
  if(disparouAlarme)
  {
    disparouAlarme = false;
    Serial.println("Hora de acordar!!");
    procedimento_acordar();
  }

  // Atualiza o alarme
  atualizar_alarme();

  // Antes de dormir, termina de enviar os dados da serial
  Serial.flush();
  // Coloca o Arduino pra dormir indefinidamente
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
}



/**
 * @brief Atualiza o alarme para o próximo horário previsto
 */

void atualizar_alarme()
{
  bool hora_certa = false;
  uint8_t proxima_hora = 25;
  uint8_t menor_hora = 23;

  // Obtem o horário atual
  RtcDateTime now = rtc.GetDateTime();

  // Procura o próximo horário
  for(uint8_t i = 0; i < QUANTIDADE_ALARMES; i++)
  {
    // Descobre a menor hora
    if (horas_alarmes[i] < menor_hora)
    {
      menor_hora = horas_alarmes[i];
    }
    // Acha o próximo horário
    if (horas_alarmes[i] > now.Hour() && horas_alarmes[i] < proxima_hora)
    {
      proxima_hora = horas_alarmes[i];
    }
  }

  // Se não achou um horário maior, é porque o próximo horário é o menor
  if (proxima_hora == 25)
  {
    proxima_hora = menor_hora;
  }

  Serial.print("Próximo horário: ");
  Serial.println(proxima_hora);

  // Configura alarme 1 para disparar no início de uma hora
  DS3231AlarmOne alarm1(
            0,
            proxima_hora,
            0,
            0,
            DS3231AlarmOneControl_HoursMinutesSecondsMatch);
  rtc.SetAlarmOne(alarm1);
}


/**
 * @brief Procedimento realizado pelo Arduino quando ele está
 * em um horário pré-definido
 */

void procedimento_acordar()
{
  // Liga LED por 1 segundo
  digitalWrite(LED_PINO, 1);
  delay(1000);
  digitalWrite(LED_PINO, 0);
}