quinta-feira, 22 de julho de 2010

Agregando um REAL TIME CLOCK (RTC) ao Arduino.

















Se você precisa registrar dados com infomações de data e hora em um projeto com Arduino, ou quer apenas ter essas informações disponíveis em um display por exemplo, este tutorial propõe uma solução com baixo custo e muito fácil de implementar.

Utilizaremos como base o circuito integrado DS1307, que é um chip RTC completo e que por utilizar interface de comunicação I2C, permite uma fácil integração ao Arduino utilizando apenas duas portas (AN4 e AN5) que podem ser inclusive compartilhadas com outros dispositivos. O custo do circuito integrado DS1307 é de R$ 4,00.



Na figura ao lado temos o diagrama de conexões entre um Microcontrolador e o DS1307.







A figura abaixo mostra os blocos funcionais do RTC DS1307:




As entradas X1 e X2 são os pontos onde conectamos um cristal oscilador de 32.768 kHz responsável pelo clock necessário para o funcionamento do chip.
A partir desse clock é gerado um sinal com frequência de 1Hz responsável pelo incremento do relógio.

No bloco Power Control temos as 3 conexões de alimentação, positivo (VCC), negativo (GND) e VBat que é o ponto onde deveremos conectar uma bateria de lítio de 3V tipo CR2032. Essa bateria será responsável por manter o relógio em funcionamento mesmo quando a alimentação do circuito for desligada.
O consumo da bateria nesse modo é de menos de 500nA (nano Amper), o que dá uma autonomia muito grande a bateria. Para se ter uma idéia, uma bateria de Lítio de 48mAh dá uma autonomia de 10 anos.

Os terminais SCL(serial clock input) e SDA(seria data input) são as conexões correspondentes a interface I2C, essa interface funciona como um barramento onde podemos conectar uma série de dispositivos , sendo que cada um terá um endereço e será gerenciado pelo Arduino.
Mais detalhes sobre a interface I2C podem ser obtidos aqui :

O terminal SQW/OUT é uma saída programável que pode ser utilizada para enviar 4 frequências distintas (1Hz, 4 Khz, 8 Khz e 32 Khz) de acordo com os valores programados nos bits 0 e 1 do registrador 07h.



OUT: Controla o nível da saída SQW/OUT quando o bit4 (SQWE) for igual a 0.
SQWE: Define se a saída SWQ/OUT será controlada pelo bit 7 ou por RS0 e RS1.
RS0 e RS1: controlam qual é a freqüência do sinal na saída SWQ/OUT quando o bit SQWE for igual a 1.

A tabela abaixo mostra de forma clara e resumida o funcionamento do registrador de controle do DS-1307:




Ao lado temos a identificação de cada terminal do DS1307 em seu dois tipos de envolucros disponíveis.



As voltagens de alimentação do DS1307 devem respeitar a tabela abaixo:



No DS-1307 as informações são armazenadas em registradores que podem ser lidos ou modificados facilmente via interface I2C.

A tabela abaixo mostra o formato desses registradores:

Obs: os valores são armazenados em BCD e portanto devem ser convertidos para ASCII antes de serem enviados para a serial ou LCD.



Os registradores de 00h a 07h são utilizados pelo RTC para armazenar as informações de data e hora, já os registradores 08h a 3Fh estão disponíveis para o usuário utilizar da forma que desejar. Vale lembrar que esses registros também são mantidos pela bateria de lítio na ocorrência de falha de alimentação.

Deve-se ter cuidado ao ler e escrever a informação de Horas (registrador 02h) pois partes dos bits mais significativos (bits 5 e 6) são utilizados para selecionar/indicar o modo de operação 12/24h e AM/PM, sendo então necessário utilizarmos uma mascara em nosso programa para ler e escrever essas informações.

Importante:
O bit 7 (CH) do registrador 00h“segundos”, é como um “ON/OFF”do relógio, portanto ele deverá sempre ser colocado em 0, pois se for colocado em 1 o relógio vai ficar parado.


Vamos ao código:

Como a comunicação com o DS1307 é via I2C, precisamos incluir em nosso código a biblioteca “WIRE”com o comando abaixo:

#include "Wire.h"

O endereço lógico do DS1307 no barramento I2C é 0x68, e portanto definimos isso com a linha abaixo:

#define DS1307_I2C_ADDRESS 0x68

Abaixo temos um exemplo de trecho de código para efetuar a escrita nos registradores do DS1307, ou seja , ajustar o relógio:

Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.send(0x00); //posiciona no primeiro registrador
Wire.send(segundos);
Wire.send(minutos);
Wire.send(horas);
Wire.send(diaDaSemana);
Wire.send(diaDoMes);
Wire.send(mes);
Wire.send(ano);
Wire.endTransmission();


Obs: Inicialmente é necessário enviar um byte para posicionar no registro desejado, no exemplo acima "0x00" significa o primeiro registrador do RTC 00h) que corresponde a informação de segundos.
A partir dai, a cada escrita efetuada o RTC automaticamente avança para o próximo registrador. O mesmo acontece com o processo de leitura.

O trecho de código abaixo é um exemplo de como ler os dados dos registradores do DS1307.


Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.send(0x00);//posiciona no primeiro registrador
Wire.endTransmission();
Wire.requestFrom(DS1307_I2C_ADDRESS, 7);//solicita leitura de 7 bytes na I2C
segundos = (Wire.receive() & 0x7f);// Alguns registros necessitam mascara
minutos = Wire.receive();
horas = (Wire.receive() & 0x3f);// necessario alterar se utilizar 12 hour am/pm
diaDaSemana = Wire.receive();
diaDoMes = Wire.receive();
mes = Wire.receive();
ano = Wire.receive();


Nas fotos abaixo, temos o circuito do RTC montado em uma protoshield e também a montagem final do shield sobre o Brasileirino (Arduino básico da Globalcode) .






Bem pessoal, para finalizar temos abaixo um código demo completo que permite programar e ler o RTC DS1307 através do teclado do PC conectado ao Arduino.
Basta carregar o código no arduino e abrir a janela de terminal monitor em 9600.



/******************************************************************************
Example program I2C-RTC interface with Arduino.

SETUP: I2C-RTC => Arduino
PIN1 => A5, PIN2 => A4, PIN3 => ground, PIN6 => +5V
Note: The program is written for address 0xD0 (Arduino address 0x68).
This program was tested using Arduino Nano
Document: DS1340 datasheet
Updated: September 4, 2008
E-mail: support@gravitech.us
Gravitech
(C) Copyright 2008 All Rights Reserved
*******************************************************************************/

#include

#define Binary 0
#define Hex 1

/*******************************************************************************
Function Prototype
*******************************************************************************/
unsigned int SerialNumRead (byte);
void SetTime();
void DisplayTime();

/*******************************************************************************
Global variables
*******************************************************************************/
const int I2C_address = 0x68; // I2C write address
byte Second; // Store second value
byte Minute; // Store minute value
byte Hour; // Store hour value
byte Day; // Store day value
byte Date; // Store date value
byte Month; // Store month value
byte Year; // Store year value

/*******************************************************************************
Setup
*******************************************************************************/
void setup()
{
Serial.begin(9600);
Wire.begin(); // join i2c bus (address optional for master)
delay(1000);
}

/*******************************************************************************
Main Loop
*******************************************************************************/
void loop()
{
boolean Readtime; // Set/Read time flag
unsigned int Incoming; // Incoming serial data

// Display prompt
Serial.println("What would you like to do?");
Serial.println("(0) To set the current time.");
Serial.println("(1) To display the current time.");
Serial.print("Enter 0 or 1: ");

Incoming = SerialNumRead (Binary); // Get input command
Serial.println(Incoming, DEC); // Echo the value
Serial.println();

if (Incoming == 0) // Process input command
SetTime();
else if (Incoming == 1)
DisplayTime();

delay (1000);
}

/*******************************************************************************
Read a input number from the Serial Monitor ASCII string
Return: A binary number or hex number
*******************************************************************************/
unsigned int SerialNumRead (byte Type)
{
unsigned int Number = 0; // Serial receive number
unsigned int digit = 1; // Digit
byte i = 0, j, k=0, n; // Counter
byte ReceiveBuf [5]; // for incoming serial data

while (Serial.available() <= 0);

while (Serial.available() > 0) // Get serial input
{
// read the incoming byte:
ReceiveBuf[i] = Serial.read();
i++;
delay(10);
}

for (j=i; j>0; j--)
{
digit = 1;

for (n=0; n < k; n++) // This act as pow() with base = 10
{
if (Type == Binary)
digit = 10 * digit;
else if (Type == Hex)
digit = 16 * digit;
}

ReceiveBuf[j-1] = ReceiveBuf[j-1] - 0x30; // Change ASCII to BIN
Number = Number + (ReceiveBuf[j-1] * digit); // Calcluate the number
k++;
}
return (Number);
}

/*******************************************************************************
Set time function
*******************************************************************************/
void SetTime()
{
Serial.print("Enter hours (00-23): ");
Hour = (byte) SerialNumRead (Hex);
Serial.println(Hour, HEX); // Echo the value
Hour = Hour & 0x3F; // Disable century
Serial.print("Enter minutes (00-59): ");
Minute = (byte) SerialNumRead (Hex);
Serial.println(Minute, HEX); // Echo the value
Serial.print("Enter seconds (00-59): ");
Second = (byte) SerialNumRead (Hex);
Serial.println(Second, HEX); // Echo the value
Second = Second & 0x7F; // Enable oscillator
Serial.print("Enter day (01-07): ");
Day = (byte) SerialNumRead (Hex);
Serial.println(Day, HEX); // Echo the value
Serial.print("Enter date (01-31): ");
Date = (byte) SerialNumRead (Hex);
Serial.println(Date, HEX); // Echo the value
Serial.print("Enter month (01-12): ");
Month = (byte) SerialNumRead (Hex);
Serial.println(Month, HEX); // Echo the value
Serial.print("Enter year (00-99): ");
Year = (byte) SerialNumRead (Hex);
Serial.println(Year, HEX); // Echo the value

Wire.beginTransmission(I2C_address);
Wire.send(0);
Wire.send(Second);
Wire.send(Minute);
Wire.send(Hour);
Wire.send(Day);
Wire.send(Date);
Wire.send(Month);
Wire.send(Year);
Wire.endTransmission();
//I2COUT SDA, I2C_WR, [0,Second,Minute,Hour,Day,Date,Month,Year]
Serial.println();
Serial.println ("The current time has been successfully set!");
}

/*******************************************************************************
Display time function
*******************************************************************************/
void DisplayTime()
{
char tempchar [7];
byte i = 0;
Wire.beginTransmission(I2C_address);
Wire.send(0);
Wire.endTransmission();

Wire.requestFrom(I2C_address, 7);

while(Wire.available()) // Checkf for data from slave
{
tempchar [i] = Wire.receive(); // receive a byte as character
i++;
}
Second = tempchar [0];
Minute = tempchar [1];
Hour = tempchar [2];
Day = tempchar [3];
Date = tempchar [4];
Month = tempchar [5];
Year = tempchar [6];

// Display time
Serial.print("The current time is ");
Serial.print(Month, HEX);
Serial.print("/");
Serial.print(Date, HEX);
Serial.print("/20");
if (Year<10)
Serial.print("0");
Serial.print(Year, HEX);
Serial.print(" ");
Serial.print(Hour, HEX);
Serial.print(":");
Serial.print(Minute, HEX);
Serial.print(":");
Serial.println(Second, HEX);
}



6 comentários:

  1. Olá, me chamo Pedro Franceschi e desenvolvi um projeto usando um AtMega328: http://www.youtube.com/watch?v=Fd4TD-N6jIo Pode dar uma olhada? Abraços!

    ResponderExcluir
  2. Opa. No ultimo codigo postado (o completo) do lado de #Include tá faltando o "Wire.h". :)

    ResponderExcluir
  3. Oi, estou usando uma placa pronta, só que já alterei entre vários cristais, com baixa precisão, a maioria atrasa a hora de 1 a 4 segundos por dia, o último que usei adianta 1 segundo por dia, tentei usar capacitores como alguns indicam, sem sucesso.

    ResponderExcluir
  4. Novidade para todos SALA DE BATE PAPO AO VIVO sobre arduino e tudo que é referente ao assunto.
    Entrem em http://bp4.bpbol.uol.com.br/room.html ou pesquisem no site do bate papo bol pela sala ARDUINO E CONTROLADORES. ESPALHEM A NOTICIA

    ResponderExcluir
  5. Preciso de um código onde tenho que acender luzes em horários programados, ex:10:00 as 22:00 também podendo ser acionado por botão, e atualizando o status da lâmpada em qualquer dispositivo na rede que também controlara o a luz.
    será controlada pelo horário, pelo botão ou por dispositivo na rede que recebera o estado da lâmpada (acesa ou apagada). se possível com login e senha para acessar a pagina.
    grato.

    ResponderExcluir
    Respostas
    1. Olá Guilherme, tudo bem ?
      Caso tenha interesse, posso desenvolver o código para você.
      atenciosamente
      José Luiz Sanchez Lorenzo
      jllorenzo@terra.com.br

      Excluir