Go Down

Topic: SD Library zu langsam.... (Read 3524 times) previous topic - next topic

wilhem

Liebe Arduinonutzer,

ich stoße gegen ein Problem, das mich schon wegen Schlafentzug kaputt gemacht hat.
Grundsätzlich möchte ich einen Datalogger entwickeln, der Daten aus meiner Platine mit Sensoren im exakten Intervallen auf einer SD Karte abspeichert.

Gut...das Problem liegt nun darin, dass die "Geschwindigkeit" der SD Bibliothek definitiv zu langsam ist. In meinem Programm werden die Sensoren aus der Platine mit einer Abtastfrequenz von 200 Hz, währenddessen die SD Bibliothek mindestens 10 Millisekunden benötigt, um Daten auf der Karte abzuspeichern.

Hiermit mein grobes Programm und noch weiter die aufgerufene Funktion

Code: [Select]
*+++++++++++++++++++++++++++++++++++++++++++++++++
*
*  Diese Version verwendet PORTD, um den Empfänger einzulesen.
*
*  Einsatz: Razor 6DOF + Arduino 2009
*
*  Diese Version ist bereits zum fliegen...
*
*************************************************/
#include <avr/interrupt.h>
#include <avr/io.h>
#include <SD.h>

#include "define_AP6.h"

/***********************************
* Definitions for analog sampling
***********************************/

const int xx_pin = A1;        // Acceleremoter X output pin, ratiometric
const int yy_pin = A0;        // Accelerometer Y output pin, ratiometric
const int zz_pin = A2;        // Accelerometer Z output pin, ratiometric
const int pitch_pin = A5;   // Gyro Output, pin not ratiometric
const int roll_pin = A4;    // Gyro Output, pin not ratiometric
const int yaw_pin = A3;     // Gyro Output, pin not ratiometric

/******************************
* Aus dem Datalogger Programm


The circuit:
* analog sensors on analog ins 0, 1, and 2
* SD card attached to SPI bus as follows:
** MOSI - pin 11
** MISO - pin 12
** CLK - pin 13
** CS - pin 4

******************************/
// On the Ethernet Shield, CS is pin 4. Note that even if it's not
// used as the CS pin, the hardware CS pin (10 on most Arduino boards,
// 53 on the Mega) must be left as an output or the SD library
// functions will not work.
const int chipSelect = 10;

/***************************************************************
* Variablen, die durch den ausgeführten Interrupt gelesen werden
***************************************************************/
volatile unsigned int radioIn[NUM_CHANNEL];
unsigned int servoOut[NUM_CHANNEL];
volatile unsigned int aileronSignalLength, aileronSignalDiff, elevatorSignalLength, elevatorSignalDiff, throttleSignalLength, throttleSignalDiff, rudderSignalLength, rudderSignalDiff, autopilotSignalLength, autopilotSignalDiff;
volatile unsigned int cnt;
volatile char CH = 1;           // Erster Kanal, der aus dem Empfänger eingelesen wird
volatile char FLAG_ail = 0, FLAG_elv = 0, FLAG_thr = 0, FLAG_rud = 0, FLAG_aut = 0;
int autoPilot = 0;
 

/********************************************************
* Variable to space every loop routine exactly of x mSec
********************************************************/
unsigned long timeFastLoop, timeSlowLoop, prevTimeFastLoop = 0, prevTimeSlowLoop = 0;


/******************************************************
* Definition von Variablen, die den Autopilot ansteuern
******************************************************/
unsigned long id = 1;         // #id
unsigned int file_index = 1;  // #id File
char filename[12];

char task = 1;

/***************************
* Output chars and variables
***************************/
const int ledRed = 9;    // Nicht 13, da der Pin schon für die SPI eingesetzt ist
const int ledGreen = 8;

//###########################################################################################
/**************
*
* Initial Setup
*
**************/
void setup(){
 
  analogReference(EXTERNAL);
 
  Serial.begin(BAUDRATE);      // Serial communication with the pc
  Serial.flush();
 
//   set_interrupt();   // Erforderlich. Durch diese Funktion werden die einzelnen Interrupts angeschaltet
 
  //************************************************************//
 
  delay(4000);
 
  Serial.println("******************FILTER 6**********************");
 
  pinMode(ledRed, OUTPUT);    // Led on digital pin 13 is used as indicator to show that the calibration is on the way...
  pinMode(ledGreen, OUTPUT);

  digitalWrite(ledRed, HIGH);
  digitalWrite(ledGreen, HIGH);
 
  pinMode(chipSelect, OUTPUT);
 
  delay(2000);

  digitalWrite(ledRed, LOW);
  digitalWrite(ledGreen, LOW);
 
  delay(2000);
 
  Serial.println("Initializing the SD Card...");
 
  // see if the card is present and can be initialized:
  if(SD.begin(chipSelect)){
   
             
     Serial.println("card initialized!");
     
     Serial.println("creating files...");
     
     createFile(file_index);
           
     for(int i = 0; i < 3; i++){
       
       digitalWrite(ledGreen, HIGH);
       delay(150);
       digitalWrite(ledGreen, LOW);
       delay(150);

       }
     
    }else{
       
      Serial.println("Card failed, or not present");

       for(int i = 0; i < 200; i++){
         digitalWrite(ledRed, HIGH);
         delay(1000);
         digitalWrite(ledRed, LOW);
         delay(1000);
         }
 
     }
}

void loop(){
 
 timeFastLoop = millis();
 timeSlowLoop = timeFastLoop;

 if(((timeFastLoop - prevTimeFastLoop) > 4)){
   prevTimeFastLoop = timeFastLoop;
       
      sensors(filename);

 }
   
}


/*****************************
*
*     Interrupt Routine
*
*****************************/
void set_interrupt(void){
 
  DDRD &= ~((1 << PIND2) | (1 << PIND3) | (1 << PIND4) | (1 << PIND5) | (1 << PIND6));   // Input Pin
  PORTB &= ~((1 << PIND2) | (1 << PIND3) | (1 << PIND4) | (1 << PIND5) | (1 << PIND6));  // Pull - up disabled
 
  // Configure external interrupts on PCINT2 (from PD0 to PD7)
  PCMSK2 = 0;                 // Disable interrupts on over a single port
  PCMSK2 |= ((1 << PCINT18)); // At the moment only PD2 enabled
  PCICR |= (1 << PCIE2);      // Enabled interrupts on PCINT23-16

}


und hier die aufgerufene Funktion:

Code: [Select]
/*
    Diese Funktion nimmt die Fileindex als Argument und öffnet der entsprechenden Datei
*/
void sensors(char* file){

// Definition  
int x_out = analogRead(xx_pin);
int y_out = analogRead(yy_pin);
int z_out = analogRead(zz_pin);
int p_out = analogRead(pitch_pin);
int r_out = analogRead(roll_pin);
int w_out = analogRead(yaw_pin);

char data[34];


sprintf(data, "%u, %u, %u, %u, %u, %u, %u", prevTimeFastLoop, x_out, y_out, z_out, p_out, r_out, w_out);

// sprintf(data, "%u", prevTimeFastLoop);

File log_file = SD.open(file, O_WRITE);

  // if the file is available, write to it:
 if (log_file) {
   log_file.println(data);
   log_file.flush();
   log_file.close();
 }  
 // if the file isn't open, pop up an error:
 else {
   
   Serial.println("error opening flugXX.dat");
   
 }
 
}



Obwohl ich schon im ganzen Forum durchgesucht habe, wie man die SD Library beschluenigen kann und die Parameter: O_CREAT und O_WRITE verwendet habe, habe ich leider meine gewünschte Abtastrate noch nicht erzielt.

Hier ist eine Probe, in der das obige Programm geändert wurde und statt die Messung aus den Sensoren die milli() Funktion in ersten Spalte abgepeichert wird.

Code: [Select]

9037, 0, 504, 517, 616, 368, 368
9060, 0, 504, 517, 616, 368, 367
9070, 0, 504, 517, 616, 368, 367
9079, 0, 504, 517, 616, 369, 369
9090, 0, 504, 516, 615, 369, 368
9100, 0, 504, 516, 615, 369, 368
9109, 0, 504, 517, 616, 369, 368
9119, 0, 504, 517, 616, 369, 368
9129, 0, 504, 516, 616, 368, 368
9140, 0, 504, 517, 615, 369, 368
9149, 0, 504, 517, 616, 369, 368
9159, 0, 504, 517, 616, 369, 368
9169, 0, 504, 517, 616, 369, 368
...............



es ist sofort ersichtlich, dass die Abtastfrequenz beträgt durchschnittlich zirka 10 Millisekunden und das ist für meine Apllikation zu hoch.

Wie kann die Bibliothek modifizieren? Gibt es eine Möglichkeit, Daten mit höherer Datenrate in der Karte abzuspeichern????

Ich danke euch herzlich

Gruß und schönen Abend noch

D.

pylon

Versuche mal den folgenden Teil in Deinem Code zu ersetzen. Das sollte eine Beschleunigung bringen. Der Grund: print() bzw. println() der Print-Klasse (wird von File geerbt) schreiben die Daten byte-weise über die write()-Methode. Das hat zum einen den Nachteil, dass die SD-Karte über Gebühr mit Schreibzugriffen eingedeckt wird (Lebensdauer wird dadurch verkürzt) und zum anderen ist natürlich die Geschwindigkeit reduziert, da für jedes Byte ein Block alloziert, umkopiert und gelöscht werden muss.

Code: [Select]
sprintf(data, "%u, %u, %u, %u, %u, %u, %u\r\n", prevTimeFastLoop, x_out, y_out, z_out, p_out, r_out, w_out);

File log_file = SD.open(file, O_WRITE);

   // if the file is available, write to it:
  if (log_file) {
    log_file.write(data, strlen(data));
    log_file.flush();
    log_file.close();
  } 


jurs


die Abtastfrequenz beträgt durchschnittlich zirka 10 Millisekunden und das ist für meine Apllikation zu hoch.

Wie kann die Bibliothek modifizieren? Gibt es eine Möglichkeit, Daten mit höherer Datenrate in der Karte abzuspeichern????


Du kannst die Schreibvorgänge stark beschleunigen, wenn Du die Datei nicht versuchst alle 10 ms zu öffnen, nur eine Zeile zu schreiben und wieder zu schließen.

Schneller wirds, wenn Du z.B. eine Schleife hundert mal laufen läßt und erst alle 100 oder 1000 Zeilen wird die Datei einmal geschlossen und wieder geöffnet.

Also statt:
- Datei öffnen
- 1 Log-Zeile schreiben
- Datei schließen

Sollte das viel schneller sein:
- Datei öffnen
- 100 oder 1000 Log-Zeilen schreiben
- Datei schließen

Dann hast Du eine kleine Verzögerung nur alle 100 oder 1000 Schreibvorgänge, wenn die Datei kurz zum Aktualisieren von Dateigröße und Änderungsdatum geschlossen und wiedergeöffnet wird.

Natürlich sind dann die zeitlichen Abstände zwischen den Samples nicht mehr exakt identisch, jedesmal beim Schließen und Öffnen der Log-Datei tritt eine kleine Verzögerung ein. Ist das akzeptabel?

Oder ist die Aufzeichnungsdauer begrenzt, so daß man sagen könnte: Ich öffne die Datei bei Aufzeichnungsbeginn, zeichne Minuten- oder Stundenlang auf und schließe die Datei erst wieder, wenn alles komplett fertig aufgezeichnet ist?

wilhem

Hallo
       danke für eure Antworten.
Also...der Tipp von Pylon, habe ich eben versucht und anscheinend schreibt Arduino nichts in der Karte.... kA
Ich habe versucht mit der log_file.write() Funktion, aber entweder kriege ich eine Fehlermeldung oder geht es einfach nicht.

Jurs...deine Idee ist sehr gut, dennoch habe ich das Problem, dass ich nicht wissen kann, wie viele Daten Arduino in sich aufnehmen kann, ohne einen Reset zu verursachen. Dadurch gehe ich das Risiko, dass die daten nicht vollständig eingelesen werden und das Programm geht in Overflow bevor die Datei abgespeichert wird.

Also die Größe eines Arrays kann ich vorherberechnen und anpassen, dennoch woher weiß ich, wie viel Platz Arduino noch frei hat?

Eine Verzögerung wäre nicht so schlimm, wenn es alle, sagen wir 1000 male sich wiederholt. Hast du jurs schon ein Beispiel dabei?

jurs


Jurs...deine Idee ist sehr gut, dennoch habe ich das Problem, dass ich nicht wissen kann, wie viele Daten Arduino in sich aufnehmen kann, ohne einen Reset zu verursachen. Dadurch gehe ich das Risiko, dass die daten nicht vollständig eingelesen werden und das Programm geht in Overflow bevor die Datei abgespeichert wird.


Der Arduino soll gar keine Daten zusätzlich "in sich aufnehmen" wie Du Dich ausdrückst, Du solltst eben nur nicht die Logdatei vor jeder einzelnen Schreiboperation öffnen und unmittelbar danach wieder schließen, sondern das nur nach jeweils 100 oder 1000 Schreibvorgängen machen. Dabei muß NICHTS zusätzlich zwischengespeichert werden außer das Datei-Handle der geöffneten Datei. Du rufst einfach die Funktionen "log_file = SD.open(file, O_WRITE);" und "log_file.close();" viel seltener auf und sparst dadurch massenhaft Zeit, indem Du in eine einmal geöffnete Datei gleich 100 oder 1000 Zeilen reinschreibst. Aber nicht 100 oder 1000 im RAM zwischengepufferte Zeilen (dazu reicht der RAM-Speicher gar nicht), sondern ganz normal Zeile für Zeile für Zeile wie bisher auch.

Also: Die Logik zum Öffnen und Schließen von "log_file" mit in die loop packen, und "log_file" als Funktionsparamter an die Funktion "sensors" übergeben, so dass diese Funktion dann einfach in die bereits geöffnete Datei schreiben kann (und zwar bei mehreren Aufrufen nacheinander, ohne dass die Datei zwischendurch geschlossen wird).

wilhem

#5
Mar 02, 2013, 10:57 am Last Edit: Mar 02, 2013, 02:34 pm by wilhem Reason: 1
Hi
  ich habe dich, Jurs, nicht so ganz verstanden.

Ich habe ein Array
Code: [Select]
char data[34]
in meiner Funktion definiert. Ich kann die Funktion wie du eben sagst, umschreiben, dennoch werden, wenn die Funktion aufgerufen wird, noch weitere Daten hintereinander hinzugefügt. In anderen Worten nimmt die Array deutlich zu, also 34 neue Symbole jedesmal wenn die Funktion ausgeführt wird.
Dadurch kann die Funktion sehr schnell die verfügbaren 2048 SRAM  bytes überschreiten (ich nutze dafür ein Arduino 2009 mit 328p Mikro). Das von dir erwähnte Prinzip, also nur alle 1000 Male abspeichern habe ich gut verstanden, aber verstehe ich nicht so ganz, wie du es meinst mit:
Quote
, sondern ganz normal Zeile für Zeile für Zeile
.
Vielleicht fehlt mir eine irgendwelche Kenntnis...kannst du mir bitte genauer sagen, wie du es meinst???

Danke!!!
Gruß

Eisebaer

hi,

Quote
Das von dir erwähnte Prinzip, also nur alle 1000 Male abspeichern habe ich gut verstanden


nein, Du hast ihn nicht verstanden, also versuch ich es mal zu erklären:

1. log_file.open stellt eine verbindung zur SD-karte her.
2. log.file.write schreibt die daten "vielleicht" auf die karte, vielleicht werden
    sie auch nur in einem cache zwischengespeichert.
3. log_file.flush stellt sicher, daß die daten jetzt "wirklich" auf die karte
    geschrieben werden.
4. log_file.close stellt ebenfalls sicher, daß die daten jetzt "wirklich" auf die karte
    geschrieben werden und trennt die verbindung zur karte.

das flush kannst Du weglassen, wenn ein close passiert.

die bessere und sicher weit schnellere methode ist die oben vorgeschlagene:
im setup das open.
die datei geöffnet lassen, während Du daten schreibst.

dann kann ich mangels erfahrung nicht sagen, wie es genau weitergeht.
die sicherste methode ist ganz bestimmt, mittels eines buttons vor dem rausziehen der karte/abschalten des gerätes sicherzustellen, daß ein log_file.close passiert. das wäre auch von der geschwindigkeit her das beste.
was passiert, wenn Du nach jedem write ein flush ausführst, ob dann die daten sicher auch ohne close drauf sind, kann ich nicht versprechen.

aber ein open-close bei jedem schreibvorgang auszuführen muß ja ewig dauern.

gruß stefan

jurs


Vielleicht fehlt mir eine irgendwelche Kenntnis...kannst du mir bitte genauer sagen, wie du es meinst???


Also entweder drücke ich mich unklar aus, oder Du hast irgendeine Blockade, es zu verstehen.

Du sollst nicht "alle 1000 male abspeichern", sondern "alle 1000 male die Datei öffnen und schließen", d.h. Du sollst während 1000 Zeilen geschrieben werden die Datei dauernd geöffnet halten!

Anyway, dann hier in Code für Arduino UNO mit SD-Kartenschacht auf einem Ethernet-Shield, vielleicht verstehst Du das in Code ja besser als meine Beschreibung in Textform. Der Code ist angelehnt an das "Datalogger" Programmierbeispiel zur Arduino SD-Library.

Code: [Select]

/*  SD card datalogger

This example shows how to log data from three analog sensors
to an SD card using the SD library.

The circuit:
* analog sensors on analog ins 0, 1, and 2
* SD card attached to SPI bus as follows:
** MOSI - pin 11
** MISO - pin 12
** CLK - pin 13
** CS - pin 4
*/

#include <SD.h>
// On the Ethernet Shield, CS is pin 4. Note that even if it's not
// used as the CS pin, the hardware CS pin (10 on most Arduino boards,
// 53 on the Mega) must be left as an output or the SD library
// functions will not work.
const int chipSelect = 4;

void setup()
{
// Open serial communications and wait for port to open:
  Serial.begin(9600);
   while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }

  Serial.print("Initializing SD card...");
  // On the Ethernet Shield, CS is pin 4. It's set as an output by default.
  // Note that even if it's not used as the CS pin, the hardware SS pin
  // (10 on most Arduino boards, 53 on the Mega) must be left as an output
  // or the SD library functions will not work.   pinMode(10, OUTPUT);     // change this to 53 on a mega
  pinMode(10, OUTPUT);     // change this to 53 on a mega
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)){
    Serial.println("Card failed, or not present");
    while (true==true); // don't do anything more
  }
  Serial.println("card initialized.");
}

void sensors(File log_file)
// Funktion schreibt eine Logzeile in das bereits geöffnete log_file
{
  int x_out = analogRead(A0);
  int y_out = analogRead(A1);
  int z_out = analogRead(A2);
  int p_out = analogRead(A3);
  int r_out = analogRead(A4);
  int w_out = analogRead(A5);
  char data[40];
  sprintf(data, "%8lu,%4u,%4u,%4u,%4u,%4u,%4u", millis(), x_out, y_out, z_out, p_out, r_out, w_out);
  log_file.println(data);
}



void write1000lines2log(boolean overWriteExistingData)
// Diese Funktion öffnet eine Logdatei zum Schreiben,
// ruft dann 1000 mal eine Funktion auf, die je eine Zeile in die Datei schreibt,
// und schließt die Logdatei danach wieder.
// Mit dem Parameter "overWriteExistingData" kann (im allgemeinen beim ersten Aufruf)
// festgelegt werden, dass eine bereits unter gleichem Namen existierende Datei
// zuerst plattgemacht werden soll, bevor Daten hineingeschrieben werden
{
  static long lastRun;
  File log_file;
  if (overWriteExistingData)  // Create new file from scratch, overwrite data if existing
    log_file = SD.open("flugXX.txt",  O_WRITE | O_CREAT | O_TRUNC);
  else 
    log_file = SD.open("flugXX.txt",  O_WRITE);
  if (!log_file) Serial.println("error opening flugXX.txt");
  else
  {
    for (int i=0;i<1000;i++)
    {
      while (millis()-lastRun<4) ;
      lastRun=millis();
      sensors(log_file);
    }
    log_file.close();
  } 
}



boolean FirstRun=true;
void loop()
{
  // Mal mindestens 10 Sekunden lang probieren,
  // wie oft je 1000 Zeilen ins Log geschrieben werden können
  while (millis()<10000)
  {
    write1000lines2log(FirstRun);
    FirstRun=false;
    Serial.println("1000 lines - done.");
  } 
  Serial.println(millis());
  while (true==true);
}


Die Log-Zeilen sind ein bischen länger als bei Dir, ausgelesen und geloggt werden immer die Werte von den sechs Analog-Pins und zusammen mit dem Stand des millis-Timers, schön in Spalten untereinander stehend.

In Deinem Programm meinte ich zu erkennen, dass eine Zeile alle 4 Millisekunden geschrieben werden soll und so habe ich es nun in den Code geschrieben.

Die Funktion "write1000lines2log" schreibt immer 1000 Zeilen hintereinander weg in die Datei, wobei die Datei nur einmal zu Anfang geöffnet und am Ende geschlossen wird.

Zwischen den 1000er Zeilenblöcken, jedesmal wenn die Datei also geschlossen und wieder geöffnet wird, entsteht eine Verzögerung, so daß zwischen den Zeilen 1000 und 1001 (2000 und 2001, 3000 und 3001 etc.) nicht die üblichen 4 Millisekunden bei der Aufzeichnung liegen wie zwischen den Zeilen innerhalb eines 1000-Zeilen Blocks, sondern eher so um die 15-16 Millisekunden zwischen den Aufzeichnungen an dieser "Trennstelle" in der Logdatei.




olikraus

Hi

SdFat war viel schneller und belegt weniger Flash-ROM.

http://code.google.com/p/sdfatlib/downloads/list

Oliver

pylon

Versuche mal den erweiterten Code (data vergrössert, damit die Werte auch aufgenommen werden können).

Code: [Select]

  char data[50];
  sprintf(data, "%u, %u, %u, %u, %u, %u, %u\r\n", prevTimeFastLoop, x_out, y_out, z_out, p_out, r_out, w_out);

File log_file = SD.open(file, O_WRITE);

   // if the file is available, write to it:
  if (log_file) {
    log_file.write(data, strlen(data));
    log_file.flush();
    log_file.close();
  } 

Go Up