Bus I2C - DS1307
1. Introducción:
Usaremos una placa comercial conocida como "Tiny Rtc".

2. Características:
Esta placa integra una memoria EEPROM 24c32 y un DS1307 compartiendo un bus I2C.
La tensión de alimentación debe estar comprendida entre 4.5 - 5.5Vdc
El DS1307 es un reloj de tiempo real que de forma autónoma puede llevar cuenta de la hora y fecha, para esto necesita
una batería de respaldo.
Características del ds1307:
- Informa de la hora, minutos, segundos, día de la semana, día del mes, mes y año.
- Años bisiestos hasta 2100.
- 56 Bytes de Ram disponible para otros usos programable un número ilimitado de veces.
- Salida de onda cuadrada programable.
- I2C como bus de comunicación.
- Bajo consumo: 500nA .


3. Registros:
Los datos de fecha y hora se almacenan en formato BCD y así en un registro de 8 bits se pueden almacenar los dos dígitos:

Para realizar la configuración y consulta de datos, accederemos al siguiente registro:

Nota: Destacamos el bit 7 (CH) de la dirección 0x00. Este bit es el que pone en funcionamiento al rtc. Debe ponerse a 0 para que funcione.
En la dirección 0x07, configuramos lo siguiente:
- Bit 7 (OUT): Controla el nivel de salida del pin SQW cuando está deshabilitado.
- Bit 4 (SQWE): Para habilitar la salida de la onda cuadrada, se pone este bit a 1.
- Bit 1 (RS1) + bit 0 (RS0): Configura la frecuencia de la onda cuadrada.
En la siguiente tabla podemos ver cómo configurar la salida de onda cuadrada:

4. Funciones de conversión BCD:
Como hemos dicho, el ds1307 usa un registro de 8 bits para guardar dos dígitos. Los cuatro bits de mayor peso serán las
decenas y los cuatro bits de menor peso las unidades.
He encontrado unas funciones sencillas, con las que podemos convertir un valor almacenado en un byte,
en dos dígitos: 10011001 -> 99.
- Ejemplo conversión a decimal del valor bcd 0101 0110 = 86:
- 86 / 16 = 5
- 5 * 10 = 50
- 86 % 16 = 6
- 50 + 6 = 56
- Ejemplo conversión a bcd del valor decimal 56 = 0011 1000:
- 56 / 10 = 5 -> 0101
- 5 * 16 = 80 -> 0101 0000
- 56 % 10 = 6 -> 0110
- 0101 0000 + 0000 0110 = 0101 0110

5. Ejemplo imprimir hora por puerto serie:
Este código, inicializa el RTC con una fecha y hora. Luego imprime cada segundo la hora. Cada minuto imprime también la fecha.
/* * File: main.c * Author: Eduardo * * Created on 5 de junio de 2017, 17:16 */ #include#include "config.h" #include "typedefs.h" #include "usart.h" #define SLAVE_I2C_ADDRESS 0x68 // Dirección del DS1307 en el bus //PROTOTIPO DE LAS FUNCIONES USADAS void delay_ms(uint16_t); void escucharMensaje(void); void i2cInit(void); void busWrite (uint8_t data, int direc); unsigned char busRead (int direc); void IdleI2C(); void StartI2C(); void repStartI2C(); void WriteI2C(unsigned char d); char ReadI2C(); void Ack(); void NotAckI2C(); void StopI2C(); void imprimirValorInt (int valor); uint8_t bcd2bin(uint8_t bcd); uint8_t bin2bcd(uint8_t bin); void cambiarHora (uint8_t horaCambio, uint8_t minCambio, uint8_t segCambio); void printHora (); void cambiarFecha (uint8_t diaCambio, uint8_t mesCambio, uint8_t anoCambio, uint8_t dsemanaCambio); void printFecha(); uint8_t hora; uint8_t minuto; uint8_t segundo; uint8_t dia; uint8_t mes; uint8_t ano; uint8_t diaSemana; void main(void) { USART_Initialize(); // Inicializamos el puerto Serie i2cInit(); // Inicializamos la comunicacion I2C delay_ms(500); while (true) { printf("Cambiar hora y fecha \n"); cambiarHora (23, 59, 00); cambiarFecha (31, 12, 17, 7); while (true) { printHora (); if (segundo == 0) printFecha (); delay_ms(1000); } } } /****************************************************************************** * FUNCION CAMBIAR HORA * * - Funcion que permite cambiar la hora del DS1307 * - Recibe como parametros la hora, minutos y segundos a cambiar *****************************************************************************/ void cambiarHora (uint8_t horaCambio, uint8_t minCambio, uint8_t segCambio) { horaCambio = (bin2bcd(horaCambio)& 0x7F); //Para habilitar el RTC bit 7 = 0 minCambio = bin2bcd(minCambio); segCambio = bin2bcd(segCambio); busWrite (horaCambio, 0x02); delay_ms(50); busWrite (minCambio, 0x01); delay_ms(50); busWrite (segCambio, 0x00); delay_ms(50); } /****************************************************************************** * FUNCION QUE IMPRIME LA HORA * * - Funcion que permite consulta la hora del DS1307 y la imprime por la uart *****************************************************************************/ void printHora () { hora = bcd2bin(busRead (0x02)); minuto = bcd2bin(busRead (0x01)); segundo = bcd2bin(busRead (0x00)); printf("Hora: "); imprimirValorInt (hora); printf(":"); imprimirValorInt (minuto); printf(":"); imprimirValorInt (segundo); printf("\n"); } /****************************************************************************** * FUNCION CAMBIAR FECHA * * - Funcion que permite cambiar la fecha del DS1307 * - Recibe como parametros el dia, mes, ano y dia de la semana *****************************************************************************/ void cambiarFecha (uint8_t diaCambio, uint8_t mesCambio, uint8_t anoCambio, uint8_t dsemanaCambio) { dsemanaCambio = bin2bcd(dsemanaCambio); //Para habilitar el RTC bit 7 = 0 diaCambio = bin2bcd(diaCambio); mesCambio = bin2bcd(mesCambio); anoCambio = bin2bcd(anoCambio); busWrite (dsemanaCambio, 0x03); delay_ms(50); busWrite (diaCambio, 0x04); delay_ms(50); busWrite (mesCambio, 0x05); delay_ms(50); busWrite (anoCambio, 0x06); delay_ms(50); } /****************************************************************************** * FUNCION QUE IMPRIME LA FECHA * * - Funcion que permite consulta la fecha del DS1307 y la imprime por la uart *****************************************************************************/ void printFecha () { diaSemana = bcd2bin(busRead (0x03)); dia = bcd2bin(busRead (0x04)); mes = bcd2bin(busRead (0x05)); ano = bcd2bin(busRead (0x06)); printf("Fecha: "); imprimirValorInt (dia); printf("/"); imprimirValorInt (mes); printf("/"); imprimirValorInt (ano); printf(" "); imprimirValorInt (diaSemana); printf("\n"); } /****************************************************************************** * FUNCION IMPRIMIR INT * * - Funcion que permite imprimir un int por la uart *****************************************************************************/ void imprimirValorInt (int valor) { unsigned char intString[4]; itoa( intString, valor,10); printf(intString); } /****************************************************************************** * FUNCION CONVERSION A DECIMAL * * - Funcion que convierte un valor que representa 2 digitos * (4 bits por digito) a un valor decimal *****************************************************************************/ uint8_t bcd2bin(uint8_t bcd) { return (bcd / 16 * 10) + (bcd % 16); } /****************************************************************************** * FUNCION CONVERSION A BCD * * - Funcion que convierte un decimal en 2 dígitos independientes - los * primero 4 bits representan un dígito y los otros 4 el otro *****************************************************************************/ uint8_t bin2bcd(uint8_t bin) { return (bin / 10 * 16) + (bin % 10); } /****************************************************************************** * FUNCION RETARDO DE mS * * - Funcion que realiza un retardo en mS segun el valor que recibe *****************************************************************************/ void delay_ms(uint16_t i) { for ( uint16_t x=0; x Fosc = 48MHz SSPSTAT = 0; //Ponemos a 0 este registro } /****************************************************************************** * FUNCION ENCARGADA DE ESCRIBIR REGISTROS EN EL SLAVE * * - Funcion realiza la secuencia necesaria para escribir registros en el * SLAVE. * - Recibe como parametros el dato y la direccion del registro a escribir *****************************************************************************/ void busWrite (uint8_t data, int direc) { StartI2C(); // Enviamos la condicion de inicio WriteI2C(((SLAVE_I2C_ADDRESS <<1) & 0xFE)); // Indicamos con que Slave nos queremos comunicar // Indicamos que vamos a escribirle WriteI2C(direc); // Indicamos el registro del Slave al que queremos apuntar WriteI2C(data); // Enviamos el dato a escribir en dicho registro StopI2C(); // Enviamos la condicion de fin } /****************************************************************************** * FUNCION ENCARGADA DE LEER REGISTROS EN EL SLAVE * * - Funcion realiza la secuencia necesaria para leer registros en el * SLAVE. * - Recibe como parametro la direccion del registro a leer *****************************************************************************/ char busRead (int direc) { unsigned char dataOut = 0; StartI2C(); // Enviamos la condicion de inicio WriteI2C(((SLAVE_I2C_ADDRESS <<1) & 0xFE)); // Indicamos con que Slave nos queremos comunicar // Indicamos que vamos a escribirle WriteI2C(direc); // Indicamos el registro del Slave al que queremos apuntar StartI2C(); // Volvemos a enviar la condicion de inicio WriteI2C(((SLAVE_I2C_ADDRESS <<1) | 0x01)); // Indicamos con que Slave nos queremos comunicar // Indicamos que vamos a leer dataOut = ReadI2C(); // Recogemos el dato del registro StopI2C(); // Enviamos la condicion de fin return dataOut; } /****************************************************************************** * FUNCION ENCARGADA DE COMPROBAR QUE NO ESTA OCUPADO EL BUS * * - Funcion que realiza una espera hasta que se puede usar el medio *****************************************************************************/ void IdleI2C() { while ((SSPSTAT & 0x04) || (0x1F & SSPCON2)); } /****************************************************************************** * FUNCION ENCARGADA DE ENVIAR UNA CONDICION DE INICIO * * - Funcion que indica que se va a transmitir por el bus *****************************************************************************/ void StartI2C() // Funcion que envia la condicion de inicio { IdleI2C(); // Esperamos el momento para transmitir SEN = 1; // SSPCON2 bit0. Envía la condición de Inicio while (SSPCON2bits.SEN); // Esperamos hasta que termine el Inicio } /****************************************************************************** * FUNCION ENCARGADA DE ENVIAR UNA CONDICION DE REINICIO * * - Funcion que indica que se continua con la transmision por el bus * Esta funcion se usa cuando el Master no quiere enviar una condicion de Fin * para no perder el bus ante otro Master. *****************************************************************************/ void repStartI2C() { IdleI2C(); // Esperamos el momento para transmitir RSEN=1; // SSPCON2 bit1. Envía la condición Reinicio while (SSPCON2bits.RSEN); // Esperamos hasta que termine el Reinicio } /****************************************************************************** * FUNCION ENCARGADA DE ENVIAR DATOS AL BUS I2C * * - Funcion que escribe en el buffer de salida *****************************************************************************/ void WriteI2C(unsigned char d) { IdleI2C(); // Esperamos el momento para transmitir SSPBUF = d; // Copiamos el dato al buffer } /****************************************************************************** * FUNCION ENCARGADA DE LEER DATOS DEL BUS I2C * * - Funcion que escribe en el buffer de salida *****************************************************************************/ uint8_t ReadI2C() { uint8_t a; IdleI2C(); // Esperamos el momento para transmitir RCEN = 1; // Habilitamos la recepcion I2C IdleI2C(); // Esperamos el momento para transmitir a = SSPBUF; // Copiamos el contenido del buffer IdleI2C(); // Esperamos el momento para transmitir NotAckI2C(); // Indicamos al Slave que hemos recogido el dato return a; // Devuelve el valor recibido } /****************************************************************************** * FUNCION ENCARGADA DE INDICAR QUE NO HUBO PROBLEMAS EN LA COMUNICACION * * - Funcion que responde indicando que los datos se han transmitido correctamente *****************************************************************************/ void Ack() { ACKDT = 0; //Indica que esperamos respuesta ACKEN = 1; //Enviamos ACK } /****************************************************************************** * FUNCION ENCARGADA DE INDICAR QUE HUBO PROBLEMAS EN LA COMUNICACION * * - Funcion que responde indicando que los datos no se han trasmitido correctamente *****************************************************************************/ void NotAckI2C() { ACKDT = 1; //Indica que no esperamos respuesta ACKEN = 1; //Enviamos ACK while (ACKEN); //Esperamos hasta que el Esclavo lo reciba SSPIF = 0; //Borramos la bandera de recepción } /****************************************************************************** * FUNCION ENCARGADA DE ENVIAR UNA CONDICION DE FIN * * - Funcion que indica que se deja el bus libre *****************************************************************************/ void StopI2C() { IdleI2C(); // Esperamos el momento para transmitir PEN = 1; // Habilitamos el bit de Stop while (SSPCON2bits.PEN); // Esperamos hasta que la condicion de Stop finalice }
Nota: La función printf ("%d", valor) debería imprimir un entero,
pero no funciona. Por eso hay una función para imprimir enteros; lo que hace es convertirlos en array de caracteres y
luego los imprime con printf.
Tendremos que investigar un poco más sobre la función printf.
- Consultando hora y fecha:
6. Descargas: