Forum: Mikrocontroller und Digitale Elektronik AVR: INI-Datei RAM-sparend durchsuchen?


von Martin M. (martin69)


Angehängte Dateien:

Lesenswert?

Hallo,

ich möchte bei meinen Projekten (Geräte am Hausbus) zukünftig die Daten 
auf einer SD-Karte ablegen. Im Anhang ein Beispiel einer INI-Datei, die 
ich mit dem AVR lesen möchte. Gibt es eine elegante Lösung, dies ohne 
großen RAM-Bedarf zu bewerkstelligen? Ich habe schon gegoogelt, habe 
aber nix gescheites gefunden.

Bei meinem Webserver Marke Eigenbau habe ich ein externes RAM drauf, da 
geht es recht einfach:
1. Datei von SD-Karte komplett ins externe RAM kopieren.
2. entsprechender Bereich (z.B. "[Zimmer 8]") in der Datei mit strstr() 
suchen. Diesen String zwischen "[Zimmer 8]" und der nächsten eckigen 
Klammer in ein neues Feld (String 2) im externen RAM kopieren.
3. String 2 mit dem Suchbegriff (z.B. "name=") mittels strstr() 
durchsuchen.

Bei meinen anderen (älteren) Platinen habe ich zwar einen SD-Kartenleser 
drauf, aber kein externes RAM. Ein Cluster der SD-Karte hat 512 Byte. 
Der RAM-Verbrauch im AVR sollte z.B. 1024 Byte nicht überschreiten (je 
weniger, desto besser). Ich sehe ein gewisses Problem, wenn die 
gesuchten Daten zwischen 2 Clustern sind (der Anfang z.B. in Cluster 6, 
der Rest in Cluster 7).

Hat jemand schon mal so etwas programmiert? Bzw. kennt einen Quellcode, 
der so etwas macht?

Gruß
Martin

PS: bin eher der Hardwaremann, daher stelle ich mich diesbezüglich auch 
etwas "ungeschickt" an. Ich denke, wenn man mit entsprechend passenderen 
Fachbegriffen googelt, findet man sicherlich im Netz etwas.

: Verschoben durch User
von Markus J. (markusj)


Lesenswert?

Hallo Martin,

bist du auf dieses Konfigurationsformat festgelegt? Schätze eher nein.
Dann wirf es weg!
Bau dir ein Binärformat mit einer festen Struktur bei dem ein einzelner 
Block wenige (und festgelegt!) viele Bytes groß ist. Je näher dieses 
Format an der internen Repräsentation ist, desto einfacher und 
effektiver geht die Verarbeitung vonstatten.
Auf so beschränkten Systemen musst du dich damit anfreunden, dass die 
Lesbarkeit von (Konfigurations)-Daten durch Menschen (meist unnötiger) 
Luxus ist.
Wenn es dich glücklicher macht, kannst du dir ja ein kleines Programm 
schreiben, dass die Ini-Datei in das interne Datenformat übersetzt.

mfG
Markus

PS: Deine Verarbeitung ist äußerst Ineffektiv: Es würde reichen, wenn du 
ab Beginn des Abschnittes solange einzelne Zeilen auswertest, bis du zum 
Beginn eines neuen Abschnittes kommst.
Also nicht mehr in Abschnitten denken, sondern stattdessen Zeilenbasiert 
(+ ein bisschen Hintergrundinformation über den gerade 
aktiven/bearbeiteten Abschnitt).

von Cornu (Gast)


Lesenswert?

Hi,
Hab gerade letztens das gleiche, auch mit dem INI Format und nem AVR mit 
SD-Karte umgesetzt.
Dabei hab ich wie markusj schreibt immer nur Zeilenweise ausgewertet, 
wobei auch jede Variable nur einmal vorkommt in der ganzen INI-Datei, 
das bedeutet, dass die einzelnen Unterabschnitte nur der Lesbarkeit 
dienen ...
Ich hab so immer nur eine Zeile eingelesen, den ersten Teil bis zum = 
verglichen und den entsprechenden Wert dann gespeichert...

von Martin M. (martin69)


Lesenswert?

Hallo,

vielen Dank für die Antworten.

Die INI-Datei sollte schon mit einem Editor (z.B. Notepad) editierbar 
sein, das macht das Handling einfacher. So ist zumindest meine Meinung. 
Mit Binärdateien habe ich bisher nichts gemacht. Wie gesagt, bin mit der 
Programmierung ja nicht so fit.

Das mit den Abschnitten möchte ich eigentlich auch beibehalten, da die 
Namen der Einträge sind oft wiederholen (z.B. "name=" kommt in jedem 
Abschnitt "[Zimmer x]" vor).
Ich könnte auch die Abschnittkennung "[Zimmer x]" weg lassen und dann 
"Zimmer00_name=", "Zimmer01_name=", "Zimmer03_name=",usw. schreiben, 
aber dann muß man bei dem Editieren mehr auf eventuelle Fehler achten. 
Momentan läßt sich die Sache einfacher erweitern, indem ich den Block 
kopiere und nur eine neue Nummer in der Abschnittkennung eintragen muß. 
Zudem wird auch die Datei größer, da die Parameterkennungen länger 
werden

Gruß
Martin

von Martin M. (martin69)


Lesenswert?

Nachtrag:

vielleicht war es nicht ganz klar, daß ich die SD-Karte mit dem AVR nur 
lese. Beschreiben tue ich diese mit dem Editor am PC. Daher muß die 
Datei lesbar sein (ASCII-Zeichen).

Ich habe mir nochmals wegen den Clusterübergängen Gedanken gemacht. Das 
ist mit den Abschnitten nicht so ganz einfach. Wenn man nun doch keine 
Abschnitte macht, sondern die Parameterkennung eindeutig ist, würde es 
die Sache mit den Clusterübergängen deutlich vereinfachen.

z.B.:

Version A:
Zeile für Zeile nacheinander in ein Array (mit z.B. 50 Bytes) kopieren 
und dort jeweils nach der Parameterkennung suchen.

Version B:
* ersten Cluster von der SD-Karte holen und in den 512 Byte des Clusters 
nach der Paramerennung suchen.
* wird die Parameterkennung nicht gefunden, nächsten Cluster holen und 
dort suchen.
* sind alle Cluster der Datei durchsucht worden und die Parameterkennung 
wurde nicht gefunden, dann muß diese an einem Clusterübergang sein. 
Hierzu müßte ich dann noch die Textzeilen, die zwischen den Clustern 
sind, in ein temporäres (kleines) Array schreiben und durchsuchen.
=> wäre sicherlich schneller als Variante A.


PS:
Daß man am Ende des Arrays ein 0x00 anhängen muß ist mir schon klar. 
Sonst erkennt strstr() das Ende des Arrays nicht.

Gruß
Martin

von Harald S. (harri)


Lesenswert?

Hi Martin,

ich hab zwar von Programmierung nicht viel Ahnung aber will trotzdem mal 
meinen Senf dazu geben ;-) Vielleicht lerne ich dabei ja auch noch was.

Was für Lesefunktionen hast du denn bereits im AVR drin? Nur 
Lowlevel-Zugriff auf die Karte oder auch Funktionen für das 
FAT-Dateisystem?

Ich gehe mal davon aus, dass Dateisystemfunktionen vorhanden sind. Sonst 
könntest du schlecht feststellen, dass in einem anderen Cluster noch 
weitere Daten liegen und welcher Cluster das ist.
Welche Funktionen bietet denn der Dateisystemtreiber an? Nur Blockweise 
lesen/schreiben oder bei Textdateien auch Zeilenweise?
In dem Fall würde ich auch (wie schon weiter oben beschreiben) die Datei 
zeilenweise einlesen. Wenn das erste Zeichen ein [ ist, dann geht es um 
einen neuen Abschnitt - Abschnittsname merken. Alle Variablen bis zur 
nächsten [-Zeile gehören zu diesem Abschnitt.
Der RAM-Bedarf dürfte so nur der Länge der längsten zu erwartenden Zeile 
zzgl. der Abschnittsmerker entsprechen.

Wenn das Dateisystem nicht zeilenweise einlesen kann, dann kommt noch 
das RAM zur Pufferung eines 512Byte Blocks dazu. Ein ganzer Cluster 
umfasst bei FAT ja mehrere Blöcke, der Cluster muss hoffentlich nicht 
komplett ins RAM - das können nämlich bis zu 32kB werden.

Ob deine Daten jetzt genau auf der Grenze von einem zum nächsten Block 
lagen, kannst du ja anhand der ini-Syntax prüfen:
- wenn die Zeile mit [ begonnen hat, dann muss sie im nächsten Block mit 
dem zugehörigen ] enden
- wenn die Zeile mit was anderem begonnen hat, dann muss erstmal ein = 
kommen und dann irgendwann ein Zeilenende (CR-LF)
- wenn im nächsten Block etwas unerwartetes kommt (erster Block hatte 
ein [, im nächsten Block kommt dann ein = oder Zeilenende), dann kannst 
du von einem defekten Dateisystem ausgehen.

mfg
Harri

von Martin M. (martin69)


Lesenswert?

Hi Harry,

ich habe SD-Kartenfunktionen verwendet, die ich im Internet gefunden 
habe. Eine FAT.c ist auch dabei. Das Auslesen von Dateien funktioniert 
ja auch problemlos.

Bisher verwende ich nur blockweises Lesen (je 1 Block mit 512 Byte). Es 
gibt in dem Sourcefile folgende Funktionen:

extern unsigned char sd_card_read_byte(void);
extern void sd_card_write_byte(unsigned char);
extern void sd_card_read_block(unsigned char *,char *,unsigned in);
extern unsigned char sd_card_init(void);
extern unsigned char sd_card_read_sector (unsigned long, char *);
extern unsigned char sd_card_write_sector (unsigned long,char *);
extern unsigned char sd_card_write_command (unsigned char *);
extern unsigned char sd_card_read_csd (char *);
extern unsigned char sd_card_read_cid (char *);

Ich denke nicht, daß eine davon das zeilenweises Einlesen der Datei 
ermöglicht... Zumindest verstehe ich dies so.

Ich glaube ich habe da Cluster und Block verwechselt. Ich meinte immer 
so einen Datenblock mit 512 Byte, aus denen eine Datei besteht.

Ich denke ich muß es zeilenweise Einlesen, sonst wird das zu aufwändig. 
So weit habe ich mich durch die Infos von Euch schon überzeugen lassen. 
Ob ich nun das mit der Abschnittkennung mache oder nicht, muß ich mal 
überlegen. Das macht die Sache programmtechnisch halt nicht gerade 
einfacher...

Gruß
Martin

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Willst du die Datei den einmalig parsen und dir dann alle 
Informationen um RAM ablegen? Oder soll die Datei "immer mal wieder" 
geparst werden und dir dann ein bestimmtes Datum zurückliefern?

von Martin M. (martin69)


Lesenswert?

Hallo Läubi,

das Ganze wird nur einmal beim Booten gelesen. Die Einträge werden im 
RAM abgelegt.

Gruß
Martin

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Dann würde ich einfach direkt auf dem Rampuffer Zeichenbasiert arbeiten.
1
while(!EOF)
2
  buffer = readSektor();
3
  for i = 0; i < 512 i++
4
   ... tu was ...
braucht auf jedenfall kaum zusätzlichen RAM (höchsten ein paar lokale 
Zustandsvariablen die am Ende wieder freigegeben werden können).

von Martin M. (martin69)


Lesenswert?

zeichenbasiert arbeiten? Du klapperst eines nach dem anderen Byte in dem 
Block ab? Wie macht man da sinnvoll eine Abfrage nach einer bestimmten 
Stringfolge wie z.B. "zimmer="?

strcpy(suchbegriff, "zimmer=");

if (
   (feld[i] == suchbegriff[0]) &&
   (feld[i+1] == suchbegriff[1]) &&
   (feld[i+1] == suchbegriff[2]) &&
...
   (feld[i+n] == suchbegriff[n-1]) &&   // wobei n = 
strlen(suchbegriff);
   )
   { // Übereinstimmung
   }

Geht sicherlich mit Zeigern einfacher, aber mit Zeigern hab ichs nicht 
so. Felder kann ich mir besser vorstellen....

Gruß
Martin

von Martin T. (mthomas) (Moderator) Benutzerseite


Lesenswert?

Vielleicht schon ausreichend kompakt und fertig vorgekaut: 
http://www.compuphase.com/minini.htm

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Also ich würde es wie schon angedeutet Zustandsbasiert machen.
Du reservierst dir einen Buffer (z.B. 30 Zeichen)
Und du hast einen Zustand, und eine aktuelle Sektion, sowie einen 
Parameter.

Dann beginnst du das parsen:
- Wenn das Zeichen ein '[' ist gehst du in den Zustand SEKTION, 
schließt die aktuelle Sektion ab (falls eine existiert) und beginnst 
eine neue.
- Wenn das Zeichen ']' auftaucht und du im Zustand SEKTION bist wird 
der Name der aktuellen Sektion auf den Inhalt des Buffers gesetzt. (bis 
zum \0 Zeichen und Bufferpostion danach wieder zurücksetzen) und du 
wechselst in den Zustand NAME
- Wenn das Zeichen ein '=' ist und du im Zustand NAME bist wird der 
Name des aktuellen Parameters gesetzt/verglichen und du wechselst in den 
Zustand WERT
- Wenn das aktuelle Zeichen ein Zeilenende-Zeichen ist und du im Zustand 
WERT bist schreibst du den aktuellen Parameter den Wert des Buffers
- In allen anderen Fällen schreibst du das aktuelle Zeichen an die 
aktuelle Buffer Postion und an Position+1 das \0 Zeichen (ggf. auf 
Puferüberlauf prüfen)

Hier ist jetzt natürlich nicht jeder Fall berücksichtigt 
(Kommentarzeilen? Leerzeilen? Ungültige Parameter) aber auf diese Weise 
bearbeitest du immer nur ein Zeichen, und wenn du einen "verarbeitbaren" 
Wert hast wird dieser genutzt und du wechselst den Zustand.

von Martin M. (martin69)


Lesenswert?

@Martin Thomas: danke für den interessanten Link.

@Läubi: danke für die Tipps. Ich denke ich habe es soweit verstanden.

Gruß
Martin

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.