DISEÑO DE RELOJ SINCRONIZADO CON LA HORA OFICIAL DE ESPAÑA
El objetivo de este proyecto es diseñar un reloj que se sincronice con el servidor del Real Instituto y Observatorio de la Armada en San Fernando, Cádiz, para obtener la hora oficial de España (peninsular) –hora central europea– y visualizarla en la pantalla de cristal líquido (LCD) de una placa TTGO t-display, que se basa en el chip ST7789.
El reloj está programado utilizando el firmware de MicroPython compilado con la librería para pantallas TFT con el chip ST7789 escrita por russhughes. Se puede descargar en GitHub (Code → Download ZIP). El firmware (firmware.bin) está dentro del fichero ZIP. en la carpeta firmware/esp32.
En la siguiente página se explica como instalarlo y su funcionamiento:
El software para hacer funcionar el reloj se basa en en 4 módulos independientes que cubren sus necesidades. Son los siguientes:
- conexionStation.py: establece la conexión Wi-Fi con el router, para poder obtener la hora del servidor.
- relojTiempoReal.py: obtiene la hora del servidor (NTP -Network Time Protocol-) y la sincroniza con el reloj del microcontrolador (RTC -Real Time Clock) con la oportuna corrección a la hora española peninsular.
- grafos.py: gestiona la visualización de la hora (y fecha) en la pantalla.
- main.py: gestiona el funcionamiento del reloj.
MÓDULOS
MÓDULO conexionStation.py
Establece y gestiona la conexión con el router (Station). Es un conexión básica, no estando previsto en el script que se produzca un fallo de conexión. El objetivo es simplificar al máximo la programación.
El funcionamiento de una conexión Station está explicado en la siguiente página:
El script es el siguiente:
import network def do_connect(SSID, PASSWORD): global sta_if sta_if = network.WLAN(network.STA_IF) if not sta_if.isconnected(): sta_if.active(True) sta_if.connect(SSID, PASSWORD) print('Conectando a la red', SSID +"...") while not sta_if.isconnected(): pass
MÓDULO relojTiempoReal.py
Tiene dos funciones time() y settime().
La función time() se utiliza para conectar el microcontrolador con el servidor del Real Instituto y Observatorio de la Armada en San Fernando, Cádiz (hora.roa.es) y obtener el EPOCH 01-01-1900 00:00:00 UTC –es decir los segundos que han pasado desde las 00:00:00 UTC del día 01-01-1900 hasta el momento en que se envía el dato– y recalcularlo para devolverlo como EPOCH 01-01-2000 00:00:00 UTC, que es el EPOCH que utiliza el microcontrolador ESP32 en el RTC (Reloj en Tiempo Real –Real Time Clock-).
La función settime() sincroniza el RTC con la hora local.
Para entender su funcionamiento conviene recordar que España peninsular (que es la zona para la que se programa el reloj) tiene la hora local UTC +1 como se puede ver en es siguiente mapa de usos horarios:
Y que en el periodo comprendido entre la 01:00:00 UTC del último domingo de marzo y la 01:00:00 UTC del último domingo de septiembre la hora local cambia de UTC +1 a UTC +2, es el horario de verano (conocido como DST o Daylight Saving Time).
settime() comprueba si el EPOCH se encuentra en el horario de verano utilizando la función sec_lastSundayMonth_1hUTC, que determina el EPOCH 01-01-2000 00:00:00 UTC de cambio de hora de los meses de marzo y septiembre y en función del resultado calcula y sincroniza la hora local con el microcontrolador (RTC).
El script es el siguiente:
import socket, struct, utime from machine import RTC host = "hora.roa.es" # El servidor proporciona el EPOCH con referencia a 1900-01-01 00:00:00 UTC (Coordinated Universal Time) NTP_DELTA = 3155673600 # El RTC (Real Time Clock) del microcontrolador utiliza el EPOCH con referencia a 2000-01-01. Es preciso corregirlo. # (date(2000, 1, 1) - date(1900, 1, 1)).days * 24*60*60 = 3155673600 def time(): # Función para obtener el EPOCH del servidor NTP_QUERY = bytearray(48) NTP_QUERY[0] = 0x1B addr = socket.getaddrinfo(host, 123)[0][-1] s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: s.settimeout(1) res = s.sendto(NTP_QUERY, addr) msg = s.recv(48) finally: s.close() val = struct.unpack("!I", msg[40:44])[0] return val - NTP_DELTA def settime(): # Función para sincronizar el RTC t = time() tm = utime.localtime(t) # Conversión de hora de EPOCH a fecha: año[0], mes[1], día[2], hora[3], minuto[4], segundo[5], díaDeLaSemana[6], díaDelAño[8] def sec_lastSundayMonth_1hUTC (month): # Función para conocer el EPOCH de la 01:00:00 UTC del último domingo de un mes de 31 días y poder corregir la hora UTC con la oficial de España sw = (tm[0], month, 31, 1, 0, 0, 0, 0, 0) # El cambio de hora en ESPAÑA se hace los últimos domingos de los meses de marzo y septiembre a la 01:00:00 UTC sw_secs = utime.mktime(sw) # Horario de verano UTC+2 - horario de invierno UTC+1 swm = utime.localtime(sw_secs) weekday_swm = swm[6] if swm[6] != 6: sw = (tm[0], month, 31-(swm[6] + 1), 1, 0, 0, 0, 0, 0) sw_secs = utime.mktime(sw) return sw_secs if sec_lastSundayMonth_1hUTC (3) <= t < sec_lastSundayMonth_1hUTC (9): # Sincronización con el RTC: año[0], mes[1], día[2], díaDeLaSemana[3], hora[4], minuto[5], segundo[6], subsegundo[7] RTC().datetime((tm[0], tm[1], tm[2], 0, tm[3]+2, tm[4], tm[5], 0)) # Horario de verano subsegundo -> cuenta atras de 255 a 0 else: RTC().datetime((tm[0], tm[1], tm[2], 0, tm[3]+1, tm[4], tm[5], 0)) # Horario de invierno
MÓDULO grafos_st7789.py
Gestiona la parte gráfica (la representación en la pantalla).
Para reducir el uso de la memoria se ha utilizado una fuente para los números inspirada en la GEOSTAR, que está construida con trazos rectos, ya que es sencillo reproducir algo similar sustituyendo los trazos en la pantalla por rectángulos.
Las dimensiones elegidas han sido de 88×44 pixeles por carácter, que permiten tanto una composición horizontal como vertical del reloj (aunque solo se utiliza la horizontal):
Las grafías de las horas y minutos se dibujan con las funciones H(numero, x, y) y M(numero, x, y), siendo los parámetros de cada función el número a representar y sus coordenadas x e y del origen. Los datos del dibujo de cada número están en las tuplas h y m. Cada tupla dentro de la tupla (es una «tupla de tuplas«) contiene la grafía del número correspondiente a su índice (p.e. m[3] tiene la grafía del número 3 de los minutos, h[5] tiene la grafía del número 5 de las horas…).
Los dos puntos del reloj en horizontal se representan con la función puntos().
El script es el siguiente:
from machine import Pin, SPI import st7789 import vga1_8x16 as font tft = st7789.ST7789( SPI(2, baudrate=30000000, polarity=1, phase=1, sck=Pin(18), mosi=Pin(19)), 135, 240, reset=Pin(23, Pin.OUT), cs=Pin(5, Pin.OUT), dc=Pin(16, Pin.OUT), backlight=Pin(4, Pin.OUT)) tft.init() tft.rotation(1) tft.offset(40,53) color_dibujo = st7789.WHITE color_fondo = st7789.BLACK def H(numero,a,b): h = (((0,0,16,88),(16,0,22,6),(38,0,6,88),(16,82,22,6)), ((5,0,25,6),(14,6,16,76),(5,82,34,6)), ((0,0,44,6),(0,6,6,18),(28,6,16,35),(0,41,44,6),(0,47,16,35),(38,64,6,18),(0,82,44,6)), ((0,0,44,6),(0,6,6,18),(28,6,16,76),(9,41,19,6),(0,64,6,18),(0,82,44,6)), ((0,0,6,41),(0,41,28,6),(28,0,16,88)), ((0,0,44,6),(0,6,16,35),(38,6,6,18),(0,41,44,6),(28,47,16,35),(0,64,6,18),(0,82,44,6)), ((0,0,44,6),(0,6,16,76),(38,6,6,18),(16,41,28,6),(38,47,6,35),(0,82,44,6)), ((0,0,44,6),(0,6,6,18),(28,6,16,82)), ((0,0,16,88),(16,0,22,6),(16,41,22,6),(38,0,6,88),(16,82,22,6)), ((0,0,44,6),(0,6,6,35),(28,6,16,76),(0,41,28,6),(0,64,6,18),(0,82,44,6)) ) tft.fill_rect(a,b,44,88,color_fondo) for x in h[numero]: tft.fill_rect(x[0]+a,x[1]+b,x[2],x[3],color_dibujo) def M(numero,a,b): m = (((0,0,44,4),(0,4,4,80),(11,4,4,80),(40,4,4,80),(0,84,44,4)), ((5,0,24,4),(15,4,4,80),(25,4,4,80),(5,84,34,4)), ((0,0,44,4),(0,4,4,20),(29,4,4,38),(40,4,4,38),(0,42,44,4),(0,46,4,38),(11,46,4,38),(40,64,4,20),(0,84,44,4)), ((0,0,44,4),(0,4,4,20),(29,4,4,80),(40,4,4,80),(9,42,20,4),(0,64,4,20),(0,84,44,4)), ((0,0,4,42),(29,0,15,4),(29,4,4,80),(40,4,4,80),(0,42,29,4),(29,84,15,4)), ((0,0,44,4),(0,4,4,38),(11,4,4,38),(40,4,4,20),(0,42,44,4),(29,46,4,38),(40,46,4,38),(0,64,4,20),(0,84,44,4)), ((0,0,44,4),(0,4,4,80),(11,4,4,80),(40,4,4,20),(15,42,29,4),(40,46,4,38),(0,84,44,4)), ((0,0,44,4),(0,4,4,20),(29,4,4,80),(40,4,4,80),(29,84,15,4)), ((0,0,44,4),(0,4,4,80),(11,4,4,80),(40,4,4,80),(15,42,29,4),(0,84,44,4)), ((0,0,44,4),(0,4,4,38),(29,4,4,80),(40,4,4,80),(0,42,29,4),(0,64,4,20),(0,84,44,4)), ) tft.fill_rect(a,b,44,88,color_fondo) for x in m[numero]: tft.fill_rect(x[0]+a,x[1]+b,x[2],x[3],color_dibujo) def puntos(): tft.fill_rect(116,43,8,8,color) tft.fill_rect(116,68,8,8,color)
MÓDULO main.py
Este script gestiona el funcionamiento del reloj. Después de importar los módulos necesarios inicia un bucle indefinido en el que comprueba 3 cosas:
- En el primero sincroniza la hora con internet utilizando los módulos conexionStation.py y relojTiempoReal.py, cuando se cumple que:
- No se dispone de una hora local previa (se acaba de iniciar la ejecución del script).
- Son las 02:00:00 o 03:00:00 horas locales (resincroniza el reloj y evitar el error del cambio horario verano/invierno – invierno/verano que se realiza a esas horas).
Una vez que ha realizado la sincronización se desconecta de internet (la conexión WiFi no permanece activa)
- En el segundo comprueba si la hora ha cambiado. Si es así imprime la hora en la pantalla LCD utilizando el módulo grafos_st7789.py.
- En el tercero hace que cada segundo parpadeen los dos puntos situados entre la hora y los minutos.
El script es el siguiente:
import conexionStation import relojTiempoReal import grafos_st7789 from machine import RTC minuto_ref = None diaSemana = {0:'Lunes', 1:'Martes', 2:'Miercoles', 3:'Jueves', 4:'Viernes', 5:'Sabado', 6:'Domingo'} while True: if minuto_ref == None or RTC().datetime()[4:7] == (2,0,0) or RTC().datetime()[4:7] == (3,0,0): conexionStation.do_connect("<nombre_de_red>","<clave_de_red>") relojTiempoReal.settime() conexionStation.sta_if.active(False) if minuto_ref != RTC().datetime()[5]: grafos_st7789.H(RTC().datetime()[4]//10,16,17) grafos_st7789.H(RTC().datetime()[4]%10,66,17) grafos_st7789.M(RTC().datetime()[5]//10,130,17) grafos_st7789.M(RTC().datetime()[5]%10,180,17) grafos_st7789.tft.text(grafos_st7789.font, "{:02d}-{:02d}-{} {} ".format(RTC().datetime()[2], RTC().datetime()[1], RTC().datetime()[0], diaSemana[RTC().datetime()[3]]), 35, 114, grafos_st7789.color_dibujo, grafos_st7789.color_fondo) minuto_ref = RTC().datetime()[5] if RTC().datetime()[6]% 2 == 0: grafos_st7789.color = grafos_st7789.color_dibujo else: grafos_st7789.color = grafos_st7789.color_fondo grafos_st7789.puntos()
Leave a Reply
Tu correo electrónico está seguro.
You must be logged in to post a comment.