Reloj Arduino sin RTC

Hola amigos.

Estoy intetando hacer un reloj despertador usando el reloj interno de un Arduino Nano y ahora mismo estoy en esta fase del prototipo:

Por ahora solo muestra horas y minutos y hace parpadear el punto intermedio un segundo ON un segundo OFF.
Con los pulsadores se pone en hora de una forma muy sencilla (más adelante utilizaré algo mas eficiente en el uso de pines): los cuatro de mas a la izquierda incrementan en una unidad cada dígito y el quinto resetea los segundos.

El código es el siguiente:

//RELOJ DIGITAL CON ALARMA Y CALENDARIO
//Miguel Berbegal 
//15/1/2017

//VARIABLES

//puertos y salidas digitales
byte ud_min = 7; //pin del dígito unidades de los minutos
byte dec_min = 8; //pin del dígito decenas de los minutos
byte ud_hor=9; //pin del dígito unidades de las horas
byte dec_hor=10; //pin del dígito decenas de las horas
byte d0 = 2; //d0 a d3 son los dígitos del número en binario que se quiera mostrar en un dígito
byte d1 = 3;
byte d2 = 4;
byte d3 = 5;
byte punto=6; //pin para el punto
byte output[] = {d0, d1, d2, d3}; //número en binario que se va a introducir al 74hc4511

//variables del reloj
byte segundos = 0;
byte minutos = 0;
byte horas = 0;
byte dia = 1;
byte mes = 1;
int anno = 2017;
byte seg_unidades = 0; 
byte seg_decenas = 0;
byte min_unidades = 0;
byte min_decenas = 0;
byte hor_unidades = 0;
byte hor_decenas = 0;
byte dia_unidades = 0;
byte dia_decenas = 0;
byte mes_unidades = 0;
byte mes_decenas = 0;
byte anno_unidades = 0;
byte anno_decenas = 0;
byte anno_centenas = 0;
byte anno_miles = 0;

//contadores
unsigned long contador = 0;
//unsigned long timer1 = 0;
//unsigned long timer2 = 0;

//parpadeo del punto
byte estado_punto=0;

//entradas digitales
byte pulsadores[]={14,15,16,17,18}; //pines de los pulsadores
byte estado_pulsador[]={1,1,1,1,1};  //estados para el detector de flanco
byte estado_pulsador_ant[]={1,1,1,1,1};

//INICIALZACION
void setup() {

  for (int i = 0; i < 4; i++) {
    pinMode(output[i], OUTPUT);
  }
  pinMode(6,OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9,OUTPUT);
  pinMode(10,OUTPUT);

 for (int i = 0; i < 5; i++) {
    pinMode(pulsadores[i], INPUT);
  }

  digitalWrite(ud_min, LOW);
  digitalWrite(dec_min, LOW);
  digitalWrite(ud_hor, LOW);
  digitalWrite(dec_hor, LOW);
  digitalWrite(d0, LOW);
  digitalWrite(d1, LOW);
  digitalWrite(d2, LOW);
  digitalWrite(d3, LOW);
  digitalWrite(punto,LOW);

  contador = millis();

}


//BUCLE
void loop() {

//puesta en hora con pulsadores

for(int i=0;i<5;i++){ //almacenamiento de los datos del bucle anterior
  estado_pulsador_ant[i]=estado_pulsador[i];
}

for(int i=0;i<5;i++){ //lectura de los pulsadores
  estado_pulsador[i]=digitalRead(pulsadores[i]);
}

if((estado_pulsador[0]==0)&&(estado_pulsador[0]!=estado_pulsador_ant[0])){
    minutos++;
}

if((estado_pulsador[1]==0)&&(estado_pulsador[1]!=estado_pulsador_ant[1])){
    minutos=minutos+10;
}

if((estado_pulsador[2]==0)&&(estado_pulsador[2]!=estado_pulsador_ant[2])){
    horas++;
}

if((estado_pulsador[3]==0)&&(estado_pulsador[3]!=estado_pulsador_ant[3])){
    horas=horas+10;
}

if((estado_pulsador[4]==0)&&(estado_pulsador[4]!=estado_pulsador_ant[4])){
    contador = millis();
    segundos=0;
    estado_punto=0;
    //timer1=timer2;
}



//lógica de segundos minutos horas dias meses y año, utilizando contador para saber cuando ha pasado un segundo

  if ((millis() - contador) > 999) { //incremento de segundos usando millis
    contador = millis();
    segundos++;
    estado_punto^=1; //cambiar cada segundo el punto de ON a OFF
  }

//  timer2=(millis()/1000);    //otra forma que probe para incrementar segundos
//  if ( timer1 != timer2 ) {
//    timer1=timer2;
//    segundos++; 
//    estado_punto^=1;       
//  }     
  
  if (segundos > 59) {
    segundos = 0;
    minutos++;
  }
  if (minutos > 59) {
    minutos = 0;
    horas++;
  }
  if (horas > 23) {
    horas = 0;
    dia++;
  }

  if ((mes == 1) || (mes == 3) || (mes == 5) || (mes == 7) || (mes == 8) || (mes == 10) || (mes == 12)) {
    if (dia > 31) {
      dia = 1;
      mes++;
    }
  } else if (mes == 2) {
    if (dia > 28) {
      dia = 1;
      mes++;
    }
  } else {
    if (dia > 30) {
      dia = 1;
      mes++;
    }
  }

  if (mes > 12) {
    mes = 1;
    anno++;
  }


  

//división en dígitos

  seg_decenas = int(segundos / 10);
  seg_unidades = segundos - seg_decenas * 10;

  min_decenas = int(minutos / 10);
  min_unidades = minutos - min_decenas * 10;

  hor_decenas = int(horas / 10);
  hor_unidades = horas - hor_decenas * 10;

  dia_decenas = int(dia / 10);
  dia_unidades = dia - dia_decenas * 10;

  mes_decenas = int(mes / 10);
  mes_unidades = mes - mes_decenas * 10;

  anno_miles = int(anno / 1000);
  anno_centenas = int((anno - anno_miles * 1000) / 100);
  anno_decenas =int((anno-anno_miles*1000-anno_centenas*100)/10);
  anno_unidades = anno-anno_miles*1000-anno_centenas*100-anno_decenas*10;


//control de displays



  //minutos
  digitalWrite(dec_hor, LOW);
  for (int i = 0; i < 4; i++) {
    if (((min_unidades >> i) & 1) == 1) {
      digitalWrite(output[i], HIGH);
    } else {
      digitalWrite(output[i], LOW);
    }
  }
  digitalWrite(ud_min, HIGH);
  delay(2.5);



  digitalWrite(ud_min, LOW);
  for (int i = 0; i < 4; i++) {
    if (((min_decenas >> i) & 1) == 1) {
      digitalWrite(output[i], HIGH);
    } else {
      digitalWrite(output[i], LOW);
    }
  }
  digitalWrite(dec_min, HIGH);
  delay(2.5);




  //horas
  digitalWrite(dec_min, LOW);
  digitalWrite(punto, estado_punto); //para que el punto intermedio se ilumine si le toca
  for (int i = 0; i < 4; i++) {
    if (((hor_unidades >> i) & 1) == 1) {
      digitalWrite(output[i], HIGH);
    } else {
      digitalWrite(output[i], LOW);
    }
  }
  digitalWrite(ud_hor, HIGH);
  delay(2.5);


  digitalWrite(ud_hor, LOW);
  digitalWrite(punto, LOW); //para que no se iluminen los puntos de los otros digitos
  for (int i = 0; i < 4; i++) {
    if (((hor_decenas >> i) & 1) == 1) {
      digitalWrite(output[i], HIGH);
    } else {
      digitalWrite(output[i], LOW);
    }
  }
  digitalWrite(dec_hor, HIGH);
  delay(2.5);



}

El problema que tengo es que el reloj se retrasa unos segundos cada hora... No veo donde está el fallo. ¿Hay algo mal en cómo estoy contando los segundos? ¿Los delays que uso para multiplexar los displays hacen que mis segundos sean mas largos? ¿Podría ser problema del propio Arduino y su oscilador?

Gracias de antemano

A menos que uses un RTC como el DS3231, usar el reloj del Arduino no garantiza precisión alguna.
No uses RTC DS1307 que es muy malo frente al DS3231.

surbyte:
A menos que uses un RTC como el DS3231, usar el reloj del Arduino no garantiza precisión alguna.
No uses RTC DS1307 que es muy malo frente al DS3231.

Gracias por tu respuesta!

Entonces, en tu opinión ¿el problema está en que el Arduino no es capaz de contar los segundos de forma precisa? ¿Tanta es su imprecisión como para atrasarse 8 segundos cada hora? ¿Crees que al unica forma de hacer un reloj con Arduino es usando RTC?

He pensado en intentar corregir el error poniendo 997 milisegundos en vez de 999, pero claro, no se si realmente el error es constante...

Quede claro que la solución RTC mas comoda para manejar el Arduino en plan de multiproyectos, es la que te propone surbyte.

No obstante hay casos en los que se necesita implementar un RTC con Arduino, asegurando una precision de 1 segundo (precision de cristal), y sin necesidad de usar hardware externo, pues bien, nuestros Arduinos tienen la posibilidad de hacerlo, aunque hay que tener algo de soltura con la electronica.

Se necesita:

  • cambiar el cristal de 16MHz por otro de 32.768 kHz (de uso muy comun).
  • reprogramar la fuente del clock de la MCU a calibrated internal RC oscillator.
  • escribir el software del reloj.

Evidentemente esto es un comentario a modo ilustrativo, ya que su implementacion lo hace totalmente desaconsejable para principiantes.

1 Like

Hola.
Obviando el tema de la precisión del cristal del arduino, también es cierto que estás perdiendo millis por ahí, porque a cada segundo reajustas tu contador. Prueba a cambiar esta rutina:

//lógica de segundos minutos horas dias meses y año, utilizando contador para saber cuando ha pasado un segundo

  if ((millis() - contador) > 999) { //incremento de segundos usando millis
    contador = millis();
    segundos++;
    estado_punto^=1; //cambiar cada segundo el punto de ON a OFF
  }

Por esta otra:

//lógica de segundos minutos horas dias meses y año, utilizando contador para saber cuando ha pasado un segundo

  if ((millis() - contador) >= 1000) { // Esto lo puedes dejar como estaba, pero creo que es más ilustrativo así
    contador += 1000; // Aquí estabas perdiendo potencialmente millis, ya que cuando reasignas millis a contador, puede haber pasado alguno más de 1000
    segundos++;
    estado_punto^=1; //cambiar cada segundo el punto de ON a OFF
  }

Y cuenta si ha cambiado la cosa.

Alfaville:
Quede claro que la solución RTC mas comoda para manejar el Arduino en plan de multiproyectos, es la que te propone surbyte.

No obstante hay casos en los que se necesita implementar un RTC con Arduino, asegurando una precision de 1 segundo (precision de cristal), y sin necesidad de usar hardware externo, pues bien, nuestros Arduinos tienen la posibilidad de hacerlo, aunque hay que tener algo de soltura con la electronica.

Se necesita:

  • cambiar el cristal de 16MHz por otro de 32.768 kHz (de uso muy comun).
  • reprogramar la fuente del clock de la MCU a calibrated internal RC oscillator.
  • escribir el software del reloj.

Evidentemente esto es un comentario a modo ilustrativo, ya que su implementacion lo hace totalmente desaconsejable para principiantes.

Desde luego suena más avanzado que mi nivel, pero muchas gracias por la aportación. Estoy empeñado en intentar hacerlo sin RTC ni modificaciones en arduino, si finalmente me rindo considerare el RTC

noter:
Hola.
Obviando el tema de la precisión del cristal del arduino, también es cierto que estás perdiendo millis por ahí, porque a cada segundo reajustas tu contador. Prueba a cambiar esta rutina:

//lógica de segundos minutos horas dias meses y año, utilizando contador para saber cuando ha pasado un segundo

if ((millis() - contador) > 999) { //incremento de segundos usando millis
   contador = millis();
   segundos++;
   estado_punto^=1; //cambiar cada segundo el punto de ON a OFF
 }



Por esta otra:


//lógica de segundos minutos horas dias meses y año, utilizando contador para saber cuando ha pasado un segundo

if ((millis() - contador) >= 1000) { // Esto lo puedes dejar como estaba, pero creo que es más ilustrativo así
   contador += 1000; // Aquí estabas perdiendo potencialmente millis, ya que cuando reasignas millis a contador, puede haber pasado alguno más de 1000
   segundos++;
   estado_punto^=1; //cambiar cada segundo el punto de ON a OFF
 }



Y cuenta si ha cambiado la cosa.

Muchas gracias! Es un apunte muy bueno, voy a probarlo.

Pues resulta que a lo largo de la noche ahora ha adelantado casi un minuto... Supongo que intentare ver si el error es constante y puedo corregirlo y si no tirare de RTC...

@Neros

Comentabas que:

El problema que tengo es que el reloj se retrasa unos segundos cada hora... No veo donde está el fallo.

¿ Realmente ahora ocurre esto ?

Neros:
Pues resulta que a lo largo de la noche ahora ha adelantado casi un minuto... Supongo que intentare ver si el error es constante y puedo corregirlo y si no tirare de RTC...

De ser asi me parece un tema interesante de investigar porque en algun punto intermedio está la exactitud

Alfaville:
@Neros

Comentabas que:

El problema que tengo es que el reloj se retrasa unos segundos cada hora... No veo donde está el fallo.

¿ Realmente ahora ocurre esto ?De ser asi me parece un tema interesante de investigar porque en algun punto intermedio está la exactitud

exacto, haciendo esto:

  if ((millis() - contador) > 999) { //incremento de segundos usando millis
    contador = millis();
    segundos++;
    estado_punto^=1; //cambiar cada segundo el punto de ON a OFF
  }

retrasa

y haciendo esto:

  if ((millis() - contador) >= 1000) { // Esto lo puedes dejar como estaba, pero creo que es más ilustrativo así
    contador += 1000; // Aquí estabas perdiendo potencialmente millis, ya que cuando reasignas millis a contador, puede haber pasado alguno más de 1000
    segundos++;
    estado_punto^=1; //cambiar cada segundo el punto de ON a OFF
  }

o esto:

timer2=(millis()/1000);    //otra forma que probe para incrementar segundos
if ( timer1 != timer2 ) {
   timer1=timer2;
   segundos++;
   estado_punto^=1;       
}

adelanta

y también he estado probando cambiando el 999 de la siguiente forma:

  if ((millis() - contador) > 990) { //incremento de segundos usando millis
    contador = millis();
    segundos++;
    estado_punto^=1; //cambiar cada segundo el punto de ON a OFF
  }

y pues a veces consigo que atrase y otras que adelante, pero no he llegado al número que haga que tenga una precisión razonable.

Quizás pruebe a cálcular el desvio y a intentar corregirlo utilizando esta forma:

  if ((millis() - contador) >= 1000) { // Esto lo puedes dejar como estaba, pero creo que es más ilustrativo así
    contador += 1000; // Aquí estabas perdiendo potencialmente millis, ya que cuando reasignas millis a contador, puede haber pasado alguno más de 1000
    segundos++;
    estado_punto^=1; //cambiar cada segundo el punto de ON a OFF
  }

que es la que más me ha gustado. Quizás jugando con el 1000 y si el error es constante pueda dejar un reloj bastante preciso... Pero es irónico que este ajustando este reloj electrónico como si de un reloj de cuerda se tratase, ajustando la "tensión del muelle" xD

Lo que me temo es que el error no sea constante y sea muy susceptible a la temperatura, la posición o cosas así....

Lo que me temo es que el error no sea constante y sea muy susceptible a la temperatura, la posición o cosas así....

Me temo que eso será lo que pase. El código que te propuse es en teoría el más exacto, pues no te saltas ningún milisegundo del conteo.
Suponemos que tomamos la primera muestra con millis=50. Cuando llega el loop a cumplir la condición, por la razón que sea no estamos en 1050, sino en 1053:

  • Con el primer método incrementaríamos el segundo, y pondríamos el conteo inicial en 1053, o siendo pesimistas, porque en el proceso ha transcurrido otro milisegundo, en 1054. El siguiente segundo se produciría cuando millis alcance 2054, osea que hemos atrasado 4 milisegundos.
  • Con el segundo método, en la misma circunstancia, incrementaríamos el segundo, y pondríamos el contador en 1050+1000=2050, con lo que el único pero es la exactitud y estabilidad del cristal.

@Neros

Espero que el reloj de referencia que usas no sea el culpable del adelanto observado con las dos ultimas variantes del programa..jejejeje

Ahora mas en serio, la variante de noter, tal y como comenta, deberia de dar toda la precision que <millis()> puede ofrecer, por lo tanto solo queda observar si el adelanto es mas o menos constante en cuyo caso seria culpa de la deriva del propio cristal:

Según Wikipedia.
La dependencia con la temperatura depende del resonador, pero un valor típico para cristales de cuarzo es de 0' 005% del valor a 25 °C, en el margen de 0 a 70 °C.

Ese porcentaje aplicado a 24 horas (86400 seg) nos daria un error de 4,32 segundos lo cual está en linea con tus observaciones.

Jajajaja, la referencia es fiable....

Muy interesante el error que comentas, pero ojalá fuera un error de 4 segundos cada 24 horas... como menciono en un post anterior, usando la variante de noter obtuve un adelanto de 60 segundos en 12 horas... Ahora mismo he hecho que los segundos sean de 1001 milisegundos para ver si compenso el error pacial o totalmente... según vaya tomando medidas más "exactas" os voy comentando...

Contra eso no puedes comparar. Compara contra un reloj de pc ajustado por un servidor NTP contra el tiempo que tienes para que se actualice.
Si dices que se actualiza contra el servidor NTP cada 12 hs ese será tu tiempo de prueba.

¿Entiendo que el reloj de portatil consulta la hora en internet de forma predeterminada no? Las primeras comparaciones las hacia contra ese reloj, pero viendo que el casio este no ha perdido ni un segundo en los tres ultimos dias con respecto al portatil, por comodidad uso el casio. Viendo que el reloj de Arduino se desvia tanto (ahora mismo se me esta adelantando entre 1 y 2 segundos a la hora) no me parece que, por ahora, necesite nada más preciso ¿no?

Si la respuesta va orientada a que conecte el Arduino al PC y haga esas comprobaciones de forma automatica o algo similar entonces no tengo ni idea de como hacerlo... De hecho si el ordenador (macBook pro) no lo esta haciendo de esa manera tampoco se como cambiarlo.. pero nunca he puesto el reloj del mac en hora...

Si te sirve de ayuda, yo uso la siguiente rutina de base, y me funciona sin apenas variación a lo hora por NTP durante días. No obstante tiene el parámetro deriva que permitiría un ajuste más preciso, pero no he llegado a usarlo

struct tipoReloj
{ byte dia=1,mes=1,diaSem=0,hora=0,minuto=0,segundo=0;
 boolean verano=false;
 int anio=2000;
 int doy;            // dia del año 1..365/366
 int deriva=0;
 unsigned long microsReferencia=0UL;
} reloj;

/////////////////////////////////////////////////////////////
///////////////// Gestión del reloj /////////////////////////

void updateReloj()
{ long int microsUnSegundo=1000000L+reloj.deriva;
 if (micros()-reloj.microsReferencia>microsUnSegundo)
 { reloj.microsReferencia+=microsUnSegundo;
   if (++reloj.segundo >=60)
   { reloj.segundo=0;
     if (++reloj.minuto >=60)
     { reloj.minuto=0;
       if (++reloj.hora >=24)
       { reloj.hora=0;
         reloj.diaSem=(++reloj.diaSem)%7;
       }
     }
   }
 }
}

void setReloj(char *car)
{

...
 reloj.microsReferencia=micros();
...

}

Manuel_Hidalgo:
Si te sirve de ayuda, yo uso la siguiente rutina de base, y me funciona sin apenas variación a lo hora por NTP durante días. No obstante tiene el parámetro deriva que permitiría un ajuste más preciso, pero no he llegado a usarlo

struct tipoReloj

{ byte dia=1,mes=1,diaSem=0,hora=0,minuto=0,segundo=0;
boolean verano=false;
int anio=2000;
int doy;            // dia del año 1..365/366
int deriva=0;
unsigned long microsReferencia=0UL;
} reloj;

/////////////////////////////////////////////////////////////
///////////////// Gestión del reloj /////////////////////////

void updateReloj()
{ long int microsUnSegundo=1000000L+reloj.deriva;
if (micros()-reloj.microsReferencia>microsUnSegundo)
{ reloj.microsReferencia+=microsUnSegundo;
  if (++reloj.segundo >=60)
  { reloj.segundo=0;
    if (++reloj.minuto >=60)
    { reloj.minuto=0;
      if (++reloj.hora >=24)
      { reloj.hora=0;
        reloj.diaSem=(++reloj.diaSem)%7;
      }
    }
  }
}
}

void setReloj(char *car)
{

...
reloj.microsReferencia=micros();
...

}

Hola! Muchas gracias por tu aportación. Tengo que estudiarme tu propuesta para entenderla, ya que soy muy novato en esto de Arduino, y, en general, programación. Además estoy con mil cosas a al vez (Master, trabajo, calses particulares...) y no puedo dedicarle a este hobby tanto tiempo como me gustaría :/. Cuando consiga entenderla e implementarla te comento :slight_smile:

Bueno, finalmente he conseguido no notar diferencia entre la hora marcada por el casio y la marcada por le arduino en 24 horas... con lo que me siento suficientemente satisfecho. Lo seguire dejando correr a ver a partir de cauntos dias se empieza a notar (si es que se nota) la diferencia.

El ajuste que he hecho es el siguiente:

if ((micros() - contador) >= 1001401) { //hago que cada segundo sea un milisegundo más largo a ver si compenso el error
    contador += 1001401; 
    segundos++;
    estado_punto^=1; //cambiar cada segundo el punto de ON a OFF
  }

Como veis esto supone que los segundos del arduino son aproximadamente un 0.14% más cortos de lo que deberian... no se hasta que punto es normal esto pero bueno... ahi queda, ya os dire en el largo plazo como se sigue portando.

Por ahora a seguir aumentando las funciones... lo siguiente la alarma :slight_smile:

Bien traído a colación el uso de micros, en lugar de millis. ¿Has llegado a tanta precisión? Lo digo por el microsegundo "suelto" (1401). Si es así y no hay muchas variaciones con la temperatura, podrías conseguir un reloj bastante preciso, aunque a buen seguro que habría que calibrarlo para cada arduino en que se instale.

Si cada segundo es 1.4 mseg mas largo en una hora tendremos 3600*1.4/1000 = 5.04 segundos de error
¿ Tan impreciso es el clock de nuestro Arduino ?
¿ Donde está realmente el problema ?