O módulo DS1302 é um módulo RTC mais básico e mais barato que outros encontrados e permite a contagem do tempo e da data automaticamente. Sendo assim, neste post, veremos como usar o DS1302 com o Arduino para adicionar à capacidade de obtenção de data e hora em nossos projetos. E aprenderemos um pouco sobre o que é o RTC de forma geral.

Informações básicas

O que é o módulo RTC DS1302

No contexto da eletrônica, RTC (Real-Time Clock) ou Relógio de Tempo Real é um tipo de circuito capaz de armazenar a hora e data e manter elas atualizadas em tempo real. Ou seja, a qualquer momento que eu quiser, esse circuito precisa me passar o valor correto da hora e data atual.

Para que o RTC alcance seu objetivo de manter a hora e data atualizadas a todo instante, é muito comum dele utilizar uma bateria para que a contagem da hora continue sendo feita mesmo se a fonte de alimentação principal for cortada. Por exemplo: os computadores sabem a hora e data correta graças ao RTC que fica na placa mãe (também sabem por meio da Internet). Mas, se a energia acabar ou a tomada do PC for desconectada, a bateria presente na placa mãe vai manter o RTC contando as horas. Assim, quando o PC for ligado novamente, a hora vai estar atualizada independente se o PC tem ou não conexão com a internet para puxar o horário atualizado.

Um RTC também pode englobar outras funcionalidades mais complexas como função de alarme, mas isso vai depender de caso para caso. O DS1302 é um RTC mais básico e, além da função de armazenar e controlar a data e hora, ele também tem uma memória interna, que pode ser usada para armazenar e ler dados.

E o DS1302 em si é um Circuito Integrado, mas o foco deste post é mostrar o módulo mais comum que utiliza esse CI. Veja uma foto dele abaixo, onde é possível ver o CI (do tipo PTH) e a bateria (CR2032) que mantém ele contando mesmo sem receber alimentação nos pinos VCC e GND:

Módulo RTC DS1302

Além da bateria, o RTC também pode contar com outro componente muito importante: o cristal (”cilindro” acima do CI na imagem acima). O cristal é utilizado na geração do clock interno que o RTC usa para contagem dos segundos. É muito comum dos RTCs utilizarem um cristal de 32768 Hz. Internamente ele acaba dividindo o clock até obter uma frequência próxima de 1 Hz para contar os segundos corretamente.

Imprecisão do horário

Conforme foi falado no tópico anterior, a contagem dos segundos advém de um clock que é gerado a partir de um cristal com valor específico. Qualquer imprecisão neste cristal faz com que o clock seja mais rápido ou lento que o ideal. Por conta disso, a contagem dos RTCs não é perfeita e depende bastante da precisão dos cristais.

O principal parâmetro do cristal que nós dá ideia dessa precisão é o ppm ou partes por milhão. A partir dele, conseguimos saber qual é o desvio que a frequência do cristal pode apresentar:

Desvio_f = \pm\frac{f_{cristal} \times ppm}{1000000} [Hz]

Dessa forma, um cristal de 32768 Hz com 30 ppm pode apresentar uma frequência de 32768 +- 0,98 Hz. 0,98 Hz pode parecer um desvio pequeno, mas esse efeito acumulado em um mês pode fazer o RTC contar 1 min a mais do que deveria (ou a menos). Ou seja, o RTC pode não ser recomendado para aplicações de máxima precisão.

Neste link tem um documento detalhando melhor o assunto.

Aplicações

Adiante estão alguns exemplos de uso do DS1302:

  • Despertador:

    Um despertador precisa de ter noção da hora atual e um RTC pode ser muito útil pra isso.

    Aqui no site tem um post que ensina a criar um despertador, mas sem RTC. A desvantagem é que a hora não é armazenada caso o dispositivo fique sem energia.

  • Aplicações onde você quer manter a funcionalidade de relógio fora do microcontrolador:

    Ao invés de armazenar e contar o tempo dentro do próprio microcontrolador, um CI dedicado pode liberar o microcontrolador para focar em outras tarefas.

    Alguns microcontroladores modernos já possui um circuito RTC interno pra lidar com todo o controle de data e hora automaticamente.

  • Dispositivos que precisam passar muito tempo dormindo para economizar energia:

    • O RTC pode ajudar nessa tarefa, pois ele consome pouca energia e, dependendo, pode ser capaz de acordar o microcontrolador em um determinado horário, evitando que ele fique ligado quando não é necessário.
  • Guardar um valor importante:
    • Como o DS1302 possui uma memória interna, o microcontrolador pode armazenar algum dado importante nela.
  • Entre outros casos. O limite é a imaginação.

Características do RTC DS1302

Neste tópico, irei mostrar as características do CI DS1302 e do módulo mais comum. 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 DS1302.

  • 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 pinos de alimentação: VCC1 e VCC2:
    • Ambos suportam de 2,0 a 5,5V;
    • A tensão típica é de 3,3V;
    • VCC1: alimentação da bateria;
    • VCC2: alimentação primária;
    • 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 Serial Síncrona:
    • Pode-se dizer que é igual a comunicação SPI de 3 fios (MISO e MOSI interligados);
    • Utiliza 3 fios:
      • CLK ou SCLK: clock da comunicação;
      • RST ou CE: pino para habilitar comunicação;
      • DAT ou I/O: pino onde os dados são enviados/recebidos.
    • Clock de no máximo 2 MHz em 5V.
  • Consumo de corrente:
    • Funcionando normalmente com alimentação primária: até 1,2 mA;
    • Modo onde apenas mantém a hora/atualizada: 1 μA;
    • O consumo depende da tensão de alimentação e do estado do RTC, então consulte o datasheet para maiores detalhes.
  • É possível configurar o DS1302 internamente para interconectar VCC2 e VCC1 para que a alimentação principal carregue a bateria ligada em VCC1. Essa configuração é relativa ao recurso chamado de Trickle Charger. Leia os detalhes no datasheet.
  • A hora pode ser configurada no formato de 24h ou de 12h am/pm.
  • O DS1302 possui uma memória RAM com 31 bytes:
    • É possível ler/gravar dados que são mantidos na memória desde que o DS1302 esteja alimentado de alguma forma. Se a alimentação principal e a bateria forem desligadas, os dados dessa RAM são apagados.

Circuito do módulo

Encontrei neste site uma imagem de um possível esquemático do módulo DS1302. Pelo próprio datasheet, a ligação sugerida já é bem simples e o esquemático não é muito diferente. Veja abaixo:

Fonte: Sunfounder

No caso do módulo que tenho (foto do início do post), ele não possui os resistores R1 a R3 e nem os capacitores C1 e C2.

Utilizando o DS1302 com o Arduino (com biblioteca)

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

Circuito

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

Ligação RTC DS1302 e Arduino

O VCC vai no 5V do Arduino e os GNDs de ambos são interligados. Em relação aos demais pinos, eles podem ser ligados em quaisquer pinos digitais do Arduino. O importante é alterar a relação depois no código do Arduino. No caso da imagem acima, RST está no pino 3, DAT no pino 4 e CLK no pino 5.

Obs: Se o VCC for ligado no 3.3V do Arduino, o limite de tensão dos pinos digitais do DS1302 será de -0,3 à 3,6 V. Ou seja, você pode danificar o DS1302 se não utilizar um conversor de sinais nos pinos DAT, CLK e RST.

Baixando biblioteca

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

Não existe um motivo específico para utilizar esta biblioteca, mas foi uma das primeiras que testei e que achei tranquilo de usar. 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 e dia da semana 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
// Bibliotecas (os arquivos vem da mesma biblioteca)
#include <ThreeWire.h>
#include <RtcDS1302.h>

// Pinos do RTC
#define DS1302_RST  3
#define DS1302_DAT  4
#define DS1302_CLK  5

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

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

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

  // Desabilita a proteção contra escrita para gravar a hora e data
  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__);
  rtc.SetDateTime(compiled);
  // Para definir um formato personalizado, você pode fazer o seguinte:
  // O mês precisa ter 3 letras abreviando o nome do mês em inglês (no exemplo abaixo é fevereiro)
  //RtcDateTime teste = RtcDateTime("Feb 12 2022", "19:32:45");
  //rtc.SetDateTime(teste);
}

void loop ()
{
  // 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());

  // Dia da semana (1 a 7, sendo 1 = segunda e 7 = domingo)
  Serial.println(now.DayOfWeek());

  delay(5000);
}

Código com outros comandos

Peguei o mesmo código de antes e adicionei alguns comandos que a biblioteca disponibiliza. Por exemplo, o comando de verificar se a data e hora obtidas são válidas. 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
#include <ThreeWire.h>
#include <RtcDS1302.h>

#define DS1302_RST  3
#define DS1302_DAT  4
#define DS1302_CLK  5

ThreeWire ligacao(DS1302_DAT, DS1302_CLK, DS1302_RST);
RtcDS1302<ThreeWire> rtc(ligacao);

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

  // 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);
    }
  }
}

void loop ()
{
  // 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());

  // Dia da semana (1 a 7, sendo 1 = segunda e 7 = domingo)
  Serial.println(now.DayOfWeek());

  // É 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 (!now.IsValid())
  {
    Serial.println("Data e hora inválidos!");
  }

  delay(5000);
}

Leitura e escrita à memória

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

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 (lembrar de colocar uma bateria no DS1302).

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
#include <ThreeWire.h>
#include <RtcDS1302.h>

#define DS1302_RST  3
#define DS1302_DAT  4
#define DS1302_CLK  5

ThreeWire ligacao(DS1302_DAT, DS1302_CLK, DS1302_RST);
RtcDS1302<ThreeWire> rtc(ligacao);

RtcDateTime now;
RtcDateTime last_now;

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

  // 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__);
  rtc.SetDateTime(compiled);
}

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 na memória
    rtc.SetMemory((const uint8_t*)msg, sizeof(msg));
  }

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

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