Para obtermos data e horas atuais no Arduino precisamos contar com algum recurso externo, como por exemplo, um módulo RTC ou através de um servidor de data e hora na internet.
Existem diversas formas de conseguir obter a data e hora atual, a forma mais viável vai depender do projeto em si. Nesse artigo, vamos ver sobre algumas das estratégias que podemos utilizar.
- Módulo RTC
- Servidor de Data e Hora
- Entrada através da Serial
- Entrada Manual
- Data e Hora de compilação
RTC
Contar com o um módulo RTC é uma boa saída para obtermos data e hora em nossas aplicações, há alguns modelos disponíveis no mercado, os quais contam com uma bateria de 3V, de modo que, mesmo sem energia na placa, o próprio módulo consegue se manter.
Servidor de Data e Hora
Caso a aplicação conte com algum meio de acesso a internet, ou mesmo acesso a uma rede interna, uma boa saída é acessar um servidor NTP. Nesse artigo falo um pouco como funciona esse recurso:
http://fabianoallex.blogspot.com.br/2015/08/arduino-ntp-acessando-servidor-de-hora.html
É preciso ficar atento ao fato de que o servidor de data e hora pode não retornar a hora no mesmo fuso-horário de onde a aplicação estiver rodando, ficando a cargo do programador fazer os ajustes necessários, além disso, alguns servidores também não retornam a data no horário de verão.
Serial
Um meio simples e eficiente de sincronizarmos a data e hora do Arduino é através da comunicação serial, caso o arduino esteja conectado a outros arduinos ou computadores que possam fornecer a data e hora. Esse tipo de solução pode ser utilizado em aplicações distribuídas, onde um Arduino tem acesso a data e hora, e consegue repassar essa informação através de uma comunicação serial. Por exemplo, se existe um Arduino com um módulo RTC o qual se conecta com outros Arduinos, não tem porque cada um dos Arduinos possuirem um módulo. Basta que o Arduino com o módulo RTC repasse a data e hora atual para os demais.
Manual
Caso não seja possível para uma aplicação contar com nenhum outro meio de obter a data e hora, ainda existe a possibilidade do próprio usuário da aplicação informar a data e hora atual. A partir do momento que o Arduino sabe qual é a data e hora atual, ele é capaz de se manter atualizado enquanto estiver energizado ou não for resetado. Obviamente não é o meio mais confiável de se obter data e hora, mas pode ser a única solução dependendo da aplicação. O único inconveniente, é que será necessário ter uma interface onde o usuário possa dar a entrada manual. Se durante a execução, existir o risco da aplicação ser reiniciada, uma saída paliativa para conseguir recuperar a data e hora, seria gravar a cada tantos segundos a data atual na eeprom (ou outro meio disponível), e caso reiniciar a aplicação, a mesma iniciaria com a última data gravada. O inconveniente deste método, é que cada vez que a aplicação reiniciar, a precisão da hora será perdida. E caso o meio utilizado pra gravar a informação for a eeprom, é preciso ficar atento ao limite de vezes que uma eeprom pode ser gravada, antes de acabar sua vida útil.
Data e Hora de compilação
Outra opção, que é mais aconselhável para ser usada durante a programação, é utilizar a data e hora de compilação do arquivo executável. Ela facilita o programador ter uma forma de testar seu código, sem ter necessariamente que contar com algum recurso externo. Essa forma de obtenção de data e hora só não é viável para se utilizar em produção, pois sempre que a aplicação for resetada, a data inicial será a mesma, então se uma aplicação foi compilada ha dois meses, ao reiniciar a aplicação, a data será a data de dois meses atrás.
Outros
Ainda há outros modos de se obter a data e hora, como por exemplo, através de um módulo de GPS ou GSM, caso a aplicação utilize-os.
No exemplo abaixo mostro um exemplo que sincroniza a data e hora com um módulo RTC, com um servidor NTP, com a entrada serial e com a data e hora de compilação do executável.
Código-fonte:
Inicialmente foi criado uma struct chamada Datetime, para que possamos ter os campos da data separados, o que facilita nosso entendimento. E também foi definido o tipo time_t, que na verdade é um long, de 4 bytes. Esse tipo na verdade é o chamado Unix Time, que nada mais é que a quantidade de segundos que se passaram desde o dia 01/01/1970. Ter a hora armazenada em um tipo de dado único facilita diversas operações com datas, como soma e subtração, por isso, a definição e utilização do tipo de dados time_t.
Como há dois tipos de dados, Datetime (que é uma struct) e time_t (que é um long), sendo que ambos representam a mesma coisa, foram criadas duas funções que converte a data de um tipo para o outro, sendo elas:
unixTimeToDatetime e datetimeToUnixTime
Com essas duas funções, fica fácil alternar entre um e outro tipo de dados. As Demais classes e funções utilizadas para tratar as datas se baseiam nesses tipos de dados definidos e nessas duas funções de conversões.
Para controlar a data atual do Arduino, foi criada uma classe chamada ArduinoDatetime. Essa classe é responsável por nos informar a data e hora atual do Arduino. Mas para isso, antes precisamos informar a data inicial. Isso é feito através do método Sync. Depois disso podemos obter a data e hora atuais através das funções getDatetime(), que retorna a data atual no formato Datetime, e através na função now() que retorna a data e hora atual no formato time_t.
Essa classe armazena duas informações: o millis no momento da sincronização e a data e hora daquele momento. Para a classe retornar a data atual, basta que classe calcule quantos segundos se passaram desde a última sincronização e somar esse valor à data e hora do momento de sincronização.
A princípio, é necessário apenas uma única sincronização para que o Arduino possa nos dar a data e hora atual, mas é aconselhável, dentro de um certo período de tempo, refazer a sincronização. Isso porque o clock do Arduino pode ter algum atraso durante longos períodos de tempo.
Veja que a classe em si não é baseada em nenhum meio específico de obtenção da data e hora, ela apenas depende de uma prévia sincronização de data e hora, sem se preocupar com a origem da data. Mais abaixo no código, temos as diferentes funções que nos dão data e hora com diferentes origens: vindo do módulo DS3231, via NTP, via Serial e ainda da data e hora da compilação. Esses são apenas alguns exemplos. É perfeitamente possível que outras funções sejam incluídas.
No loop, o código fica alternando a sincronização da data com as diversas funções que criamos que podem nos retornar a data inicial. Obviamente que em uma aplicação não será necessário ter mais de uma. O que foi feito aqui é apenas para demonstrar as possibilidades.
#include "Wire.h"
#include <LiquidCrystal_I2C.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include "Dns.h"
LiquidCrystal_I2C lcd(0x3F,20,4); // set the LCD address to 0x27 for a 16 chars and 2 line display
struct Datetime { //sizeof(Datetime) ==> 5 bytes --- sem bitfields teria 8 bytes
byte second : 6; //0..63
byte minute : 6; //0..63
byte hour : 5; //0..31
byte dayOfWeek : 3; //0..7
byte dayOfMonth : 5; //0..31
byte month : 4; //0..15
unsigned int year : 11; //0..2047
};
typedef unsigned long time_t; //variavel de 32 bits. para maior precisão (datas acima do ano de 2038. utilizar a linha abaixo.)
//typedef unsigned long long time_t; //variavel de 64 bits
/****************************************************************************************
************************************ETHERNET CONFIG/FUNCTIONS****************************
*****************************************************************************************
se for rodar numa rede com firewall, verifique se os ip utilizado está liberado.
Servidores da NTP.br
a.st1.ntp.br 200.160.7.186 e 2001:12ff:0:7::186
b.st1.ntp.br 201.49.148.135
c.st1.ntp.br 200.186.125.195
d.st1.ntp.br 200.20.186.76
a.ntp.br 200.160.0.8 e 2001:12ff::8
b.ntp.br 200.189.40.8
c.ntp.br 200.192.232.8
****************************************************************************************/
byte mac[] = { 0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD};
unsigned int localPort = 8888; // local port to listen for UDP packets
IPAddress timeServer(200, 192, 232, 8); // time.nist.gov NTP server (fallback) - segunda tentativa caso a primeira de erro
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; // buffer to hold incoming and outgoing packets
//host para a primeira tentativa
const char* host = "a.st1.ntp.br"; // servidor da NTP.br - ver lista acima para todos os servidores da NTP.br
//const char* host = "192.168.200.254"; // servidor interno 01 - caso tenha um servidor de hora interno, pode ser configurado o nome ou ip na variavel host
EthernetUDP Udp;
DNSClient Dns;
IPAddress rem_add;
unsigned long sendNTPpacket(IPAddress& address) { // send an NTP request to the time server at the given address
memset(packetBuffer, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
packetBuffer[0] = 0b11100011; // LI, Version, Mode // Initialize values needed to form NTP request (see URL above for details on the packets)
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
packetBuffer[12] = 49; // 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
Udp.beginPacket(address, 123); //NTP requests are to port 123 // all NTP fields have been given values, now you can send a packet requesting a timestamp:
Udp.write(packetBuffer,NTP_PACKET_SIZE);
Udp.endPacket();
}
/****************************************************************************************
************************************FIM ETHERNET CONFIG/FUNCTIONS************************
****************************************************************************************/
/****************************************************************************************
****************************DATA/HORA*****************************************************
****************************************************************************************/
#define LEAP_YEAR(_year) ((_year%4)==0)
static byte monthDays[] = {31, 28, 31, 30 , 31, 30, 31, 31, 30, 31, 30, 31};
#define DS3231_I2C_ADDRESS 0x68
enum WeekDay {WD_SUNDAY=0, WD_MONDAY, WD_TUESDAY, WD_WEDNESDAYM, WD_THURSDAY, WD_FRIDEY, WD_SATURDAY};
Datetime unixTimeToDatetime(time_t timep) {
Datetime dt = {0,0,0,0,0,0,0};
unsigned long long epoch = timep;
byte year, month, monthLength;
unsigned long days;
dt.second = epoch % 60;
epoch /= 60; // now it is minutes
dt.minute = epoch % 60;
epoch /= 60; // now it is hours
dt.hour = epoch % 24;
epoch /= 24; // now it is days
dt.dayOfWeek = (epoch+4) % 7;
year = 70;
days = 0;
while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= epoch) { year++; }
dt.year = year; // is returned as years from 1900
days -= LEAP_YEAR(year) ? 366 : 365;
epoch -= days; // now it is days in this year, starting at 0
for (month=0; month<12; month++) {
monthLength = ( (month==1) && LEAP_YEAR(year) ) ? 29 : monthDays[month]; // month==1 -> february
if (epoch >= monthLength) { epoch -= monthLength; } else { break; }
}
dt.month = month+1; // jan is month 0
dt.dayOfMonth = epoch+1; // day of month
dt.year += 1900;
return dt;
}
time_t datetimeToUnixTime(Datetime dt) { // note year argument is full four digit year (or digits since 2000), i.e.1975, (year 8 is 2008)
time_t seconds;
if(dt.year < 69) { dt.year += 2000; } // seconds from 1970 till 1 jan 00:00:00 this year
seconds = (dt.year-1970)*(60*60*24L*365);
for (int i=1970; i<dt.year; i++) { if (LEAP_YEAR(i)) { seconds += 60*60*24L; } } // add extra days for leap years
for (int i=0; i<dt.month-1; i++) { seconds += (i==1 && LEAP_YEAR(dt.year)) ? 60*60*24L*29 : 60*60*24L*monthDays[i]; } // add days for this year
seconds += (dt.dayOfMonth-1)*3600*24L + dt.hour*3600L + dt.minute*60L + dt.second;
return seconds;
}
class DS3231{
private:
static byte decToBcd(byte val) { return( (val/10*16) + (val%10) ); } // Convert normal decimal numbers to binary coded decimal
static byte bcdToDec(byte val) { return( (val/16*10) + (val%16) ); } // Convert binary coded decimal to normal decimal numbers
public:
static void sync(time_t t) { sync(unixTimeToDatetime(t)); }
static void sync(Datetime dt) {
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0); // set next input to start at the seconds register
Wire.write(decToBcd(dt.second)); // set seconds
Wire.write(decToBcd(dt.minute)); // set minutes
Wire.write(decToBcd(dt.hour)); // set hours
Wire.write(decToBcd(dt.dayOfWeek+1));// set day of week (1=Sunday, 7=Saturday)
Wire.write(decToBcd(dt.dayOfMonth)); // set date (1 to 31)
Wire.write(decToBcd(dt.month)); // set month
Wire.write(decToBcd( (byte)(dt.year-2000) )); // set year (0 to 99)
//Wire.write(2000-decToBcd(dt.year)); // set year (0 to 99) --> substituido pela linha acima corrigido 07/06/201
Wire.endTransmission();
}
static Datetime getDatetime() {
Datetime dt;
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0); // set DS3231 register pointer to 00h
Wire.endTransmission();
Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
dt.second = bcdToDec(Wire.read() & 0x7f); // request seven bytes of data from DS3231 starting from register 00h
dt.minute = bcdToDec(Wire.read());
dt.hour = bcdToDec(Wire.read() & 0x3f);
dt.dayOfWeek = bcdToDec(Wire.read())-1;
dt.dayOfMonth = bcdToDec(Wire.read());
dt.month = bcdToDec(Wire.read());
dt.year = bcdToDec(Wire.read())+2000;
return dt;
}
static time_t now(){ return datetimeToUnixTime(getDatetime()); }
};
class ArduinoDatetime {
private:
static unsigned long _mRef; //millis de referencia na hora do sincronismo
static time_t _time; //data e hora no momento do último sincronismo
public:
static void sync(Datetime dt) { sync(datetimeToUnixTime(dt)); }
static void sync(time_t t) { _mRef = millis(); _time = t; }
static time_t now() { return _time + (millis() - _mRef)/1000; }
static Datetime getDatetime() { return unixTimeToDatetime(now()) ;}
};
unsigned long ArduinoDatetime::_mRef = 0;
time_t ArduinoDatetime::_time = 0;
//retorna a data e hora que o programa foi compilado
Datetime compiledDatetime() {
Datetime dt;
char s_month[5];
int day, hour, minute, second, year;
static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
sscanf(__DATE__, "%s %d %d", s_month, &day, &year);
sscanf(__TIME__, "%2d %*c %2d %*c %2d", &hour, &minute, &second);
dt.second = second;
dt.minute = minute;
dt.hour = hour;
dt.dayOfMonth = day;
dt.month = (strstr(month_names, s_month) - month_names) / 3 + 1;
dt.year = year;
return dt;
}
//retorna a data e hora de um servidor ntp (sem fuso-horário)
Datetime NTPDatetime(){
if(Dns.getHostByName(host, rem_add) == 1 ){
Serial.println("DNS resolve...");
Serial.print(host);
Serial.print(" = ");
Serial.println(rem_add);
sendNTPpacket(rem_add);
} else {
Serial.println("DNS fail...");
Serial.print("time.nist.gov = ");
Serial.println(timeServer); // caso a primeira tentativa não retorne um host válido
sendNTPpacket(timeServer); // send an NTP packet to a time server
}
delay(1000); //aguarda um segundo, para receber os dados enviados.
if ( Udp.parsePacket() ) {
Udp.read(packetBuffer, NTP_PACKET_SIZE); // We've received a packet, read the data from it. read the packet into the buffer
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); // the timestamp starts at byte 40 of the received packet and is four bytes or two words, long. First, esxtract the two words:
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
unsigned long secsSince1900 = highWord << 16 | lowWord; // combine the four bytes (two words) into a long integer this is NTP time (seconds since Jan 1 1900):
Serial.print("Segundos desde 1 de Jan. de 1900 = " );
Serial.println(secsSince1900);
const unsigned long seventyYears = 2208988800UL; // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
unsigned long epoch = secsSince1900 - seventyYears; //desconta 70 anos
return unixTimeToDatetime(epoch);
}
return unixTimeToDatetime(0); //01/01/1970 00:00:00
}
//---------------------funcoes para sincronia da data e hora da Classe ArduinoDatetime
void syncCompiledDatetime(){
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("Sinc pela compilacao");
ArduinoDatetime::sync(compiledDatetime());
}
void syncDS3231(){
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("Sinc. pelo rtc");
ArduinoDatetime::sync(DS3231::getDatetime());
}
void syncNTP(){
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("Sinc via NTP");
ArduinoDatetime::sync(NTPDatetime());
}
void syncSerial() {
//formato |DATETIME|DIA|MES|ANO|HORA|MINUTO|SEGUNDO|
while (Serial.available() ) {
String str = Serial.readStringUntil('|');
if (str == F("DATETIME")) {
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("Sinc. pela serial");
Datetime dt;
dt.dayOfMonth = Serial.parseInt();
Serial.readStringUntil('|');
dt.month = Serial.parseInt();
Serial.readStringUntil('|');
dt.year = Serial.parseInt();
Serial.readStringUntil('|');
dt.hour = Serial.parseInt();
Serial.readStringUntil('|');
dt.minute = Serial.parseInt();
Serial.readStringUntil('|');
dt.second = Serial.parseInt();
Serial.readStringUntil('|');
ArduinoDatetime::sync(dt);
}
}
}
String zero(int a){ if(a>=10) {return (String)a+"";} else { return "0"+(String)a;} }
/****************************************************************************************
****************************FIM DATA/HORA************************************************
****************************************************************************************/
void setup() {
Serial.begin(9600);
Wire.begin();
lcd.init(); // initialize the lcd
lcd.backlight();
pinMode(4, OUTPUT); //ANTES DE INICIAR O ETHERNET, DESABILITA O SDCARD
digitalWrite(4, HIGH);
if (Ethernet.begin(mac) == 0) { Serial.println("Failed to configure Ethernet using DHCP"); }
Udp.begin(localPort);
Dns.begin(Ethernet.dnsServerIP() );
syncNTP();
}
void loop() {
syncSerial(); //aguarda a entrada de dados via serial pra configurar a data.
Datetime dt = ArduinoDatetime::getDatetime();
lcd.setCursor(0, 1);
lcd.print(zero(dt.dayOfMonth));
lcd.print("/");
lcd.print(zero(dt.month));
lcd.print("/");
lcd.print(dt.year);
lcd.print(" ");
lcd.print(zero(dt.hour));
lcd.print(":");
lcd.print(zero(dt.minute));
lcd.print(":");
lcd.print(zero(dt.second));
delay(999);
static unsigned long m = 0;
static int cont = 0;
if (millis() - m > 10000){
m = millis();
if (cont == 0){ syncDS3231(); }
if (cont == 1){ syncCompiledDatetime(); }
if (cont == 2){ syncNTP(); }
cont++;
if (cont == 3){ cont = 0; }
}
}
Vídeo
Boa noite, Fabiano. Tudo bem?
ResponderExcluirMeu nome é Bruno Jordão e gostaria, se for possível, que você me ajudasse.
Estou desenvolvendo um projeto de um relógio com arduino para minha tese de mestrado, mas tenho que trocar a base de tempo do cristal de 16MHz por uma base de 10MHz de um relógio atômico. Como faço para mudar o FUSE do arduino e programar com essa nova base de tempo?
Desde já quero parabenizar pelos vídeos que são muito interessantes.
ResponderExcluirFique com DEUS.
Este comentário foi removido pelo autor.
ResponderExcluir#define LEAP_YEAR(_year) ((_year%4)==0)
ResponderExcluir//variaveis globais
static uint8_t ULTIMODIADOSMES[] = {31, 28, 31, 30 , 31, 30, 31, 31, 30, 31, 30, 31};
uint8_t SEGUNDO ; //0..63
uint8_t MINUTO ; //0..63
uint8_t HORA ; //0..31
uint8_t DIADASEMANA ; //0..7
uint8_t DATA ; //0..31
uint8_t MES ; //0..15
uint16_t ANO ; //0..2047
//variaveis globais
int UNIXT_TO_DATE(uint64_t EPOCH)
{
uint8_t MONTH, MONTHLENGTH;
uint32_t DAYS, YEAR;
SEGUNDO = 0;
MINUTO = 0;
HORA = 0;
DIADASEMANA = 0;
DATA = 0;
MES = 0;
ANO = 0;
YEAR = 70;
DAYS = 0;
SEGUNDO = EPOCH % 60;
EPOCH /= 60; // now it is minutes
MINUTO = EPOCH % 60;
EPOCH /= 60; // now it is hours
HORA = EPOCH % 24;
EPOCH /= 24; // now it is days
DIADASEMANA = ((EPOCH + 4) % 7)+1;
while((uint32_t)(DAYS += (LEAP_YEAR(YEAR) ? 366 : 365)) <= EPOCH)
{
YEAR++;
} // ** Acredito que o erro esteja ness laço, mas nao consigo ver *
ANO = YEAR; // is returned as years from 1900
DAYS -= LEAP_YEAR(YEAR) ? 366 : 365;
EPOCH -= DAYS; // now it is days in this year, starting at 0
for (MONTH=0; MONTH < 12; MONTH++)
{
MONTHLENGTH = ( (MONTH == 1) && LEAP_YEAR(YEAR) ) ? 29 : ULTIMODIADOSMES[MES]; // month==1 -> february
if (EPOCH >= MONTHLENGTH)
{
EPOCH -= MONTHLENGTH;
}
else
{
break;
}
}
MES = MONTH + 1; // jan is month 0
DATA = EPOCH + 1; // day of month
//ANO += 1900;
}