Hola amigos la primera version de este proyecto la publique en el Foro de
www.micros.mforos.com quien ha sido mi casa ultimamente, con esta publicacion no pretendo llenar los foros que visito con el mismo material, pero queria compartir con todopic esto tambien.
El proyecto en el concepto es muy simple: "hacer un reloj sin RTC's o Timer's, solo con el protoclo de red NTP", aunque la programacion me ha llevado toda una semana no resulto muy sencillo que digamos; ademas todavia falta añadir las rutinas para poder cambiar el GMT y el servidor NTP. Por ahora solo muestra la hora GMT de México sin el Horario de Verano que entro hoy por la noche y tiene un server estatico.
Bueno vamos con un poco de teoria tambien acerca de lo que es NTP:
Network Time Protocol (NTP) es un protocolo de internet para sincronizar los relojes de los sistemas informáticos a través de ruteo de paquetes en redes con latencia variable. NTP utiliza UDP como su capa de transporte, usando el puerto 123. Está diseñado para resistir los efectos de la latencia variable.
NTP utiliza el Algoritmo de Marzullo con la escala de tiempo UTC, incluyendo soporte para características como segundos intercalares. NTPv4 puede mantenerse a tiempo con hasta 10 milisegundos (1/100 segundos) a través de Internet, y puede llegar a acercarse hasta 200 microsegundos (1/5000 segundos) o más en redes de área local sobre condiciones ideales.
NTP es uno de los protocolos de internet más viejos que siguen en uso (desde antes de 1985). NTP fue diseñado originalmente por Dave Mills de la Universidad de Delaware, el cual lo sigue manteniendo, en conjunto con un equipo de voluntarios.
NTP utiliza un sistema de jerarquía de estratos de reloj, en donde los sistemas de estrato 1 están sincronizados con un reloj externo tal como un reloj GPS ó algún reloj de radio. Los sistemas de estrato 2 de NTP derivan su tiempo de uno ó más de los sistemas de estrato 1, y así consecutivamente (cabe mencionar que esto es diferente de los estrato de reloj utilizados en los sistemas de telecomunicaciones).
Las estampas de tiempo utilizadas por NTP consisten en un segundo de 32-bit y una parte fraccional de 32-bit, dando con esto una escala de 232 segundos (136 años), con una resolución teórica de 2−32 segundos (0.233 nanosegundos). Aunque las escalas de tiempo NTP se redondean cada 232 segundos, las implementaciones deberían desambigüar el tiempo NTP utilizando el tiempo aproximado de otras fuentes. Esto no es un problema en la utilización general ya que esto solamente requiere un tiempo cercano a unas cuantas décadas.
Los detalles operacionales de NTP se encuentran ilustrados en el RFC 778, RFC 891, RFC 956, RFC 958 y RFC 1305. (NTP no debe ser confundido con daytime (RFC 867) ó los protocolos de tiempo (RFC 868)). La versión actual de NTP es la versión 4; hasta el 2005, sólo las versiones superiores a la versión 3 han sido documentadas en los RFCs. El grupo de trabajo de NTP IETF ha sido formado para estandarizar el trabajo de la comunidad de NTP desde RFC 1305.
La construccion del paquete es la siguiente:
Byte 0: flags:
Bits 7-6: Leap Indicate (zero for request)
Bits 5-3: Version (4)
Bits 2-0: Mode (i.e. 3 for client (or maybe 1 for symmetric active))
Byte 1: stratum (zero for request)
Byte 2: poll (zero for request)
Byte 3: precision (zero for request)
Bytes 4-7: root delay (zero for request)
Bytes 8-11: root dispersion (zero for request)
Bytes 12-15: reference id (zero for request)
Bytes 16-23: reference timestamp (zero for request)
Bytes 24-31: originate timestamp (zero for request)
Bytes 32-39: receive timestamp (zero for request)
Bytes 40-47: transmit timestamp (set to current time)
Por ejemplo el byte 0 puede ser 00 100 011 = 0x23
Los bytes 1 a 39 pueden ser 0. Los Bytes 40-47 contienen el tiempo que nosotros transmitimos tambien puede ser 0
El server nos va a contestar con un paquete de igual tamaño, como usamos NTP en formato largo, los bytes 40-43 son un numero de 32 bits,el MSB primero, Este numero de 32 bits representa los segundos transcurridos desde el 1ro de Enero de 1900. los Bytes 44-47 son un numero de 32 bits tambien y representan la precision en segundos del tiempo. Usar un servidor NTP nos da una precision de 2
32 lo cual permite ser muy muy precisos, pero en mi proyecto descartamos este valor no busco ser super exacto.
Entonces los bytes que nos interesan captar son los bytes 40 al 43 para pasarlo a una variable de 32 bits y poder hacer el calculo. Como para todo lo de ethernet estoy usando Mikrobasic nos facilita el trabajo ya que MKB posee una libreria para el tiempo EPOCH, este tiempo esta representado en segundos tambien pero desde el 1ro de Enero de 1970 lo que nos permite hacer uso de esta libreria, basta con hacer la diferencia entre los segundos UTC a los segundos UNIX -asi tambien se llama el tiempo EPOCH-.
La
RFC958 es imperdible pues es donde explican la constuccion del paquete NTP, aunque yo no lo tenia muy claro hasta que
Andrew Gregory me lo puso justo como arriba.
Ahora que ha quedado
a grosso modo medio explicado aqui va el programa lo mas comentado que pude.
El programa hace la peticion NTP cada 10s y es mostrada la hora que nos envia el servidor NTP en un LCD de 16x2 y tambien en la pagina web del PIC la cual corresponde a la direccon IP del PIC. La Hora del LCD y la de la pagina WEB se muestran con diferencia en los segundos pues no se despliegan al mismo tiempo. Sobre todo porque la pagina WEB se recarga automaticamente cada 20s. Por lo que los segundos no son continuos. El formato de hora es UTC Mexico, es decir el tiempo UTC - GMT correspondiente a mi Pais.
El circuito -que habia olvidado- guardando las diferencias ya que uso la M2ETHBOARD:
Ahora si, aqui el codigo el cual esta divido en 3 partes, El proyecto y 2 modulos:
program PICNTP
include "enc_ss" ' modulo donde se implementa el codigo
include "eth_enc28j60" ' modulo usado por el compilador
dim mymacaddr as byte [6] ' variables para MAC, IP, DNS,GW,MASCARA, Paquete NTP
dim myipaddr as byte [4]
dim gwipaddr as byte [4]
dim dnsipaddr as byte [4]
dim ipmask as byte [4]
dim NTP_Pack as byte [48]
dim dum as byte
sub procedure pausa() ' Pausa para el retardo NTP y recepcion de Paquetes
for i=0 to 100
spi_ethernet_dopacket() ' aun en la Pausa escuchamos peticiones UDP o TCP
delay_ms(100)
spi_ethernet_dopacket()
next i
end sub
'/////////////////// Datos del Paquete UDP explicacion Gracias a: Andrew Gregory //////////////////////
'
'http://tools.ietf.org/html/rfc1305
'http://tools.ietf.org/html/rfc867
'
'Byte 0: flags:
' Bits 7-6: Leap Indicate (zero for request)
' Bits 5-3: Version (4)
' Bits 2-0: Mode (i.e. 3 for client (or maybe 1 for symmetric active))
'Byte 1: stratum (zero for request)
'Byte 2: poll (zero for request)
'Byte 3: precision (zero for request)
'Bytes 4-7: root delay (zero for request)
'Bytes 8-11: root dispersion (zero for request)
'Bytes 12-15: reference id (zero for request)
'Bytes 16-23: reference timestamp (zero for request)
'Bytes 24-31: originate timestamp (zero for request)
'Bytes 32-39: receive timestamp (zero for request)
'Bytes 40-47: transmit timestamp (set to current time)
'
'////////////////////////////////////////////////////////////////////////////////////////////
sub procedure req_ntp() ' Construccion de Paquete NTP
NTP_Pack[0]= 0x23 ' Enviamos modo Cliente, sin estrato
for dum=1 to 47 ' Todos los demas Bytes son 0
NTP_Pack[dum]=0
next dum
spi_ethernet_sendudp(IPD,123,123,@NTP_Pack,48) 'Enviamos el paquete NTP al Servidor
end sub
'//////////////////////////////////// Configuracion de Variables ////////////////////////////
main:
adcon0=0 ' Sin ADC's, Sin Comparadores PORTA Entrada
adcon1=15
cmcon=7
trisa=255
porta=0
trisd=0 ' PORTD de Salida =0
portd=0
mymacaddr[0]= 0x00 ' MAC del PIC
mymacaddr[1]= 0x00
mymacaddr[2]= 0x00
mymacaddr[3]= "M"
mymacaddr[4]= "A"
mymacaddr[5]= "X"
myipaddr[0]=20 ' IP del PIC
myipaddr[1]=0
myipaddr[2]=4
myipaddr[3]=20
' Mascara del PIC
ipmask[0]=255
ipmask[1]=255
ipmask[2]=255
ipmask[3]=0
dnsipaddr[0]=20 'DNS y GW del PIC
dnsipaddr[1]=0
dnsipaddr[2]=4
dnsipaddr[3]=1
gwipaddr[0]=20
gwipaddr[1]=0
gwipaddr[2]=4
gwipaddr[3]=1
IPD[0]=20'148
IPD[1]=0'243 'IP del Servidor NTP
IPD[2]=4'7
IPD[3]=10'30
LCD_CONFIG(PORTB,7,6,5,4,PORTB,0,2,1) 'Configuramos el PORTB para el LCD
LCD_OUT(1,1,"INICIALIZANDO")
DELAY_MS(3000)
spi_init() 'iniciamos SPI en el PIC
spi_ethernet_init(portc,0,portc,1,mymacaddr,myipaddr,1) 'Configuramos e Iniciamos el Ethernet del PIC
spi_ethernet_confnetwork(ipmask,gwipaddr,dnsipaddr)
for i=0 to 2 ' Blink para ver si esta vivo Termina con RD7 en ON
portd.7=0
delay_ms(500)
portd.7=1
delay_ms(500)
next i
tryntp: ' empezamos la recepcion de paquetes
while true
spi_ethernet_dopacket() ' Escuchamos peticiones TCP y UDP
req_ntp() ' Pedimos la hora UTC cada 10s.
cls ' Limpiamos el LCD
lcd_out(1,1,fecha.str1) ' Mostramos en la primer linea el dia, mes y año
lcd_out(2,1,fecha.str2) ' Mostramos en la segunda linea la Hora sin puntitos
pausa() ' Llamamos a Pausa
spi_ethernet_dopacket() ' Escuchamos peticiones TCP y UDP
wend
goto tryntp ' Loop
end.
module enc_ss
include "eth_enc28j60_api" ' modulo del compilador
include "unixtime"
dim getRequest as byte[20] ' variables de uso general
dyna as byte[30]
NTPr as byte[48] ' Variable donde se guarda los datos provenienes del Server
txt as string[20]
i,l as byte
dim IPD as byte [4]
const httpHeader as string[31] = "HTTP/1.1 200 OK"+chr(10)+"Content-type: " ' HTTP header
const httpMimeTypeHTML as string[13] = "text/html"+chr(10)+chr(10) ' HTML MIME type
const httpMimeTypeScript as string[14] = "text/plain"+chr(10)+chr(10) ' TEXT MIME type
const httpMethod as string[5] = "GET /"
'///////////////////////////// ' Pagina a mostrar ////////////////////////////////
const pagina as string [837]=
"<meta http-equiv=" + Chr(34) + "refresh" + Chr(34) + " content=" + Chr(34) + "20;url=/" + Chr(34) + chr(62)+
"<html>"+
"<head>"+
"<script src=/x></script>"+
"<title>PIC NTP</title>"+
"</head>"+
"<body>"+
"<div align='center' style='font-size:3cm'>"+
"<strong><script>document.write(Fecha)</script></strong><br>"+
"<strong><script>document.write(Hora)</script></strong><br>"+
"</div>"+
"<hr><br>"+
"Actualice la Configuración<br><br>"+
"<form method='get'><table width='273' border='1' bordercolor='black'>"+
"<tr>"+
"<td width='119' height='40'>"+
"Servidor NTP <br>"+
"</td>"+
"<td width='144'><input type='text' maxlength='15' name='ip'></td>"+
"</tr>"+
"<tr>"+
"<td height='40'>GMT"+
"<input type='checkbox' name='m'>"+
chr(43)+
"<input type='checkbox' name='n'>"+
" - </td>"+
"<td><input type='text' maxlength='4' name='g'></td>"+
"</tr>"+
"<tr>"+
"<td height='47' colspan='2'> "+
"<div align='center'>"+
"<input name='Enviar' type='submit' value='Actualizar'>"+
"</div></td>"+
"</tr>"+
"</table></form>"+
"</body>"+
"</html>"
'/////////////////////////// Implementacion TCP
sub function Spi_Ethernet_UserTCP(dim byref remoteHost as byte[4], dim remotePort, localPort, reqLength as word) as word
result=0
if localport<>80 then 'escuchamos puerto 80
result=0
end if
for i = 0 to 23
getRequest[i] = Spi_Ethernet_getByte()
next i
getRequest[i] = 0
i = 0
while (httpMethod[i] <> 0)
txt[i] = httpMethod[i]
i = i + 1
wend
if(memcmp(@getRequest, @txt, 5)<>0) then ' Solo se soporta en metodo GET
result = 0
exit
end if
if(getRequest[5] = "x") then 'Si el byte 5 corresponde a "x" mostramos los valores
'el texto puede interpretarse como javascritp /x
result = Spi_Ethernet_putConstString(@httpHeader) ' HTTP header
result = result + Spi_Ethernet_putConstString(@httpMimeTypeScript) ' MIME type
'Mostramos Fecha Contruyendo una variable tipo string JS: var= " fecha";
dyna = fecha.str1
txt = "var Fecha= "
result = result + Spi_Ethernet_putString(@txt)
txt = ""+chr(34)
result = result + Spi_Ethernet_putString(@txt)
result = result + Spi_Ethernet_putString(@dyna)
txt = ""+chr(34)
result = result + Spi_Ethernet_putString(@txt)
txt = ";"
result = result + Spi_Ethernet_putString(@txt)
' Mostramos Hora Contruyendo una variable tipo string JS: var= " Hora";
txt = "var Hora= "
result = result + Spi_Ethernet_putString(@txt)
dyna = fecha.str2
txt = ""+chr(34)
result = result + Spi_Ethernet_putString(@txt)
result = result + Spi_Ethernet_putString(@dyna)
txt = ""+chr(34)
result = result + Spi_Ethernet_putString(@txt)
txt = ";"
result = result + Spi_Ethernet_putString(@txt)
end if
if(result = 0) then 'Mostrar por defalt
result = Spi_Ethernet_putConstString(@httpHeader) ' HTTP header
result = result + Spi_Ethernet_putConstString(@httpMimeTypeHTML) ' HTML MIME type
result = result + Spi_Ethernet_putConstString(@pagina) ' Pagina HTML
end if
end sub
'//////////////////////////// Implementacion UDP
sub function Spi_Ethernet_UserUDP(dim byref remoteHost as byte[4], dim remotePort, destPort, reqLength as word) as word
result = 0 ' reseteo de la funcion
if destport = 123 then ' Escuchamos por el puerto 123 para capturar datos NTP
for i=0 to 47
NTPr[i]=spi_ethernet_getbyte() 'Grabamos los datos en la variable
next i
FSR2Ptr = @UTCseg ' Converimos los valores separados en una variable de 32bits
POSTINC2 = NTPr[43]
POSTINC2 = NTPr[42]
POSTINC2 = NTPr[41]
INDF2 = NTPr[40]
actualdate() ' Llamamos al Procedimiento para convertir la hora.
end if
end sub
end.
module UnixTime
dim diat as string[3] ' Variables para convertir a cadena
mdt as string[3]
mes as string[3]
yyt as string[5]
hht as string[3]
mmt as string[3]
sst as string[3]
UnixTimec as longword ' Variable para tiempo UNIX
UTCseg as longword ' Variable que contiene los segundos UTC de 32bits
const USRGMT = 18000 ' Constante GMT para Mexico
structure TIEMPO
dim ss as byte ' segundos
dim mn as byte ' minutos
dim hh as byte ' Horas
dim md as byte ' Dia del mes de 1 a 31
dim wd as byte ' Dia de la Semana Lunes=1....Domingo=6
dim mo as byte ' Mes del año de 1 a 12
dim yy as word ' Año YK2
dim str1 as string[17] 'Donde guardamos la Fecha
dim str2 as string[16] 'Donde Guardamos la Hora
end structure
dim fecha as TIEMPO ' variable para la fecha
const offset = 2208988800 ' Diferencia en segundos entre 1900 y 1970
sub procedure cls
lcd_cmd(lcd_clear) ' Rutina para limpiar LCD
delay_ms(200)
end sub
'//////////////////////////////////////// Procedimiento Para obtener Fecha en Formato Humano /////////////////////////////
sub procedure ActualDate()
UnixTimec = ((UTCseg - USRGMT)+ 9) - offset ' Calculamos el TiempoUnix restando a los segundos UTC el gmt del usuario y el offset UNIX
Time_epochToDate(UnixTimec,fecha) ' Obtenemos la fecha actual en Formato Humano
select case fecha.wd ' Escogemos el Dia segun su valor
case 0
diat="Lun"
case 1
diat="Mar"
case 2
diat="Mie"
case 3
diat="Jue"
case 4
diat="Vie"
case 5
diat="Sab"
case 6
diat="Dom"
end select
select case fecha.mo 'Escogemos el mes segun su valor
case 1
mes="Ene"
case 2
mes="Feb"
case 3
mes="Mar"
case 4
mes="Abr"
case 5
mes="May"
case 6
mes="Jun"
case 7
mes="Jul"
case 8
mes="Ago"
case 9
mes="Sep"
case 10
mes="Oct"
case 11
mes="Nov"
case 12
mes="Dic"
end select
' convertimos a cadena la Fecha
bytetostr(fecha.md,mdt)
wordtostr(fecha.yy,yyt)
bytetostr(fecha.hh,hht)
bytetostr(fecha.mn,mmt)
bytetostr(fecha.ss,sst)
fecha.str1= diat 'Asignamos el dia a la variable Fecha.str1 y anexamos despues los demas valores
strcat(fecha.str1,mdt)
strcat(fecha.str1,mes)
strcat(fecha.str1,yyt)
fecha.str2= hht 'Asignamos la hora a la variable Fecha.str2 y anexamos despues los demas valores
strcat(fecha.str2,mmt)
strcat(fecha.str2,sst)
end sub
end.
Las imagenes correspondientes:
La pagina web
Por ultmio agradecer a Andrew gregory por el apoyo y a mi Hermano pues me ayudo toda esta semana a comprender como se saca la fecha en formato humano sin necesidad de una libreria.
Saludos.