Kleines Dateisystem für Mikrocontroller

Beim portieren einer C-Bibliothek vom PC auf einen Mikrocontroller bin ich auf ein Problem gestoßen: Die Bibliothek arbeitet mit Konfigurationsdateien die zuvor geladen werden müssen. Das Umschreiben der Lib wäre sehr aufwändig gewesen und der Quellcode danach nicht mehr kompatibel, also nicht mehr wartbar, wenn eine neuere Version herauskommen.

Eine andere Lösung ist es die verwendeten Dateifunktionen (fopen, fscanf, fclose) zu implementieren und ein „virtuelles Dateisystem“ vorzugaukeln. Wobei doch irgendwie jedes Dateisystem virtuell und abstrahiert daher kommt…

Als erstes habe ich natürlich das Netz nach bereits umgesetzten Lösungen abgesucht und leider nichts gefunden (Tipps gern per Kommentar). Also musste ich mir was eigenes schreiben.

Verwendet man einen CortexM Mikrocontroller, kann man auf den Luxus einer bereits portierten newlib zurückgreifen und die POSIX-konformen Dateiaufrufe verebben in den sogenannten syscalls. Die syscalls können auch als Backend für die libc-Funktionen betrachten werden und die Funktion der syscalls werden von einem Betriebssystem auf entsprechende Ressourcen umgeleitet.

Die syscalls stellen z.B. einen bequemen Weg bereit über den printf-Aufruf Debugnachrichten auf ein UART-Interface umzuleiten. Dazu sollte die Datei syscalls.c mit der Minimal-Variante in das Projekt eingefügt werden (Linkereinstellungen der newlib beachten!). Anschließend werden in der write-Funktion die übergebenen Zeichen auf die gewünschte UART weitergeleitet.

Ganz ähnlich funktioniert die read-Funktion. Es wird nach einer bestimmten Anzahl von Zeichen gefragt. Diese sind aus dem übergebenen Datei-Diskriptor zu laden. Für stdin, stdout und stderr sind die Diskriptoren festgelegt und müssen nicht geöffnet werden, bevor gelesen oder geschrieben wird. Eine geöffnete Datei muss ebenfalls vom Betriebssystem einen Diskriptor zugewiesen bekommen, also einen unverwechselbaren File-Handle.

Nun ist es jedoch so, dass es kein Dateisystem auf dem Mikrocontroller gibt und damit auch keine Implementierung die auf ein fopen etwas anderes als ein -1, also Fehler beim Öffnen, zurückliefern kann. Dazu die Minimal-Implementierung der newlib syscalls:

int open(const char *name, int flags, int mode) {
    return -1;
}

Nun sind die nötigen Funktionen schon fast ausfindig gemacht. Weiterhin wichtig für ein Dateihandling, ist eine Funktion um in einer Datei den Lese-/Schreibpointer zu setzen: lseek. Diese wird nicht nur durch manuelles setzen des Pointers angesprungen (fseek), sondern auch durch den Aufruf von fscanf.

Der Ablauf zum Lesen einer Datei ist also folgendermaßen:

Wir rufen fopen auf und wollen eine Datei mit dem gegebenen Pfad/Dateinamen öffnen. Der Befehl landet in der open-Funktion der syscalls.c.

Implementierung open: Vergleichen des Dateinamens mit einer internen Dateiliste und beim Vorhandensein der Datei wird ein File-Pointer zurückgegeben (ungleich 0, 1 oder 2). Ein interner Zeiger wird auf den Anfang der Datei/Speicherstelle gesetzt.

Mit fscanf wird ein Parameter aus der Datei eingelesen.

Implementierung read: Vergleichen ob der übergebene File-Pointer einer geöffneten Datei angehört. Lesen der Datei/Speicherstelle ab der zuletzt bekannten Position.

Implementierung lseek: Vergleichen ob der übergebene File-Pointer einer geöffneten Datei angehört. Setzen des File-Pointers unter Berücksichtigung der Dateigrenzen.

Mit fclose wird die Datei wieder geschlossen.

Implementierung close: Vergleichen ob der übergebene File-Pointer einer geöffneten Datei angehört. Die virtuelle Datei wird als geschlossen markiert.

Mit fwrite in eine Datei schreiben.

Implementierung write: Soweit habe ich das kleine Dateisystem nicht getrieben. Da ein Flash-Page Write und Errase besonders Handling benötigt, würde diese Implementierung auch etwas aufwändiger ausfallen, als nur vom Flash zu lesen. Damit ist das Dateisystem bisher read-only!

Die Datei auf dem Mikrocontroller abzulegen ist übrigens recht einfach. Zum Beispiel auf dieser Seite kann eine Datei in ein const char array gewandelt werden. Dies wird dann in eine Header-Datei abgelegt und der Pointer auf das Array und die Länge der Datei wird in einer Struktur eingearbeitet. Diese Struktur repräsentiert das Dateisystem, im Grunde eine sehr simple File Allocation Table (FAT).

Das Hinzufügen einer neuen Datei erfolgt so:

  1. Datei in char-Array wandeln: http://fuxx.h1.ru/bin2c/index.en.php
  2. Datei/char-Array in einer Header-Datei speichern (im Github-Beispiel: my_config_files.h)
  3. Dateiname, -pointer und -größe in flash_fs.c Struktur eintragen und define mit der Anzahl an Dateien inkrementieren:
#define FILE_FS_NUMBER_OF_FILES 2
static const char *file_names[FILE_FS_NUMBER_OF_FILES] =
{
 "config.txt",
 "config_dbg.txt"
};

flash_file my_files[FILE_FS_NUMBER_OF_FILES] =
{
 {
 config_txt, sizeof(config_txt), -1, 0
 },
 {
 config_dbg_txt, sizeof(config_dbg_txt), -1, 0
 }
};

Anschließend kann die Datei im Programm, ähnlich wie von einem PC gewohnt, verarbeitet werden:

FILE *cfg = fopen(„config.txt“, „r“);
int reading;
fscanf(cfg,“PWMDutyCycle=%d\n“, &reading);
fclose(cfg);

Das komplette Demo-Projekt für das STM32F4 Discovery-Board gibt es auf meinem Github-Account:

https://github.com/Counterfeiter/CortexM-read-only-FileSystem

Limitierungen und mögliche Drawbacks des aktuellen Dateisystems:

  • kein binäres Dateihandling möglich
  • kein Schreiben von Dateien möglich
  • die newlib reserviert dynamischen Speicher im Hintergrund

Vorteile:

  • benötigt nur sehr wenig Flash/RAM (Demoprojekt: 14 KByte Flash)
  • wird das Dateisystem nicht benötigt, wird in der syscalls.c die Headerdatei (flash_fs.h) einfach nicht inkludiert
  • stdin/stdout/stderr können weiterhin parallel verwendet werden (Debug printf)
  • Bibliotheken die für den PC geschrieben wurden und auf Dateien lesen möchten können einfacher für den Mikrocontroller portiert werden.

Wer möchte kann das Dateisystem gern weiter optimieren. Meine Ideen wären:

  • Schreiben von Dateien als RAM-Kopie oder direkt in den Flash
  • Erstellen eines PC-Programms, um alle Dateien eines Ordners schnell in eine Header-Datei zu überführen und die nötige Struktur automatisch zu erstellen -> Einbindung in den Build-Prozess
  • Dateien als verkette Liste ablegen -> ersten 4 Byte nicht 0, dann ist es Pointer auf nächste Datei

Tipp: Wer mit der newlib Fließkommazahlen lesen und schreiben will, muss folgende Linker-Flags setzen:

-u _printf_float -u _scanf_float
Advertisements
Dieser Beitrag wurde unter Allgemein veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Eine Antwort zu Kleines Dateisystem für Mikrocontroller

  1. Pingback: Machine learning auf einem Mikrocontroller | Electronic Stuff

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s