puedo utilizar 2 buffers para no perder información al transcribir en SD

Buenos días, tengo una duda que no he podido resolver he trabajado en un código que que lee información Analoga por analogread() y debo transmitirla por SPI a una SD card a altas velocidades (la máxima posible) pero debo de intentar al menos argumentar que no se pierda información o lo menos posible. Es posible utilizar buffers que mientras se llena buffer 1 el buffer 2 espere, y mientras transcribe información a la SD card el buffer 1 el buffer 2 este recolectando datos, y una vez vaciado el buffer 1 en la SD este vuelva a 0 datos y comience a tomar datos. otro detalle seria la sincronía de los buffer, quiero agregar que la recolección de datos la estoy haciendo con interrupciones cada segundo para obtener en la memoria sd 500 muestras del convertidor analógico digital Estoy utilizando Arduino Uno se que es limitado, pero me gustaría saber si lo que quiero hacer es posible.

Este tema se ha debatido

Resumiendo, un buffer de 512 bytes se puede guardar en la SD en 5mseg. Ese tiempo permite hacerlo.

Lee este hilo Fastest Data Logging? With SD Card?, creo que resume todo lo que preguntas

daniboy93: la recolección de datos la estoy haciendo con interrupciones cada segundo para obtener en la memoria sd 500 muestras del convertidor analógico digital

¿500 muestras por segundo? ¿O una por segundo?

Si la respuesta es la última, cualquier función que involucre escribir será suficiente. De lo contrario, puede que sí y puede que no; mas no creo que sea necesario complicarse implementando búferes adicionales cuando la librería ya implementa uno, y además de que hace los malabares necesarios para más bien no haya pérdida de información.

gracias surbyte, si leí el post, y todos los links, pero me quedaron dudas al respecto ya que muchos opinaban lo contrario a otros. soy nuevo en el mundo de Arduino, ademas nadie dijo nada en base a un sketch o algo con el uso de los 2 buffers, así que en base a tu experiencia, crees que es viable realizarlo tal como lo planteo?, cual seria la ventaja o desventaja de usar interrupciones? sera mejor método que unicamente usar Analog read, y myfile.write directamente en la SD card?

lucario, Buen día muchas gracias por tu interés, la respuesta es: 500 muestras en 1 segundo, entonces por eso quiero que por ejemplo leer cada 1 segundo y que en ese segundo se llene el buffer A de 500 muestras, mientras se vacia a la SD, un buffer B se este llenando el siguiente segundo, y luego que este se vacié nose con alguna bandera indicar cuando ya este lleno para que el buffer A vuelva a comenzar a llenarse. en el post que estaba en ingles discutieron muchas cosas y terminaron confundiéndome.

ya se que la Libreria SD escribe bloques de 512 bytes, pero a que te refieres con "malabares para que no haya perdida de información"? me seria muy útil la explicación aunque para ti probablemente sea muy básica.

Veamos: una lectura analógica ocupa al menos dos bytes; entonces 500 lecturas requieren de 1000 bytes. Un búfer de lecturas + el búfer para la tarjeta SD = 3/4 de la memoria RAM total. El programa tendrá que limitarse solo a eso; o podría colgarse por memoria llena. La idea de una interrupción no es descabellada; ya que necesitamos que el muestro corra "en paralelo" a la escritura de la tarjeta SD. Aclaro de una vez que no es paralelismo verdadero (como el que existe en los microprocesadores de PC actuales), sino que el programa principal literalmente es interrumpido (de ahí el nombre) por un lapso que se supone que es muy muy muy corto (unos cuantos microsegundos).

El programa principal (loop()) se encargará únicamente de colocar las lecturas guardadas en el búfer de la SD (mediante archivo.write()); mientras que la interrupción de 500 Hz se encargará de realizar y almacenar las lecturas. La implementación necesaria se llama "búfer circular"; similar a la estructura de datos llamada "cola". El modelo a utilizar será el de "productor-consumidor": la interrupción "produce" datos mientras que el archivo en la SD los "consume".

daniboy93: pero a que te refieres con "malabares para que no haya perdida de información"? me seria muy útil la explicación.

"Malabares" porque lidiar con un sistema de archivos (FAT32 para ser exacto) no es tan fácil como parece; sumado a que todas las operaciones con la tarjeta SD se hacen por bloques y no por bytes individuales (como sí sucede con las EEPROM). El simple hecho de escribir 512 bytes a un archivo no solo implica hacerlo en el espacio físico, sino que también buscar la entrada en la tabla de asignaciones para actualizar atributos como el tamaño y los clústeres utilizados (clúster = bloque según sistema de archivos; suele rondar entre los 512 y 32768 bytes). Si el archivo necesita crecer pero se topa con espacio ocupado por otro archivo... ¡ay Dios! ¡La fragmentación complica las cosas aún más!

Afortunadamente, para eso existe la librería: para poder desentendernos de todo ese dolor de cabeza y manipular archivos de manera muy simplificada pero efectiva. Toda librería debería ayudarnos a no tener que enfocarse en problemas irrelevantes al proyecto. Toda esta larga explicación del concepto de "malabares" debería explicar el por qué escribir a un archivo en una tarjeta SD suele ser un enorme obstáculo cuando todo tiene que hacerse muy rápido.

Por otra parte, el hecho de que "no haya pérdida de información" se debe a que la librería sabe cómo utilizar su propio búfer. Sabe cómo utilizarlo para leer un archivo, sabe cómo utilizarlo para escribir y sobrescribir en un archivo, sabe cómo utilizarlo para mantener consistencia entre lo que se espera leer y se espera guardar; sin crear mucho "cuello de botella" en el programa.

jaja oh rayos!! bendita libreria SD entonces, facilita mucho las cosas!!
pero lamentablemente para un proyecto escolar me están pidiendo que lo haga del método con interrupciones tengo este código para capturar 500 muestras al activar 1 boton (gracias Lucario) con hora y fecha de inicio y final, segun otro sketch que probe con interrupciones de prender y apagar 2 leds intente acomodar o mezclar ambos codigos para que cada segundo, se active la ISR que en este caso quiero que capture las muestras del Analogread () pero me marca error aqui esta el codigo

#include<LiquidCrystal.h>
/*
   LCD RS pin to digital pin 9
   LCD Enable pin to digital pin 8
   LCD D4 pin to digital pin 7
   LCD D5 pin to digital pin 6
   LCD D6 pin to digital pin 5
   LCD D7 pin to digital pin 4
   LCD R/W pint to ground
   10k resistor:
   ends to 5V and ground
   wiper to LCD VO pin (pin 3)


  SD card attached to SPI bus as follows:
  MOSI-pin 11
  MISO-pin 12
  CLK-SLK-pin 13
  CS-pin 10(arduino-uno) selector de chip SS
*/
#include <SPI.h>
#include <SD.h>
#include<LiquidCrystal.h>
LiquidCrystal lcd(9, 8, 7, 6, 5, 4);
const byte chipSelect = 10;
#include <DS3231.h>
#include <Wire.h>
#define FACTOR_VOLTAJE (5.0 / 1023.0) // Calcular esto desde el compilador, así le quitamos una carga al Arduino

DS3231 Clock;

byte year, month, date, DoW, hour, minute, second;
char linea[64]; // ¿64 caracteres por línea (máximo) serán suficientes?


#include <TimerOne.h>

volatile float voltaje;
volatile boolean onOrOff = LOW; // set led on or off inside the ISR
volatile unsigned long functionCounter = 0; // count how many times the function is called

unsigned int mainLoopCounter = 0; // to pick up the value of the ISR function loop

 void setup()
{
  lcd.begin(16, 2);
  //lcd.print("Bienvenidos");
  lcd.clear();
  lcd.print("Inicializando SD card...");
  if (!SD.begin(chipSelect))
  {
    lcd.home();
    lcd.print("Fallo SD card...");
    while (1); // No hacer nada si no se pudo inicializar la tarjeta
  }
  lcd.print("SD card Inicializada");
  pinMode(2, INPUT_PULLUP); // Un botón en el pin 2 iniciará el muestreo.
  mensajeEspera();
}

void mensajeEspera() {
  lcd.setCursor(0, 1);
  lcd.print("Esperando boton");
}

void loop() {
  if (!digitalRead(2)) grabar();
}

void grabar() {
  while (!digitalRead(2)); // Espera a que el botón se suelte
  File myFile = SD.open("datalog1.txt", FILE_WRITE);//abrimos  el archivo

  if (!myFile) {
    lcd.home();
    lcd.print("Error de archivo");
    mensajeEspera();
    return;
  }
  Clock.getTime(year, month, date, DoW, hour, minute, second);
  lcd.home(); lcd.print("Grabando...");
  myFile.write(linea, sprintf(linea, "Iniciado el %02d/%02d/20%02d, a las %02d:%02d:%02d\r\n", date, month, year, hour, minute, second)); // Cabecera del archivo

  Timer1.initialize(1000000); // 100,000 microseconds or 1 seconds
  Timer1.attachInterrupt(timerIsr); // attach service routine
  }
void timerIsr()
{
float voltaje = analogRead(A0) * FACTOR_VOLTAJE;
myFile.write(linea, sprintf(linea, "%s\r\n", String(analogRead(A0) * FACTOR_VOLTAJE).c_str()));
  
}
  Clock.getTime(year, month, date, DoW, hour, minute, second);
  myFile.write(linea, sprintf(linea, "Finalizado el %02d/%02d/20%02d, a las %02d:%02d:%02d\r\n", date, month, year, hour, minute, second)); // Pie del archivo
  myFile.close(); //cerramos el archivo
  lcd.home(); lcd.print("Finalizado");
  mensajeEspera();
}
 
}

void loop() {
  // put your main code here, to run repeatedly:
}

agradezco de ante mano, y si me pueden hechar una corregidita al codigo :frowning: para que haga el muestreo cada 1 segundo con la interrupcion (ahi no esta incluido el doble buffer si se puede hacer de una vez seria super pero ando guiandome apenas)

Lucario, te adjunto la información sobre el ring buffer del codigo que te envié. espero sirva de ayuda para realizar lo que se requiere Gracias.

https://forum.arduino.cc/index.php?topic=332419.0

aquí esta un diagramita de lo que necesito realizar:
y para no confundir a posibles ayudantes: subo de forma independiente los codigos que ya estan funcionando:

este código registra 500 muestras del pin analógico y las pasa al SD con hora y fecha (totalmente efectivo, pero en mi proyecto me piden que sea por interrupciones)

#include<LiquidCrystal.h>
/*
   LCD RS pin to digital pin 9
   LCD Enable pin to digital pin 8
   LCD D4 pin to digital pin 7
   LCD D5 pin to digital pin 6
   LCD D6 pin to digital pin 5
   LCD D7 pin to digital pin 4
   LCD R/W pint to ground
   10k resistor:
   ends to 5V and ground
   wiper to LCD VO pin (pin 3)


  SD card attached to SPI bus as follows:
  MOSI-pin 11
  MISO-pin 12
  CLK-SLK-pin 13
  CS-pin 10(arduino-uno) selector de chip SS
*/
#include <SPI.h>
#include <SD.h>
#include<LiquidCrystal.h>
LiquidCrystal lcd(9, 8, 7, 6, 5, 4);
const byte chipSelect = 10;
#include <DS3231.h>
#include <Wire.h>
#define FACTOR_VOLTAJE (5.0 / 1023.0) // Calcular esto desde el compilador, así le quitamos una carga al Arduino

DS3231 Clock;

byte year, month, date, DoW, hour, minute, second;
char linea[64]; // ¿64 caracteres por línea (máximo) serán suficientes?

void setup()
{
  lcd.begin(16, 2);
  //lcd.print("Bienvenidos");
  lcd.clear();
  lcd.print("Inicializando SD card...");
  if (!SD.begin(chipSelect))
  {
    lcd.home();
    lcd.print("Fallo SD card...");
    while (1); // No hacer nada si no se pudo inicializar la tarjeta
  }
  lcd.print("SD card Inicializada");
  pinMode(2, INPUT_PULLUP); // Un botón en el pin 2 iniciará el muestreo.
  mensajeEspera();
}

void mensajeEspera() {
  lcd.setCursor(0, 1);
  lcd.print("Esperando boton");
}

void loop() {
  if (!digitalRead(2)) grabar();
}

void grabar() {
  while (!digitalRead(2)); // Espera a que el botón se suelte
  File myFile = SD.open("datalog1.txt", FILE_WRITE);//abrimos  el archivo

  if (!myFile) {
    lcd.home();
    lcd.print("Error de archivo");
    mensajeEspera();
    return;
  }
  Clock.getTime(year, month, date, DoW, hour, minute, second);
  lcd.home(); lcd.print("Grabando...");
  myFile.write(linea, sprintf(linea, "Iniciado el %02d/%02d/20%02d, a las %02d:%02d:%02d\r\n", date, month, year, hour, minute, second)); // Cabecera del archivo

 unsigned long tAnterior = 0;
  while (PIND & B100) { // digitalRead en pin 2, solo que mucho más rápido. Básicamente repite el ciclo hasta que el botón sea pulsado de nuevo.
    myFile.write(linea, sprintf(linea, "%s\t%02d/%02d/20%02d\t%02d:%02d:%02d\r\n", String(analogRead(A0) * FACTOR_VOLTAJE).c_str(), date, month, year, hour, minute, second));
    
    unsigned long tActual = millis();
    if (tActual >= tAnterior) { // Pide información nueva al RTC cada segundo
      tAnterior = tActual + 1000;
      Clock.getTime(year, month, date, DoW, hour, minute, second); // Depende de cuán lento sea el bus de I2C, puede perderse la captura de una o dos muestras.
      delayMicroseconds(100); // Aquí es un delay distinto, debe ser menor al siguiente para compensar el retraso generado por el RTC.
    } else delayMicroseconds(1100); // El valor a introducir se hallará con "ensayo y error". Cuando lo encuentres, obtendrás la frecuencia deseada.
  }
  Clock.getTime(year, month, date, DoW, hour, minute, second);
  myFile.write(linea, sprintf(linea, "Finalizado el %02d/%02d/20%02d, a las %02d:%02d:%02d\r\n", date, month, year, hour, minute, second)); // Pie del archivo
  myFile.close(); //cerramos el archivo
  lcd.home(); lcd.print("Finalizado");
  mensajeEspera();
}

y este código juega con 2 leds en el pin 2 y 3 y un contador utilizando interrupciones cada segundo.
necesito mezclar ambos, y posteriormente hacer uso de 2 buffers como lo menciono en el titulo.

#include <TimerOne.h>

int timerInterruptLed1 = 3;
int timerInterruptLed = 2;

volatile boolean onOrOff = LOW; // set led on or off inside the ISR
volatile unsigned long functionCounter = 0; // count how many times the function is called

unsigned int mainLoopCounter = 0; // to pick up the value of the ISR function loop

void setup() {
  // put your setup code here, to run once:
  pinMode(timerInterruptLed, OUTPUT);
  pinMode(timerInterruptLed1, OUTPUT);
  Timer1.initialize(1000000); // 100,000 microseconds or 0.1 seconds
  Timer1.attachInterrupt(timerIsr); // attach service routine
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  noInterrupts();
  mainLoopCounter = functionCounter;
  interrupts();
  Serial.print("function is called = ");
  Serial.println(mainLoopCounter);

  if(mainLoopCounter >= 1000){
    functionCounter = 0;
  }
  
  digitalWrite(timerInterruptLed1, HIGH);
  delay(1000),
  digitalWrite(timerInterruptLed1, LOW);
  delay(1000);

}

//----------------------------------
//Timer interrupt service routine function
//-----------------------------------
void timerIsr(){
  digitalWrite(timerInterruptLed, onOrOff);
  onOrOff =! onOrOff;
  functionCounter ++;
  
}

agradezco de antemano, se aceptan sugerencias también de asesoría bajo comisión $$

18449940_10158534522850167_307272496_n.jpg

En el adjunto te dejo una versión optimizada de la librería para tu proyecto. No hace falta cambiar el #include, pero sí reemplazar los archivos que ya tienes.
Eliminé las funciones innecesarias, dejando únicamente las básicas: read, write y available.
write está modificada para realmente escribir un int, no un solo byte.

Si tienes dudas con la realización del programa, pregunta por aquí :wink:

RingBuffer_modif.zip (814 Bytes)

Actualizo: dos usuarios se me cruzaron en este hilo; espero y ojalá acabe "matando dos pájaros de un tiro" y no creando una confusión ::) :sweat_smile:

lucario que tal, jaja yo tambien subi mis codigos? un poco de ayuda por aqui :$ te lo agradecere? o puedo utilizar ese ring buffer de la chica alexandra para lo que necesito?

daniboy93: puedo utilizar ese ring buffer de la chica alexandra para lo que necesito?

Bueno... en tu caso creo que tengo que hacerle otras modificaciones. Esa implementación ocupa 1008 bytes de memoria RAM, y las lecturas analógicas se guardan en forma binaria. Tendría que adaptarla para tu situación: menos consumo de RAM y habilitar el uso de print y println (ya que veo que tú escribes como texto).

Esta sería la adecuada para la situación de daniboy93.

Para incluirla en el sketch, igual se hace con:

#include <RingBuffer.h>

RingBuffer_daniboy93.zip (799 Bytes)

Lucario que tal, muchas gracias por la libreria modificada, estuve leyendo el link del post que subio alejandra y vi que usan este codigo para usar la libreria de ring buffer

#include "RingBuffer.h"

RingBuffer myBuffer;
RingBuffer myOtherBuffer;

void setup()
{
  Serial.begin(9600);
  myOtherBuffer.write(60);
  myOtherBuffer.write(20);

  for (int i = 0; i < 50; i++) {
    myBuffer.write(i);
    Serial.print("myBuffer  ");
    Serial.println(myBuffer.read() );
  }

  Serial.print("myOtherBuffer  ");
  Serial.println(myOtherBuffer.read() );
}

void loop()
{
}

osea que llenan 2 buffer primero 1 y cuando este esta lleno se pasa al my otherbuffer o buffer 2
pero mi pregunta es por que tienen distintos tamaños estos buffer en mi caso yo necesito que tengan 500 muestras vi que en la libreria modificada pusiste que fueran de 512 bytes es correcto? entonces estas lineas ya no serian necesario especificar myOtherBuffer.write(60);
myOtherBuffer.write(20); ??

Veamos si me explico bien:

El tamaño lo fijé de 512 bytes por la coincidencia con el tamaño del bloque SD; mas no podía utilizar un valor seguro ya que todo lo escribes como texto. La forma que esperaba que lo utilizaras, es la siguiente: loop va "consumiendo" lo que hay en el búfer; mientras que la interrupción lo va llenando.

void loop() {
if (buffer.available()) archivo.write(buffer.read());
}

void isr() {
  // Lectura analógica y el resto de cosas.
  // Aunque lo ideal sería que la interrupción no tardase tanto...
}