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.
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).
- En la figura 3 se selecciona el correcto valor del registro TWBR y de los bits de pre-escaler (TWPSX).
- En la figura 4 se crean los prototipos de las funciones necesarias para el manejo del bus.
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.
- 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.
- I2C_Stop envía una condición de stop y espera a que se haya ejecutado.
- 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.
- 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.
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.
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.
- 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.
- 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) .
file rtc.c
- RTC_Init sólo inicializa el bus I2C.
- En la figura 14 se muestran las funciones de lectura/escritura para cualquier registro del DS3231.
- 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.
- 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.
- 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.
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.
Aquí presento el programa.
- En la figura 19 se muestran los includes que necesita el main.
- 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.
- 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.
- 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.
- 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’.
- 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.
Enlaces de programas:
Programa RTC DS3231
HORA+FECHA.exe
I have read the blog and I would like to implement this on a ATmega328P using atmel studio, any ideas or suggestions?
ResponderEliminarIf 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.
EliminarAs 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.
Bruno Ricci. Thank you for answering the question above.
EliminarMuchas gracias por las librerías!!!
ResponderEliminarEstoy 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!
Gracias por tu aporte, lo tendré en cuenta.
EliminarAh!, en caso programes xmega te comparto esto:
https://es.scribd.com/document/364902996/XMEGA-Clases
Gracias por el material, sin dudas lo voy a consultar cuando lo necesite.
EliminarMe 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.
Hola man, sabes como hacer esto con el attiny85?
ResponderEliminare 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