lunes, 28 de marzo de 2016

I2C + RTC DS3231 usando C


En el siguiente post mostraré cómo leer y escribir, mediante I2C, un rtc DS3231 de Maxim integrated.

El DS3231 es un RTC serial con una exactitud muy alta gracias a su oscilador interno de 32KHz que es compensado ante los cambios de temperatura (TCXO u Osciladores de Cristal Compensados por Temperatura). La hoja de datos nos dice que podemos tener una exactitud de ±2ppm que podemos traducir en unos 126 seg por año.
La lectura y escritura de registros del DS3231 se realiza mediante la interfaz I2C a una velocidad de 400KHz.

El ejemplo presentado en este post es para un ATmega32, pero puede ser fácilmente adaptado a otros microcontroladores. 
En este post anterior (I2C con ATmega8) se explica el funcionamiento del bus I2C, aquí sólo mostraré y explicare las librerías I2C y RTC necesarias para la escritura y lectura del DS3231.


I2C ATmega

El ATmega32 cuenta con 4 registros los cuales son mostrados en la figura 1 y que debemos conocer . En ellos se configura la velocidad del bus, se controla el bus, se tiene acceso a los estados del bus y de puede leer/escribir los datos recibidos/enviados.

Figura 1.image


PARTE 1. Files I2C

file i2c.h.  Contiene las definiciones necesarias para configurar el bus I2C.
  • En la figura 2 se calculan los posibles valores del registro TWBR en base a la velocidad del bus I2C (400KHz).

Figura 2.image

  • En la figura 3 se selecciona el correcto valor del registro TWBR y de los bits de pre-escaler (TWPSX).

Figura 3.image

  • En la figura 4 se crean los prototipos de las funciones necesarias para el manejo del bus.

Figura 4.image



file i2c.c  Contiene la implementación de las funciones declaradas en el archivo de cabecera .h.

  • En I2C_Init se configura la velocidad del bus I2C.

Figura 5.image

  • I2C_Start envía una condición de start y espera que el envío se haya realizado. Retorna un 1 si el envío fue satisfactorio.
  • I2C_ReStart envía una nueva condición de start y espera que el envío se haya realizado. Retorna un 1 si el envío fue satisfactorio.

Figura 6.image


  • I2C_Stop envía una condición de stop y espera a que se haya ejecutado.

Figura 7.
image


  • I2C_Write envía el dato hacia el esclavo y retorna un 1 si el envío fue realizado y se ha recibido un ACK.

Figura 8.
image

  • I2C_Read espera que el bus se encuentre habilitado para iniciar una lectura y enviar un ACK o NACK de acuerdo al valor de la variable ACK_NACK.

Figura 9.image



PARTE 2. Files RTC

Antes de empezar con los files rtc.h y rtc.c explicaré brevemente lo que son las estructuras y las uniones.
  • Una estructura es un agrupamiento de variables bajo un nombre común. Estos agrupamientos pueden contener diferentes tipos de datos. Para definir una estructura se hace uso de la palabra reservada “struct”.
La estructura ejemplo contiene 3 miembros de diferente tipo (uint8_t, int16_t y un array int8_t).
La declaración de variable de tipo estructura es como sigue:
struct ejemplo var1: Aquí “var1” es una variable del tipo estructura ejemplo.
struct ejemplo
{
    uint8_t c;
    int16_t i;
    int8_t k[4];
};
Para acceder a los miembros de la estructura se hace uso del operador punto (.).
var1.c; var.i; var1.k[0], var1.k[1], var1.k[2] y var1.k[3].
Algo mas sencillo al momento de crear estructuras es la declaración de un alias usando la palabra cable “typedef”. Con esto evitaremos poner “struct ejemplo” cada ve que se quiera declarar una nueva variable.
Será suficiente colocar “ejemplo var2”.
typedef struct
{
    uint8_t c;
    int16_t i;
    int8_t k[4];
}ejemplo;
Las estructuras también pueden ser punteros y apuntadas por punteros tan igual como las tipos nativos (char, int, etc).  En estos casos se accede  a los miembros mediante el operador fecha (->).
variable: ejemplo var1.puntero: ejemplo *pt= &var1;
uso: pt->c; pt->i; pt->k[0], pt->k[1], pt->k[2] y pt->k[3].


  • Una union es similar a una estructura, pero estas difieren en que cada elemento en las estructuras tiene un espacio de memoria mientras en las uniones todos los elementos ocupan el mismo espacio de memoria. El tamaño de la unión depende del miembro mas grande.
La union mostrada en la parte derecha ocupa sólo 4 bytes.
Después de la asignación los valores quedan distribuidos como en la siguiente figura en donde el byte mas significativo esta último ya que ATmel usa little endian.
                  image
El usuario verá las variables como en la siguiente figura.

             image

Creación:

         typedef union
         {
              uint8_t u8;
              uint16_t u16;
              uint32_t u32;
         }ejemplo;


Declaración:

            ejemplo var1;

Asignación:

           var1.u32 = 0x01234567

Ahora si pasaremos a los archivos rtc.

file rtc.h
  • En la figura 10 se crean las estructuras para hora y fecha. También hay una tercera estructura (RTC_t) que esta conformada por las estructuras Hora_t y Fecha_t.

Figura 10.image

  • En la figura 11 se definen las direcciones de los registros internos de DS3231. También una mascara para filtrar los datos leídos desde el DS3231. Finalmente, y ya que la dirección del DS3231 es 0b1101000, se define DS3231_READ y DS3231_WRITE para las lecturas y escrituras desde y hacia el RTC.

Figura 11.image

  • En la figura 12 se declaran los prototipos de las funciones para leer/escribir el RTC. Las 6 últimas funciones tienen un puntero a estructura(Hora_t, Fecha_t y RTC_t) .

Figura 12.image


file rtc.c
  • RTC_Init sólo inicializa el bus I2C.

Figura 13.image

  • En la figura 14 se muestran las funciones de lectura/escritura para cualquier registro del DS3231.

Figura 14.image

  • RTC_SetHora escribe una nueva hora en el DS3231. La nueva hora proviene de una estructura del tipo Hora_t. Del mismo modo RTC_SetFecha escribe una nueva fecha en el DS3231.

Figura 15.image

  • RTC_GetHora leer la hora del DS3231 y lo guarda en una estructura del tipo Hora_t que es apuntada por el puntero hora. RTC_GetFecha hace una tarea similar con la fecha.

Figura 16.image

  • RTC_GetTime hace uso de las funciones ya mencionadas para leer la hora y la fecha y guardarla en la estructura apuntada por el puntero rtc de tipo RTC_t. RTC_SetTime se encarga de escribir la hora y fecha de manera similar.

Figura 17.image



PARTE 3. main

En el main se configura el uart para enviar/recibir la hora y la fecha hacia/desde un programa hecho en Visual C#.
También se configura una interrupción cada 1 segundo para leer el DS3231.

Figura 18.image


Aquí presento el programa.

  • En la figura 19 se muestran los includes que necesita el main.

Figura 19.image

  • En la figura 20 se muestra la definición de la union TRAMA_t que une una estructura y un array de 10 elementos. Este nuevo tipo de dato (TRAMA_t) servirá para el envío y recepción de información hacia y desde el programa en la PC. En la linea 28 se crea la variable “tx_trama”.
  • En la linea 27 se declaran dos variables(rtc y rx_rtc) del tipo RTC_t. En la variable rtc se almacena la hora y fecha leidas del DS3231 cada 1 segundo. rx_rtc sirve para almacenar la hora y fecha recibidas desde el programa en la PC antes de ser enviadas al DS3231.
  • Las variables update_rtc y send_rtc son actualizadas en la interrupción del uart y sirven para actualizar el DS3231 o enviar la hora y fecha hacia la PC.

Figura 20.image

  • En las lineas 37-40 (Figura 21) se configura el uart.
  • En las lineas 42-44 se configura la interrupción del timer1 cada 1 segundo.
  • En las lineas 48 y 49 se inicializan las variables update_rtc y send_rtc.
  • También se inicializa la variable tx_trama y se activa el bit de interrupción global.

Figura 21.image

  • Sí el programa de la PC envía una nueva actualización para el RTC esta será guardada en rx_rtc (en la interrupción por recepción) y el RTC será actualizado en el while. También se enviará la hora y fecha actual hacia el PC si es requerido.

Figura 22.image

  • En la figura 23 se muestra la interrupción por recepción del uart. Aquí se reciben todos los datos, se hace un chequeo de integridad de los datos y de acuerdo al comando de lectura (‘R’) o escritura (‘W’) se actualizará las variables ‘update_rtc’ y ‘send rtc’.

Figura 23.image

  • Finalmente, en la interrupción del timer1 se lee la hora y fecha del rtc. También se muestran las funciones del lectura y escritura en el uart.

Figura 24.image


Enlaces de programas:

Programa RTC DS3231
HORA+FECHA.exe





7 comentarios:

  1. Dario E. Gómez Heredia3 de octubre de 2017, 21:54

    I have read the blog and I would like to implement this on a ATmega328P using atmel studio, any ideas or suggestions?

    ResponderEliminar
    Respuestas
    1. If you download the "programa rtc DS3231" file in the link, you will get a .7z file which you will have to extract. In there you can find the AVR studio 7 project file, the main.c and "i2c.c" and "i2c.h" files individually. You can copy and modify the code to your application.

      As Atmega32/Atmega8/128/328 families are register compatible (or they mostly are), you can implement the same library without any modification and you should get it to work. Take into account that you will have to check the i2c.h file in order to configure the bus frequency to get an acceptable SCL rate (check your IC datasheet, some can operate at 100KHz maximum, and some others at 400KHz, don't exceed the operating frequency because it will lead to errors or malfunctions).

      Hope being useful, just ask if any doubt.

      Eliminar
    2. Bruno Ricci. Thank you for answering the question above.

      Eliminar
  2. Muchas gracias por las librerías!!!

    Estoy utilizando una memoria EEPROM 24LC256 con el Atmega8, y la verdad que no dispongo de mucho tiempo como para ver la hoja de datos del micro y aprender a usar cada registro. Lo único que saqué fue el delay muerto del ciclo de escritura, una mejora sería hacer polling de la memoria enviándole START+write, y si no responde ACK, es porque sigue escribiendo la EEPROM, entonces el micro podría realizar otras tareas o ir a modo power down.

    Funcionan a la perfección, te agradezco mucho el material y espero sigas contribuyendo al mundo de la electrónica con estos tutoriales.

    Saludos desde Argentina! Gracias nuevamente!

    ResponderEliminar
    Respuestas
    1. Gracias por tu aporte, lo tendré en cuenta.
      Ah!, en caso programes xmega te comparto esto:
      https://es.scribd.com/document/364902996/XMEGA-Clases

      Eliminar
    2. Gracias por el material, sin dudas lo voy a consultar cuando lo necesite.

      Me olvidaba de decir, el método de polling que mencioné sirve para la memoria que estoy utilizando, quizás otros integrados utilicen otro método para hacer notar su estado (disponible u ocupado); algunos, al detectar que se envía su dirección al bus (Start+7bits), ponen la línea SCL (clocK) en estado bajo ellos mismos, de modo que el master "lea" el estado lógico de la misma y notar que cuando quiere poner "1", este sigue en "0" entonces el slave está ocupado. Para interactuar con otros posibles slaves, se debe enviar "STOP" y llamar al próximo esclavo.

      Eliminar
  3. Hola man, sabes como hacer esto con el attiny85?
    e estado mucho tiempo investigando e intentando y no e logrado entender como trabajar con el twi en ese micro, pero si es algo confuso como funciona el USI, aparte de eso solo e encontrado ejemplos para arduino que directamente usan librerias o explican el i2c sin realmente implementarlo a nada mas que solo enviar o solo recibir una sola vez y no a estar cambiando entre enviar y recibir

    ResponderEliminar