O módulo RTC DS3231 é um módulo bem interessante, pois o DS3231 apresenta recursos bem úteis que um RTC comum não tem, como a criação de alarmes. Portanto, neste post, aprenderemos algumas características sobre o DS3231 e veremos como usá-lo com o Arduino.

Informações básicas

No post sobre o módulo RTC DS1302, eu expliquei o que é RTC de forma geral, falei sobre a imprecisão na contagem das horas e mostrei algumas aplicações. Pra não ser repetitivo, não mostrarei estas informações no post atual. Caso tenha interesse, dê uma lida no outro post.

O que é o módulo RTC DS3231

O DS3231 em si é um Circuito Integrado, mas o foco deste post é mostrar o módulo mais comum que utiliza esse CI. Ele está mostrado nas fotos abaixo, onde é possível ver o CI (do tipo SMD) de um lado da PCB e o suporte de bateria (CR2032) do outro lado. A bateria é que mantém ele contando mesmo sem receber alimentação nos pinos VCC e GND:

Módulo RTC DS3231 frente
Módulo RTC DS3231 trás

É possível ver também outro CI além do DS3231, que é o 24C32N, que é uma memória EEPROM de 32 kbit ou 4 kbyte.

Você talvez tenha reparado que a PCB não possui um cristal, ao contrário do que ocorre no DS1302. Isso, porque o DS3231 possui um cristal interno com compensação de temperatura (TCXO). Esse cristal pode apresentar um ppm de 3,5, o que dá um desvio de +- 0,11 Hz (veja como calcular no post sobre o DS1302).

Esse desvio de 0,11 Hz pode produzir um erro de +- 9 segundos em um mês. À titulo de comparação, o DS1302 conectado com um cristal de 30 ppm pode gerar um erro de mais de 1 minuto por mês. Ou seja, o DS3231 é bastante preciso com o cristal interno.

Características do RTC DS3231

Neste tópico, irei mostrar as características do CI DS3231, do módulo mais comum e falarei brevemente sobre a memória EEPROM 24C32. Não serão informações cruciais para a implementação no Arduino. Então, se desejar, pode pular para o tópico seguinte.

Características

As informações adiante foram extraídas do datasheet do DS3231.

  • RTC capaz de contar:
    • Segundos;
    • Minutos;
    • Horas;
    • Dia do mês;
    • Mês;
    • Dia da semana;
    • Ano com compensação de ano bissexto até o ano 2100.
  • Possui dois alarmes programáveis.
  • É possível configurar uma saída para gerar uma onda quadrada.
  • Possui um sensor de temperatura interno:
    • Resolução de 0,25 ºC;
    • Não achei o range de medição, mas provavelmente é maior ou igual ao range de temperatura que o dispositivo suporta: 0 a 70ºC.
  • Possui dois pinos de alimentação: VCC e VBAT:
    • Ambos operam de 2,3 a 5,5V (recomendado);
    • VBAT: alimentação da bateria;
      • A tensão típica é de 3,0 V;
    • VCC: alimentação primária;
      • A tensão típica é de 3,3 V;
    • O CI usa a alimentação que tiver tensão mais alta (tem um pequeno detalhe nesta questão que pode ser lido no datasheet).
  • Tensão máxima dos pinos digitais:
    • -0,3 V à (VCC + 0,3 V);
    • Se VCC = 5V, então, suporta de -0,3 à 5,3 V.
  • Interface I2C:
    • Clock de no máximo 400 kHz (aceita modo standard e fast);
    • O endereço é 0x68.
  • Consumo de corrente:
    • Funcionando normalmente com alimentação primária em 5,5 V: até 300 μA;
    • Modo onde apenas mantém a hora/data atualizada: até 3,5 μA;
    • O consumo depende da tensão de alimentação e do estado do RTC, então consulte o datasheet para maiores detalhes.
  • A hora pode ser configurada no formato de 24h ou de 12h am/pm.

Memória EEPROM 24C32

É um pouco estranho a princípio saber que o módulo também implementa uma memória EEPROM, mas, possivelmente, isso foi feito para manter o recurso de memória presente no DS1302 (possui memória interna).

Então, a memória EEPROM não tem relação nenhuma com o RTC DS3231, sendo apenas um recurso adicional do módulo. Vejamos algumas das características dela que extrai do datasheet:

  • Memória EEPROM de 32 kbits ou 4 kbytes.
    • Por ser EEPROM, mesmo sem bateria, os dados armazenados não se perdem (ao contrário do DS1302).
  • Tensão de operação de 4,5 a 5,5 V.
    • Obs: se você for usar o DS3231 em 3,3 V, é importante saber que a memória não vai funcionar corretamente.
  • Tensão máxima dos pinos digitais:
    • -0,6 V à (VCC + 1,0 V);
    • Se VCC = 5V, então, suporta de -0,6 à 6,0 V.
  • Interface I2C:
    • Clock de 100 a 400 kHz (só aceita modo fast);
    • O endereço é 1010A2A1A0 (binário):
      • A2, A1 e A0 são os níveis lógicos definidos nos pinos A2, A1 e A0 respectivamente;
      • No caso do módulo DS3231, esses pinos são ligados em VCC via pull-up e o endereço padrão do 24C32 é o 0x57 (1010111);
      • O módulo também disponibiliza um “acesso” aos pinos A0, A1, A2 para podermos alterar o endereço facilmente (veja a primeira imagem no início do post).
  • Consumo de corrente:
    • Escrevendo: até 3 mA;
    • Lendo: até 150 μA;
    • Standby: 1 μA;
    • O datasheet usa uns símbolos estranhos que pressupus que era pra fazer referência ao μ.

Circuito do módulo

Encontrei neste site uma imagem de um possível esquemático do módulo DS3231. Veja abaixo:

O circuito pode parecer grande, mas ele é até bem simples e tranquilo de entender. Fora os CIs, existem alguns capacitores de desacoplamento, um LED para indicar que o dispositivo está ligado, resistores de pull-up, jumpers nos pinos A0, A1 e A2 da memória e diodo para interligar VCC ao VBAT e carregar a bateria.

Utilizando o DS3231 com o Arduino (com biblioteca)

Agora vejamos como usar o DS3231 com o Arduino para realizar algumas tarefas diferentes.

Obs: pro meu azar, o DS3231 do meu módulo não quis funcionar de forma alguma. Por um momento, pensei que o CI era falsificado, mas acho que me venderam um modulo com CI queimado mesmo. Por conta disso, não pude testar os códigos adiante (exceto o da memória), mas pesquisei bastante antes de colocá-los.

Circuito

A ligação do Arduino com o DS3231 é bem direta, pois você não precisa de nenhum outro componente:

Ligação RTC DS3231 e Arduino

O VCC vai no 5V do Arduino e os GNDs de ambos são interligados. Sobre os demais pinos, o SCL vai no A5 e o SDA vai no pino A4 do Arduino. Além disso, também liguei o SQW no pino digital 2 do Arduino (pode ser outro pino digital). Ele vai servir pra alertar sobre os alarmes.

Baixando biblioteca

A biblioteca pode ser baixada na própria IDE do Arduino em Sketch -> Incluir Biblioteca -> Gerenciar Bibliotecas… E é só pesquisar por “DS3231” e baixar a opção que tem como autor “Rtc_by_Makuna”.

Não existe um motivo específico para utilizar esta biblioteca. Usei, pois já tinha ela instalada (tinha baixado para controlar o DS1302). Fique a vontade para usar outra, mas tenha em mente que os códigos seguintes foram feitos usando a biblioteca que apresentei.

Código básico

O código abaixo e os seguintes são uma adaptação dos exemplos que tem na biblioteca. No caso, ele configura a hora do RTC no void setup e, no void loop, ele fica printando a data, hora, dia da semana e temperatura a cada 5 segundos.

A configuração da data e hora usa o #define __DATE__ e __TIME__ (são declarados implicitamente pela linguagem C++). Esses #defines informam ao programa a data e hora em que ele foi compilado. Então isso evita da gente ter que ficar digitando toda vez a data e hora que queremos. 

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

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
#include <Wire.h>
#include <RtcDS3231.h>

// Instância do RTC
RtcDS3231<TwoWire> rtc(Wire);

void setup ()
{
  // Inicia a comunicação serial
  Serial.begin(9600);
 
  // 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 ()
{
  // É possível verificar se a data e hora são válidas
  // Isso pode ocorrer se o RTC estiver sem a bateria ou ela estiver com tensão baixa
  if (!rtc.IsDateTimeValid())
  {
    // Devido a erro na comunicação
    if (rtc.LastError() != 0)
    {
      Serial.print("Erro de comunicação ");
      Serial.println(rtc.LastError());
    }
    // Devido ao RTC estar sem bateria ou ela estiver com tensão baixa
    else
    {
      Serial.println("Data e hora inválidos!");
    }
  }

  // Obtem a data e hora e imprime os valores
  RtcDateTime now = rtc.GetDateTime();
  Serial.print(now.Day());
  Serial.print("/");
  Serial.print(now.Month());
  Serial.print("/");
  Serial.println(now.Year());

  Serial.print(now.Hour());
  Serial.print(":");
  Serial.print(now.Minute());
  Serial.print(":");
  Serial.println(now.Second());

  // Obtem o valor de temperatura
  RtcTemperature temp = rtc.GetTemperature();
  Serial.println(temp.AsFloatDegC());

  delay(5000);
}

Código com alarme

Peguei o mesmo código de antes e adicionei alguns comandos para configurar os 2 alarmes. Substitui o código do void loop para ficar verificando constantemente se o alarme foi disparado. A verificação constante é do valor de uma flag e não do estado do pino de interrupção em si. Como o alarme é sinalizado na interrupção, não precisaria ficar verificando constantemente. Alias, recomendo a leitura do post ensinando a mexer com interrupção no Arduino.

Na página 12 do datasheet, é possível ler quais configurações são permitidas pelos alarmes. Por exemplo: o alarme 1 pode ser disparado quando o dia, as horas, os minutos e os segundos forem iguais aos valores definidos. Já o alarme 2 não suporta a configuração dos segundos, apenas do dia, das horas e minutos. Sendo assim nenhum deles suporta a verificação do mês e isto precisaria ser feito dentro do próprio código quando o alarme fosse disparado (now.Month() == mês_desejado).

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

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
#include <Wire.h>
#include <RtcDS3231.h>

// Pino interrupção
#define DS3231_SQW  2

// Instância do RTC
RtcDS3231<TwoWire> rtc(Wire);

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

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


void setup ()
{
  // Configura o pino de interrupção
  pinMode(DS3231_SQW, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(DS3231_SQW), funcao_alarme, FALLING);
   
  // Inicia a comunicação serial
  Serial.begin(9600);
 
  // 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);


  // Configura alarme 1 para um dia e horário específico:
  // Dia 18 às 14:35:20
  DS3231AlarmOne alarm1(
            18,
            14,
            35,
            20,
            DS3231AlarmOneControl_HoursMinutesSecondsDayOfMonthMatch);
  rtc.SetAlarmOne(alarm1);

  // Configura alarme 2 para um dia e horário específico:
  // Dia 12 às 8:40
  DS3231AlarmTwo alarm2(
            12,
            8,
            40,
            DS3231AlarmTwoControl_HoursMinutesDayOfMonthMatch);
  rtc.SetAlarmTwo(alarm2);

  // Limpa os estados dos alarmes
  rtc.LatchAlarmsTriggeredFlags();
}

void loop ()
{
  // Verifica se o alarme foi disparado
  if (disparouAlarme)
  {
    disparouAlarme = false; // Limpa flag
   
    // Descobre qual alarme foi
    DS3231AlarmFlag flag = rtc.LatchAlarmsTriggeredFlags();
   
    if (flag & DS3231AlarmFlag_Alarm1)
    {
      Serial.println("Alarme 1 disparado!");
    }
    if (flag & DS3231AlarmFlag_Alarm2)
    {
      Serial.println("Alarme 2 disparado!");
    }
  }
}

Leitura e escrita à memória

Por fim, o código adiante mostra como escrever e ler dados da memória EEPROM 24C32. No caso, ele fica lendo a Serial e o que for escrito nela é transferido para a EEPROM. E, a cada 1 segundo o programa lê e printa 10 bytes da memória.

Para testar o código você pode: abrir o monitor serial, escrever um texto de até 10 letras e aperta enter. Em seguida, o texto será printado no monitor serial a cada 1 segundo. Você pode então desligar o Arduino e ligar de novo para ver se o valor foi mantido.

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

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
#include <Wire.h>
#include <RtcDS3231.h>
#include <EepromAT24C32.h>

// Instância do RTC
RtcDS3231<TwoWire> rtc(Wire);
EepromAt24c32<TwoWire> rtc_eeprom(Wire);

RtcDateTime now;
RtcDateTime last_now;

void setup ()
{
  // Inicia a comunicação serial
  Serial.begin(9600);
 
  // Inicia a comunicação com o RTC e a EEPROM
  rtc.Begin();
  rtc_eeprom.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 ()
{
  now = rtc.GetDateTime();
 
  // Lê uma mensagem recebida no monitor serial
  if(Serial.available())
  {
    char msg[10] = "";
    uint8_t * msg_p = msg;
    uint8_t i = 0;
    while(Serial.available() && i < 10)
    {
      *msg_p = (char)Serial.read();
      msg_p++;
      i++;
      delay(1); // Pequeno delay para dar tempo do próximo byte ser recebido
    }
   
    // Grava a mensagem a partir da posição 0 da memória
    rtc_eeprom.SetMemory(0, (const uint8_t*)msg, sizeof(msg));
  }

  // Entra aqui a cada 1 segundo
  //if (now.Second() != last_now.Second())
  if(millis()%1000 == 0)
  {
    last_now = now;
    char msg[10] = "";

    // Lê a memória RAM do DS1302
    rtc_eeprom.GetMemory(0, msg, 10);
   
    for(uint8_t i = 0; i < 10; i++)
    {
      Serial.print(msg[i]);
    }
    Serial.println("");
  }  
}