[SOLUCIONADO] Transferir un archivo a la SD

Después de llevar varios días (semanas) dando vueltas por Internet y por el foro, buscando tutoriales y respuestas no consigo dar con la solución al problema. En los ejemplos de Arduino (Dumpfile,etc, en ningún momento pone como abrir un archivo que se encuentre fuera de la tarjeta SD). Muchas respuestas indefinidas y pocas (ninguna) soluciones concretas, así que intentaré hacer la preguna bien clara:
¿Como copiar un archivo cualquiera (de texto o no de texto) desde el PC a la tarjeta SD?
Gracias de antemano.

Mira por donde, esta tarde al final he encontrado una solución indirecta en una página de Internet. Aunque en principio solo está pensada para archivos de texto y con un delimitador de EOF (final de archivo). El código es el siguiente:

#include <SPI.h>
#include <SD.h>
char EOF_MARKER = '

Luego hay que abrir un terminal serie (he usado el Teraterm, seleccionar el COM del Arduino, configurar el envio y recepción, como CR+LF y con la opción Send File, enviar el archivo. De momento, como digo el archivo tiene que ser de texto, poniéndole un $ al final del mismo.
Ahora tengo que ir haciendo pruebas con un archivo de texto normal (sin modificar) y luego con un archivo binario.
Si alguien sabe alguna forma más elegante de hacerlo, sería más cómodo. Yo, de momento sigo haciendo pruebas y os informo de los avances.; //Marca de EOF en archivo
File miArchivo;
char ch;
boolean okEOF = false;

void setup() {
  Serial.begin(9600);
  SD.begin(8);
  Serial.print("\nInicializando SD…");

if ( miArchivo = SD.open(“ejemplo4.txt”, O_CREAT | O_TRUNC | O_WRITE))
  {
    Serial.print(“OK”);
  }
  else {
    Serial.print(“MIAU”);
  }
}

void creaStr() {
  okEOF = false;
  while (1) {
    if (Serial.available()) {
      ch = (char)Serial.read();
      Serial.print (ch);
      if (ch == ’


Luego hay que abrir un terminal serie (he usado el Teraterm, seleccionar el COM del Arduino, configurar el envio y recepción, como CR+LF y con la opción Send File, enviar el archivo. De momento, como digo el archivo tiene que ser de texto, poniéndole un $ al final del mismo.
Ahora tengo que ir haciendo pruebas con un archivo de texto normal (sin modificar) y luego con un archivo binario.
Si alguien sabe alguna forma más elegante de hacerlo, sería más cómodo. Yo, de momento sigo haciendo pruebas y os informo de los avances.) {
        Serial.flush();
        if (miArchivo) miArchivo.close();
        okEOF = true;
        break;
      }
      if (miArchivo) {
        Serial.print (ch);
        miArchivo.print((char)ch);
      }
    }
  }

  if (okEOF) {
    Serial.println("Encontrado EOF y escrito en tarjeta SD ");
  }
  else {
    Serial.println("No se puede encontrar EOF !!");
  }
  Serial.flush();
}


void loop() {
  if (Serial.available()) {
    creaStr();
  }
}

Luego hay que abrir un terminal serie (he usado el Teraterm, seleccionar el COM del Arduino, configurar el envio y recepción, como CR+LF y con la opción Send File, enviar el archivo. De momento, como digo el archivo tiene que ser de texto, poniéndole un $ al final del mismo.
Ahora tengo que ir haciendo pruebas con un archivo de texto normal (sin modificar) y luego con un archivo binario.
Si alguien sabe alguna forma más elegante de hacerlo, sería más cómodo. Yo, de momento sigo haciendo pruebas y os informo de los avances.

jlsogorb:
¿Como copiar un archivo cualquiera (de texto o no de texto) desde el PC a la tarjeta SD?

Similar a como lo hiciste en Teraterm, excepto que con los archivos binarios hay un problema: el contenido es arbitrario. Con esto quiero decir que no hay forma directa de delimitar el final del archivo; lo que en texto se usa como EOF, en binario podría ser parte del contenido pero no necesariamente siendo el último byte.

Hay dos formas de delimitar un archivo binario:

  • Por timeout del puerto serial.
  • Conociendo de antemano la longitud (en bytes) del archivo.

jlsogorb:
Si alguien sabe alguna forma más elegante de hacerlo, sería más cómodo..

Sería crear un programa especializado para PC. Yo siempre he tenido en mente crear un tipo explorador de archivos de Arduino (así que puede tanto enviar como recibir), de interfaz gráfica por supuesto.

PD: para archivos relativamente grandes puede que prefieras un lector de SD USB al Arduino; recuerda que son 9600 bits por segundo y 11 bits por byte (8 de datos + 2 de inicio + 1 de parada); por lo tanto 9600/11 = 873 bytes por segundo. ¡Un documento Word con solo texto podría tomar hasta medio minuto en transferirse!.

Podrías pisar el acelerador más a fondo hasta los 115200 bps, lo cual te llevaría a 10 KB/s. Una mejora sustanciosa, pero podría tornarse desesperante con archivos a partir de 1 MB.

Si el convertidor USB/Serial no fuera un cuello de botella, estarías limitado a 40 KB/s de todas maneras. Esto se debe principalmente a tres motivos:

  • Una tarjeta SD operando en SPI no es lo más veloz.
  • Sobrecargo del programa (proceso de traspasar bytes entre buffers). Aquí optimizar el código es crucial
  • La frecuencia de reloj del micro es de 16 MHz.

Si la velocidad de transferencia no es importante, adelante; caso contrario, considerarlo.

Lucario448:
Sería crear un programa especializado para PC. Yo siempre he tenido en mente crear un tipo explorador de archivos de Arduino (así que puede tanto enviar como recibir), de interfaz gráfica por supuesto.

Pues no estaría nada mal que lo hicieras porque buena falta hace :wink:

Lucario448:
Si la velocidad de transferencia no es importante, adelante; caso contrario, considerarlo.

La velocidad de transferencia no es importante para lo que yo quiero.

Al final el Teraterm tiene la opción de mandar también archivos binarios y transformando un poco el código he conseguido lo que quería. He podido transferir tanto archivos de texto (sin necesidad de delimitador de EOF) como un JPG de más de 3megas (la velocidad de transferencia ha sido de unos 16kb/sg y ha tardado unos 3 minutos pero lo ha copiado impecablemente). Le he tenido que poner un retardo a la hora de leer/escribir los datos porque si lo hacía directo se quedaba colgado. Así es como ha quedado de simple:

/******Dump_Binario_SD*****************/
//Copia cualquier archivo del disco duro a la tarjeta SD
//Con el programa cargado, abrir un terminal serie (yo he usado el Teraterm), 
//seleccionar el COM del Arduino, 
//configurar el envio y recepción como CR+LF y con la opción Send File
// marcar como Binary, enviar el archivo.
/***************************/
#include "SD.h"
#include "SPI.h"

File miArchivo;
unsigned long ultimoLeido = 0;
long contaje = 0;
bool leido = 0;

void setup() {
  Serial.begin(9600);
  if (!SD.begin(8))  // Inicializa mi tarjeta SD
  {
    Serial.println("Error inicializando SD!");
    return;
  }
}

void loop() {
  if (!leido) {
    miArchivo = SD.open("42.jpg", O_CREAT | O_WRITE);   //Poner el nombre y extensión del archivo que queramos crear
   while (millis() - ultimoLeido < 1000 ||  contaje == 0) //probar a cambiar el valor de ultimoLeido (retardo) si no funciona
    {
      while (Serial.available() > 0)
      {
        miArchivo.write(Serial.read());
        ultimoLeido = millis();
        contaje++;
     }
    }
    miArchivo.close();
    leido = 1;
 Serial.print ("Archivo copiado correctamente");
  }
}

Durante el fín de semana haré distintas pruebas e intentaré provocar que se cuelgue o ver sus limitaciones y si funciona correctamente lo daré por solucionado.

jlsogorb:
Pues no estaría nada mal que lo hicieras porque buena falta hace :wink:

Mis conocimientos de programación están limitados al C++ de Arduino y Java; así que si lo desarrollo, sería en ese último lenguaje.
Java es portable a través de todos los sistemas operativos, pero no es lo más eficiente. Considerando la velocidad de transferencia real, la ineficiencia de una JVM dudo mucho que sea un problema.

Incluso sería mejor si fuera un controlador para el sistema operativo, que explorar archivos del Arduino sea como con pendrive.

jlsogorb:
Durante el fín de semana haré distintas pruebas e intentaré provocar que se cuelgue o ver sus limitaciones y si funciona correctamente lo daré por solucionado.

Provocar cuelgues... solo que hagas algo verdaderamente estúpido (perdona la palabra) en el código, o desconectes el USB durante una transferencia.

Te lo dejo así:

  • Prácticamente no hay uso de la asignación dinámica de memoria por el lado del Arduino; por lo tanto, un cuelgue es imposible que ocurra por software.
  • TeraTerm debería ser lo suficientemente robusto para manejar errores como la desconexión repentina del puerto serial; caso contrario sería una limitación.
  • Si los baudios son demasiado altos, se satura el buffer RX del Arduino; lo que lleva a recibir un archivo incompleto.
  • millis() se desborda cada 49 días; el momento justo que eso ocurra, el timeout se te va a disparar prematuramente.
  • Desconozco el comportamiento de la librería SD cuando la tarjeta se llena, me imagino que en un desastre.
  • Desconozco el comportamiento de la librería SD cuando el archivo alcance los 4 GB; si tienes un par de días de tiempo para una trasferencia así de descomunal, eres bienvenido para intentarlo.

Lucario448:
Provocar cuelgues… solo que hagas algo verdaderamente estúpido (perdona la palabra) en el código, o desconectes el USB durante una transferencia.

Cuando hablaba de cuelgues tampoco me refería a una catástrofe total, simplemente con quitarle el retardo al leer y copiar los datos ya me producía que el fichero se quedara bloqueado en un 93% y a los 5 minutos decidí abortarlo por si acaso.
Pero, bueno tampoco he tenido tiempo para probar cosas, porque en realidad todavía no he solucionado el problema totalmente.
Os cuento. El código que puse funciona perfectamente con una tarjeta Esplora (con procesador igual al que lleva la placa Leonardo) y usando la libreria SD y en principio debería funcionar igual con cualquier Arduino que use esta librería.
Pero,… yo en realidad lo quiero usar con una placa 4Duino (también basada en Leonardo) que lleva incorporada una pantalla táctil , tarjeta SD y módulo WIFI, y me he encontrado, al querer adaptar el código, con que la librería SD.h no funciona ya que tiene una librería específica ( PicasoSerialLib). He cambiado las funciones por las equivalentes:
‘SD.open’ por ‘Display.File_Open’, ‘miArchivo.close’ por ‘Display.file_Close’ y hasta aquí todo bien, pero cuando intento que me escriba en el archivo lo leido por Serial.read() (que en el caso de la libreria SD se traga lo que le eches sin importarle si es texto o binario), en la equivalente de esta librería , que tiene la siguiente declaración: Display.file_Write (sizeof(data), char *data, Handle) me da error ya que solo aceptaría un puntero a un char (p. ejemplo funciona bien si le pones algo del tipo : Display.file_Write(4,“Hola”, handle).
¿Alguna idea sobre como podría conseguir que aceptara datos binarios ? Os pongo el pedazo del código en cuestión:

 if (!leido) {
     word handle;
  handle = Display.file_Open("42.jpg",'w');     //miArchivo = SD.open("42.jpg", O_CREAT | O_WRITE);
  while (millis() - ultimo < 1000 ||  contaje== 0)     
    {
      while(Serial.available() > 0)
      {    
Display.file_Write(1, Serial.read(),handle);     // miArchivo.write(Serial.read());
        ultimo= millis();
        contaje++;
     }
    }
      Display.file_Close(handle);      //  miArchivo.close();
    leido = 1;
  Display.putstr("Hecho!");
  }

Curiosa librería, parece que combina varias para hacen uso de todos los componentes de la placa.

Si realmente es necesario usarla en vez de programar un Arduino Leonardo de la manera usual, el problema se podría sobrellevar de la siguiente forma:

byte b = Serial.read();
Display.file_Write(1, (char*)&b, handle);

Gracias, Lucario. El tema de los punteros y los direccionamientos siempre han sido mi talón de Aquiles. El programa ha compilado sin problemas pero no ha transferido los datos ni en un fichero de texto ni en uno binario. La verdad es que me ha llamado la atención que la transferencia a través del terminal serie era muchísimo más lenta que con la solución de las otras tarjetas (con la librería SD). Cuando tenga tiempo intentaré ir depurando el código para ver qué manda, qué recibe, etc. Probaré también a ir cambiando el retardo a la hora de leer/escribir los datos. ¿Alguna sugerencia?

Al final no he conseguido que funcionara correctamente, pero creo que más bien es problema de la función de esta librería en concreto. Conseguí ponerme en contacto con los desarrolladores de la placa (4Duino ), les pregunté por qué no funcionaba la librería SD y me contestaron que precisamente está en fase de desarrollo para esta plataforma. Me mandaron amablemente una copia de la librería para que la probara y en eso he andado estos días.
No está totalmente acabada y hay funciones que todavía están por pulir, pero las básicas que yo necesitaba sí que me han valido (SD.open, SD.remove, SD.exists, SD.getNextFilename, SD.getFirstFilename, openNextFile) y la más importante para el problema que tenía: ya puedo hacer directamente ‘myFile.write(Serial.read());’ y me funciona impecablemente tanto con archivos de texto como binarios. Así que daré el tema como solucionado, dando las gracias especialmente a Lucario por sus aportes.
Adjunto una copia de la librería, por si alguien tiene interés en echarle un vistazo y una foto de mi proyecto ya terminado (que consistía en construir una caja adecuada para la placa 4Duino en que tuviera los elementos necesarios para ser compacta). Aparte del chip Wifi ESP8266 y tarjeta SD que incluye la placa de fábrica, yo le he añadido una tarjeta HC05 Bluetooth, un conversor DC/DC de 9 a 5v, alimentado por una pila recargable, una entrada de jack para su recarga, un conmutador para cambiar entre funcionamiento normal o carga, y un conector de entrada USB que me sirve tanto para cargar los programas como para acceder a la tarjeta SD por el puerto serie (y que era el principal problema por lo que había abierto ese hilo).
En la foto se ve la imagen del primer archivo binario que he conseguido transferir de esta manera :wink:

SD4dPicaso.zip (4.54 KB)