Un reloj preciso con la fecha mostrada en una pantalla LCD de 16×2 usando solo el Arduino, la pantalla y algunos botones. No se requiere módulo RTC.

COMPONENTES Y SUMINISTROS

  • Arduino Nano R3
  • Protoboard 800p
  • LCD alfanumérico, 16 x 2
  • Interruptor táctil, accionado por la parte superior
  • Potenciómetro de ajuste, 10 kohm
  • Cables de puente

El código del reloj

Las principales áreas de interés en el código de reloj son la configuración de la interrupción, cómo se usa y la forma en que se mantiene y se manipula la fecha.

La interrupción

El siguiente código establecerá una interrupción que se activará cada milisegundo. Esto desvía la interrupción utilizada para mantener millis () por lo que millis () y delay () ya no funcionarán.

// Set up  time interrupt - millis() rolls over after 50 days so
 // we are using our own millisecond counter which we can reset at
 // the end of each day
 //Set the CTC mode Compare time and trigger interrupt
 TCCR0A = (1 << WGM01);      
 //Set value for time to compare to ORC0A for 1ms = 249  (8 bits so max is 256)
 //[(Clock speed/Prescaler value)*Time in seconds] - 1
 //[(16,000,000/64) * .001] - 1 = 249 = 1 millisecond
 OCR0A = 0xF9;               
 //set timer compare interrupt
 TIMSK0 |= (1 << OCIE0A);  
 //Set the prescale 1/64 clock  
 // ie 110 for last 3 bits
 TCCR0B |= (1 << CS01);      
 TCCR0B |= (1 << CS00);      
 //initialize counter value to 0
 TCNT0  = 0;         
 //Enable interrupt       
 sei();  

Este es el código que se llamará cada segundo:

// This is interrupt is called when the compare time has been reached
// hence will be called once a millisecond based on the  
// OCR0A register setting.
ISR(TIMER0_COMPA_vect) {
 if (currentMode != SET_TIME)
   currentTime++;
 elapsed++;
} 

currentTime y transcurrido son variables largas sin firmar. Tenga en cuenta que estos se califican como volátiles cuando se definen, ya que también estamos manipulando las variables en el código principal. Esto obliga al sistema a leer la variable cada vez que se utiliza y no a utilizar un valor almacenado en caché.

currentTime almacena el número de milisegundos desde la medianoche y hay rutinas para convertir esto a HH: MM: SS y restablecerlo cuando establezca la hora.

Cuando han transcurrido 24 horas, el sistema deduce el número de milisegundos en un día de la hora y aumenta la fecha en 1 día. Por lo tanto, el reloj no se ve afectado por el valor máximo que puede almacenar la variable, a diferencia de millis ().

// If at end of the day reset time and increase date
 if ((currentMode == SHOW_TIME) && 
     (currentTime > millisecondsInADay)) {
   //Next day
   // Stop interrupts while reset time
   noInterrupts();
   currentTime -= millisecondsInADay;
   interrupts();
   currentDate++;
 }

Tenga en cuenta que deshabilitamos las interrupciones mientras manipulamos la variable currentTime, de lo contrario, la llamada de interrupción podría activarse en medio del cálculo para deducir milisegundos de InADay que corrompen el cálculo.

Después de que ha pasado cada hora, el sistema ajusta el número de milisegundos transcurridos por el ajuste de velocidad que calculamos anteriormente, ajustando la hora actual para compensar el reloj interno rápido o lento.

// At the end of each hour adjust the elapsed time for 
 // the inacuracy in the Arduino clock
 if (elapsed >= millisecondsInHour) {
   noInterrupts();
   // Adjust time for slow/fast running Arduino clock
   currentTime += speedCorrection;
   // Reset to count the next hour
   elapsed = 0;
   interrupts();
 }

Almacenamiento y cálculo de fechas

La fecha se lleva a cabo como una fecha juliana, que es el número de días que han transcurrido desde el lunes 1 de enero de 4713 a. C. Se incluyen rutinas para calcular la fecha juliana y convertirla de nuevo al calendario gregoriano.

float JulianDate(int iday, int imonth, int iyear) {
 // Calculate julian date (tested up to the year 20,000)
 unsigned long d = iday;
 unsigned long m = imonth;
 unsigned long y = iyear;
 if (m < 3) {
   m = m + 12;
   y = y - 1;
 }
 unsigned long t1 = (153 * m - 457) / 5;
 unsigned long t2 = 365 * y + (y / 4) - (y / 100) + (y / 400);
 return 1721118.5 + d + t1 + t2;
}
void GregorianDate(float jd, int &iday, int &imonth, int &iyear) {
 // Note 2100 is the next skipped leap year - compensates for skipped leap years
 unsigned long f = jd + 68569.5;
 unsigned long e = (4.0 * f) / 146097;
 unsigned long g = f - (146097 * e + 3) / 4;
 unsigned long h = 4000ul * (g + 1) / 1461001;
 unsigned long t = g - (1461 * h / 4) + 31;
 unsigned long u = (80ul * t) / 2447;
 unsigned long v = u / 11;
 iyear = 100 * (e - 49) + h + v;
 imonth = u + 2 - 12 * v;
 iday = t - 2447 * u / 80;
}

Los botones de ajuste

El botón Modo avanza el modo actual de Mostrar hora, a Establecer hora, Establecer año, Establecer fecha, Establecer ajuste de velocidad y volver a Mostrar hora. Cada uno de estos se explica por sí mismo y utiliza los otros 2 botones para ajustar la configuración actual.

Una vez que el reloj está funcionando, si está ganando o perdiendo tiempo, puede cambiar el ajuste de velocidad, accediendo al modo Establecer ajuste de velocidad y usando el botón arriba y abajo para aumentar o reducir esto en 5 segundos a la vez.

Programa de reloj

// Incluya el controlador de la biblioteca para su visualización:
#include  <LiquidCrystal.h>

// LCD de cristal líquido (RS, EN, D4, D5, D6, D7)
LiquidCrystal  lcd ( 12 ,  13 ,  6 ,  7 ,  8 ,  9 );  // crea un objeto lcd y asigna los pines

// Definir botones y conexiones de zumbador
#define MODE_BUTTON 2
#define HOUR_BUTTON 3                   // El mismo botón, diferentes definiciones para
#define UP_BUTTON 3                     // hace que el código sea más fácil de entender
#define DAY_BUTTON 3
#define MINUTE_BUTTON 4                 // El mismo botón, diferentes definiciones para
#define DOWN_BUTTON 4                   // hace que el código sea más fácil de entender
#define MONTH_BUTTON 4

// Configuración del modo actual
#define SHOW_TIME 1                     // 1 = corriendo - mostrar hora
#define SET_TIME 2                     // 2 = tiempo establecido
#define SET_YEAR 3                     // 3 = año establecido
#define SET_DATE 4                     // 4 = día / mes establecido
#define SET_SPEED_ADJ 5                // 5 = modificar la variable speedCorrection

int  speedCorrection  =  3545 ;             // Número de milisegundos que mi reloj Nano funciona lento por hora
// número negativo aquí si se está ejecutando rápido
// cambia para que coincida con tu Arduino

// Variables volátiles modificadas en una interrupción y
// Necesito forzar al sistema a leer la variable real
// cuando se usa fuera de la interrupción y no usa una versión en caché
volatile  unsigned  long  currentTime ;     // Duración en milisegundos desde la medianoche
unsigned  long  lastTime  =  - 1000 ;         // lastTime que se llamó ShowTime inicializado a -1000 por lo que se muestra inmediatamente
volátil  sin firmar  mucho tiempo  transcurrido ;         // Temporizador utilizado para demora y recuento de horas

unsigned  long  millisecondsInADay ;       // Milisegundos en 24 horas
unsigned  long  millisecondsInHour ;       // Milisegundos en 1 hora
int  currentMode ;                        // 1 = corriendo - mostrar la hora
// 2 = tiempo establecido
// 3 = año establecido
// 4 = día / mes establecido

float  currentDate ;                      // Fecha de Julian
flotar  lastDate  =  0.0 ;                   // última fecha en la que se llamó ShowDate
int  currentDay ;
int  currentMonth ;
int  currentYear ;

char  * dayArray []  =  {   "Tue." ,        // Mostrará una advertencia del compilador pero funciona bien
                      "Mié" ,
                      "Jue." ,
                      "Vie." ,
                      "Sáb." ,
                      "Sol" ,
                      "Lun. "
                   };

 configuración vacía ()  {
  // Configurar interrupción de tiempo: millis () se transfiere después de 50 días, por lo que
  // estamos usando nuestro propio contador de milisegundos que podemos restablecer en
  // el final de cada día
  TCCR0A  =  ( 1  <<  WGM01 );       // Establecer el modo CTC Comparar tiempo y disparar interrupción
  OCR0A  =  0xF9 ;                // Establezca el valor del tiempo para compararlo con ORC0A durante 1 ms = 249 (8 bits, por lo que el máximo es 256)
  // [(Velocidad del reloj / Valor de preescalador) * Tiempo en segundos] - 1
  // [(16,000,000 / 64) * .001] - 1 = 249 = 1 milisegundo
  TIMSK0  | =  ( 1  <<  OCIE0A );     // establecer interrupción de comparación del temporizador
  TCCR0B  | =  ( 1  <<  CS01 );       // Establecer el reloj de preescala 1/64
  TCCR0B  | =  ( 1  <<  CS00 );       // es decir, 110 para los últimos 3 bits
  TCNT0   =  0 ;                  // inicializar el valor del contador a 0
  sei ();                       // Habilitar interrupción

  pinMode ( MINUTE_BUTTON ,  INPUT_PULLUP );
  pinMode ( BOTÓN_HORA ,  INPUT_PULLUP );
  pinMode ( MODE_BUTTON ,  INPUT_PULLUP );
  // pinMode (ZUMBADOR, SALIDA);
  currentTime  =  0 ;                           // Establecer la hora actual a la noche mental
  currentDate  =  JulianDate ( 1 ,  1 ,  2021 );      // Establecer fecha base
  transcurrido  =  0 ;                               // Establecer el contador de períodos en 0
  milisegundosInADay  =  24ul  *  60  *  60  *  1000 ;
  milisegundosInHour  =  60ul  *  60  *  1000 ;
  CurrentMode  =  SHOW_TIME ;                   // El modo inicial se está ejecutando y muestra la fecha y la hora
  // Configurar LCD
  lcd . comenzar ( 16 ,  2 );
  lcd . noAutoscroll ();
  lcd . display ();
  lcd . claro ();
  ShowTime ( currentTime );
}

 bucle vacío ()  {
  // el bucle se ejecuta cada 150 milisegundos

  // Si al final del día restablece la hora y aumenta la fecha
  si  (( CurrentMode  ==  SHOW_TIME )  &&
      ( currentTime  >  milisegundosInADay ))  {
    //Día siguiente
    // Detener las interrupciones mientras se reinicia el tiempo
    noInterrupts ();
    currentTime  - =  milisegundosInADay ;
    interrumpe ();
    currentDate ++ ;
  }
  // Al final de cada hora ajusta el tiempo transcurrido para
  // la inexactitud en el reloj Arduino
  if  ( transcurrido  > =  milisegundosEnHora )  {
    noInterrupts ();
    // Ajustar el tiempo para el reloj Arduino de funcionamiento lento / rápido
    currentTime  + =  speedCorrection ;
    // Restablecer para contar la próxima hora
    transcurrido  =  0 ;
    interrumpe ();
  }

  // Comprueba si se ha pulsado algún botón
  CheckButtons ();

  // Mostrar pantalla según el modo actual
  switch  ( currentMode )  {
    caso  SHOW_TIME :
      // Muestra la fecha y la hora actuales
      ShowTime ( currentTime );
      ShowDate ( currentDate );
      romper ;
    caso  SET_TIME :
      // Pantalla de visualización para configurar la hora
      ShowTimeSet ( currentTime );
      romper ;
    caso  SET_YEAR :
      // Pantalla de visualización para configurar el año
      ShowYearSet ( currentDate );
      romper ;
    caso  SET_DATE :
      // Pantalla de visualización para configurar el día y el mes
      ShowDDMMSet ( currentDate );
      romper ;
    caso  SET_SPEED_ADJ :
      // Pantalla de visualización para ajustar la corrección de velocidad
      ShowSpeedSet ();
      romper ;
  }
  Espere ( 150 );
}

// Esta interrupción se llama cuando se alcanza el tiempo de comparación
// por lo tanto, se llamará una vez por milisegundo según el
// Configuración de registro OCR0A.
ISR ( TIMER0_COMPA_vect )  {
  if  ( currentMode  ! =  SET_TIME )
    currentTime ++ ;
  transcurrido ++ ;
}

float  JulianDate ( int  iday ,  int  imonth ,  int  iyear )  {
  // Calcular la fecha juliana (probado hasta el año 20.000)
  unsigned  long  d  =  iday ;
  unsigned  long  m  =  imonth ;
  unsigned  long  y  =  iyear ;
  si  ( m  <  3 )  {
    m  =  m  +  12 ;
    y  =  y  -  1 ;
  }
  unsigned  long  t1  =  ( 153  *  m  -  457 )  /  5 ;
   largo  sin signo t2  =  365  *  y  +  ( y  /  4 )  -  ( y  /  100 )  +  ( y  /  400 );
  return  1721118.5  +  d  +  t1  +  t2 ;
}

void  GregorianDate ( float  jd ,  int  & iday ,  int  & imonth ,  int  & iyear )  {
  // Nota 2100 es el próximo año bisiesto omitido: compensa los años bisiestos omitidos
  unsigned  long  f  =  jd  +  68569.5 ;
  unsigned  long  e  =  ( 4.0  *  f )  /  146097 ;
   largo  sin signo g  =  f  -  ( 146097  *  e  +  3 )  /  4 ;
   largo  sin signo h  =  4000ul  *  ( g  +  1 )  /  1461001 ;
   largo  sin signo t  =  g  -  ( 1461  *  h  /  4 )  +  31 ;
  unsigned  long  u  =  ( 80ul  *  t )  /  2447 ;
  unsigned  long  v  =  u  /  11 ;
  i año  =  100  *  ( e  -  49 )  +  h  +  v ;
  mes  =  u  +  2  -  12  *  v ;
  viernes  =  t  -  2447  *  u  /  80 ;
}

void  SplitTime ( unsigned  long  curr ,  unsigned  long  & ulHour ,
               unsigned  long  & ulMin ,  unsigned  long  & ulSec )  {
  // Calcular HH: MM: SS a partir del recuento de milisegundos
  ulSec  =  curr  /  1000 ;
  ulMin  =  ulSec  /  60 ;
  ulHour  =  ulMin  /  60 ;
  ulMin  - =  ulHour  *  60 ;
  ulSec  =  ulSec  -  ulMin  *  60  -  ulHour  *  3600 ;
}

unsigned  long  SetTime ( unsigned  long  ulHour ,  unsigned  long  ulMin ,
                      unsigned  long  ulSec )  {
  // Establece el número de milisegundos desde la medianoche hasta la hora actual
  retorno  ( ulHour  *  60  *  60  *  1000 )  +
         ( ulMin  *  60  *  1000 )  +
         ( ulSec  *  1000 );
}

void  Wait ( valor largo sin firmar  ) {  
  // Crea nuestra propia función de reparto
  // Hemos establecido nuestra propia interrupción en TCCR0A
  // por lo tanto millis () y delay () ya no funcionarán
   tiempo de  inicio  largo sin firmar =  transcurrido ;
  while  (( transcurrido  -  hora de inicio )  <  valor )  {
    // Solo espera
  }
}

void  CheckButtons ()  {
  // Si se ha pulsado el botón de modo, el pin pasará a BAJO
  if  ( digitalRead ( MODE_BUTTON )  ==  LOW )  {
    // Avanzar al siguiente modo
    switch  ( currentMode )  {
      caso  SHOW_TIME :
        CurrentMode  =  SET_TIME ;
        lcd . claro ();
        romper ;
      caso  SET_TIME :
        CurrentMode  =  SET_YEAR ;
        lcd . claro ();
        romper ;
      caso  SET_YEAR :
        CurrentMode  =  SET_DATE ;
        lcd . claro ();
        romper ;
      caso  SET_DATE :
        CurrentMode  =  SET_SPEED_ADJ ;
        lcd . claro ();
        romper ;
      caso  SET_SPEED_ADJ :
        CurrentMode  =  SHOW_TIME ;
        lcd . claro ();
        // Restablecer las variables para que la pantalla se vea obligada a actualizarse
        // la próxima vez que se llame a ShowTime y ShowDate
        lastTime  =  0 ;
        lastDate  =  0.0 ;
        romper ;
    }
  }
  if  ( currentMode  ! =  SHOW_TIME )  {
    switch  ( currentMode )  {
      // Si el modo es diferente a los botones de verificación SHOW_TIME
      // El pin pasa a BAJO cuando se presiona el botón asociado
      caso  SET_TIME :
        if  ( digitalRead ( MINUTE_BUTTON )  ==  LOW )  {
          // Avanzar minuto
           iHours largos  sin firmar ;
           iMinutes largos  sin firmar ;
           iSeconds largos  sin firmar ;
          SplitTime ( CurrentTime ,  iHours ,  iMinutes ,  iSeconds );
          si  ( iMinutos  <  59 )  {
            iMinutes ++ ;
          }
          else  {
            iMinutos  =  0 ;
          }
          // Establecer milisegundos almacenados según la configuración actual
          noInterrupts ();
          currentTime  =  SetTime ( iHours ,  iMinutes ,  0 );
          transcurrido  =  0 ;
          interrumpe ();
        }
        if  ( digitalRead ( HOUR_BUTTON )  ==  LOW )  {
          // Hora de avance
           iHours largos  sin firmar ;
           iMinutes largos  sin firmar ;
           iSeconds largos  sin firmar ;
          SplitTime ( CurrentTime ,  iHours ,  iMinutes ,  iSeconds );
          si  ( iHoras  <  23 )  {
            iHours ++ ;
          }
          else  {
            iHoras  =  0 ;
          }
          // Establecer milisegundos almacenados según la configuración actual
          noInterrupts ();
          currentTime  =  SetTime ( iHours ,  iMinutes ,  0 );
          transcurrido  =  0 ;
          interrumpe ();
        }
        romper ;
      caso  SET_YEAR :
        if  ( digitalRead ( UP_BUTTON )  ==  LOW )  {
          // Incrementar año
          int  iDay ;
          int  iMonth ;
          int  iYear ;
          GregorianDate ( currentDate ,  iDay ,  iMonth ,  iYear );
          iYear ++ ;
          // Establecer la fecha almacenada según la configuración actual
          currentDate  =  JulianDate ( iDay ,  iMonth ,  iYear );
        }
        if  ( digitalRead ( DOWN_BUTTON )  ==  LOW )  {
          // Disminuir año
          int  iDay ;
          int  iMonth ;
          int  iYear ;
          GregorianDate ( currentDate ,  iDay ,  iMonth ,  iYear );
          iYear - ;
          // Establecer la fecha almacenada según la configuración actual
          currentDate  =  JulianDate ( iDay ,  iMonth ,  iYear );
        }
        romper ;
      caso  SET_DATE :
        if  ( digitalRead ( MONTH_BUTTON )  ==  LOW )  {
          // Mes de avance
          int  iDay ;
          int  iMonth ;
          int  iYear ;
          GregorianDate ( currentDate ,  iDay ,  iMonth ,  iYear );
          iMonth ++ ;
          if  ( iMonth  >  12 )  {
            iMes  =  1 ;
          }
          // Establecer la fecha almacenada según la configuración actual
          currentDate  =  JulianDate ( iDay ,  iMonth ,  iYear );
        }
        if  ( digitalRead ( DAY_BUTTON )  ==  LOW )  {
          // Día de avance
          int  iDay ;
          int  iMonth ;
          int  iYear ;
          GregorianDate ( currentDate ,  iDay ,  iMonth ,  iYear );
          iDay ++ ;
          if  ( iDay  >  31 )  {
            iDay  =  1 ;
          }
          si  ((( iMonth  ==  4 )  ||  ( iMonth  ==  6 )  ||  ( iMonth  ==  9 )  ||  ( iMonth  ==  11 ))
              &&  ( iDay  >  30 ))  {
            iDay  =  1 ;
          }
          if  (( iMonth  ==  2 )  &&  ( iDay  >  29 ))  {
            iDay  =  1 ;
          }
          if  (( iMonth  ==  2 )  &&  (( iYear  %  4 )  ! =  0 )  &&  ( iDay  >  28 ))  {
            iDay  =  1 ;
          }
          // Establecer la fecha almacenada según la configuración actual
          // Si posteriormente ajusta el mes para que el día no sea válido
          // luego la pantalla avanzará a la siguiente fecha válida
          currentDate  =  JulianDate ( iDay ,  iMonth ,  iYear );
        }
        romper ;
      caso  SET_SPEED_ADJ :
        // aumentar o disminuir la corrección en 5 milisegundos
        if  ( digitalRead ( UP_BUTTON )  ==  LOW )  {
          speedCorrection  + =  5 ;
        }
        if  ( digitalRead ( DOWN_BUTTON )  ==  LOW )  {
          speedCorrection  - =  5 ;
        }
        romper ;
    }
  }
}

String  FormatNumber ( valor int  ) { 
  // Para agregar un 0 a la izquierda si es necesario
  si  ( valor  <  10 )  {
    devuelve  "0"  +  Cadena ( valor );
  }
  else  {
    return  String ( valor );
  }
}

void  ShowTime ( valor largo sin firmar  ) {  
  // Actualizar la pantalla una vez por segundo
  // o cuando pasa la medianoche
  if  (( valor  >  lastTime  +  1000 )  ||  ( valor  <  lastTime ))  {
    lastTime  =  valor ;
     iHours largos  sin firmar ;
     iMinutes largos  sin firmar ;
     iSeconds largos  sin firmar ;
    SplitTime ( valor ,  iHours ,  iMinutes ,  iSeconds );

    // Muestra la hora en la línea 0
    lcd . setCursor ( 0 ,  0 );
    lcd . print ( "Hora:"  +  FormatNumber ( iHours )  +  ":"  +
              FormatNumber ( iMinutes )  +  ":"  +
              FormatNumber ( iSeconds ));
  }
}

void  ShowDate ( valor flotante  ) { 
  // Actualizar la pantalla si la fecha ha cambiado desde
  // la fecha se mostró por última vez
  if  ( lastDate  ! =  value )  {
    lastDate  =  valor ;
    int  iday ;
    int  imonth ;
    int  iyear ;
    String  currentDay ;
    GregorianDate ( valor ,  iday ,  imonth ,  iyear );
    int  dayOfWeek  =  ( unsigned  long ) valor  %  7 ;
    // Muestra la fecha en la línea 0
    lcd . setCursor ( 0 ,  1 );
    lcd . print ( dayArray [ dayOfWeek ]);
    lcd . imprimir ( FormatNumber ( iday )  +  ":"  +
              FormatNumber ( imonth )  +  ":"  +
              un año );
  }
}

void  ShowDDMMSet ( valor flotante  ) { 
  int  iday ;
  int  imonth ;
  int  iyear ;
  String  currentDay ;
  GregorianDate ( valor ,  iday ,  imonth ,  iyear );
  // Mostrar día y mes para ajustar
  lcd . setCursor ( 0 ,  0 );
  lcd . print ( "Establecer día y mes:" );
  lcd . setCursor ( 0 ,  1 );
  lcd . print ( "Día:"  +  FormatNumber ( iday )  +  "Mes:"  +
            FormatNumber ( imonth ));
}


void  ShowYearSet ( float  jd )  {
  int  iday ;
  int  imonth ;
  int  iyear ;
  GregorianDate ( jd ,  iday ,  imonth ,  iyear );
  // Mostrar año para ajustar
  lcd . setCursor ( 0 ,  0 );
  lcd . print ( "Establecer año:" );
  lcd . setCursor ( 0 ,  1 );
  lcd . print ( "Año:"  +  FormatNumber ( iyear ));
}

void  ShowTimeSet ( valor largo sin firmar  ) {  
   iHours largos  sin firmar ;
   iMinutes largos  sin firmar ;
   iSeconds largos  sin firmar ;
  // Mostrar tiempo para ajustar
  SplitTime ( valor ,  iHours ,  iMinutes ,  iSeconds );
  lcd . setCursor ( 0 ,  0 );
  lcd . print ( "Establecer hora:" );
  lcd . setCursor ( 0 ,  1 );
  lcd . print ( "Horas:"  +  FormatNumber ( iHours )  +  "Mins:"  +
            FormatNumber ( iMinutes ));
}

void  ShowSpeedSet ()  {
  // Muestra la figura de corrección de velocidad para ajustar
  // podría ser + o -
  lcd . setCursor ( 0 ,  0 );
  lcd . print ( "Ajustar velocidad ajusta:" );
  lcd . setCursor ( 0 ,  1 );
  lcd . imprimir ( "Millis:" );
  lcd . imprimir ( speedCorrection );
  lcd . imprimir ( "" );
}

Programa de temporizador

int  inByte  =  0 ;
unsigned  long  firstReading  =  100000 ;    // millis () cuando se envía la primera lectura

 configuración vacía ()  {
  Serial . comenzar ( 9600 );
  // Envía el byte de saludo a Processing
  sayHello ();
}

 bucle vacío ()  {
  // si se recibe un byte en el puerto serie
  // luego leer y descartar y enviar actual 
  // valor de millis ()
  if  ( Serial . disponible ()  >  0 ) {
    // obtener el byte entrante
    inByte  =  Serie . leer ();
    // enviar el tiempo transcurrido desde el procesamiento de la primera lectura
    Serial . print ( millis ()  -  firstReading );
    Serial . imprimir ( 'E' );
    // repetir cada 2 segundos
    retraso ( 2000 );
  }
}

void  sayHello () {
  // Espere hasta que el puerto serie esté disponible
  // luego envía un byte de saludo para iniciar el protocolo de enlace
  while  ( Serial . disponible ()  <= 0 ) {
    Serial . imprimir ( 'Z' );                 // Enviar Z al procesamiento para decir hola
    retraso ( 200 );
  }
  firstReading  =  millis ();
}

Procesamiento de secuencia de comandos de prueba de temporizador 

import processing.serial.*;

Serial theSerialPort;                   // create the serial port object

int[] serialBytesArray = new int[15];   // array to store incoming bytes
int bytesCount = 0;                     // current number of bytes received
boolean init = false;                   // false until handshake completed by receiving the character Z
int fillColor = 255;                    // defining the initial fill colour
long mills = 0;                         // last reading received
long first = 0;                         // time of first mills received so we can calculate the difference over an hour
long now;                               // number of millis elapsed since first mills received
long firstReading = 100000;             // millis() in processing of first message received from Arduino
long DiffPerHour = 0;                   // the difference after the first hour has passed 
int inByte;                             // last byte read

void setup() {
  // define some canvas and drawing parameters
  size(500, 500);
  background(70);
  noStroke();

  // print the list of all serial devices so you know which one to set for the Arduino
  // will need to run program and edit if the correct port is not set below
  printArray(Serial.list());
  // instantate the Serial Communication
  String thePortName = Serial.list()[1];
  theSerialPort = new Serial(this, thePortName, 9600);
}
void draw() {
  // Display time settings
  background(70);
  fill(fillColor);
  textSize(25);
  text(hour() + ":" + minute() + ":" + second(), 50, 50);
  // the last read millis sent by the Arduino
  text("Incoming elapsed: " + mills, 50, 100);
  // the current elapsed since first read in Processing
  text("Local elapsed: " + (now - firstReading), 50, 150);
  // display the current difference
  text("Diff: " + (mills - (now - firstReading)), 50, 200);
  // Check if 1 hour has passed and if the first hour store the difference
  if (((now - firstReading)>= 3600000) && (DiffPerHour == 0)){
    DiffPerHour = mills - (now - firstReading);
  }
  // Display the first difference and the difference after the first hour
  text("Diff after 1 hour: " + DiffPerHour, 50, 300);
}

void serialEvent(Serial myPort) {
  // read a byte from the serial port
  inByte = myPort.read();
  if (init == false) {         // if not yet handshaked the see if handshake byte
    if (inByte == 'Z') {       // if the byte read is Z
      myPort.clear();          // clear the serial port buffer
      init = true;             // store the fact we had the first hello
      myPort.write('Z');       // tell the Arduino to send more
      if (first == 0){
        first = millis();
      }
    }
  } 
  else {                       
    // if there already was the first hello
    // Add the latest byte from the serial port to array
    if (inByte != 69) {        // Check not the end of message character E    
      if (bytesCount < 14) {
        serialBytesArray[bytesCount] = inByte;
        bytesCount++;
      }
    }
    if (inByte == 69) {
      // End of message
      // store local time elapsed 
      now  = millis();
      // calculate incoming millis()
      mills = 0;
      for (int i = 1; i <= bytesCount; i++) {
        mills += (serialBytesArray[i - 1] - 48) * pow(10, (bytesCount - i));
      }
      // Say we are ready to accept next message
      // if this is the first reading then set the first difference 
      if (firstReading == 100000) {
        firstReading = now;
      }
      myPort.write('Z');
      // Reset bytesCount:
      bytesCount = 0;
    }
  }
}
https://create.arduino.cc/