Hi,
ich protokolliere bei meiner Heizungssteuerung rund 90 Werte
(verschiedene Sensoren, Reglervariablen, Zählerimpulse....). Dazu werden
die Daten auf eine SD-Karte geschrieben. Sie können dann seriell
abgerufen werden und landen in einer Datenbank.
Ich möchte die Daten auf der Karte nun platzsparender speichern, da die
SD-Karte (Software-SPI) beim Lesen vieler angesammelter Datensätze sehr
langsam ist.
Das aktuelle Format sieht so aus:
#xxx;x;xx;xxxx;xxxxx@#xxx;x;xx;xxxx;xxxxx@#xxx;x;xx;xxxx;xxxxx@
# ist die Startmarkierung eines Datensatzes, das @ das Ende. Die Felder
sind mit ; getrennt und enthalten im Klartext lesbare Werte, etwa 231,
oder auch 16-10-31 für das Datum.
Meine Idee wäre, alle Variablen mit 16 Bit zu speichern und auf
Feldtrenner zu verzichten. Bei 90 Werten hätte ich dann 180 Bytes. Die
Sekunden sind nicht relevant, beim Datum müsste ich schauen, wie (ob)
ich es mit 16 Bit abbilden kann.
Aber wie markiere ich Start und Ende eines Datensatzes? Mit einem
Muster? Wie lange sollte diese sein, um einmalig zu sein?
Sowohl der Controller muss ja beim Lesen erkennen, ab wo ein neuer
Datensatz beginnt (es wird pro Anfrage immer nur einer ausgegeben). Das
abrufende System muss Anfang und Ende signalisiert bekommen, damit es
weiß, ob der Datensatz vollständig ist (Länge wird natürlich auch
geprüft).
Danke schön!
Gruß,
Chris
eventuell reicht es ja schon, wenn du die Daten als HEX speicherst. Dan
würden 16 bit noch 4 Zeichen brauchen, hätten den Vorteil das noch
ausreichend Steuerzeichen für Start und Ende frei sind.
Was auch noch geht ist Base64.
Chris schrieb:> Ich möchte die Daten auf der Karte nun platzsparender speichern, da die> SD-Karte (Software-SPI) beim Lesen vieler angesammelter Datensätze sehr> langsam ist.
Bist du sicher, dass das langsame Lesen an der Datenmenge und nicht an
der Zugriffsmethode liegt?
Wird der Zugriff nur langsam, wenn du mittendrin etwas lesen möchtest,
oder auch wenn du sequentiell eine Datei komplett überträgst?
> Peter II (Gast)> Was auch noch geht ist Base64.
Unfug: bläht den Datensatz noch weiter auf.
@ To
> Aber wie markiere ich Start und Ende eines Datensatzes?
Wenn die Sätze immer die gleiche Länge haben: überhaupt nicht.
Ihr habt alle keine Ahnung, WIE ÜBLICH!
Chris, dein Ansatz ist völlig richtig. Du brauchst keinen Trenner. Wenn
du Datensatz 100 auslesen willst, überspringst du einfach 99*180 Bytes.
Das ist das schöne an der Binärcodierung. Die Datensatzlänge ist immer
gleich lang und du kannst jeden Datensatz direkt und ganz ohne
Indizierung anspringen. Datensatz #887785? Kein Problem. Alles klar?
Meine Nutzlast hat im Moment 376 Zeichen. Das sind ja deutlich mehr als
90x2=180 Zeichen + Start/Ende-Markierung. Daher dürfte das schon was
bringen, oder? Im Moment gehen 90 Byte allein für die Trennzeichen
drauf.
Ich denke auch, dass die Zugriffsmethode das Problem ist. Es kann ja
nicht so lange dauern, 376 + 2 Byte zu lesen....
Die Ansteuerung nutzt Sofware-SPI. Soweit ich den Code verstehe,
arbeitet sie nicht mit Timeouts, sondern prüft eine Leitung bzw. ließt
ein Register aus, bevor es weiter geht.
Für jede Anfrage geschieht auf dem Controller folgendes:
- Anfrage vom PC wird empfangen
- fileopen
- dateilaenge=getfilelength()
- ist (dateilaenge >= nextpos+datensatzlänge)?
- fileseek zu nextpos //nextpos ist Position des ersten ungelesenen
Datensatzes)
- solange (gelesenebytes < datensatzlänge) und (zeichen !=
Ende-Markierung) {
zeichen=fileread() //1 Zeichen lesen
uartsenden(zeichen)
}
- fileclose
- Auf Bestätigung warten, dann nextpos=aktuellepos
Beim Zugriff beobachte ich:
- PC sendet Anfrage (RX-LED am Controller blitzt kaum sichtbar)
- LED an der SD-Karte geht dauerhaft an, flimmert am Ende ein paar mal
schnell, geht aus (dauert insgesammt gut 1 Sekunde!!!).
- TX-LED blitzt kurz, PC erhält Datensatz
- PC bestätigt Datensatz (RX-KED am Controller blitzt kaum sichtbar).
Zwischen Anfrage und Reaktion bzw. Bestätigung des PCs vergeht kaum
Zeit. Nach 100 ms werfen Client oder Controller einen Timeout, um ewiges
Warten zu verhindern, wenn mal was nicht klappt.
Der einzige Zugriff, den ich einsparen kann, ist getfilelength(),
eventuell auch fileopen und fileclose. Ich muss die Datei ja nicht jedes
mal neu öffnen/schließen.
Beim Schreiben gehe ich so vor:
- fileopen
- fileseek(filelength)
- filewrite(logdataalsstring)
- fileclose.
Ich werde mal beobachten, ob das auch so lange braucht.
@ASM Superprofi:
Gute Idee, aber wenn etwas verrutscht, lese ich ab dort nur noch Müll
und merke es nicht. Es ist nicht schlimm, wenn mal ein Datensatz
verloren geht, aber falsche Daten würden vieles durcheinander bringen.
Daher muss ich Anfang und Ende schon erkennen können. Wenn die Länge
dazwischen nicht passt, wird der Datensatz verworfen.
Burgfried schrieb:> Unfug: bläht den Datensatz noch weiter auf.
nein, man spart die Trennzeichen und bei großen Zahlen auch noch 1
Zeichen.
> @ To>>> Aber wie markiere ich Start und Ende eines Datensatzes?> Wenn die Sätze immer die gleiche Länge haben: überhaupt nicht.
viel zu unsicher, und unflexibel. Was ist wenn er mal ein Wert mehr
speichern will?
Arduino F. schrieb:> Chris schrieb:>> aber wenn etwas verrutscht,> Irre!
Wie sagte mein Statistikprof immer:
Man darf sich ruhig mal in der Zeile vertun aber lieber nicht in der
Spalte :-D
Ich öffne nun die Datei nur noch bei Programmstart. Lesen und Schreiben
ist somit deutlich schneller. Ich hoffe, es macht nichts, wenn die
Stromzufuhr unterbrochen wird, während die Datei offen ist.
Gruß,
chris
Chris schrieb:> Gute Idee, aber wenn etwas verrutscht, lese ich ab dort nur noch Müll> und merke es nicht.
Der Begriff "Prüfsumme" ist dir bekannt?
> Ich öffne nun die Datei nur noch bei Programmstart. Lesen und> Schreiben ist somit deutlich schneller.
Unter der Annahme, dass du FAT benutzt, ist das auch logisch, denn dein
fopen() muss ja die ganze FAT durchsuchen, um den nächsten freien
Cluster zu finden.
> Ich hoffe, es macht nichts, wenn die> Stromzufuhr unterbrochen wird, während die Datei offen ist.
Doch, tut es. Weder die Datensicherheit noch das Timing ist bei
SD-Karten im Allgemeinen in irgendeiner Form garantiert. Über eine
bestimmte SD-Karte kannst du hingegen durchaus Aussagen treffen.
Es gab dazu letztens eine längliche Diskussion hier:
Beitrag "FatFS Dateisystem"
376 vs. 180 macht gerade 50% Einsparung. Dafuer wuerde ich keine
Kopfstaende machen, bei einer Binaerdatei weiss nachher keiner mehr, was
damit anzufangen ist.
Für meine Messwerterfassung habe ich ein Datenformat mit lauter 16-Bit
Werten. Hat sich ganz gut bewährt.
Als Datensatzende gibt es ein 0xFFFF. Falls es als Wert vorkommt, wird
es in 0xFFFE geändert. Das erste Wort je Datensatz ist die Uhrzeit in
Doppelsekunden, die passen ganz gut in 16 Bit.
Der Tag kommt in den Dateinamen, im Format JJJJMMTT der Sortierung
wegen.
Lass die Feldtrenner weg und begrenze jedes Datenfeld auf die wirklich
nötige Größe. Das mit dem Verrutschen ist Schwachsinn, aber auch das
kann mit einer einfachen CRC32 hinter jedem Datensatz gelöst werden.
Kein FS.
Ein Block = Ein Datensatz
Blockanfang = Datensatzanfang
Kein öffnen und schließen notwendig.
Eine Checksumme über die Daten im Block markiert einen gültigen
Block/Datensatz.
In Block 0 liegt ein Salt, welches zur Prüfsummenberechnung herangezogen
wird. Wird dies verändert, sind alle Blöcke ungültig und damit frei.
Bei Neustart oder wenn das System feststellt, dass eine neue Karte
eingesteckt wurde, wird die Karte nach dem Baumprinzip nach der ersten
freien Stelle durchsucht.
Gruß
Jobst
Chris schrieb:> Aber wie markiere ich Start und Ende eines Datensatzes?
Garnicht. Bei Text ist einfach das '\n' das Ende eines Datensatzes und
logischer Weise das erste Zeichen nach einem '\n' ein neuer Datensatz.
Das Datum braucht man auch nur bei einem Wechsel speichern.
Falls die Datensätze in einem konstanten Zeitinterval erzeugt werden,
braucht man auch nicht jedesmal die Zeit speichern.
Je nachdem wie viele Daten sich nur langsam oder gar nicht ändern könnte
man darüber nachdenken nur Änderungen zu speichern. Dann muss man aber
jedes Datenfeld identifizieren und darf nichts zwischendrin verlieren.
Das lohnt also nur wenn der große Teil der Werte sich deutlich langsamer
ändert als dein Loggingintervall ist.
S. R. schrieb:> Chris schrieb:>> Gute Idee, aber wenn etwas verrutscht, lese ich ab dort nur noch Müll>> und merke es nicht.>> Der Begriff "Prüfsumme" ist dir bekannt?
Dann liest er ab dort nur noch Müll und merkt es. Naja, immerhin ein
kleiner Fortschritt.
>> Ich hoffe, es macht nichts, wenn die>> Stromzufuhr unterbrochen wird, während die Datei offen ist.>> Doch, tut es. Weder die Datensicherheit noch das Timing ist bei> SD-Karten im Allgemeinen in irgendeiner Form garantiert. Über eine> bestimmte SD-Karte kannst du hingegen durchaus Aussagen treffen.
Die SD-Karte weiß doch gar nicht, ob die Datei offen ist. Sie weiß nicht
mal, was eine Datei ist. Damit kann es nicht von der SD-Karte abhängig
sein, ob es ein Problem ist, wenn man die Datei nicht nach jedem Zugriff
schließt und wieder öffnet.
Jobst M. schrieb:> Kein FS.
Genau. Warum machst du eigentlich mit FAT rum, wenn a) du nur mit dem
Mikrocontroller liest und schreibst und b) nur eine Datei (einen
Datensatz) hast?
Vorschlag 1: Benutze FAT und lies die Karte mit nem PC aus -> 1000x
schneller.
Vorschlag 2: Beschreibe die SD-Karte direkt und rück die Daten nur über
den µC raus.
Zwecks Start/Endsymbolen: Pack doch deine Daten in structs und lass den
Compiler bzw. Mikrocontroller sich drum kümmern!
Wenn dir das zu unsicher ist: Speicher ist deutlich billiger als die
Arbeitszeit, da besonderes Framing reinzubasteln (oder am Ende Daten
wieder herzustellen). Darum: speichere doch einfach aligned an der
nächsthöheren Zweierpotenz!
Ein Beispiel: Dein Datensatz ist 90 Bytes. Nächsthöhere Zweierpotenz ist
128. Speichere Datensatz "n" also an der Speicherstelle "128*n". Dadurch
müssen die niedrigsten 7 Bit deiner Adressen immer Null sein. Dadurch
kannst du Fehler detektieren (Wenn Adresse & 0x007F != 0 -> Fehler),
Fehler vermeiden (Adresse = Adresse & 0xFF80) und hast popeleinfache
Zugriffe (for(int i=0;;i++){serial_transmit(start_pointer + i << 7);}
Grund für FAT: ursprünglich habe ich die SD-Karte am PC ausgelesen und
die Daten in Excel übernommen. Der Abruf über Seriell (und dsnn weiter
über Ethernet/TCP) kam erst später.
Ich habe nun folgende Lösung umgesetzt: Die Datei wird beim Abruf
geöffnet und 10 Sekunden nach der letzten Anfrage geschlossen. Somit
gehen aufeinanderfolgende Anfragen schnell genug.
Danke nochmal für die vielen Vorschläge!
Gruß
Chris
Rolf M. schrieb:>> Der Begriff "Prüfsumme" ist dir bekannt?>> Dann liest er ab dort nur noch Müll und merkt es. Naja, immerhin ein> kleiner Fortschritt.
Er schrieb, dass Müll ein echtes Problem ist. Dagegen hilft die
Prüfsumme.
Willst du etwa einen adaptiven Fensterungsalgorithmus bauen, der den
Offset bei Datenverschiebungen automatisch nachführt? Wie soll der
reagieren, wenn ein 4 MB großer Datenblock irgendwo einfach fehlt, weil
die SD-Karte einen Eraseblock beim Abschalten zerstört hat?
> Die SD-Karte weiß doch gar nicht, ob die Datei offen ist. Sie weiß nicht> mal, was eine Datei ist. Damit kann es nicht von der SD-Karte abhängig> sein, ob es ein Problem ist, wenn man die Datei nicht nach jedem Zugriff> schließt und wieder öffnet.
Erstens: Eine SD-Karte weiß sehr wohl, ob ein Eraseblock offen ist oder
nicht. Sie weiß oft sogar, ob der Eraseblock zur FAT gehört oder nicht.
Der Controller der SD-Karte entscheidet, ob offene Eraseblocks bei
Abschaltung gerettet werden oder nicht.
Zweitens: Eine offene Datei kann Daten enthalten, die noch nichtmal an
die SD-Karte gesendet wurden. Es hängt vom Stream ab, ob und wie er
gepuffert ist und Plattencaches gibt es zu allem übel auch noch (selbst
auf Controllern, dann für Teile der FAT). Erst ein fclose() garantiert,
dass die Daten auch tatsächlich in das Gerät geschrieben wurden. Wie
soll die SD-Karte denn deiner Meinung nach Daten absturzfest schreiben,
die sie nie erhalten hat?
Ich habe nicht ohne Grund auf einen anderen Thread verlinkt. Lies den.
Welcher FAT library benutzt du ? Ich benutze FATFS
Da gibt es die funktion f_sync die sehr wichtig is die chance auf
problemen mit spannungsverluss zu minimalisieren. (siehe FATFS docu,
'Critical Section' )
Dabei kannst du am besten den Datensatz 128, 256 oder 512 bytes grosz
damit du viel besser planen kannst wie lange jede schreib/leseaktion
dauert. Wenn ich mich nicht vergisz, kann man sogar der gleichen file
geoefnet halten fuer schreiben und gleich auch (altere) daten auslesen
von gleichen file.
Ich benutze selber auch ein solches system und benutze ZMODEM protocol
um files von micro zum PC ueber zu setzen. Fuer entwicklung benutzte ich
immer Tera Term (vorher auch Hyperterm) weil diese terminals gute
unterstuetzung fuer ZMODEM haben. Wenn dir ZMODEM zu schwierig ist, dann
YMODEM oder ZMODEM.
Fuer PC software habe ich nachdem auch ZMODEM implementiert und bin sehr
zufrienden damit.
Patrick
@bla: Ich schötze 50-100 ms pro Datensatz, inklusive Schreiben in die
DB, Debug, Ausgaben und Unterbrechung durch Dinge, die der Controller
sonst noch so tut (auch während des Datenbrufs wird zwischen 2 Abrufen
die Hauptschleife durchlaufen).
Für meine Anwendung reicht es so auf jeden Fall.