Forum: Mikrocontroller und Digitale Elektronik 16 Bit Werte platzsparend loggen (AVR, C)


von Chris (Gast)


Lesenswert?

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

von Sascha_ (Gast)


Lesenswert?

Fixed Length ist vermutlich nicht viel platzsparender.

Ne rudimentäre Komprimierung könnte aber helfen.

von Peter II (Gast)


Lesenswert?

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.

von Wolfgang (Gast)


Lesenswert?

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?

von Burgfried (Gast)


Lesenswert?

> 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.

von Einer K. (Gast)


Lesenswert?

Sascha_ schrieb:
> Fixed Length
Und nicht im Textformat, sondern roh.

Chris schrieb:
> Mit einem
> Muster?
Brauchst du dann nicht mehr.

von ASM Superprofi (Gast)


Lesenswert?

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?

von Chris (Gast)


Lesenswert?

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.

von Chris (Gast)


Lesenswert?

@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.

von Einer K. (Gast)


Lesenswert?

Chris schrieb:
> aber wenn etwas verrutscht,
Irre!

von Peter II (Gast)


Lesenswert?

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?

von Statistiker (Gast)


Lesenswert?

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

von Chris (Gast)


Lesenswert?

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

von S. R. (svenska)


Lesenswert?

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"

von vorticon (Gast)


Lesenswert?

376 vs. 180 macht gerade 50% Einsparung. Dafuer wuerde ich keine 
Kopfstaende machen, bei einer Binaerdatei weiss nachher keiner mehr, was 
damit anzufangen ist.

von Jobst Q. (joquis)


Lesenswert?

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.

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

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.

von Jobst M. (jobstens-de)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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.

von Der Andere (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Bla (Gast)


Lesenswert?

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);}

von Chris (Gast)


Lesenswert?

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

von S. R. (svenska)


Lesenswert?

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.

von Bla (Gast)


Lesenswert?

Chris schrieb:
> Somit gehen aufeinanderfolgende Anfragen schnell genug.

Aus Interesse - was heißt “schnell genug“?

von pcrom (Gast)


Lesenswert?

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

von Chris (Gast)


Lesenswert?

@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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.