Zeitgesteuertes Loggen auf SD Karte

Hallo mal wieder, Aruduinofans,

ich schlage mich zur Zeit mit einem kleinern Problem rum, dass ich noch nicht wirklich lösen konnte, es geht um das Loggen von Daten.

Auch hier wieder das Projekt in wenigen Sätzen:
Ein Versuchsfahrzeug besitzt ein Arudino und einen 4d Systems Touchscreen als Glascockpit zur Anzeige der Werte, sowie eine SD Card. (Geloggt und Angezeigt werden zig Werte, wie GPS, Geschwindigkeit, Werte einer IMU, 10 Radsensoren, Motorwerte usw...)
Ebenfals können 5 verschiedene Parameter über Aktuatoren verändertet werden) funktioniert alles wunderbar.

Der Log erfolgt zur Zeit so, im main loop:

if (millis() >= timer3)  //Ist die eingestellt Zeit abgelaufen wird ein Datensatz geloggt.
    {
    if(RecOn==1) sdwirte();   // sdwrite : Unterfkt öffnet Datei, schreibt werte, schließ datei usw...
    timer3 = millis() + 50;    //Hier also wenn delta t>50 ms
     
  }

Das hat jedoch einen großen Nachteil. Der Log erfolgt wenn das Programm an dieser Stell ankommt und delta t gleich oder länger als 50ms ist. Zu spätern Auswertung ist das aber ungünstig, da der Log Beispielhaft etwas so aussieht:
t1 = 52 ms; t2 = 105 ms; t3 = 166 ms; t4 = 225 ms; usw...

Lässt sich das exakt lösen, ich denke da an einen Interrupt? Also das jede Aufzeichung exat bei t1=50 ms; t2 =100 ms; t3 = 150 ms; t4 = 200 ms;

Und wenn ja, wie muss das dann grob aussehen? (Noch nie benutzt)

Vielen Dank
Gruß Michael

Na, das ist doch gute alte Blinkwithoutdelay Ansatz.

Bist Du denn aber sicher, dass der Durchlauf mit dem Schreiben der ganzen Daten in 50ms erledigt ist?

Wenn Deine Komponenten mehr als 50ms benötigen, um Dir die Werte zu liefern, dann würde ich den Logzeitraum etwas höher ansetzen. Das Schreiben auf die SD dauert letztlich auch noch etwas Zeit. Wird es denn korrekt, wenn Du z.B. mal alle 100ms loggst? Oder ist das Programm dann auch schon wieder mit der Datenbeschaffung beschäftigt? Kommt es denn auf glatte Werte an?
Ich habe mir für meine Projekte überlegt, die Logdaten zu schreiben, wenn die Daten anfallen. Ich kann Dir ja mal kurz meine Strategie schreiben:

Geloggt werden sollen bei mir Telemetrie- und GPS-Daten. Die Telemetriedaten werden relativ häufig pro Sekunde abgefragt, die GPS-Daten immer 1x pro Sekunde. Ich habe die Logdatei dauerhaft geöffnet und schreibe immer dann, wenn "was ankommt". Die GPS-Daten sind dann der - wenn man so will - "Anker" zum Schließen der Logzeile. Durch mein JSON-Format lässt sich das relativ einfach umsetzen. Jedes Mal ist ein Zeitstempel dabei, sodass man innerhalb einer Sekunde die Daten recht gut analysieren kann.

{
	"telemetry":[
		{
		"ts":"2014-04-13 12:23:45.004",
		"dx":"90",
		"dy":"45",
		"dz":"30",
		"beliebige":"andere Daten"
		},
		{
		"ts":"2014-04-13 12:23:45.023",
		"dx":"87",
		"dy":"49",
		"dz":"13",
		"beliebige":"andere Daten"
		}
	],
	"gps":{
		"lon":"5109.3456",
		"lat":"726.6789",
		"weitere":"GPS Daten"
	}
}

Natürlich werden die Daten unformatiert in eine Zeile geschrieben. Aber eigentlich läuft das so:

  1. Eröffne eine neue Logzeitle mit "{"telemetry":[" und fange an, Sensordaten zu ermitteln.
  2. Schreibe pro Sensor-Set die Daten als eigenständiges Objekt: "{"feld1":"wert1","feld2:"wert2"} Wegen der Komata am Ende dieser einzelnen Objekte muss das System sich mit einem Boolean-Wert merken, ob die erste Zeile der Sensordaten geschrieben wird oder eine Folgezeile. In Folgezeilen stelle ich dann das Komma noch voran.
  3. Kommt ein GPS-Set rein, dann beende die Telemetrie mit "]" und logge dann noch das GPS-Objekt mit ","gps":{...}"
  4. Beende die komplette Logzeile dann noch mit "}".

Natürlich muss in Schritt 1. auch noch gecheckt werden, ob ein neues Objekt begonnen werden muss etc. Aber das sind ja Feinheiten.

Das Ergebnis ist dann ein relativ detailgetreues Abbild der Wirklichkeit, weil die Sensordaten immer dann geschrieben werden, wenn sie gerade anfallen. In anderen Programmiersprachen/Scriptsprachen kann die Logdatei dann Zeile für Zeile ausgelesen werden und dann in PHP z.B. mit

<?php
$logrow = json_decode($row);
?>

zu einem Objekt konvertiert werden. In diesem Objekt liegen die Daten dann wieder sauber vor über:

$logrow->telemetry[0]->feld1 = "wert1";
$logrow->gps->lat = "....";

Grüße
Tim

TEC_MICHL:
Lässt sich das exakt lösen, ich denke da an einen Interrupt? Also das jede Aufzeichung exat bei t1=50 ms; t2 =100 ms; t3 = 150 ms; t4 = 200 ms;

Und wenn ja, wie muss das dann grob aussehen? (Noch nie benutzt)

Grob gesagt benötigst Du einen Timer-Interrupt und und einen FIFO-Ringpuffer.

Drei Schritte:

  1. Den Timer-Interrupt programmierst Du so, dass er alle 50 ms läuft.

  2. Innerhalb der Interrupt-Behandlungsroutine machst Du die Messung und schiebst den Messwert in den FIFO-Ringpuffer.

  3. Innerhalb der loop-Funktion schaust Du laufend nach, ob Daten im FIFO drin sind, und wenn ja, dann schreibst Du diese Daten auf SD-Karte weg.

P.S.: "Messungen" sind dabei natürlich auf solche Art Messungen beschränkt, die ohne die Verwendung von Interrupts funktionieren, damit sie innerhalb einer Interrupt-Behandlungsroutine laufen können.

Hallo Tim,

vielen Dank für Deine ausführliche Antwort! :slight_smile:

Ja, das ist bei mir ähnlich, die Lagesensoren (IMU9) laufen im Moment mit ca 20 Hz (mehr geht nicht, die Berechnung der DCM und Fehlerkorrektur läuft ebenfalls auf dem Arduino. GPS rufe ich ebenfalls mit 1Hz auf. Dann kommen bei mir noch 3 Ultraschalls dazu sowie min 10 andere Werte, die quer Beet anfallen.
Bei mir schaut das im Moment so aus, ich baue einfach jedesmal wenn wir an der Stelle im main loop (siehe code 1. Post) ankommen den String zusammen und baller ihn in eine Zeile, semicolon-getrennt.
Jeder neue Log in die nächste Zeile, usw.. Öffnen kann ich es dann in Excel, Matlab whatever.

 void sdwirte()
 
 {

  if (myFile) 
   { 
  
     sprintf (SDbuf, "%ld; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i.%i; %c; %i.%i; %c;",
                     time_elap, cm[0], cm[1] ,cm[2] ,roll_1, pitch_1, yaw_1, roll_2, pitch_2, yaw_2, ang, speed_cms, RPM, volt_s[0], volt_s[1], volt_s[2], volt_s[3], volt_s[4], speed_max[0], speed_max[1], speed_max[2], speed_max[3], speed_max[4], SD_lat_deg, SD_lat_min, GPS.lat, SD_lon_deg, SD_lon_min, GPS.lon);
                     
    myFile.println(SDbuf);
    //Serial.println(SDbuf);
    }   
 }

Das Problem ist, dass je nach dem, was am Touchscreen gemacht wird, die Schleifendauer stark variiert, von ca 30 bis 200 ms. (Ich verwende den Mega) Vor allem das schreiben, der Werte auf das Display dauert ewig. (das ist dann eine 200 ms Schleife).

Ich habe schon probiert, ebenfalls über timer das Schreiben auf den screen über mehrere Schleifen aufzuteilen. Also in jedem Durchlauf wird nur ein Teil der Daten geschrieben. Insgesamt habe ich dann eine RR von ca 10 Hz auf dem Monitor, was gerade noch zumutbar ist und die Schleifendauer levelt sich ein (ca 100ms). Das ist aber etwas murkelig, da man eigentlich nur mit den Timerzeiten am probieren ist.

Hast Du oder jemand andere damit Erfahung:

http://playground.arduino.cc/Deutsch/HalloWeltMitInterruptUndTimerlibrary

Wenn ich das richtig verstehe, dann löst der immer zu einem bestimmten Zeitpunkt den Log aus. Somit stimmen die Zeiten schon mal und es wird jeweils der letzte Wert in der jeweiligen Variable in den Sting gespeichert... Es ist sicher nicht möglich, alle Daten so zu timen, das sie zum Zeitpunkt (kurz vorher) des Log gemessen werden können.

Ich meine hier irgendwo anders gelesen zu haben, dass die Ansteuerung per Interrupt zwar den loop() programmtechnisch erst einmal sauber hält, aber dass die Funktion, die durch den Interrupt angesprungen wird, dann natürlich ebenfalls Zeit zur Abarbeitung braucht und gleichzeitig unabhängig davon ausgeführt wird, ob Dein Datenset bereits vollständig vorliegt. Wenn Du das per boolschem Wert speicherst (also "kann gespeichert werden" oder eben nicht), und der Interrupt ausgerechnet gerade 5ms vor dem Vervollständigen aller Daten aufgerufen wird, dann erhältst Du ggf. kein "Go" zum Speichern und wartest vielleicht eine weitere Timer-Einheit bis zum nächsten Interrupt (so habe ich jedenfalls Interrupts verstanden, wenn das falsch ist, dann wäre ich über eine Korrektur dankbar).

Wenn Deine Messdaten in den Variabeln allerdings immer nur überschrieben werden und es im Prinzip egal ist, ob sie aus der aktuellen Timer-Einheit stammen oder eben nicht, dann ist die Steuerung per Interrupt sicherlich überlegenswert, jedoch nimmst Du dann auch in Kauf, vielleicht Daten zu haben, die älter sind. Und da Du in Millisekunden denkst, ist das vielleicht nicht so unwichtig mit der Aktualität?

Liebe Grüße

Erstmal kannst du deinen Code verbessern, wenn du es so machst:

static unsigned long timer3;
if (millis() - timer3  >= 50)  //Ist die eingestellt Zeit abgelaufen wird ein Datensatz geloggt.
{
    if(RecOn==1) sdwrite();   // sdwrite : Unterfkt öffnet Datei, schreibt werte, schließ datei usw...
    timer3 += 50;    //Hier also wenn delta t>50 ms
}

Da hast du immerhin die richtige Anzahl Datensätze und die sind auch so gleichmässig wie möglich alle 50 ms verteilt.
Und du hast keine Probleme beim Überlauf von millis().

Die grundsätzlichen Bedenken ( unterschiedlich lange loop()-Dauern, schaffst du es überhaupt alle 50 ms, wie synchron sind die einzelnen Messungen, etc ... ) bleiben natürlich.

...dass je nach dem, was am Touchscreen gemacht wird, die Schleifendauer stark variiert, von ca 30 bis 200 ms.

Wo die 200 ms herkommen, wäre die eigentliche Frage...

Danke nochmal für die vielen hilfreichen Tips.
:slight_smile:

Die 200 ms kommen daher, dass ich das Display alle ca 150 ms update und das erfolgt über die Serielle Schnittstelle. Da müßen dann je nach aktiver Oberfläche bis zu 40 Werte geschrieben werden... Das dauert recht lang, obwohl er schon mit 115200 schaufelt... Warum das so lahm ist, weiß ich nicht.

Übrigens wegen deinem sprintf() da oben:

Man sollte bei so extrem langen Format-Strings an dieser Stelle sprintf_P() und das PSTR() Makro verwenden. Dann belegt der Format String nur Flash und kein RAM:

sprintf _P(SDbuf, PSTR("%ld; %i; %i; ...."), var, ....);

Hallo,
"Warum das so lahm ist, weiß ich nicht."
Ist das ein TFT? Dann kannst Du es bei der Anzahl an Werten vergessen. Alles steng nach "EVA" programmiert?
Probiere doch einmal ein GLCD über 8 Datenleitungen, oder einen Arduino DUE.
DUE wäre ja schön und gut, aber nur wenn der DisplayController und dessen Speicher die Datenmenge schaffen.
Gruß und Spaß
Andreas

Hi,

das Display ist das hier http://www.4dsystems.com.au/product/uLCD_43_PT_AR/ an einem Arduino Mega.
Hat noch einige Pins frei, geht das mit dem?

Das DUE hatte ich auch schon länger im Blick, Problem ist, dass bei mir fast alles, vor allem die Aktuatoren, die ich verwende, 5 Volt brauchen, bzw bis zu 5 Volt ausgeben. Verfahrgeschwindigkeit 0-5 Volt, Fahren bei 5 Volt, Posrückmeldung 0,5 - 4,5 Volt... Und noch ein paar andere 5 Volt Eingänge sowie Ausgänge. Da müßte ich nochmal kräftig den Lotkolben in die Hand nehmen um das alles anzupassen.

Hallo,
die Displays von 4dsystems habe ich auch schon im Blick gehabt. Ich bin da
aber sehr vorsichtig geworden.

Was für Möglichkeiten es bietet- um es auf verschiedene Weise an den Arduino
zu betreiben, könnte im Datenblatt stehen.
ICH glaube- das diese ganzen Schnittstellen (I2C, SPI, seriell u.a.) zum
betreiben eines Display (für blitzschnelle Darstellung) alles Krücken sind.

Wenn man an einem Display "volleLeistung" haben will, wird es nur über den
vollen Datenbus gehen.
Ja, der DUE mit seinen 3,3v. Dafür gibt es ein LevelShifterShield nach 5v,
aber ich weiß nicht ob das etwas ist.

Eine Möglichkeit sollte es aber noch geben. Einige Displays habe einen Speicher
für eine 2. Seite. Die könntest Du im Hintergrund beschreiben und dann auf
Seite 2 umschalten. Das sollte recht schnell gehen. Aber keine Ahnung wie
es funktioniert.
Gruß und Spaß
Andreas

http://www.image-artwork.de/fileadmin/imgart/redakteur/Foto.jpg

Das ist das Display, aber mir sind die Zusätzllichen Ports nicht ganz klar. (Expasions Headers)

Hallo,
das müßte sich auch anders betreiben lassen als über "seriell"
Das ist ein DatenBus (Bus0 - Bus7) dann brauchst Du noch RES, RD, WR, SCL und oder SDA.
Ohne Versorgungsspannung gehen da dann 12 Pin´s drauf.
Dann brauchst Du noch ne´ Lib für den Controller.
Mach da mal ein neues Thema auf, mit dem Bild.
Zu den Anschlüssen und welche Lib, können Dir andere bestimmt etwas sagen.
Gruß und Spaß
Andreas