Serie Bus I2C

PRACTICO RELOJ.

En el tutorial ds1307reloj ya puse de manifiesto las características y algunas capacidades de este pequeño y práctico dispositivo, en el que documenté el código que permitía poner en hora el reloj que soporta el dispositivo DS1307.

Ahora, trataré de llevar a cabo un proyecto en el que podamos dar cierta utilidad a las posibilidades que ofrece esta pequeña maravilla (es cierto que el mismo fabricante, dispone de productos más especializados con los que, se puede disponer de más opciones de lectura de los registros que pone a nuestro servicio, alarmas, cronómetros y más), sin embargo como el único que tengo a mano es el DS1307A (*), es con el que voy a realzar esta práctica.

Partiendo del montaje que se desarrolló en el mencionado ds1307reloj, voy a introducir unas variables y rutinas en el código descrito allí para mejorar las prestaciones que nos proporciona y podemos aprovechar a nuestra voluntad.

rtc_ds1307-002 identpin_ds1307
Fig. 1 Montaje del RTC DS1307

Como se desprende de la foto, realicé este montaje sobre una pequeña porción de placa preformada, para dar una más rápida solución al proyecto. A pesar de todo, la exactitud del reloj obtenido, es digna de tener en cuenta, vengo observando que, la deriva después de tres meses es de 1 segundo, aunque no puedo ‘afinar’ más dado que la referencia que utilizo es el reloj de mi PC.

Este montaje está basado en el esquema del propio ©Dallas, actualmente ©Maxim, que es el siguiente:

circuito_ds1307Fig. 2 Esquemático

Como se aprecia es muy sencillo, el dispositivo más critico, como siempre es el cristal de cuarzo, la exactitud del reloj depende casi exclusivamente de este dispositivo. Personalmente recomiendo siempre que sea posible uno de los que llevan los relojes de pulsera que tengamos en desuso, estará bien por que aprovecharemos un producto probado y nos resulta económico, por otra parte cumple los requisitos básicos, tiene una frecuencia fundamental de oscilación perfectamente calibrada a los 32.768 kHz, en cuanto a la estabilidad, es conveniente soldar la cápsula a masa de forma que no adquiera una alta temperatura al soldarla, para no producir alteraciones en la pastilla de cuarzo.

CARACTERÍSTICAS.

Las características del proyecto que emprendemos, pueden ser mejoradas tantas veces como le parezca necesario al interesado, sin embargo por el momento son las siguientes:

   1.- Puesta en hora y fecha, para iniciar el reloj, mediante la introducción de los datos anteponiendo una "T", en la forma: T(seg) (min) (hora)(día s)(día) (mes) (año) T(00-59)(00-59)(00-23) (1-7) (01-31)(01-12)(00-99).
   2.- Podemos consultar el contenido de la memoria, en pantalla, mediante Q y el parámetro (1 o 2)
       (1- Consultar Memoria y 2- Volcado de Memoria del RTC).
   3.- Consultar la fecha mediante el parámetro L, podemos leer la fecha y la hora actual.
   4.- Al mismo tiempo nos indicará la onomástica de algún familiar o la fiesta del día. 
   ....
   n-1.- El interesado, puede usar la característica de cronómetro, mediante su propio código.

EL CÓDIGO.

En principio, vamos a utilizar el mismo código mencionado del ejemplo anterior, ds1307reloj, al que iremos ampliando en cada nuevo parámetro. Respecto al código, le doy la versión 2, debido a las mejoras introducidas por mi, si bien es cierto que me apoyo en rutinas que extraigo de distintas partes (vamos que, como dice aquel «no fabrico los ladrillos, ni las ventanas, ni los utensilios de baño, para construir el edificio, y sin embargo, el mérito es del arquitecto»).

DS1307 RTC.

/*
* RTC Control v.01
* by  John Vaughters
* Credit to:
* Maurice Ribble - http://www.glacialwanderer.com/hobbyrobotics for RTC DS1307 code
*
* Con este código se puede establecer la fecha y la hora, recuperar la fecha y la hora 
* y usar la memoria adicional de un chip DS1307 RTC.
* El programa también pone todo el espacio de memoria suplementaria a 0xff.
* método de Comunicación Serie con el Arduino que, utiliza un Carácter Mayúscula para cada 
* orden descrita a continuación. Ahora también, admite el carácter q (minúscula). 
* Comandos: T(00-59)(00-59)(00-23)(1-7)(01-31)(01-12)(00-99) 
* 
* T(sec)(min)(hour)(dayOfWeek)(dayOfMonth)(month)(year) - T pone la fecha del chip RTC DS1307. 
* Ejemplo para poner la fecha y hora: 02-Feb-09 @ 19:57:11 para el día 3 de la semana, 
* use la orden - T1157193020209 // T11 57 19 3 02 02 09.
* Q(1-2) -:- (Q1) Consulta Memoria (Q2) RTC - Volcado Memoria 
* mm:ss:hh:ds:DD:MM:AA
* T20:11:18: 4:15:10:09 
* T2011184151009 otro: T4034195251209 
* ultimo: T4052001110110 
* 
* Modificado el: 19-09-2009.
* by V. García v.01.2 
*
* Ahora, puedes leer la hora y día de la fecha con introducir l o L
* Para poner en hora y la fecha del chip RTC DS1307, usa t o T:
* ss:mm:hh:ds:DD:MM:AA
* T20:11:18: 4:15:10:09 
* ultimo: T4052001110110
*
* El código funciona bien, 
* 
* Modificado el: 11-01-2010.
* by V. García v.02 
*
*/ 

#include "Wire.h"
#define DS1307_I2C_ADDRESS 0x68 // Esta es la address I2C 
// Variables Globales RTC

int command = 0; // Es el comando de carácter, en formato ASCII, enviados desde el puerto serie 
int i;
long previousMillis = 0; // almacenará la última vez que Temp se ha actualizado
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
byte test; 
int ledpin = 13; // activa el LED cada consulta

// Convierte números decimales normales a binario codificado decimal
byte decToBcd(byte val) {
return ( (val/10*16) + (val%10) );
}

// Convierte binario codificado decimal a números decimales normales
byte bcdToDec(byte val) {
return ( (val/16*10) + (val%16) );
}

// 1) Establece la fecha y la hora en el ds1307
// 2) Inicia el reloj
// 3) Establece el modo de hora de reloj a 24 horas 
// Se supone que está pasando números válidos, probablemente 
// tendrá que controlar poner números válidos.

void setDateDs1307() { 
// Uso de (byte) tipo de conversión ASCII y matemáticas para alcanzar el resultado. 
second = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48)); 
minute = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48));
hour = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48));
dayOfWeek = (byte) (Serial.read() - 48);
dayOfMonth = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48));
month = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48));
year = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48)); Wire.beginTransmission(DS1307_I2C_ADDRESS); // Empieza transmisión.
Wire.send(0x00);
Wire.send(decToBcd(second)); // 0 a bit 7 Inicia el reloj
Wire.send(decToBcd(minute));
Wire.send(decToBcd(hour)); // Si quiere 12 horas am/pm tiene que poner 
// bit 6 (también tiene que cambiar readDateDs1307)
Wire.send(decToBcd(dayOfWeek));
Wire.send(decToBcd(dayOfMonth));
Wire.send(decToBcd(month));
Wire.send(decToBcd(year));
Wire.endTransmission(); // Termina transmisión
}
// Extrae la fecha y el tiempo del ds1307 e imprime el resultado
void getDateDs1307(){ 
// Resetea el registro puntero
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.send(0x00);
Wire.endTransmission();

Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
// Alguna necesitará enmascarar porque ciertos bits son bits de control
second = bcdToDec(Wire.receive() & 0x7f);
minute = bcdToDec(Wire.receive());
hour = bcdToDec(Wire.receive() & 0x3f); // Tiene que cambiar esto para 12 hora am/pm.
dayOfWeek = bcdToDec(Wire.receive());
dayOfMonth = bcdToDec(Wire.receive());
month = bcdToDec(Wire.receive());
year = bcdToDec(Wire.receive());

if (hour < 10) Serial.print("0"); 
Serial.print(hour, DEC);
Serial.print(":");
if (minute < 10) Serial.print("0"); 
Serial.print(minute, DEC);
Serial.print(":");
if (second < 10) Serial.print("0"); 
Serial.print(second, DEC);
Serial.print(" ");
if (dayOfMonth < 10) Serial.print("0");
Serial.print(dayOfMonth, DEC);
Serial.print("-"); 

// if (month < 10) Serial.print("0"); // esto para poner mes de 01 a 12 
// Serial.print(month, DEC);

switch (month) // pone el nombre en e del mes
{
case 1: Serial.print("Ene"); break;
case 2: Serial.print("Feb"); break;
case 3: Serial.print("Mar"); break;
case 4: Serial.print("Abr"); break;
case 5: Serial.print("May"); break;
case 6: Serial.print("Jun"); break;
case 7: Serial.print("Jul"); break;
case 8: Serial.print("Ago"); break;
case 9: Serial.print("Sep"); break;
case 10: Serial.print("Oct"); break;
case 11: Serial.print("Nov"); break;
case 12: Serial.print("Dic"); break;
}
Serial.print("-");
Serial.print("20");
if (year < 10) Serial.print("0");
Serial.print(year, DEC); 
// Serial.print(" Hoy es:"); // Día de la semana:
switch (dayOfWeek) { // Esto pone nombre del día 
case 1: Serial.println(" Lunes"); break;
case 2: Serial.println(" Martes"); break;
case 3: Serial.println(" Miercoles"); break;
case 4: Serial.println(" Jueves"); break;
case 5: Serial.println(" Viernes"); break;
case 6: Serial.println(" Sabado"); break;
case 7: Serial.println(" Domingo"); break;
}
}

//
void lectura() { 
// ==== modificado, puesto aquí para que lea bien
int hour; 
int minute; 
int second; 
int month; 
int dayOfWeek; 
int dayOfMonth; 
int year; 
// ==== // Below required to reset the register address to 0.
Wire.beginTransmission(104); // transmit to device #104, the ds1307
Wire.send(0x00);
Wire.endTransmission(); // stop transmitting
Wire.requestFrom(104, 7); // request 7 bytes from slave ds1307, 
we'll assume it'll send them all even though it doesn't have to
second = Wire.receive();
minute = Wire.receive();
hour = Wire.receive();
dayOfWeek = Wire.receive();
dayOfMonth = Wire.receive();
month = Wire.receive();
year = Wire.receive();

// Convertir todos los valores BCD que podrán tener "decenas" a decimal. 
// Mucha gente en arduino hace esto w/shits pero esto solamente lo veo más fácil.
hour=hour/16 * 10 + hour % 16;
minute=minute/16 * 10 + minute % 16;
second=second/16 * 10 + second % 16;
dayOfMonth=dayOfMonth/16 * 10 + dayOfMonth % 16;
month=month/16 * 10 + month % 16;
year=2000 + year/16 * 10 + year % 16;

// Algunas fiestas
Serial.print ("HOLA, hoy es: ");
if (month == 12 && dayOfMonth == 25){ Serial.println ("NAVIDAD "); } 
if (month == 12 && dayOfMonth == 31){ Serial.println ("FIN DE A? "); } 
if (month == 01 && dayOfMonth == 01){ Serial.println ("FELIZ A? NUEVO "); } 
if (month == 03 && dayOfMonth == 17){ Serial.println ("S. JOSE, FALLAS "); } 
// if (month == XX && dayOfMonth == XX){ Serial.print ("Pon el cumple de xxx "); } 

if (year < 10) { Serial.print("0"); }
Serial.print(year);
Serial.print("-"); 
if (month < 10) { Serial.print("0"); } 
Serial.print(month);
Serial.print("-"); 
if (dayOfMonth < 10) { Serial.print("0"); } 
Serial.print(dayOfMonth); 
Serial.print(" "); 
if (hour < 10) { Serial.print("0"); }
Serial.print(hour);
Serial.print(":"); 
if (minute < 10) { Serial.print("0"); }
Serial.print(minute);
Serial.print(":");
if (second < 10) { Serial.print("0"); }
Serial.print(second);
Serial.print(" "); 

// Serial.print(" "); // Esto pone nombre del día de la semana:
switch (dayOfWeek) { case 1: Serial.println(" Lunes"); break;
case 2: Serial.println(" Martes"); break;
case 3: Serial.println(" Miercoles"); break;
case 4: Serial.println(" Jueves"); break;
case 5: Serial.println(" Viernes"); break;
case 6: Serial.println(" Sabado"); break;
case 7: Serial.println(" Domingo"); break;
}

delay(1000); // espera 1 segundo.
} 

void setup() {
Wire.begin();
Serial.begin(57600); 
pinMode(ledpin, OUTPUT); // para el LED
}

void loop() {
if (Serial.available()) // busca carácter en serie y procesa si se encuentra
{
command = Serial.read();
if (command == 84 or command == 116) // Si command = "T" o "t" xa establecer fecha
{
setDateDs1307();
getDateDs1307();
Serial.println(" ");
} 
else if (command == 81 or command == 113) //Si command = "Q" o "q" funciones de memoria
{
delay(50); 
if (Serial.available()) 
{
command = Serial.read(); 
if (command == 49) // Si command = "1" RTC1307 Inicializa Memoria - 
{ // Todos los datos se establecen en 255 (0xff). 
// Por lo tanto 255 o 0 será un valor no válido. 
//255 será el valor de inicio y 0 será considerado un error 
Wire.beginTransmission(DS1307_I2C_ADDRESS); 
// que se produce cuando el RTC están modo de batería.
Wire.send(0x08);// Ajusta el registro puntero un poco más allá los registros de fecha/hora.
for (i = 1; i <= 27; i++) 
{ Wire.send(0xff);
delay(60); // Al poner 50 reduce tiempo respuesta. Ajusta el reloj??
} // las líneas comentadas que siguen, no parecen necesarias, descomentar si procede.
Wire.endTransmission();
getDateDs1307();
// Serial.println(": RTC1307 Initialized Memory");
}
else if (command == 50) // Si command = "2" Volcado de Memoria RTC1307
{ getDateDs1307();
// Serial.println(": RTC 1307 Dump Begin");
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.send(0x00);
Wire.endTransmission();
Wire.requestFrom(DS1307_I2C_ADDRESS, 64);
for (i = 1; i <= 64; i++) 
{ test = Wire.receive();
Serial.print(i);
Serial.print(":");
Serial.println(test, DEC);
} 
// Serial.println(" RTC1307 Dump end");
}
} 
}
// rutina para, leer fecha actual
else if (command == 76 or command ==108) //Si command = L o l
{
lectura();
} 
} 
command = 0; // reset command 
delay(70); 
}
//*****************************************************The End***********************/

QUE HACE EL CÓDIGO.

A pesar de que está muy comentado el código, conviene describir un poco, algunas rutinas del código, para recalcar su importancia. La siguiente línea, se hace la llamada a las librerías implicadas en el desarrollo del proyecto, este paso no siempre es necesario, sin embargo, es conveniente acostumbrarse a utilizarlo para evitar sorpresas al interpretar los mensajes de error que puedan producirse al procesar el código.

#include "Wire.h"

La siguiente línea (en este caso), le indica al compilador la dirección donde debe empezar la lectura de datos del DS1307A que es, 104 en decimal, en hex68h y en binario b1101000. El lector, debe tener muy en cuenta, lo que recomienda el fabricante en sus Hojas de Datos, evitando así quebraderos de cabeza.

#define DS1307_I2C_ADDRESS 0x68  // Esta es la address I2C

A partir de aquí se presentan las declaraciones de variables generales

// Variables Globales  
int command = 0;  // Es el comando de carácter, en formato ASCII, enviados desde el puerto serie 
int i;
long previousMillis = 0; // almacenará la última vez que Temp se ha actualizado
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
byte test;

Es una buena práctica, distribuir las rutinas, después de las declaraciones de variables, antes de la configuración de estados o setup();.

Se deben resaltar, las subrutinas de conversión de decimal a binario y viceversa, ambas las podemos ver en:

// Convierte números decimales normales a binario codificado decimal
byte decToBcd(byte val)
{
  return ( (val/10*16) + (val%10) );
}
// Convierte binario codificado decimal a números decimales normales
byte bcdToDec(byte val)
{
  return ( (val/16*10) + (val%16) );
}

No menos importante es la subrutina matemática de (byte) tipo de conversión ASCII (que interpretará los datos leídos a dígitos característicos de presentación de la fecha), presente en el código como:

   second = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48)); 
   minute = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48));
   hour   = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48));
   dayOfWeek  = (byte) (Serial.read() - 48);
   dayOfMonth = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48));
   month  = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48));
   year   = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48));
   Wire.beginTransmission(DS1307_I2C_ADDRESS); // Empieza transmisión.
   Wire.send(0x00);
   Wire.send(decToBcd(second)); // 0 a bit 7  Inicia el reloj
   Wire.send(decToBcd(minute));
   Wire.send(decToBcd(hour));   // Si quiere 12 horas am/pm tiene que poner
                                // bit 6 (también tiene que cambiar readDateDs1307) 
   Wire.send(decToBcd(dayOfWeek));
   Wire.send(decToBcd(dayOfMonth));
   Wire.send(decToBcd(month));
   Wire.send(decToBcd(year));
   Wire.endTransmission();    // Termina transmisión

Por cierto, observemos que, para leer de un dispositivo, se inicia con un comando como:

Wire.beginTransmission(DS1307_I2C_ADDRESS)


y debe terminar con el correspondiente:

Wire.endTransmission();    para cerrar la transmisión. Esto es muy importante.

Otra rutina interesante en este proyecto es la que se encarga de extraer la fecha y el tiempo del registro interno del ds1307 y que nos permite imprimir el resultado, se trata de la rutina siguiente:

void getDateDs1307(){ 
   // Primero resetea el registro puntero
   Wire.beginTransmission(DS1307_I2C_ADDRESS);
   Wire.send(0x00);  // pone todo a cero.
   Wire.endTransmission();
   // luego requiere la lectura de los primeros 7bytes, donde están los datos que necesitamos.
   Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
   
   // Alguna orden necesitará enmascarar porque ciertos bits son bits de control
   second     = bcdToDec(Wire.receive() & 0x7f);
   minute     = bcdToDec(Wire.receive());
   hour       = bcdToDec(Wire.receive() & 0x3f);  // Tiene que cambiar esto para 12 hora am/pm.
   dayOfWeek  = bcdToDec(Wire.receive());
   dayOfMonth = bcdToDec(Wire.receive());
   month      = bcdToDec(Wire.receive());
   year       = bcdToDec(Wire.receive()); 
   ... 
   }

A este código, le sigue una forma de presentación de los datos extraídos y que cada cual puede mostrar como mejor le parezca, siempre respetando las reglas de código. Entre los datos se pueden mostrar los segundos, minutos, horas, día de la semana y la forma de fecha tradicional de cada zona horaria. A este respecto, debo indicar que aunque en principio no se aprecie, el modo que se deberá utilizar, por sus ventajas es el de AA-MM-DD, en lugar de dd-mm-aa. A la hora de ordenar una lista de fechas, viene a demostrar su importancia.

Quiero resaltar que he dado nombre a los meses y los días de la semana en español y otra particularidad es el hecho de mostrar el mensaje de la festividad del día o la atención recordatoria de la onomástica de alguna persona afín, cuando pongo:

// Algunas fiestas
  Serial.print ("HOLA, hoy es: ");
  if (month == XX && dayOfMonth == XX){ Serial.print ("Pon el cumple de tu pareja "); }

Observar la variante de la orden Serial.print(» «); que muestra el contenido que hay entre las comillas y/o el valor de la variable y sigue en la misma línea, en cambio la orden Serial.println(» «); que muestra el contenido que hay entre las comillas y/o el valor de la variable y sigue con un fin de línea y un salto de línea (CR/CL).

Por Ej. de Serial.print():

 int b = 79;
 Serial.print(b, DEC) // muestra el valor de b como un número decimal. Mostrará la cadena "79" 
 Serial.print(b, HEX) // muestra el valor de b como un número número (79) decimal en valor hexadecimal "4F". 
 Serial.print(b, OCT) // muestra el valor de b como un número número (79) decimal en valor octal "117".
 Serial.print(b, BIN) // muestra el valor de b como un número número (79) decimal en valor binario "1001111".

SENTENCIA SWITCH/CASE.

El principiante, debe fijarse en la estructura de la sentencia SWITCH/case y en la forma que aquí se ha empleado. SWITCH, puede tomarse como interruptor o mejor, conmutador, ya que depende de la entrada, optará por dar una única salida para esa entrada, si cambia el valor de la entrada así hará la salida. Aquí, se aplica dos veces sucesivas, una para determinar el nombre del mes que corresponde y en segundo lugar para el nombre del día de la semana. En al primer caso el valor que adquiera la variable «month», determinará el nombre del mes, así mismo el valor que tome «dayOfWeek» determinará a su vez el nombre del día de la semana, parece fácil.

LA NOVEDAD.

Hasta este punto, podemos decir que ya estaba claro el listado del código, la «novedad», reside en este punto que, hasta ahora, no podíamos consultar la fecha y hora actuales, salvo que aplicáramos un nuevo tiempo T en la entrada (que, por otra parte se antoja compleja). Aquí empieza la parte más amigable de este reloj en tiempo real, ya que cada vez que deseemos conocer la hora actual, bastará con introducir el carácter «L» o en minúsculas «l» de lectura y el programa responderá mostrando en el monitor la fecha, hora y si lo hemos previsto la fiesta que le corresponde, ahí esta la novedad.

De esto se encarga la subrutina lectura();, el interesado puede revisar el código para comprender los pasos que se siguen para lograr este resultado.

respuesta_del_rtcFig. 3 Respuesta

Aunque, para ser exactos, debe realizar unos pequeños ‘retoques’ al código, para lograr el mismo resultado o incluso mejorarlo. En un próximo artículo, veremos cómo mejorar el código. ¿Se atreve alguien?.

A TENER EN CUENTA (NOTA DEL FABRICANTE).

Cuando el sistema se enciende, la posición de RAM 0x3f será comparada con el valor 0x20. Si éste existe, entonces, esto quiere decir que la batería de reserva ha salvado el contenido de la RAM y todos los registros, por lo tanto, han sido inicializados. Entonces el software se salta la secuencia inicialización.

Si no se encuentra el valor 0x20 en dicha dirección, entonces el dispositivo Real Time Clock es inicializado a los valores siguientes:

tabla-addresFig. 4

Estos son todos los números de código binario decimal con que trabaja el dispositivo reloj de tiempo real. Tenga en cuenta que los valores de inicialización se han elegido para que pueda entender lo que la pantalla está mostrando.

Como siempre, comentarios, criticas y sugerencias para mejorar este artículo, son bienvenidos y apreciados.

Esto es todo, por este simple tutorial.

DS1307 Pactico
Etiquetado en:

Deja un comentario