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