Hola!
Estoy haciendo un pequeño proyecto personal que requiere el envío de un archivo csv mediante bluetooth desde mi celular a una tarjeta SD en una placa arduino Mega.
Mi archivo tiene unos 600 registros pero el código solo alcanza a grabar unos 163.
El código es el siguiente:
// Declaraciones requeridas para el manejo de la tarjeta SD
#include <SPI.h> // libreria de comunicaciones SPI
#include <SD.h> // librería para la tarjeta de memoria
// Declaraciones requeridas para el manejo del archivo Ciudades.txt
String fichero = "Ciudades.csv"; // Para el nombre del archivo que queremos crear, grabar o leer de la tarjeta de memoria, para evitar fallos máximo 8 caracteres para el nombre del archivo antes de la extensión .txt
byte caracter; // Para leer cada caracter del fichero
File myFile; // Creamos el objeto con el que accedemos a la tarjeta
// Se usa el Serial1 de la placa Arduino Mega 2560
// #define BT_RX pin ARDUINO TX1 18
// #define BT_TX PIN ARDUINO RX1 19
#define PIN_SD_OK 7
void setup() {
Serial.begin(9600);
pinMode(PIN_SD_OK, OUTPUT);
digitalWrite(PIN_SD_OK,SD.begin(53));
Serial.println("Iniciando tarjeta de memoria.");
if (!PIN_SD_OK) { // iniciamos indicando el pin que hemos utilizado para chip select (CS)
Serial.println("No se pudo inicializar la tarjeta");
while (1) ;
}
Serial1.begin(9600);
}
void loop(){
if (Serial1.available()){
myFile = SD.open(fichero, FILE_WRITE); // Primero creamos el fichero si no existe
while (Serial1.available()>0){
String myString=Serial1.readStringUntil("\n");
myFile.print(myString);
Serial.println(myString);
myFile.close();
Serial.println(fichero+" Cerrado") ;
}
}
}
Aparentemente el ciclo while (Serial1.available()>0){
String myString=Serial1.readStringUntil("\n"); ...
deja de leer ya que la la salida en el monitor en línea así lo muestra.
Estoy usando una app Serial Bluetooth terminal para enviar el archivo. Los settings son Line delay 10 ms, character delay 0 ms, Buffer size 200 k (el archivo mide unos 25k) .
Inicialmente solo leia unos 90 registros. En la medida que quité variables y arreglos y funciones no usadas se incremento a 163. Otro tema que no entiendo es porque la impresión hacia el monitor en linea empieza solo hasta que el celular marca que la transmisión ya finalizo.
Hablar de archivos de 25k o buffer size de 200k es desconocer con que estas trabajando.
Una arduino Mega tiene 8K de SRAM, tiene si 256K de flash de las cuales 8K se destinan al bootloader.
Entonces hablar de buffer de 200k es ridículo. No es posible!!
Si pruebas con archivos pequeños verás que si funciona, pero con un determinado tamaño obviamente no lo hará.
El método que se usa es enviar por tramos, se envia un tramo, luego otro y otro hasta completar el archivo completo.
Hasta que no lea un '\n' no guardará los datos en la SD y recién hecho esto lo imprime porque asi esta el código.
Si quieres que haga otra cosa debes programar otra cosa.
Está negando el número de un pin, en este caso 7, lo que dará 0 por lo que la condición no se cumple nunca.
En todo caso debería usar
if (!digitalRead(PIN_SD_OK)) {
Pero no le veo sentido, es más sencillo guardar el valor devuelto por SD.begin() en una variable que modificar y luego consultar el estado de un pin.
Es válido pero no es práctico, incluso si ese pin manejase un LED u otro dispositivo.
Si lo que desea es ahorrar el byte ocupado por la variable, la declara local y se libera al salir de setup().
Además de lo que señala @Surbyte, en mi opinión el while() está de más, es redundante.
¡Tienes Razón!
En mi afán de tener una señal visible sobre la correcta inicialización de la tarjeta fue que agregué un led. El código que puse fue la primera versión y no me había dado cuenta del error ya que si se inicializaba la tarjeta y funcionaba con el truncamiento del archivo.
Finalmente esa lógica quedó así:
Serial.println("Iniciando tarjeta de memoria.");
if (SD.begin(53)) { digitalWrite(PIN_SD_OK,HIGH); }
else {digitalWrite(PIN_SD_NOK,HIGH);
Serial.println("Error al iniciar la tarjeta SD");
while (1);}
Es cierto. Desconozco las funcionalidad del arduino. Tengo desde feb 24 que empecé a hacer pruebas con él.
El ciclo hace lo que yo quiero que haga. Es decir, una vez que se encuentra un "\n", interrumpe la lectura de caracteres, graba el registro en el archivo de salida, lo despliega en el monitor en línea, cierra el archivo y procede a leer otro registro.
Mi duda es porqué el ciclo inicia actividad visible (en el monitor en línea) solo hasta después de que la app de mi celular indica que se finalizó la transmisión. Puede ser que dado el tamaño de 200 kb del buffer definido en los settings de la app hacen que el envío de datos se contiene hasta que se llena o se acaban los datos por transmitir. En realidad, eso no es problema. Solo quiero entender el funcionamiento de los módulos bluetooth.
Mi verdadero problema es el truncamiento del archivo. Buscaré la forma de leer por bloques.
¿Alguna sugerencia de dónde buscar?
Hay otro error y creo que es lo que causa el problema.
myFile = SD.open(fichero, FILE_WRITE); // Primero creamos el fichero si no existe
while (Serial1.available()>0){
String myString=Serial1.readStringUntil("\n");
myFile.print(myString);
Serial.println(myString);
myFile.close(); // aca hay un problema!
Serial.println(fichero+" Cerrado");
}
Me enfoco solo en el archivo...
Lo abre para escribir, entra al while(), lee los datos del puerto, escribe los datos en el archivo, y cierra el archivo.
Hasta ahí parece que todo está bien pero qué pasa si hay más datos listos para leer por lo que no se sale del while()?
Se leen los datos del serial, se intenta guardar los datos en un archivo que está cerrado por lo que no se guardan.
Como dije antes, ese while() molesta, simplemente elimínelo.
Si, pero igual hay un error.
Se cumpla o no el timeout si el archivo está cerrado no se pueden guardar datos.
Por otro lado, según ArduWiki Hay un "bug" o fallo con esta función: si el flujo de entrada es constante (tiempo de espera nunca se agota) y nunca se encuentra el caracter terminador, se corre el riesgo de colgar completamente el programa.
La razón se debe a que, conceptualmente, String es ilimitado en tamaño; pero como la SRAM no es así, eventualmente el programa llegará a colapsar.
De verificarse que ésto es así, el problema con archivos grandes sería la memoria (más allá del error encontrado en este caso en particular).
Insisto en que el problema es ese while() redundante.
Y digo redundante porque ya hay un lazo, el propio loop() y para llegar al while() primero se verifica que haya datos en el buffer serie entonces no tiene sentido entrar en otro lazo cuya condición es que haya datos en el buffer serie.
Si a pesar de todo se quiere conservar el while() (aunque me sorprende de alguien con el currículum del PO) hay que mover la apertura del archivo dentro del mismo, no antes como está ahora.
Y hay otro detalle. readStringUntil() lee el caracter limitador del buffer (en este caso '\n') pero no lo incluye en la cadena devuelta.
Tal como está el código, usando myfile.print() en lugar de myfile.println(), el contenido del archivo en la SD no tendría finales de línea.
Finalmente puede lograr una transferencia completa y exacta. Gracias por tus sugerencias y observaciones. El código final se basa en un método llamado SerialEvent. Lo descubrí al investigar el tema de setTimeout que menciona Surbyte (gracias). El código es el siguiente:
#include <SPI.h>
#include <SD.h>
unsigned long elapsedTime=0;
const int chipSelect = 53; // Pin CS de la tarjeta SD
#define PIN_SD_OK 7
#define PIN_SD_NOK 8
File myFile; // Objeto para el archivo de datos
String myFileName = "Ciudades.csv";
String inputString = "";
String inputString1 = ""; // a String to hold incoming data
bool string1Complete = false; // whether the string is complete
bool stringComplete = false; // whether the string is complete
void setup() {
// initialize serial:
Serial.begin(9600);
Serial1.begin(9600);
inputString1.reserve(200); // reserve 200 bytes for the inputString:
inputString.reserve(200);
pinMode(PIN_SD_OK, OUTPUT);
pinMode(PIN_SD_NOK, OUTPUT);
Serial.println("Iniciando tarjeta de memoria.");
if (SD.begin(chipSelect)) {
digitalWrite(PIN_SD_OK, HIGH);
Serial.println("Tarjeta SD inicializada correctamente.");
} else {
digitalWrite(PIN_SD_NOK, HIGH);
Serial.println("Error al iniciar la tarjeta SD");
return;
}
}
void loop() {
// print the string when a newline arrives:
if (string1Complete) {
Serial.print(inputString1);
myFile.print(inputString1);
// clear the string:
inputString1 = "";
string1Complete = false;
elapsedTime = millis();
}
if (elapsedTime > 0 && millis() - elapsedTime > 5000) {
myFile.close(); // Cerrar el archivo solo si elapsedTime es mayor que 0
Serial.println("Fin de la transferencia");
elapsedTime = 0;
}
if (stringComplete) {
myFileName = inputString;
myFileName.replace("\n", "");
myFile = SD.open(myFileName, FILE_WRITE);
if (myFile) {
// Serial.print("Nombre del archivo en formato hexadecimal: ");
// for (int i = 0; i < myFileName.length(); i++) {
// Serial.print("0x");
// Serial.print(myFileName[i], HEX);
// Serial.print(" ");
// }
// Serial.println();
Serial.println("Archivo abierto correctamente.");
} else {
// Serial.print("Nombre del archivo en formato hexadecimal: ");
// for (int i = 0; i < myFileName.length(); i++) {
// Serial.print("0x");
// Serial.print(myFileName[i], HEX);
// Serial.print(" ");
// }
Serial.println();
Serial.println("Error al abrir el archivo " + myFileName);
}
// clear the string:
inputString = "";
stringComplete = false;
}
}
/*
SerialEvent occurs whenever a new data comes in the hardware serial RX. This
routine is run between each time loop() runs, so using delay inside loop can
delay response. Multiple bytes of data may be available.
*/
void serialEvent1() {
while (Serial1.available()) {
// get the new byte:
char inChar = (char)Serial1.read();
// add it to the inputString:
inputString1 += inChar;
// if the incoming character is a newline, set a flag so the main loop can
// do something about it:
if (inChar == '\n') {
string1Complete = true;
}
}
}
void serialEvent() {
while (Serial.available()) {
// get the new byte:
char inChar = (char)Serial.read();
// add it to the inputString:
inputString += inChar;
// if the incoming character is a newline, set a flag so the main loop can
// do something about it:
if (inChar == '\n') {
stringComplete = true;
}
}
}
Seguiré con mi proyecto para ver donde mas me atoro.
Nota: descubrí que los nombres de archivos deben ser de 8 caracteres o menos mas la extension de 3 y que no deben llevar caracteres especiales (por eso la impresión del nombre en hexadecimal).
Por si interesa.
El código que escribimos en arduino al momento de compilarse se transforma para ajustarse a la estructura de C, quedando algo así (código completo en archivo main.cpp del core de arduino)
#include <Arduino.h>
int main(void) {
// algunas inicializaciones aqui ...
setup();
for (;;) {
loop();
if (serialEventRun) serialEventRun();
}
return 0;
}
Como se ve, hay un lazo for() infinito dentro del cual se ejecutan loop() y serialEvent() si estuviera definida.
Esto significa (y lo dice la documentación de serialEvent()) que el mismo código de manejo del puerto serie puede incluirse directamente dentro de loop().
Incluso la documentación aclara que por compatibilidad es preferible no usar serialEvent() ya que los core de las nuevas placas no la soportan.
En resumen, usar serialEvent() no trae ningún beneficio, por el contrario genera problemas de portabilidad del código.