viernes, 15 de agosto de 2014

FAT + SD Card + ATmega128



En este post mostraré como usar la librería FatFs ( Generic FAT File System Module) la cual podemos encontrar en el sitio web FatFs.
Para este fin usaremos una tarjeta de prueba con ATmega128, un adaptador para microSD, un RTC DS3231 y una tarjeta microSD Samsung de 16GB clase 10.

Aqui el programa final : Programa

                          SKU146934b                                images
 samsungcards-m


El ATmega128 corre con un cristal de 7.3728MHz.
El RTC es para obtener la fecha y hora al momento de crear y modificar los archivos creados.
El adaptador de microSD se comunica mediante SPI, tiene 6 señales (CS, MISO,MOSI,SCK,VCC y GND).
La tarjeta fue previamente formateada con FAT32.
En las pruebas se obtuvo velocidades de escritura de 148KB/s (más de 1Mb/s).






Los archivos de ejemplo, para varias marcas de microcontroladores, se pueden descargar en este link SAMPLES, y es en base a la carpeta avr_complex que se desarrolló este post.
Primero escribiremos las rutinas I2C para el RTC DS3231.
Aquí se presentan los archivos i2c.h y i2c.c.

 
/*
* i2c.h
*
* Created: 04/08/2014 11:50:27 p.m.
*  Author: Jonathan
*/

#ifndef I2C_H_
#define I2C_H_


#include <avr/io.h>
#include <util/twi.h>


#define        F_SCL     100000UL
#define        TWBRX0     (((F_CPU/F_SCL)-16)/2)
#define        TWBRX1     TWBRX0/4
#define        TWBRX2     TWBRX0/16
#define        TWBRX3     TWBRX0/64


#if        (TWBRX0<=0xFF)
    #define        TWBRX    TWBRX0
    #define        TWPSX    0
#elif    (TWBRX1<=0xFF)
#define        TWBRX    TWBRX1
    #define        TWPSX    1
#elif    (TWBRX2<=0xFF)
    #define        TWBRX    TWBRX2
    #define        TWPSX    2
#elif    (TWBRX3<=0xFF)
    #define        TWBRX    TWBRX3
    #define        TWPSX    3
#else
    #define        TWBRX    0
    #define        TWPSX    0                   
#endif

#define        NACK       0
#define        ACK        1


    void           I2C_Init(void);       
    uint8_t        I2C_Start(void);
    uint8_t        I2C_ReStart(void);
    void           I2C_Stop(void);
    uint8_t        I2C_Write(uint8_t );
    uint8_t        I2C_Read(uint8_t );   

#endif /* I2C_H_ */

/*
* i2c.c
*
* Created: 04/08/2014 11:52:16 p.m.
*  Author: Jonathan
*/


#include <stdio.h>
#include "i2c.h"


void    I2C_Init(void)
{
    TWBR = TWBRX;
    TWSR = TWPSX;
    TWAR = 0x00;
    TWCR = (1<<TWEN);
    DDRD &=~(1<<PIND0 | 1<<PIND1);
    PORTD |= 1<<PIND0 | 1<<PIND1;
}


uint8_t I2C_Start( void )
{
    TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
   
    while (!(TWCR & (1<<TWINT))){}
   
    if ((TWSR & 0xF8) != TW_START)
        return 0;
   
    return 1;
}


uint8_t I2C_ReStart( void )
{
    TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
   
    while (!(TWCR & (1<<TWINT)));
if ((TWSR & 0xF8) != TW_REP_START)
        return 0;
   
    return 1;
}


void    I2C_Stop( void )
{
    TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
   
    while(TWCR & (1<<TWSTO));
}


uint8_t I2C_Write( uint8_t data)
{
    TWDR = data;   
   
    TWCR = (1<<TWINT)|(1<<TWEN) ;
   
    while (!(TWCR & (1<<TWINT)));
   
    if ((TWSR & 0xF8) != TW_MT_SLA_ACK)
        return 0;
   
    return 1;
}


uint8_t I2C_Read( uint8_t ACK_NACK)
{
    while (!(TWCR & (1<<TWINT)));
   
    TWCR = (ACK_NACK)?((1<<TWINT)|(1<<TWEN)|
                      (1<<TWEA)):((1<<TWINT)|(1<<TWEN));
   
    while (!(TWCR & (1<<TWINT))){}
   
    return    TWDR;
}



Ahora los archivos DS3231.h y DS3231.c.

/*
* DS3231.h
* Created: 05/08/2014 12:03:19 a.m.
*  Author: Jonathan
*/


#ifndef DS3231_H_
#define DS3231_H_


#include "I2C.h"
#include "integer.h"


typedef struct {
    WORD    year;    /* 2000..2099 */
    BYTE    month;   /* 1..12 */
    BYTE    mday;    /* 1.. 31 */
    BYTE    wday;    /* 1..7 */
    BYTE    hour;    /* 0..23 */
    BYTE    min;     /* 0..59 */
    BYTE    sec;     /* 0..59 */
} RTC_t;


#define        DS3231_SECONDS         0x00       
#define        DS3231_MINUTES         0x01
#define        DS3231_HOURS           0x02
#define        DS3231_WEEKDAYS        0x03
#define        DS3231_DAYS            0x04
#define        DS3231_MONTHS           0x05
#define        DS3231_YEARS            0x06


#define        MASK_SEC            0B01111111
#define        MASK_MIN            0B01111111
#define        MASK_HORA           0B00111111
#define        MASK_DIA            0B00111111
#define        MASK_MES            0B00011111
#define        MASK_ANIO           0B11111111


#define        DS3231_READ         0xD1
#define        DS3231_WRITE        0xD0


#define   BCD2BIN(val) (((val)&0x0F)+((val)>>4)*10)
#define   BIN2BCD(val) ((((val)/10)<<4)+(val)%10)


void      DS3231_Init();
void      RTC_GetTime(RTC_t *);
void      RTC_SetTime(RTC_t *);   
void      DS3231_SetHora (uint8_t *);
void      DS3231_GetHora (uint8_t *);
void      DS3231_SetFecha(uint8_t *);
void      DS3231_GetFech(uint8_t );               
uint8_t   DS3231_GetReg(uint8_t );
void      DS3231_SetReg(uint8_t , uint8_t );


#endif /* DS3231_H_ */

/*
* DS3231.c
*
* Created: 05/08/2014 12:16:47 a.m.
*  Author: Jonathan
*/ 


#include <util/delay.h>
#include "DS3231.h"


void DS3231_Init()
{
    I2C_Init();   
}


void DS3231_SetHora (uint8_t *data)
{
    I2C_Start();
    I2C_Write(DS3231_WRITE);
    I2C_Write(DS3231_SECONDS);
    I2C_Write(data[2]);
    I2C_Write(data[1]);
    I2C_Write(data[0]);
    I2C_Stop();       
}
   
void DS3231_GetHora (uint8_t *data)
{
    I2C_Start();
    I2C_Write(DS3231_WRITE);
    I2C_Write(DS3231_SECONDS);
    I2C_ReStart();
    I2C_Write(DS3231_READ);
    data[2] =  (I2C_Read(ACK))& MASK_SEC;
    data[1] =  (I2C_Read(ACK))& MASK_MIN;
    data[0] =  (I2C_Read(NACK))& MASK_HORA;
    I2C_Stop();
}


void DS3231_SetFecha(uint8_t *data)
{
    I2C_Start();
    I2C_Write(DS3231_WRITE);
    I2C_Write(DS3231_DAYS);
    I2C_Write(data[0]);
    I2C_Write(data[1]);
    I2C_Write(data[2]);
    I2C_Stop();
}
   
void DS3231_GetFecha(uint8_t *data)
{
    I2C_Start();
    I2C_Write(DS3231_WRITE);
    I2C_Write(DS3231_DAYS);
    I2C_ReStart();
    I2C_Write(DS3231_READ);
    data[0] =  (I2C_Read(ACK)) & MASK_DIA;
    data[1] =  (I2C_Read(ACK)) & MASK_MES;
    data[2] =  (I2C_Read(NACK)) & MASK_ANIO;
    I2C_Stop();
}


uint8_t DS3231_GetReg(uint8_t address )
{
    uint8_t    ret;
    I2C_Start();
    I2C_Write(DS3231_WRITE);
    I2C_Write(address);
    I2C_ReStart();
    I2C_Write(DS3231_READ);
    ret = I2C_Read(NACK);
    I2C_Stop();   
    return ret;
}


void DS3231_SetReg( uint8_t address, uint8_t val )
{
    I2C_Start();
    I2C_Write(DS3231_WRITE);
    I2C_Write(address);
    I2C_Write(val);
    I2C_Stop();
    _delay_ms(1);
}


void RTC_SetTime( RTC_t *rtc )
{
    I2C_Start();
    I2C_Write(DS3231_WRITE);
    I2C_Write(DS3231_SECONDS);
    I2C_Write(BIN2BCD(rtc->sec));
    I2C_Write(BIN2BCD(rtc->min));
    I2C_Write(BIN2BCD(rtc->hour));
    I2C_Write(BIN2BCD(rtc->wday));
    I2C_Write(BIN2BCD(rtc->mday));
    I2C_Write(BIN2BCD(rtc->month));
    I2C_Write(BIN2BCD(rtc->year));
    I2C_Stop();
}


void RTC_GetTime( RTC_t *rtc )
{
    uint8_t buf[7];
   
    DS3231_GetHora(&buf[0]);
    DS3231_GetFecha(&buf[4]);
   
    rtc->sec = (buf[2]&0x0F) + ((buf[2]>>4)&7)*10;
    rtc->min = (buf[1]&0x0F) + (buf[1]>>4)*10;
    rtc->hour = (buf[0]&0x0F) + ((buf[0]>>4)&3)*10;
    rtc->wday = (buf[3]&0x07);
    rtc->mday = (buf[4]&0x0F) + ((buf[4]>>4)&3)*10;
    rtc->month = (buf[5]&0x0F) + ((buf[5]>>4)&1)*10;
    rtc->year = 2000+(buf[6]&0x0F) + (buf[6]>>4)*10;
}

Hasta ahora, y con las rutinas escritas, ya podemos comunicarnos con el RTC. Vale mencionar que estas rutinas puedes ser fácilmente modificadas para ser usadas con otros RTC como DS1307 ó  PCF8563. También debemos decir que el RTC no es necesario para el funcionamiento de FatFs, pero en ausencia de éste la hora y fecha de creación y modificación de los archivos generados será nula.

Ahora pasaremos a ver los archivos FatFs. Como dije antes, esto lo haremos usando los archivos de la carpeta “avrcomplex”. Como este ejemplo está hecho para un ATmega64, nosotros podemos usarlo sin mayores cambios para un ATmega128. Si quisiéramos usar este ejemplo con algún otro uC debemos realizar modificaciones en el archivo “mmc_avr.c”, específicamente en las rutinas “Power Control” y “Transmit/Receive data from/to MMC via SPI ”, ya que estas depende del uC usado.

De todos los archivos que se encuentran aquí sólo necesitaremos:

- integer.h
- ccsbcs_avr.c
- mmc_avr.c
- diskio.h
- ffconf.h
- ff.h y ff.c

Adicionalmente los archivos creados i2c.h, i2c.c, DS3231.h y DS3231.c.

En el archivos “ffconf.h” debemos realizar las siguientes modificaciones.

- Poner _FS_TINY a 0.
- _CODE_PAGE a 850.
- _USE_LFN a 2.

Si nuestro adaptador no tiene “power control” debemos eliminar los bloques indicados con “Remove this block if no socket power control” en “power_on” y “power_off” dentro de mmc_avr.
Si no tiene pin “card detect” o “Write Protect” debemos comentar el siguiente código en “disk_timerproc” dentro de mmc_avr.c.

   if (MMC_WP)                /* Write protected */
        s |= STA_PROTECT;
    else                    /* Write enabled */
        s &= ~STA_PROTECT;

    if (MMC_CD)                /* Card inserted */
        s &= ~STA_NODISK;
    else                    /* Socket empty */
        s |= (STA_NODISK | STA_NOINIT);


Con estos cambios estaremos listo para escribir la aplicación principal, para ello escribiremos un mensaje simple en un archivo de texto.

/*
* Prueba_1.c
*
* Created: 04/08/2014 11:49:54 p.m.
*  Author: Jonathan
*/


#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "DS3231.h"
#include "ff.h"
#include "diskio.h"


FATFS fs;
FIL   file;
FRESULT resul;

DRESULT dst;
VUINT Timer;
UINT  temp;       
char setup_file[] = "0:ARCHIVO_0.txt";
char cadena[] = ".......DATOS A ESCRIBIR...";
RTC_t rtc_;


int main(void)
{           
    _delay_ms(1000);
    DS3231_Init();   
       
    OCR2 = (F_CPU/1024/100)-1;
    TCCR2 = 1<<WGM21 | 1<<CS22 | 1<<CS20;
    TIMSK |= 1<<OCIE2;
  

    // 15/08/2014 10:23:00
    //rtc_.mday  = 15;
    //rtc_.month = 8;
    //rtc_.year  = 14;
    //rtc_.hour  = 10;
    //rtc_.min   = 23;
    //rtc_.sec   = 0;
    //RTC_SetTime(&rtc_);   
   
    sei();       
   
    dst =  disk_initialize(0);
    resul = f_mount(&fs,(char const *)setup_file,1);
    resul = f_open(&file,(char const *)setup_file, FA_CREATE_ALWAYS | FA_WRITE);       
    resul = f_write(&file,cadena,sizeof(cadena),&temp);   
    f_close(&file);   
   
    while(1)
   {                              
        RTC_GetTime(&rtc);   
        _delay_ms(1000);
    }
}


//***************************************************************************

ISR(TIMER2_COMP_vect)
{
    Timer++;           
    disk_timerproc();   
}


DWORD get_fattime (void)
{
    RTC_t rtc;

    RTC_GetTime(&rtc);
   
    return      ((DWORD)(rtc.year - 1980) << 25)
                | ((DWORD)rtc.month << 21)
                | ((DWORD)rtc.mday << 16)
                | ((DWORD)rtc.hour << 11)
                | ((DWORD)rtc.min << 5)
                | ((DWORD)rtc.sec >> 1);

}

El código Anterior debería generar un archivo de nombre “ARCHIVO_0.txt” con el texto “.......DATOS A ESCRIBIR...” dentro del mismo.
Si el RTC fue configurado anteriormente a la hora y fecha correctos, podríamos verificar esto en el archivo generado.
Si aún no fue configurado, se puede usar el código comentado para poner la hora y fecha correcta. Luego debemos volver a comentar esa parte del código.
Tener presente que el Timer2 es configurado para generar interrupciones cada 10ms, las cuales son necesarias para la libreria Fat.

Por último escribiremos 1MByte de datos para verificar la velocidad de escritura que podemos alcanzar. Para ello usaremos el Timer0, éste generará interrupciones cada 100us, y en cada interrupción se incrementará la variable “time”. Al iniciar la escritura guardaremos time en t0, y al terminar la escritura obtendremos el tiempo transcurrido en t1 = time – t0, luego calcularemos el tiempo tomado para escribir los 2048*512 bytes y lo escribiremos al final del archivo.

/*
* Prueba_2.c
*
* Created: 04/08/2014 11:49:54 p.m.
*  Author: Jonathan
*/


#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include "DS3231.h"
#include "ff.h"
#include "diskio.h"


FATFS fs;
FIL   file;
FRESULT resul;

DRESULT dst;

VUINT Timer;
UINT temp;       
char setup_file[] = "0:ARCHIVO_0.txt";
RTC_t rtc_;
char    datos[512];   
volatile uint32_t time,t0,t1;   


int main(void)
{           
    _delay_ms(1000);
    DS3231_Init();   
       
    OCR2 = (F_CPU/1024/100)-1;
    TCCR2 = 1<<WGM21 | 1<<CS22 | 1<<CS20;
    TIMSK |= 1<<OCIE2;
   
    OCR0 = (F_CPU/8/10000)-1;
    TCCR0 = 1<<WGM21 | 0<<CS02 | 1<<CS01 | 0<<CS00;
    TIMSK |= 1<<OCIE0;
   
    sei();       
   
    dst =  disk_initialize(0);
    resul = f_mount(&fs,(char const *)setup_file,1);
    resul = f_open(&file,(char const *)setup_file, FA_CREATE_ALWAYS | FA_WRITE);       
   
    for (uint16_t i=0; i<512; i++)
    {   
        datos[i]=i;
    }

    t0 = time;
       
    for (uint16_t i=0; i<2048; i++)                // 2048*512 = 1MB   
    {
        f_write(&file,datos,sizeof(datos),&temp);   
    }
    t1=time - t0;
    float rate = 1024.0*10000.0/t1;
    char rate_s[15];
    sprintf(&rate_s,"\r\n%f KBs",rate);    
    f_write(&file,rate_s,15,&temp);
    f_close(&file);   
   
    while(1)
   {                              
        RTC_GetTime(&rtc);   
        _delay_ms(1000);
    }
}

//******************************************************************************
//******************************************************************************


ISR(TIMER2_COMP_vect)
{
    Timer++;           
    disk_timerproc();   
}


ISR(TIMER0_COMP_vect)
{   
    time++;
}


DWORD get_fattime (void)
{
    RTC_t rtc;

    RTC_GetTime(&rtc);
   
    return      ((DWORD)(rtc.year - 1980) << 25)
    | ((DWORD)rtc.month << 21)
    | ((DWORD)rtc.mday << 16)
    | ((DWORD)rtc.hour << 11)
    | ((DWORD)rtc.min << 5)
    | ((DWORD)rtc.sec >> 1);

}


El resultado final se vera así:

archivo


Aquí vemos que la velocidad de escritura fue de 148KB/s.

Espero que esto sea de su ayuda. Hasta la próxima.

Saludos.

11 comentarios:

  1. para los atxmega nesecitamos un programador que soporte PDI, tal vez tengas algún material para hacer un programador para los Xmega

    ResponderEliminar
    Respuestas
    1. Estimado.
      No tengo lo que necesitas, lo siento. Yo personalmente uso mi JTAGICE3. Por mi parte te recomendaria comprar algo similar, si ya estas programando XMEGA seria bueno invertir en un depurador mas PRO.

      Saludos

      Eliminar
  2. Hola, gracias Jonathan por el tutorial, estaba intentando modificarlo para un atmega328p pero hasta ahora no tuve exito ni siquiera con el primer ejemplo, cambie los pines SCK MOSI MISO y SS en mmc_avr.c, ademas configure el timer, ademas como no dispongo de un RTC deje la hora estatica, alguien lo logro hacer andar para un ATMEGA328p ?

    ResponderEliminar
  3. Que bueno!.. es justo lo que te pensaba recomendar. Situaciones similares me ocurrieron.
    Saludos y buena suerte.

    ResponderEliminar
  4. HOLA, la placa del atmega128 lo vendes?, soy de Perú...

    ResponderEliminar
  5. No has pensado en abrir un curso de programación de estos micros ?.saludos

    ResponderEliminar
  6. Hace algún tiempo me pidieron algo sobre xmega, te lo comparto por si te interesa.
    https://drive.google.com/file/d/0B3OsL7DPo1s0bC16eUFlSG5LM0U/view?usp=drivesdk

    ResponderEliminar
  7. Si algún día me piden curso de ATmega podría.

    ResponderEliminar
  8. realiza publicidad, yo te ayudo a difundirlo por redes sociales y vemos que tal va.

    ResponderEliminar