Vous êtes sur la page 1sur 13

Simple FAT y SD Tutorial Parte 4

En las partes anteriores de este tutorial , hemos construido tanto una biblioteca FAT como
un programa de prueba para comunicarnos con la tarjeta SD . Ahora es el momento de terminar
con las partes finales del código para leer un archivo de la tarjeta SD e imprimirlo.
Esta parte utilizará la biblioteca FAT previamente hecha con algunos ajustes: resultó que
algunos bytes y valores de tamaño de palabra necesitaban conversión explícita para unsigned
longque los cálculos funcionaran como debían. La nueva biblioteca y todos los códigos que se
muestran aquí se pueden encontrar desde el zip del proyecto actualizado .
Inicializando la tarjeta SD automáticamente
En lugar de presionar manualmente 1, 2 y 3, ahora escribiremos una sola función para
inicializar la tarjeta SD al modo SPI. Para las tarjetas SD estándar (no de alta capacidad
SDHC), es suficiente para:
1. Enviar pulsos de reloj para 80 ciclos ("leer" 10 bytes)
2. Enviar comando 0x40 (debe devolver 1)
3. Enviar el comando 0x41 hasta que devuelva 0 (devuelve 1 mientras la tarjeta está ocupada)
4. Enviar el comando 0x50 para establecer el tamaño del bloque de lectura (así como el tamaño
del bloque de escritura)
Aquí está el código para hacer exactamente eso (sd_sector y sd_pos se usarán en breve):

unsigned long sd_sector ; unsigned short sd_pos ; char SD_init () { char i ; //] r:
10 CS_DISABLE (); para ( i = 0 ; i < 10 ; i ++) // inactivo para 1 bytes / 80
relojes SPI_write ( 0xFF ); // [0x40 0x00 0x00 0x00 0x00 0x95 r: 8] hasta que obtengamos
"1" para ( i = 0 ; i < 10 && SD_command
( 0x40 , 0x00000000 , 0x95 , 8 ) ! = 1 ; i ++) _delay_ms ( 100 ); if ( i == 10 ) // la
tarjeta no respondió a la inicialización return - 1 ; // CMD1 hasta que la tarjeta salga de
inactividad, pero un máximo de 10 veces para ( i = 0 ; i < 10 && SD_command ( 0x41 ,
0x00000000 , 0xFF ,

8 ) ! = 0 ; i ++) _delay_ms ( 100 ); si ( i == 10 ) // la tarjeta no salió de la devolución


inactiva - 2 ; // SET_BLOCKLEN a 512 SD_command ( 0x50 , 0x00000200 , 0xFF , 8
); sd_sector = sd_pos = 0 ; return 0 ; }

Lectura de datos de la tarjeta SD


Leer datos de tarjetas SD (además de escribirlos) se realiza en bloques discretos. Al comando
de lectura se le asigna el desplazamiento de los datos a leer, y devuelve X bytes, donde X es el
tamaño del bloque de transferencia establecido durante la inicialización. Para las tarjetas SD
normales, el tamaño del bloque puede ser menor que el 512 utilizado en el código de
inicialización, pero para las tarjetas SDHC y SDXC de mayor capacidad el tamaño del bloque
es siempre de 512 bytes, así que decidí usarlo para que hubiera menos cambios de código en
caso de que quisiera soportar tarjetas SDHC más adelante.
Como quería admitir sistemas que solo tienen 128 o 256 bytes de SRAM (si recuerda de partes
anteriores, la biblioteca FAT usa un búfer de 32 bytes), no es posible leer todos los 512 bytes
en la memoria. En cambio, capturamos una pequeña ventana de los datos. Por ejemplo, para
leer los bytes 32-63 (indexación basada en cero, por lo que 32 bytes comenzando desde el 33º
byte) desde un sector de 512 bytes, emitimos un comando de lectura para todo el sector, pero
descartamos los primeros 32 bytes, luego capturamos el siguiente 32 bytes, y luego omita los
448 bytes restantes más 2 bytes CRC. Aquí está el comando de lectura:
// TODO: Esta función no se cerrará correctamente si la tarjeta SD no hace lo que se debe
anular la SD_read ( sin firmar larga sector , sin firmar corto desplazamiento , sin firmar Char *
búfer , sin firmar corta len ) { unsigned corta i , pos = 0 ; CS_ENABLE (); SPI_write (
0x51 ); SPI_write ( sector >> 15 ); // sector * 512 >> 24
SPI_write ( sector >> 7 ); // sector * 512 >> 16 SPI_write ( sector << 1 ); // sector * 512
>> 8 SPI_write ( 0 ); // sector * 512 SPI_write ( 0xFF ); para ( i = 0 ; i < 10 && SPI_write
( 0xFF ) =! 0x00 ; i ++) {} // esperar a 0 para ( i = 0 ;

i < 10 && SPI_write ( 0xFF ) ! = 0xFE ; i ++) {} // esperar el inicio de datos para ( i = 0 ; i <
offset ; i ++) // "saltar" bytes SPI_write ( 0xFF ); para ( i = 0 ; i < len ; i ++) // leer len
bytes buffer [ i ] = SPI_write

( 0xFF ); for ( i + = offset ; i < 512 ; i ++) // "saltar" nuevamente SPI_write ( 0xFF ); //
omitir suma de comprobación SPI_write ( 0xFF ); SPI_write ( 0xFF ); CS_DISABLE
(); }

El comando de lectura (0x51) es muy parecido al resto de los comandos SD y toma una
dirección de 32 bits: usamos el número de sector y lo multiplicamos por 512
( sector<<9). Tenga en cuenta que las tarjetas SDHC utilizan direcciones de sectores y no de
bytes, por lo que no serían necesarias para SDHC. Después de enviar el comando, la tarjeta SD
responde con "0" para indicar que se recibió el comando, y luego envía 0xFF hasta que los
datos estén listos, en ese punto envía 0xFE, luego los 512 bytes de datos y finalmente 2 bytes
de suma de comprobación. ¡Muy claro!
Proporcionar funciones de disco de la biblioteca
FAT
Como probablemente recuerde, ahora necesitamos proporcionar a la biblioteca FAT dos
funciones para navegar alrededor de la tarjeta SD: fat16_seek()y fat16_read(). Ahora
que tenemos nuestra SD_read()función flexible , solo se trata de mantener dos punteros,
sector SD actual ( sd_sector) y desplazamiento dentro de ese ( sd_offset), que
acabamos de establecer cuando la biblioteca quiere buscar, y pasar a SD_readcuando la
biblioteca FAT quiere leer bytes (e incrementar los punteros luego). Sin más preámbulos, aquí
están las funciones de envoltura:
void fat16_seek ( desplazamiento largo sin signo ) { sd_sector = offset >> 9 ; sd_pos =
desplazamiento & 511 ; } char fat16_read ( bytes char sin signo ) { SD_read ( sd_sector ,
sd_pos , fat16_buffer , bytes ); sd_pos + = ( sin firmar cortos ) bytes ; if ( sd_pos

== 512 ) { sd_pos = 0 ; sd_sector ++; } bytes de retorno ; }

Tenga en cuenta que la función de lectura puede ser muy simple porque sabemos que las
lecturas nunca cruzan los límites del sector: la biblioteca FAT lee 32 bytes a la vez, y 512 es un
múltiplo de eso. Si eso no fuera cierto, la función de lectura necesitaría alguna lógica adicional
para manejarlo.
Envolviéndolo todo
Ahora tenemos todas las funciones de ayuda que necesitamos. Tenemos el UART. Hablamos a
través del SPI. Nosotros ordenamos la tarjeta SD. Entendemos la FAT. Aquí hay una función
principal simple para disfrutar de nuestros logros leyendo un archivo llamado README.TXTy
mostrándolo en UART:
int main ( int argc , char * argv []) { char i , ret ; desplazamiento corto = 0x1B0 ; USARTInit (
64 ); // 20 MHz / (16 * 19200 baudios) - 1 = 64.104x SPI_init (); uwrite_str ( "Iniciar \ r \
n" ); if ( ret = SD_init ()) { uwrite_str ( "SD err:" ); uwrite_hex ( ret );

return - 1 ; } if ( ret = fat16_init ()) { uwrite_str ( "FAT err:" ); uwrite_hex ( ret


); return - 1 ; } if ( ret = fat16_open_file ( "README" , "TXT" )) { uwrite_str ( "Abrir:"
); uwrite_hex ( ret ); return - 1 ; }

while ( fat16_state . file_left ) { ret = fat16_read_file ( FAT16_BUFFER_SIZE ); para (


i = 0 ; i < ret ; i ++) USARTWriteChar ( fat16_buffer [ i ]); } return 0 ; }

Aquí está la salida de nuestro glorioso programa de prueba:

¡Eso es todo! Ahora puede leer tarjetas SD con su ATmega. Y lo mejor de todo es que también
debes entender cada rincón y cada grieta del código utilizado para hacer eso. ¡Muy genial! Si
tiene un ATmega con 1 kB de SRAM, puede implementar fácilmente la escritura en SD, o
escribir algún buen código para navegar por los directorios de la tarjeta SD. O tal vez le
gustaría actualizar la biblioteca para admitir tarjetas FAT32 o SDHC. ¡La decisión es tuya!
Gracias por leer el tutorial, espero que lo haya encontrado útil. Recuerde suscribirse al feed
para obtener más tutoriales y proyectos interesantes en el futuro. Y, por supuesto, ¡todos los
comentarios son bienvenidos también!

PUBLICADO POR

Joonas Pihlajamaa
Codificación desde 1990 en Basic, C / C ++, Perl, Java, PHP, Ruby y Python, por nombrar algunos. También está
interesado en matemáticas, películas, anime y ocasionalmente slashdot de vez en cuando. Ah, y también tengo una vida
real, ¡pero no hablemos de eso! Ver todos los mensajes de Joonas Pihlajamaa
Publicado en27 de abril de 2012AutorJoonas
PihlajamaaCategoríasElectrónica Etiquetasatmega88 , fat ,sd , sdhc , tutorial , uart
33 pensamientos sobre "Simple FAT y SD Tutorial Parte 4"

1. Pingback: Análisis lógico con Bus Pirate »Código y vida

2. Jackdice:
17 de mayo de 2012 a las 20:07

Hola: parece que falta un enlace a la Parte 2. (Lo encontré de todos modos, pero
presumiblemente
una caza furtiva no es lo que pretendías ...
Cosas útiles: gracias por aguantarlo.
Jack
RESPUESTA

1. jokkebkdice:
17 de mayo de 2012 a las 22:57

Hmm, hay un enlace a la parte 2 desde el principio en este artículo, aunque un poco
escondido. Sería bueno tener algún tipo de "tabla de contenidos" automática para el conjunto
completo de tutoriales, pero hasta ahora acabo de vincular cada publicación con la parte
anterior.
De todos modos, ahora que me lo recordó, también agregaré enlaces a la siguiente parte hasta el
final de cada parte.
RESPUESTA

3. Pingback: el analizador lógico más simple del mundo por $ 5 »Código y vida

4. David Cookdice:
28 de mayo de 2012 a las 07:38

Muchas gracias, Sr. Pihlajamaa, por este maravilloso tutorial.


Les ahorras a los principiantes mucho tiempo al ayudarlos a superar los puntos de fricción.
Dave Cook
RESPUESTA

5. Stephendice:
27 de septiembre de 2012 a las 09:11

buena leccion
RESPUESTA

6. nyoman yudidice:
12 de diciembre de 2012 a las 03:23

Gracias ... finalmente lo hice funcionar después de 2 días


modifico tu ejemplo e hice attiny2313 play wav
el video está aquí http://www.youtube.com/watch?v=N-ZO4oiwpXY
Una vez más, muchas gracias
RESPUESTA

1. jokkebkdice:
27 de diciembre de 2012 a las 15:27

De nada. ¡Qué bueno ver este proyecto! Todavía no he intentado reproducir ningún audio con
proyectos de MCU, su ejemplo me da ganas de probarlo. :)
RESPUESTA

7. Gokaydice:
31 de diciembre de 2012 a las 22:45
Hola, es un tutorial muy fructífero. En mi opinión, tengo un problema con la escritura de un
solo bloque (512 bytes) de datos en la tarjeta SD. Inicialicé la tarjeta SD (CMD0,0x95)
(CMD1,0xFF) y configuré la longitud del bloque ( CMD16,0xff) con éxito.
A continuación puede ver el resultado.
CMD 40 FF 01 FF FF FF FF FF FF FF
CMD 41 FC 07 FF FF FF FF FF FF FF
CMD 41 FC 07 FF FF FF FF FF FF FF
CMD 41 FF FF FF FF FF FF FF FF
CMD 41 FF 00 FF FF FF FF FF FF FF
CMD 50 FF 00 FF FF FF FF FF FF FF
El problema es que cuando envío el comando 'Single Block Write' a la tarjeta SD recibo una
respuesta 0x00. ¡Sé que está bien! Después de enviar datos token que son 0xFE y enviar 512
bytes de datos y enviar dos bytes CRC
consecutivamente. No puedo obtener ninguna respuesta si los datos son aceptados o no.
A continuación, puede ver el resultado.
CMD 58 FF 00 FF FF FF FF FF FF FF.
Y a continuación puedes encontrar mi código abreviado.
respuesta = send_SDCommand (WRITE_SINGLE_BLOCK, startBlock << 9,0xFF);
if (respuesta! = 0)
{
respuesta de retorno;
}

SD_CS_ASSERT (); // CS Low


// ???????????? lo que está pasando aquí
SPI_transmit (0xFE); token de datos tenemos que enviarlo antes
para (i = 0; i <512; i ++)
SPI_transmit ( 'A');
SPI_transmit (0xFF); // \
SPI_transmit (0xFF); // \ 16 bit dummy CRC
// Cada bloque de datos escrito en la tarjeta es reconocido por un token de respuesta de
datos. Es un byte largo y tiene el siguiente formato
// XXX0SSS1 // S = STATUS bits (3)
// si S es 010 = Datos aceptados.
// si S's 101'-Data rechazado debido a un error CRC.
respuesta = SPI_transmit (0xFF); // 110'-Datos rechazados debido a un error de escritura.
if ((respuesta & 0x1F)! = 0x05)
{
SD_CS_DEASSERT ();
rs232_Stream ("0x05 errorrrrrr ...");
return 1;
}

Gracias !
RESPUESTA

1. jokkebkdice:
20 de abril de 2013 a las 17:05

Parece que has hecho un buen trabajo en la parte escrita, ¡felicidades por
eso! Lamentablemente, no he intentado escribir tarjetas SD, así que no puedo ayudarte
mucho. ¿Tal vez algún foro de microcontroladores tendría personas que podrían ayudar?
Lo único que me viene a la mente es que si quieres enviar pulsos de reloj a la tarjeta para que
pueda procesarlos, tal vez si envías datos falsos después de escribirlos, obtienes una respuesta
después de un tiempo. ? Ha pasado un año desde que jugué con SD y SPI, así que estoy muy
mal en esta, sin embargo ...
RESPUESTA

8. Ganeshdice:
9 de enero de 2013 a las 17:40

gracias un trabajo muy útil


RESPUESTA

9. Ahmed Eldamarawydice:
30 de abril de 2013 a las 02:32

gracias por la presentación agradable y organizada que hizo para todos nosotros.
Espero que también puedan explicar cómo escribir en FAT16 SD.
RESPUESTA

1. jokkebkdice:
9 de junio de 2013 a las 15:49

¡Gracias! Probablemente escribiré una publicación sobre escritura FAT en los meses de verano,
estad atentos. :)
RESPUESTA

10. andrazdice:
25 de agosto de 2014 a las 19:15

Hola. Gracias por este tutorial, ha ayudado mucho. Tengo un problema sin embargo. En fuction
sd_init, la oración para (i = 0; i <10 && SD_command (0x40, 0x00000000, 0x95, 8)! = 1; i
++)
no se compila ya que SD_command es nulo. Tenemos R1 escrito en buffer. Siempre obtengo la
respuesta: FF01FFFFFFFFFFFF ¿Es correcto y qué función debería devolver SD_command?
Me disculpo si sueno estúpido, pero soy muy nuevo en esto ...
RESPUESTA
1. Joonas Pihlajamaadice:
25 de agosto de 2014 a las 20:53

No hay problema. El único problema es que en 30 meses, también he olvidado todo sobre
esto. El comando SD_ está cubierto en la parte previa del tutorial, es posible que desee probarlo
primero, la parte 3 también cubre al menos parcialmente el supuesto resultado.
RESPUESTA

1. andrazdice:
26 de agosto de 2014 a las 12:24

Entiendo. He leído la parte anterior y SD_command está declarado como vacío por lo que no
devuelve nada.
RESPUESTA

1. Joonas Pihlajamaadice:
26 de agosto de 2014 a las 12:56

Ah, lo siento, mi mal. El archivo fat88.c en el zip del proyecto actualizado (enlace desde el
principio en esta parte 4) contiene una versión que devuelve char sin signo.
RESPUESTA

1. andrazdice:
27 de agosto de 2014 a las 19:28

Vale genial. Gracias.

11. tzdice:
29 de enero de 2015 a las 01:57

También escribí un controlador de tarjeta SD, principalmente para openlog, pero tengo algunas
variaciones. La lib está en https://github.com/tz1/sparkfun/tree/master/fat32lib , pero también
hay un "hiperlog" que agrega una RAM SPI externa a la mezcla, ya que parte de la
especificación de la tarjeta SD es que puede llevar hasta 250mS para hacer la limpieza, por lo
que podría perder datos a velocidades más altas.
RESPUESTA

12. Omkardice:
1 de septiembre de 2015 a las 15:31

Por favor, dame el enlace para la parte 1 y la parte 2


Además, si este código se modifica para pic16 wud, ¿funciona igual?
RESPUESTA

1. Joonas Pihlajamaadice:
1 de septiembre de 2015 a las 19:41

Puede encontrar el enlace a la primera parte al comienzo de este artículo:


http://codeandlife.com/2012/04/02/simple-fat-and-sd-tutorial-part-1/
Al final de la parte 1, hay un enlace a la parte 2, y así sucesivamente. :)
Pic16 debería funcionar igual, a menos que sus voltajes sean diferentes a los de un chip AVR.
RESPUESTA

1. Omkardice:
4 de septiembre de 2015 a las 13:51

Por favor podría decirme las modificaciones para una tarjeta sd fat32
RESPUESTA

1. Joonas Pihlajamaadice:
6 de septiembre de 2015 a las 20:11

Es posible que originalmente haya incluido algunos enlaces a FAT32, pero básicamente tendrá
que leer las especificaciones FAT32 usted mismo y ver cuál es la diferencia, yo mismo no lo he
hecho. Google rápido en "especificación fat32" dio, por ejemplo, esta página:
https://www.pjrc.com/tech/8051/ide/fat32.html
RESPUESTA

13. Omkardice:
5 de septiembre de 2015 a las 14:39

El comando sd_command es una función vacía así que ¿cómo puede devolver cualquier valor?
Usted ha utilizado el valor de retorno de la función sd_command en el ciclo for en la función
sd_init.
Por favor dígame cualquier solución.
Muchas gracias
RESPUESTA

1. Joonas Pihlajamaadice:
6 de septiembre de 2015 a las 20:10

¡Hola! Buena pregunta. Eché un vistazo a mis archivos fuente y hay una versión de
SD_command en fat88.c que de hecho devuelve un valor. Entonces deberías verificarlo. :)
RESPUESTA

1. Omkardice:
9 de septiembre de 2015 a las 14:48

Has usado la función fat16_read en la cual hay una variable fat_buffer pero no está definida en
ningún lado.
Entonces, ¿dónde está definido?
Gracias.
RESPUESTA

14. Pingback: Simple FAT y SD Tutorial Parte 3 | Código y vida


15. Pingback: Picoscope 2208B MSO Review - Código y vida

16. Jdice:
22 de junio de 2017 a las 15:19

Una simple pregunta. Me irrita, tal vez tengo algo mal, pero ¿por qué se define un
"desplazamiento corto = 0x1B0;" en el método principal, que no se utiliza en absoluto?
RESPUESTA

1. Joonas Pihlajamaadice:
23 de junio de 2017 a las 12:58

Probablemente sobrante de alguna iteración anterior de mi código. Puedes (intentar)


ignorarlo. :)
RESPUESTA

17. Vinoddice:
11 de abril de 2018 a las 16:15

Tutorial muy útil para principiantes. Solo estaba buscando este rey de cosas elaboradas. Planeo
hacerlo en un chip MSP430.
Empezaré con las lecturas de lectura RAW y luego cambiaré a FAT32 para que sea legible en
una PC.
Muchas gracias.
Vinod
RESPUESTA
18. Sheza Adice:
28 de junio de 2018 a las 21:16

Hola,
Estoy intentando implementar esto usando un ATmega 2560. Cambié los parámetros relevantes
en fat88.c para hacerlo compatible con mi dispositivo.
Sin embargo, para ejecutar este proyecto como está, ¿qué archivos de su archivo comprimido
usaría? Si los uso todos, mi compilador arroja errores ya que las mismas funciones están
definidas en múltiples archivos / repetidos.
RESPUESTA

1. Joonas Pihlajamaadice:
28 de junio de 2018 a las 23:37

Puede mirar el archivo Makefile dentro del zip, contiene instrucciones sobre qué archivos
componen qué archivo .hex (destinado a compilar el proyecto desde la línea de comandos):
fat88.elf: fat88.o fat16.o.
Esto significa que fat88.elf (archivo intermedio para obtener el .hex) depende de fat88.o y
fat16.o, los cuales la utilidad make puede compilar a partir de los archivos .c
correspondientes. Entonces fat88.hex usa fat88.c y fat16.c.
Del mismo modo, test_spi.hex se puede compilar a partir de test_spi.c y fat16.c
Ha pasado mucho tiempo desde que hice esto, así que no hay garantías si eso funcionará en un
estudio Atmel reciente sin errores, lo siento ...