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
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í:
Aquí vemos que la velocidad de escritura fue de 148KB/s.
Espero que esto sea de su ayuda. Hasta la próxima.
Saludos.
para los atxmega nesecitamos un programador que soporte PDI, tal vez tengas algún material para hacer un programador para los Xmega
ResponderEliminarEstimado.
EliminarNo 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
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 ?
ResponderEliminarPuse otra tarjeta Sd y ahora si funciona. Gracias!
EliminarQue bueno!.. es justo lo que te pensaba recomendar. Situaciones similares me ocurrieron.
ResponderEliminarSaludos y buena suerte.
HOLA, la placa del atmega128 lo vendes?, soy de Perú...
ResponderEliminarYa no la tengo, la vendí hace mucho.
EliminarNo has pensado en abrir un curso de programación de estos micros ?.saludos
ResponderEliminarHace algún tiempo me pidieron algo sobre xmega, te lo comparto por si te interesa.
ResponderEliminarhttps://drive.google.com/file/d/0B3OsL7DPo1s0bC16eUFlSG5LM0U/view?usp=drivesdk
Si algún día me piden curso de ATmega podría.
ResponderEliminarrealiza publicidad, yo te ayudo a difundirlo por redes sociales y vemos que tal va.
ResponderEliminar