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





sábado, 12 de marzo de 2016

Primer proyecto con Atmel Studio 7


Para iniciar la programación de uC AVR necesitamos instalar la Plataforma Integrada de Desarrollo (IDP) de ATmel, me refiero al Atmel Studio 7.
Aquí el enlace para la descarga : Atmel Studio 7.0.790
Instalado el software, pasaremos a crear nuestro primer proyecto.
Una vez abierto el software, en la parte izquierda veremos la figura 1 donde debemos dar click en “New Project”.


Figura 1.image


Luego debemos seleccionar el tipo de proyecto y el lenguaje (C, C++, ASM). En este ejemplo usaremos el lenguaje C como lo vemos en la figura 2.


Figura 2.image



Una vez seleccionado el lenguaje, debemos darle un nombre al proyecto, en nuestro caso lo llamaremos “First_project” (Figura 3). El software creará una carpeta con el nombre del proyecto en la ruta “C:\Users\user\Documents\Atmel Studio”.

Figura 3.image


Ahora debemos seleccionar el dispositivo que deseamos usar, en este caso seleccionamos el ATmega32.

Figura 4.image



A la derecha de la misma ventana veremos (figura 4) veremos una lista de las herramientas de depuración y programación que se pueden usar en el microcontrolador seleccionado. También un enlace a la hoja de datos del dispositivo (Figura 5).

Figura 5.image


Luego de dar click en en botón aceptar veremos el archivo main.c tal como en la figura 6. Aquí es donde debemos escribir nuestro programa. 

Figura 6.image



En la figura 7 se muestra la aplicación desarrollada. En esta programa se tendrán dos entradas para dos pulsadores en el puerto B, y dos salidas para dos leds en el puerto D.

  • En 11 se declaran dos variables de tipo unsigned char (uint8_t) para guardar en estado actual y el estado pasado de un pin de entrada.
  • En 15 se ponen los el puerto B como entradas.
  • En 16 activamos las resistencias pull up de los pines PB0 y PB1.
  • En 17 se ponen los pines PD7 y PD3 como salidas para los led.
  • En 20 preguntamos sí el pin PB0 fue presionado
  • En 21 se pondrá a 1 el pin PD7 sí PB0 es 0. El código de la linea 22 es igual al de la linea 21.
  • En 24 se pondr’a a 0 el pin PD7 si PB0 es 1. El código de la linea 25 es igual al de la linea 24.
  • En 27 se guarda el valor anterior del pin PB1.
  • En 28 se guarda el valor actual del pin PB1.
  • En 29 se pregunta sí ocurrió un flanco de subida.
  • En 31 se conmuta, usando un xor (^), el pin PD3. El código de la linea 32 es igual al de la linea 31.

Figura 7.image


En C podemos usar el operador :?, y gracias a esto podemos reducir las lineas 20 a 25 por el código de la figura 8.



Figura 8.image



En la figura 9 se muestra la equivalencia entre un if-else y el operador :?.

Figura 9.image



Luego debemos compilar el programa usando el botón de la figura 10.

Figura 10.image


Finalmente, tendremos como salida el archivo .hex (Figura 11) para poder programar nuestro microcontrolador.

Figura 11.image



Link Proyecto




Si queremos ver la numeración de lineas en nuestro ventana de edición tenemos que ir a Tools>Options (Figura 12).
Figura 12.image


Luego en “All Languages” seleccionamos “Line numbers”  (Figura 13).

Figura 13.image

Espero este post sea útil.
Hasta la próxima.

viernes, 11 de marzo de 2016

Bootloader USBasp


Ya muchos conocemos el programador USBasp (gracias a Thomas Fischl) y lo bien que funciona este. El USBasp debe ser uno de los programadores mas conocidos para descargar código a un ATmega, esto gracias a que usa un muy económico ATmega8, que aun no teniendo puerto USB, es capaz de trabajar con una gran cantidad de dispositivos. … y lo mejor de todo: “Es gratis”.
Personalmente lo vengo usando desde el año 2010. Ahora tengo un AVRISPmk2(al parecer ya descontinuado) y un JTAGICE3 (que ya fue remplazado por el Atmel-ICE) y aún en algunas ocasiones uso mi USBasp.

Bueno, es este post mostraré cómo usar el “USBasp“  como bootloader.

Un programador USBasp consta básicamente de un ATmega8 que ha sido programado, en la sección de aplicación de su flash, con un código que permite comunicación USB (low speed 1.5Mbps) con el programador y programar otros uCs. Los anteriormente descrito se muestra en la figura 1, donde la sección de booteo del uC (ATmega8) no es usada.
 
Figura 1.image
 

Pero cuando hablamos de usar el “USBasp” cómo bootloader tendremos un esquema similar a la figura 2. Aquí, el código, “USBasp” , que realizará la comunicación con el programa en la PC (ej. AVRDUDE) es localizado en la sección de booteo de la flash, y es este código el que nos permite poner nuestra aplicación en la sección de aplicación de la flash (usando instrucciones SPM y LPM).
Es decir que no necesitaremos un programador USBasp ya que este lo tendremos integrado en el mismo uC. Lo mejor de todo es que lo podemos poner un diferentes uCs y con diferentes osciladores. Personalmente ya lo he probado con ATmega8 (a 12 y 16MHz), ATmega32(a 12 y 16MHz) y ATmega324P (a 12,16 y 20 MHz).
 
Figura 2.image
 
El hardware necesario los muestro en la figura 3. Aquí se debe tener algunas consideraciones tales como.
  • D+ debe necesariamente ir conectado al pin INT0.
  • D- puede ser cualquier pin del puerto D.
  • El jumper para iniciar el bootloader puede ser cualquier pin.

Figura 3.
image
 

En la figura 4 se muestra el diagrama del flujo del bootloader.
Despues de un reset se inicia en bootloader y este se ejecutara sí:
  • La fuente de reset es externa (MCUCSR & (1 <<  EXTRF)).
  • Si el jumper esta colocado ((PINB & (1 << JUMPER_BIT)) == 0).
En otras palabras, para ejecutar el bootloader hay que colocar el jumper y presionar el boton de reset.
Para salir del bootloader sólo hay que quitar el jumper.
 
    Figura 4.image
     

    Antes de programar el bootoalder debemos debemos modificar los siguientes fuses.
    • En High Fuse debemos seleccionar una de las secciones que tengan como mínimo 1024 words (2048 bytes) ya que el bootloader ocupa más de 1900 bytes. También debemos seleccionar “Boot Reset Enable” para que el programa inicie en la sección de booteo y no en la de aplicación. Para ATmega8 recomiendo 0xC8.
    • En Low Fuses debemos seleccionar alguna de las opciones Ext. Crystal/Resonator High Freq. Para un ATmega8 recomiendo 0xFF.



    Figura 5.
    image
     
    En la figura 6 se muestra el mapa de memoria de la sección de booteo de un ATmega8. Como ya se dijo antes, de esta usaremos mas de 1900 bytes, por ello en los fuses se seleccionará la mayor área.
     
    Figura 6.image

    SOFTWARE


    El código original lo pueden encontrar en el siguiente enlace link.
    Aquí les dejo el proyecto en ATMEL STUDIO 7 USBasp-boot. El proyecto esta configurado para un ATmega8 a 12MHz, pero puede ser cambiado facilmente a otros dispositivos.
    Las modificaciones necesarias para usarlo con otros dispositivos son las siguientes:
    • Sí se quiere usar otras frecuencias de oscilador debemos hacer el cambio en Toolchain>Symbols. Las frecuencia pueden ser de 12,15,16,18 y 20 MHz. En las figuras 7 y 8 se muestran los lugares donde se deben realizar los cambios de frecuencia.



    Figura 7.
    image



    Figura 8.
    image
     
    También debemos decirle al compilador que el código generado debe iniciar en el sección de booteo seleccionada en los High Fuses. En el ejemplo de la figura 5 se selecciono “Boot start address=0x0C00” donde 0xC00 es la dirección, en words, de inicio de la sección de booteo.
    Tenemos dos formas de reasignar el inicio del código:
    En la figura 9 se hace la reasignación usando el inicio de la dirección en words (0x0C00).
    En la figura 10 se hace la reasignación usando el inicio de la dirección en bytes (0x1800).
    Nosotros debemos seleccionar sóla una de las dos formas (Figura 9 o 10).
    Hay que tener en cuenta que 0x0C00 words = 0x1800 bytes.
     
    Figura 9.image
     
    Figura 10.image
     

    En la figura 11 se muestra parte del código generado. Aquí vemos que el inicio es en la dirección 0x1080 (para 2014 bytes). Cuando el código generado es para la sección de aplicación la dirección de inicio es 0x0000.
     
    Figura 11.image
     

    En la figura 12 se indica que el se usará PD3 para conectar D- del USB. Esto lo podemos cambiar a algún otro pin en el mismo puerto.
     
    Figura 12.image
     

    En la figura 13 se indica el pin que se usará para el jumper de inicio del bootloader. Este puede ser cualquier pin que no se esté usando.
     
    Figura 13.image


    En la figura 14 se activa la resistencia pull up correspondiente al pin del jumper.
     
    Figura 14.image
     

    En la figura 15 se evalúa la condición para permanecer o salir del área de booteo.
     
    Figura 15.image


    En los caso de la figura 14 y 15 se pueden usar el pin y el puerto que se deseé.
    Si el ATmega que queremos usar  no se encontrase en la lista de la figura 16 corresponde agregarlo con sus respectivos “SIGNATURE BYTES”.
     
    Figura 16.image
     

    El proyecto es para un ATmega8, cómo cambiamos de dispositivo ?
     
    • Damos click en l icono         image
    • Click en “Change Device…”    image
    • Seleccionamos el nuevo dispositivo.    image
    • Ya habremos cambiado nuestro nuevo dispositivo. image
    • Luego sólo queda compilar y tendremos el .hex del bootloader para poder programarlo.
    Finalmente, y cuando el bootolader se encuentre instalado, podremos usar programas como SinaProg para descargar nuestra aplicación sin afectar el bootloader.
    hay que resaltar que el bootloader no permite hacer cambio de los fuses.
    En resumen:
    1. Preparar el hardware (figura 3).
    2. Con un programador cualquiera cambiar los fuses del uC (figura 5).
    3. Hacer los cambios en el software, sólo si son requeridos: F_CPU (figuras 7 y 8), .text (figura 9 ó 10), D- (figura 12), JUMPER_BIT (figura 13), PORT/PIN JUMPER (figuras 14 y 15).
    4. Agregar un nuevo uC si falta (figura 16).

    Espero que esto les sea de utilidad, ya que no necesitar un programador puede ser muy útil.