Translate

viernes, 12 de abril de 2013

Tarjetas SD/SDHC (lectura/escritura de datos)


Tras aprender como inicializar una tarjeta SD y obtener información sobre su capacidad, etc, es hora de usarlas para lo que sirven: leer/escribir información.

En esta entrada completaremos nuestra introducción a las comunicaciones de bajo nivel entre un PIC y una tarjeta SD viendo las lecturas/escrituras a los sectores de datos de la tarjeta.  

Como siempre haremos algunos comentarios sobre como optimizar las rutinas para maximizar la velocidad de transferencia de datos.

Recordar que las rutinas que vamos a usar son de bajo nivel, escribiendo directamente sobre los sectores de la tarjeta. No saben nada del tipo de formato, sistema de archivos, etc, por lo que pueden destruir datos en la tarjeta que usemos. He escogido un número alto de sector (5000) para empezar a escribir por lo que posiblemente no estropearán el formato de la tarjeta pero si machacarán los datos de dichos sectores. No obstante, incluso si se estropea el formato siempre se puede volver a reformatear la tarjeta en el PC sin ningún problema. Obviamente debéis que evitar hacer pruebas con una tarjeta con datos de interés. 

Para verificar que las cosas están funcionando es útil contar con un lector de tarjetas para el PC y un software (tipo WINHEX o similar) que permita editar los sectores lógicos de un disco para ver si nuestros programas escriben lo que queremos y donde queremos. 



Código asociado a esta entrada: SPI_sdhc_v2.c18f4520_g.lkr



--------------------------------------------------------------------

Estrategias de buffering de datos en el PIC:

Nuestro objetivo será leer/escribir una gran cantidad de datos entre el PIC y la tarjeta SD. LO primero que hay que decidir es que tipo de estrategia de buffers vamos a usar en el PIC. 

Pensad en una típica aplicación: un datalogger. En el PIC se generan datos (p.e. leyendo un sensor) y se desean volcarse a la tarjeta SD para almacenamiento. Podríamos pensar en que cada vez que tengamos un dato nuevo podríamos grabarlo en la tarjeta. Sin embargo esto sería un grave error, porque la lectura/escritura de la tarjeta está optimizada para un acceso por bloques. De hecho en tarjetas SDHC el acceso por bloques (512 bytes) es el único posible, no siendo posible leer/escribir menos de un bloque.

Nota: en las especificaciones SD un bloque de datos (típicamente 512 bytes) es el conjunto de datos a los que puede accederse en una única operación de lectura/escritura y que se corresponde con lo que solemos llamar un sector de un disco. Sin embargo, la especificación reserva el nombre de sector para un conjunto de bloques (entre 1 y 64 normalmente) que son afectados conjuntamente por una operación de borrado. Esto tiene que ver con las peculiaridades de la memoria tipo flash. En esta entrada usaremos bloque o sector para referirnos al conjunto de bytes que será nuestra unidad de lectura/escritura.

Incluso en tarjetas SDSC donde se permiten accesos a bytes individuales haremos bien en usar un buffer lo más grande posible en el PIC e ir llenándolo con los datos. Cuando el buffer esté lleno se arrancará el proceso de volcado a la tarjeta y se volvera a reusar el buffer para los nuevos datos. Igualmente en las operaciones de lectura leeremos un bloque de datos lo más grande posible y luego iremos "leyendo" los bytes individuales en el PIC.

En cuanto al tamaño del buffer idealmente debería de ser de 512 bytes, el tamaño de un sector típico de las tarjetas SD. Sólo si tenemos problemas de memoria en nuestro dispositivo podríamos hacerlo más pequeño, pero tratando de no bajar de 256 o 128 bytes. 

Si se va a usar un buffer de menos de 512 bytes es preciso avisar a la tarjeta del nuevo tamaño de bloque a usar con el comando CMD_SET_BLOCKLEN (que usamos en la inicialización para fijar el bloque a 512 bytes). 

En nuestro caso vamos a usar un buffer de 512 bytes. Como se comento en la entrada del levitador magnético crear un array que atraviese los bancos de memoria (256 bytes) del PIC requiere usar una función modificada del archivo 18f45209_g.lkr con instrucciones sobre el mapa de memoria para el linker:


DATABANK   NAME=gpr0       START=0x80           END=0xFF
DATABANK   NAME=gpr1       START=0x100         END=0x1FF
DATABANK   NAME=gpr2       START=0x200         END=0x2FF
DATABANK   NAME=big         START=0x300         END=0x4FF     PROTECTED
SECTION      NAME=big  RAM=big


En el código anterior reservamos 2 bancos de memoria (de 0x300 a 0x4FF)
en una sección a la que llamamos big. En el programa principal usamos #pragma udata para posicionar un array de 512 bytes en dicha sección:


#pragma udata big
byte tmp[512];
#pragma udata
byte *data=&tmp[0];



Rutina de lectura de un sector:

Con la rutina read_data_block que escribimos para leer los registro CSD y CID tenemos lo necesario para leer un sector o bloque de datos de la tarjeta, ya que el protocolo es el mismo:

  1. Mandar el comando correspondiente CMD_READ_SECTOR usando como argumento la dirección de la memoria donde iniciar la lectura. 
  2. Hacer una lectura de un bloque de 512 bytes.
       
Un comentario muy importante respecto a la dirección de los datos a leer. En las tarjetas SDSC (típicamente hasta 2 Gb de capacidad) la dirección de la memoria de da en bytes. Sin embargo en las memorias SDHC (>2 Gb) dicha dirección se da en bloques (de 512 bytes).

Como nuestra rutina va a ser de lectura de sectores completos le pasaremos siempre como argumento el número de sector a leer. Internamente dicho numero se multiplica por 512 (caso de SDSC) para tener dirección en bytes o se usa tal cual (SDHC). La decisión puede hacerse con un #define si se sabe previamente que tipo de tarjeta usaremos o con una condición. En este último caso podríamos saber el tipo de tarjeta a través de los registros CSD / OCR.

El código de la rutina de lectura de un sector queda:


byte read_sector_sd(unsigned long s, unsigned char* buf)
{
 byte res;

 #ifdef SDSC
    s<<=9;  // Start address of sector in bytes (sector # x 512)
 #endif

 select_SD;
 res=send_command_R1(CMD_READ_SECTOR,s);
 if(res==0) res=read_data_block(buf,(uint16)512);  // Read data block
 deselect_SD;

 return res;
}


que es casi idéntico al código para leer los registros CSD/CID.  

Si se ha fijado un distinto tamaño de bloque (p.e. 128 bytes) la tarjeta enviará únicamente 128 bytes tras el comando de petición de sector. Para leer todo el bloque (512 bytes) deberemos modificar la rutina anterior para especificar un offset adicional dentro del sector deseado

         address =  (sector<<9 + offset)

Así, si quisieramos leer el sector 37  tendriamos que llamar cuatro veces a la rutina con argumentos (37,0) (37,128) (37,256) y (37,384). Esto sólo sería posible hacerlo con tarjetas de baja capacidad.


Escritura de un sector o bloque de datos:

Nos falta una rutina de escritura. El esquema de una operación de escritura es el siguiente:

Tras el intercambio CMD/R el micro debe transmitir un bloque de datos usando spi_tx.  La siguiente rutina es la análoga a read_data_block en escritura:



uint8 write_data_block(uint8* buf, uint16 n)   // Write data block
{
 uint16 k;
 uint8 res,crc;

 spi_tx(0xFE); // SEnd data token
 for(k=0;k<n;k++) spi_tx(buf[k]); // send n bytes
 spi_tx(0xFF); spi_tx(0xFF);  // Dummy CRC (16 bits)
  
 spi_rx(res); // Get answer from card with Write Status
 res = (res>>1)&0b00000111; // Extract status bits
 if(res!=2) return res;     // Error, data not accepted

 res=0x00; while (res!=0xFF) spi_rx(res);  // Wait while card BUSY

 return(0);
}



Al igual que en la lectura el bloque de datos va enmarcado por un DataToken = 0xFE y un CRC de 16 bits (que puede ser cualquier cosa, a menos que tengamos habilitado la verificación de CRCs). La diferencia con la lectura es que ahora la tarjeta mandará una Data Response (1 byte) tras la recepción del bloque de datos, indicando si ha habido algún problema. La estructura de dicha respuesta es:

donde un valor 010 (2 dec) en status indica que los datos han sido aceptados.

Tras dicha respuesta la tarjeta entra en un estado BUSY hasta terminar de mover procesar los datos recibido (moverlos del buffer de recepción al correspondiente sector de la memoria). Dicho estado BUSY se indica con un nivel BAJO en la línea MISO (de tarjeta a micro). 

Notad que en un comando de lectura no existía dicho estado tras el intercambio porque una vez que mandaba los datos la tarjeta no tiene trabajo pendiente. 

El código para la escritura de un sector que usa la rutina anterior será:



byte write_sector_sd(unsigned long s, unsigned char* buf)
{
 byte res;

 #ifdef SDSC
   s<<=9;  // Start address of sector in bytes (sector # x 512)
 #endif

 select_SD;
 res=send_command_R1(CMD_WRITE_SECTOR,s);
 if(res==0)
  {
   spi_clock();  // 1 byte "margin" before sending data
   res=write_data_block(buf,(uint16)512);  // Write data block
  }
 deselect_SD;

 return res;
}


Al igual que antes el argumento que recibe la función es la dirección del sector. Dependiendo del tipo de tarjeta se pasará o no a bytes.

Con esto ya tenemos lo necesario para escribir un programa de usuario que escriba y lea de cualquier sector.

El siguiente bucle barre 1024 sectores (desde el sector 5000 para no interferir con el formato de la tarjeta) llenando cada sector consecutivamente con 00, 01, 02, etc. A continuación se lee el mismo sector y si hay alguna diferencia entre los datos escritos y leídos se incrementa un contador de errores:


#define start_SEC 5000L
#define NSEC  1024
  
 for(k=0;k<NSEC;k++) 
   {
    for(i=0; i<=511; i++) data[i] = k&0xFF;  // write buffer in PIC
   
    LATBbits.LATB0=1;
    res = write_sector_sd(start_SEC+k,data);    // Write buffer to SD card
    LATBbits.LATB0=0;

    for(i=0; i<=511; i++) data[i] = 0;  // Clear PIC buffer
   
    LATBbits.LATB0=1;
    res = read_sector_sd(start_SEC+k,data); // Read SD sector back to buffer
    LATBbits.LATB0=0;

    // Checks for discrepancies
    difs=0; for(i=0; i<512; i++) if (data[i]!=(k&0xFF)) difs++;

    if(difs) err++; // If errors, increment bad_sectors

    printf("Sectors checked: %4d BAD=%02d%c",k+1,err,13);
   
    if (err==0) LATD++;
   }




Los resultados del test se vuelcan por el puerto serie: 


------------------------------------
init_SD --> OK
Sectors checked: 1024  Err=00
End SD test
------------------------------------

En el bucle anterior usamos el pin RB0 para mostrar el tiempo que tarda en ejecutarse un comando de escritura / lectura de un sector (512 bytes). En la siguiente imágen tenemos los resultados para una vieja tarjeta SDSC de 32 Mbytes: 




La traza superior (amarilla) corresponde a RB0. Valores altos indican que se está ejecutando una escritura (1º pulso) o una lectura (2º pulso). Se observa que la escritura es mucho más lenta que la lectura (especialmente el el caso de la tarjeta de 32 Mb). La lectura son unos 4/5 msec mientras que la escritura son unos 17.5 msec (dependiendo de la tarjeta).

En la traza inferior se monitoriza RB1. En el código, RB1 se pone a 1 estrictamente durante la transferencia de los 512 bytes (dentro de read_data_block y write_data_block), ignorando los tiempos de espera debidos a la tarjeta.

Se observa que ambos tiempos ahora son muy parecidos (se tarda lo mismo en mandar 512 bytes que en recibirlos, unos 4.4 msec). Además, en el caso de la lectura el tiempo para leer un sector es prácticamente el tiempo necesario para transferir los bytes. En cambio para la lectura se observa que la tarjeta pasa mucho tiempo ocupada tras recibir los datos. Esto es debido a las diferencias que comentamos entre la lectura/escritura en una memoria flash.

Los tiempos anteriores nos dan unas velocidades máximas de transferencia de unos 120 Kb/sec de lectura y unos 30 Kb/sec para escritura. Usando una tarjeta más moderna (que previsiblemente es más eficaz grabando los datos) el tiempo de escritura se reduce a unos 8/9 msec (50-60 Kb/sec) pero aún así sigue siendo el doble de lento que la lectura.


Optimización (uso de macros para spi_tx, spi_rx, etc):


En primer lugar vamos a intentar reducir el tiempo que dedicamos a mover los 512 bytes por el puerto SPI. Obviamente no podemos hacer que el módulo SPI saque los bytes más rápidamente (salvo subiendo el reloj, pero eso sería hacer trampa).

Si que podemos intentar mejorar un poco el rendimiento cambiando las rutinas de transmisión y recepción de datos (spi_tx, spi_rx) por macros, de forma que se evite la sobrecarga de llamar a una función, pasar argumentos, etc.

Las antiguas macros, basadas en la función spi_transfer:



// SPI functions aliases
#define spi_tx(x)   spi_transfer(x)    // sends TX data, ignores return value.
#define spi_rx()    spi_transfer(0xFF) // sends dummy data, returns RX data.
#define spi_clock() spi_transfer(0xFF) // send 8 clocks (nobody cares about data)


se transforman en las siguientes, que no usan llamadas a funciones.


#define spi_tx(x)   { SSPBUF=x;    while(SSPSTATbits.BF==0); }
#define spi_rx(x)   { SSPBUF=0xFF; while(SSPSTATbits.BF==0); x=SSPBUF;}
#define spi_clock() { SSPBUF=0xFF; while(SSPSTATbits.BF==0); }


La única pega es que en el caso de la recepción, hay que cambiar los comandos res=spi_rx()  por spi_rx(res). No hay que cambiar nada para spi_tx y spi_clock.


En la imagen se observa la mejora en (estrictamente) el tiempo de transferencia de datos. Los pulsos de la traza de abajo son más estrechos. En el detalle de la derecha se ve que el tiempo dedicado a transferir los 512 bytes ha pasado de 4.4 msec a 3.2 msec, un ahorro de un 25%.

Un tiempo de 3.2 msec en la lectura de 512 bytes supone una velocidad de unos 160 Kb/sec. Sin embargo la escritura no ha mejorado apenas, porque aunque hemos ahorrado 1 msec en mandar los datos, la tarjeta sigue tardando el mismo tiempo en procesarlos. En el siguiente apartado veremos como es posible en ciertas circunstancias optimizar la fase de escritura.


Optimización (uso de comandos de lectura/escritura múltiples):

En muchas aplicaciones (pensar el el datalogger de antes) no se alternan operaciones de lectura/escritura como en el ejemplo anterior, sino que solamente escribimos o leemos datos. En ese caso es posible optimizar la transferencia utilizando los comandos de lectura/escritura de múltiples bloques (0x12=18 y 0x19=25).

El protocolo de una lectura múltiple es sencillo:
Tras el comando (0x12=18) y la respuesta de la tarjeta llegan sucesivos bloques de datos (Data TOKEN 0xFE + 512 datos + CRC16). En cualquier momento (incluso en mitad de un paquete de datos) el micro puede interrumpir el flujo de datos con un comando 12.  

La escritura de múltiples bloques es similar. Tras el comando CMD25 (0x19) y su respuesta el micro empieza a mandar bloques de datos (TOKEN+512 bytes + CRC16). A diferencia los demás casos el DATA TOKEN es ahora 0xFC en lugar de 0xFE. Al igual que antes, tras cada paquete la tarjeta hace un acknowlegde y esta BUSY durante un tiempo (linea BAJA). Cuando la línea se libera se pueden seguir mandando paquetes. Cuando el micro ya no desea mandar más bloques, envía un TOKEN de fin de datos (0xFD o Stop Transmission).


Estas dos figuras las he cogido de http://elm-chan.org/docs/mmc/mmc_e.html una muy buena referencia que ya he citado en la entrada anterior.  

Veamos un ejemplo de aplicación del comando de escritura múltiple (recordad que la escritura era el caso menos eficiente):


#define DATA_TOKEN_MULT_WRITE 0xFC
#define STOP_TRAN  0xFD


byte multiple_write_sector(unsigned long s, unsigned char* buf, uint16 n)
{
 byte res;
 unsigned int k;
 uint16 ss,err=0;

#ifdef SDSC
 s<<=9;  // Start address of sector in bytes (sector # x 512)
#endif

 select_SD;

 res=send_command_R1(CMD_WRITE_MULT,s); // Multiple sector write
 if(res) {deselect_SD; return res;}   // Error

 spi_rx(res); // 1 byte "margin" before sending data

 for (ss=0;ss<n;ss++)  // Envio de n sectores
  {
   LATBbits.LATB0=1;
   spi_tx(DATA_TOKEN_MULT_WRITE);  // Sends Data Token
   for(k=0;k<512;k++) spi_tx(buf[k]); // Sends data 
   spi_tx(0xFF); spi_tx(0xFF);  // dummy CRC
   LATBbits.LATB0=0;
   LATD++;

   spi_rx(res); // Get answer from card with Write Status
   res = (res>>1)&0b00000111; // Extract status bits
   if(res!=2) err++; // Error, data not accepted

   res=0x00; while (res!=0xFF) spi_rx(res);  // Wait until line is clear

   Delay10KTCYx(4); // 5 msec delays between DATA blocks (no needed)

  }

 spi_tx(STOP_TRAN); spi_clock(); // Send STOP_TRAN and "wait" 1 byte 

 res=0x00; while (res!=0xFF) spi_rx(res);  // Wait until line is clear

 deselect_SD;

 return err;
}



En el bucle donde enviamos los sucesivos bloques hemos añadido un delay de 40,000 ciclos (5 msec con la configuración de reloj usada). 
Este delay es completamente innecesario y se ha puesto para tener una referencia en la traza del osciloscopio. El pin RB0 marca como antes el tiempo estricto de transmisión de los 512 datos. 



Vemos que el tiempo de transferencia siguen siendo los mismos 3.1 msec de antes. Pero ahora la separación entre pulsos de transferencia es de exactamente los 5 msec programados en el delay. Esto quiere decir que el tiempo que la tarjeta esta BUSY tras la transferencia es ahora despreciable. 

Otra recomendación para mejorar la escritura (en el caso en que se conozca a-priori el número de sectores a escribir) es informar previamente a la tarjeta del número de sectores a escribir mediante el comando ACMD23 (con argumento = número de sectores). Esto permite a la tarjeta optimizar sus operaciones de borrado, que como sabemos pueden afectar a varios bloques a la vez. El uso de este "pre-aviso" no exime al usuario de la necesitad de enviar el byte de STOP_TRAN una vez que los bloques deseados hayan sido enviados.

Usando estas mejoras es posible conseguir tasas de escritura tan altas como las de lectura, del orden de unos 150 Kb/sec limitadas básicamente por la velocidad de la comunicación SPI.


Obviamente, el problema de intentar llegar a estas tasas de transferencia (tanto de lectura como de escritura) es que el PIC estará al 100% ocupado enviando los datos del buffer a la tarjeta a través de SPI. Pero si esta ocupado al 100% no tendrá tiempo para llenar ese buffer con cosas interesantes que merezca la pena guardar. De hecho, en los ejemplos anteriores escribíamos los mismos contenidos una y otra vez. 

El problema es algo similar a lo que pasaba en el caso de intentar llegar al límite en la tasa de muestreo del ADC. Eramos capaces de muestrear muy rápido pero no nos quedaba tiempo para hacer nada con las muestras adquiridas. La solución será la misma de entonces, el uso de interrupciones (en este caso la interrupción del módulo SSP).








20 comentarios:

  1. Hola Antonio,

    ¡Muy bueno tu blog! ¡Me encanta! Los temas son muy interesantes y muy útiles. Bueno, después de estos halagos te hago una pregunta jejeje.

    A ver si no entendí mal como es el tema de las direcciones en una tarjeta SDSC y una SDHC.

    Para simplificar, en ambos casos uso un buffer de 512 bytes, tal como lo describiste en la estrategia de buffering.

    En el caso de una tarjeta SDSC, suponte que le mando el comando escribir 0x58, con la dirección en los 4 bytes del argumento, 0x00, 0x04, 0x00 y 0x00 (o sea, 0x40000 en hexa o 262144 en decimal) la tarjeta entendería esa dirección en bytes, no?. Me posiciono en esa dirección y le paso los 512 bytes con un for por ejemplo. Si yo después quisiera escribir otros 512 bytes, tendría que sumarle 512 a la dirección, no? es decir, tendría que mandarle como argumento 0x00, 0x04, 0x02, 0x00 (o 262656) como nueva dirección, no?

    Y en el caso de una tarjeta SDHC, si quisiera escribir en la misma dirección tendría que mandarle, como argumento del comando 0x58, la dirección (262144/512=512 en decimal o 0x200 en hexa) 0x00, 0x00, 0x02, 0x00, no? Esa dirección, por estar inicializada la tarjeta como SDHC entendería esa dirección en bloques, no? Ahí mandaría los 512 datos del vector por el módulo SPI. Y si quisiera escribir otros 512 datos, tendría que modificar la dirección incrementándola en 1? Es decir, 0x00, 0x00, 0x02, 0x01 (o 513)?

    Espero que no sea demasiado rebuscada mi pregunta jeje, es que se me hizo un lío con eso de las direcciones!!!

    Desde ya, muchas gracias!!!!!!

    ResponderEliminar
    Respuestas
    1. En efecto, esa es la principal diferencia (además de la inicialización) entre SDSC y SDHC. En SDHC las direcciones se dan en bloques (porque con una dirección de 4 bytes estarías limitado a 2^32 bytes = 4 Gbytes). La desventaja es que en SDHC todo se hace por bloques y no puedes leer solo unos pocos bytes como en las SDDC.

      El código relevante es la función write_sector_sd a la que se le pasa siempre número de sector. Si la tarjeta es una SDHC se usa tal cual y si es una SDSC se pasa a bytes (*512,<<9) antes de usar el comando 0x58.

      Antonio

      Eliminar
    2. Muchas gracias. Me quedó mucho mas claro.

      Saludos!!

      Eliminar
  2. Otra consultita. En ese ejemplo del bucle que barre 1024 sectores, dice start_sec 5000L. Ese 5000L ¿corresponde a un 5000 en hexa?¿en decinal? ¿en bloque?¿o bytes? ¿En que byte estarias escribiendo?

    Desde ya, muchas gracias!!!!

    ResponderEliminar
    Respuestas
    1. Son 5000 en decimal y se refiere a sectores. Estarías escribiendo en los 512 bytes del sector 5000. De hecho el programa de la demo verifica la escritura/lectura de 1024 sectores comenzando en el 5000.
      Es un valor arbitrario, suficientemente alto para no fastidiar el formato de la tarjeta (sector de inicio, FATS, etc) que se encuentran en los primeros sectores (nada que no se pueda arreglar con un formateo).

      Antonio

      Eliminar
  3. ¡Genial! ¡muchas gracias!

    Entonces, eso quiere decir que si ejecuto el ejemplo, creo una imagen de la tarjeta (siempre hablando de tarjetas SDHC) y abro la imagen con el WinHex, lo que debería observar es que los datos que le grabé (los 1024 buffers de 512 bytes) empiezan a partir del offset en decimal 5000*512 (=2560000) y terminan en el offset 3084288 (=(5000+1024)*512). ¿es eso correcto?

    Desde ya, muchas gracias!!!

    ResponderEliminar
    Respuestas
    1. Efectivamente. Bueno, si queremos ser exactos el último sector escrito sería el 5000+1023, por lo que el último byte escrito sería el (5000+1023)*512 + 511, uno menos del que tu indicas.
      El (5000+1024)*512 sería el primer byte del siguiente sector y no sería modificado.

      Antonio

      Eliminar
    2. Si, entiendo. Me sobraría un byte :P.

      Eliminar
  4. Otra consultita,

    En tu secuencia de inicialización veo que, básicamente para tarjetas SDHC, mandas
    CMD00 (CMD_GO_IDLE_STATE con argumento 0 y CRC 0x95; y la respuesta esperada es 0x00),
    CMD08 (CMD_SEND_IF_COND con argumento 0x1AA y CRC 0x87; y la respuesta esperada es 0xAA) y
    CMD01 (0x01 o CMD_SEND_OP_COND con argumento 0x04000000 y CRC cualquiera; con respuesta esperada 0x00).
    ¿Esa secuencia te funciona para inicializar, escribir y leer en tarjetas SDHC (de 4GB por ejemplo)?. Por otro lado, he visto en varios otros foros que la secuencia que usan generalmente es CMD00, CMD08,
    CMD55 (0x77, con argumento 0 y CRC cualquiera; no verifico la respuesta, se que tarjeta tengo) y
    CMD41 (0x69, con argumento 0x04000000 y CRC cualquiera; con respuesta esperada 0x00), ¿tenes idea cual es la diferencia entre ambas secuencias?
    Si bien, estoy usando otra función de inicialización (no la que vos propones en tu ejemplo, porque ya venia trabajando con tarjetas de 2GB y tengo otras funciones, de spi y todo eso y quiero pasar a 4GB y por eso necesito otra inicialización y otro direccionamiento) no pretendo que me des soporte para mi función pero si me interesan las secuencias porque mandando CMD00, CMD08 y CMD41 (como en tu secuencia de inicialización) y realizando ese ejemplo que vos propones, mis resultados son distintos a lo que yo esperaría. Es decir, la tarjeta parece que se inicializa bien, responde con lo esperado a todos los comandos. pero me queda la duda con la diferencia en la inicialización que encontre por otro lados.

    ¡¡Desde ya, muchas gracias por tu tiempo!!!!

    ResponderEliminar
    Respuestas
    1. La secuencia de inicialización descrita (ver entrada anterior) me ha funcionado para todas (4 o 5) tarjetas que he probado, incluida una SDHC de 8 Gbytes.
      El problema es que como comento en la entrada anterior el protocolo completo de inicialización es complejo, por lo que en casi todos los códigos que te encuentras por ahí (y estas páginas no son una excepción) la gente implementa un sub-conjunto del protocolo. Una vez que les funciona para las tarjetas que están probando pasan a entretenerse con la parte mas interesante de la aplicación.
      Tendría que mirarlo con más cuidado, pero los comandos que mencionas CMD55 y CMD41 me suenan a la parte (que yo no implemento) de verificar el tipo de tarjeta (MMC, etc), pero como ya te digo a mi me funciona para SDHC. Cuando dices que los resultados son distintos a los esperados, ¿te refieres a que fallan los comandos posteriores?¿Puedes leer los registros?
      Si estas interesado en una inicialización lo más robusta posible, puedes consultar algunos de los enlaces citados en la entrada anterior (inicialización y lectura de registros). Algunos de ellos son más completos que el que yo presento (aunque creo recordar que ninguno de ellos implementaba el protocolo completo).

      No se si te he aclarado mucho, esta es una de esas partes en las que no puedo hablar con mucha confianza, porque soy consciente de que al no dominar el protocolo completo, lo que a mi me funciona puede dejar de hacerlo para una tarjeta distinta.

      Un saludo, Antonio.

      Eliminar
    2. Hola Antonio, gracias por tu tiempo.

      En respuesta a tus preguntas, puedo ejecutar comando posteriores pero no muy bien. Por ejemplo, yo necesito escribir una secuencia en al byte 262144 (sector 0x200 pero como es SDHC sería 0x200=512 decimal multiplicado por 512 => 262144 bytes). Simplemente para pruebas, escribo 10000 sectores con AA. Eso esta lejos del sector de formato de la tarjeta, pero aún asi despues de ejecutar la escritura, cuando inserto la tarjeta en la PC me dice que la tarjeta no tiene formato. Y cuando inspecciono la imagen de la tarjeta con el winhex, efectivamente las AA que escribí empiezan en el byte 0. Capáz que el problema no es la inicializacion sino la escritura pero me tiene perplejo el hecho de que fijo la direccion en 0x200 (y la empiezo a aumentar de 1 en 1 a partir de ahi) y no me puedo explicar como es que escribe en 0x00. ¿nunca te paso algo asi? Ademas, esa rutina de escritura que tengo, funciona bien para tarjetas de 2GB. Algo parecido me pasa si pongo otras direcciones. Probé fijando la dirección de inicio en 0x4000 y cuando inspecciono la imagen con el winhex, veo que la escritura empieza en 0x400000. No le puedo encontrar un sentido a ese resultado. ¿alguna idea?

      ¡¡Desde ya, muchas gracias!!

      Eliminar
    3. ¿O que dirección tendria que ponerle a tu funcion para escribir en el byte 262144?

      Eliminar
    4. Efectivamente es raro lo que cuentas. Mis problemas con el uso de tarjetas SD siempre han estado relacionados con la inicialización (la rutina que uso aquí es con la que más éxito he tenido, un 100% para mi reducido número de pruebas, 3/4 con SDSC y 1 SDHC). Una vez inicializada correctamente nunca he tenido problemas de lectura/escritura. Lo que tu comentas de que escribes en un sector distinto tiene más pìnta de un error (con suerte trivial) de tu código.

      En mi código, para conseguir lo que quieres (escribir en el sector 0x200 y sucesivos) bastaría hacer

      #define start_SEC 512L // Sector inicial = 512 = 0x200

      Otra cosa, mi código no detecta (aunque se podría hacer y sería más profesional) si la tarjeta introducida es SDSC o SDHC. Hay que hacerlo manualmente con un define. En el código publicado hay un #define SDSC
      Si quitas o comentas esa línea trabajará con tarjetas SDHC (lo único que hace es multiplicar el sector dado por 512 para obtener la dirección en bytes en las ordenes de lectura/escritura).

      Antonio

      Eliminar
    5. Hola Antonio, muchas gracias por tu tiempo.

      Despues de muchas escrituras e inspecciones con el winhex, encontré un factor común en todas las tarjetas sdhc que probé. A todas, mi sistema (por alguna fantasmal razón), le resta 8191 sectores a la dirección que yo le pase. Entonces, por ejemplo, si le paso la dirección 10000, empieza a escribir en la dirección 1809 (siempre hablando de sectores). Pude con éxito escribir en el sector 0x200 (512 decimal) diciendole que escriba en la dirección (512 + 8191 = 8703 -> 0x21FF). Es muy raro lo que pasa. Estoy probando ahora con otras marcas de tarjetas pero no si tendrá mucho que ver. Muy raro.

      Desde ya, muchas gracias!!!

      Eliminar
    6. Pues si que es raro. Yo en tu caso aunque solo sea por curiosidad usaría algún debugger (o un printf al puerto serie o encender LEDs) justo antes del envio de datos por SPI del comando de escritura para asegurarme que lo que mandas en lo que quieres. Para un comando de escritura en el sector 0x200 debería estar mandando los 6 bytes siguientes:

      0x18 0x00 0x00 0x02 0x00 0xFF
      CMD 0x200L = most Sign byte first Fake CRC (cualquiera valdria).

      Es que me extraña mucho lo de los 8192 sectores de menos. Cierto que no me he leído el protocolo completo pero nunca he visto nada de un offset en el número de sector.

      DE todas formas a ver si tengo un rato y vuelvo a probar con alguna SDHC para ver si veo algo similar.
      Un saludo,

      Eliminar
    7. Buenas,

      Si tengo un debugger, un pickit2 y efectivamente comprobé las direcciones y son las que corresponden. Me voy a poner a leer yo tambien sobre ese tema del offset. ¿no habrá un comando que setee el primer sector o algo asi?

      Saludos.

      Eliminar
    8. Hola no se si aun sigue vivo este hilo
      yo tengo un problema con mi SDHC
      Hago un lectura de 200 sectores por ejemplo en modo continuo, osea con el comando 12, pero luego no puedo pararlo para enviarle otro comando, por ejemplo leer otros 200 sectores en otra direccion, lo que me hace es que sigue leyendo todo a continuacion, no consigo que responda al comando de parar la transmision
      se os ocurre algo gracias !!!

      Eliminar
  5. Hola Antonio, Juan o David:
    Necesito contratar alguien en México que sepa programar para leer/escribir en sectores de discos duros, tarjetas de cualquier tipo o cualquier dispositivo de almacenaje electrónico. Me podrían recomendar a alguien? O me podrían decir dónde buscar? Pagaré bien. victreath@yahoo.com.

    ResponderEliminar
  6. Hola Antonio, Juan o David:
    Necesito contratar alguien en México que sepa programar para leer/escribir en sectores de discos duros, tarjetas de cualquier tipo o cualquier dispositivo de almacenaje electrónico. Me podrían recomendar a alguien? O me podrían decir dónde buscar? Pagaré bien. victreath@yahoo.com.

    ResponderEliminar
  7. esteban ellelawlliet3 de junio de 2016, 21:07

    Hola tengo que Escribir una memoria sd los datos obtenidos de un puerto analógico del
    microcontrolador. con Pic 16f873a , tenes algun tutorial o idea como hacerlo. Mi mail chaco-x@hotmail.com

    ResponderEliminar