Einführung in das Nut/OS

From Nutwiki
Jump to: navigation, search

System Initialisierung

Gundsätzlich beginnen C-Programme mit main(). Hieran ändert sich auch nichts durch die Verwendung von Nut/OS. Durch das Einbinden der notwendigen Bibliotheken wird vor dem Ausführen der Funktion main() der Mikrocontroller initialisiert. Die Initialisierung findet mit der Routine nutinit statt. Dabei wird unter anderem das Speichermanagement und ein Threadsystem initialisiert. Anschließend wird ein Thread mit einem Idle-Thread gestartet der auch die Timer initialisiert. Zuletzt wird die Kontrolle des Programmablaufs der Funktion main() übergeben.

Ein Beipielprogramm könnte so aussehen:

#include <compiler.h>
int main(void)
{
    for (;;)
}

Bis auf die Zeile mit "#include <compiler.h>" enthält das Programm keine Besonderheiten. Diese Zeile dient dazu ein Problem mit dem AVR-GCC zu beheben. Bei Verwendung des AVR-GCC ohne diese Zeile wird beim Aufruf der main()-Funktion der Stackpointer erneut gesetzt. Um dieses Problem zu beheben wird bei Verwendung des AVR-GCC die main()-Funktion in NutAppMain() umbenannt. Falls eine andere Header-Datei verwendet wird, braucht compiler.h nicht ausdrücklich eingebunden werden, da dieses in den anderen Headerdateien automatisch geschieht.

Thread Management

Häufig sollen mehre Aufgaben scheinbar gleichzeitig von der Hardware ausgeführt werden. Beispielsweise gehört dazu die regelmäßige abfrage von Sensoren während im Hintergrund Anfragen über das Netzwerk beantwortet werden sollen. klassischerweise werden solche Probleme mittels Interruptroutinen gelöst. Eleganter ist es, dieses mit Threads zu lösen. Threads sind bei Nut/OS um so wichtiger, da nur eine Applikation gleichzeitig laufen kann. Einen Thread kann man sich als eine Art Unterapplikation vorstellen, die unabhängig von dem anderen Thread läuft. Bei Nut/OS sind Threads als kooperatives Multithreading implementiert. Anders als beim preemptiven Multithreading wird die jeweilige Rechenzeit dem jeweiligen Thread nicht fest zugeteilt. Vielmehr kann ein Thread Rechenzeit solange beanspruchen, bis er Rechenzeit an einen anderen Thread abgibt. Gibt der Thread die Rechenzeit nicht ab, können die anderen Threads nicht laufen. Dies kann eine Fehlerquelle darstellen. Vorteil dieses Threadmdodells ist, dass wenig Rechenzeit für das Multithreading verloren geht. Da alle Threads im selben Adressbereich liegen und dieselbe Hardware benutzen, ist der Overhead für den Taskwechsel sehr kurz. Gleichzeitig muss bei dem Teilen der Resourcen darauf geachtet werden, dass die Threads nicht gegenseitig sich durch Überschreiben von Speicherbereichen oder Zugriff auf Resourcen stören. Dieses stellt aber auch einen Vorteil da, da Threads sich Resourcen auch teilen können. Welcher Thread bei einem Taskwechsel als nächstes läuft, hängt davon ab welche Priorität er hat. Jeder Task hat eine Nummer zwischen 254 und 0. Je kleiner der Wert ist, desto höher ist die Priorität. Ein Thread wird sofern er die Kontrolle nicht abgibt nur von Interrupts unterbrochen. Die Funktion main() ist immer ein eigener Thread, der zusammen mit dem Idle-Thread läuft. Der Idle-Thread ist von der Applikation aus nicht sichtbar.

Ein neuer Thread wird mit der Funktion NutThreadCreate erzeugt. Ein Thread ist nichts anderes als eine C-Funktion. Um plattformspezifische Deteils zu verbergen, wird zur Deklaration das THREAD-Makro verwendet.


THREAD(Thread1, arg)
{
    for (;;) {
        NutSleep(125)
    }
}

int main(void)
{
    NutThreadCreate("t1", Thread1, 0, 512);
    for (;;) {
        NutSleep(125)
    }
}

Das Beispielprogramm besteht aus zwei Endlosschleifen. Zunächst wird in der main()-Funkion der Thread erzeugt. Anschließend läuft das Programm in eine Endlosschleife. Normalerweise könnte andere Code-Teile nicht mehr ausgeführt werden, das das Programm in der Endlosschleife "hängt". Da die Endlosschleife die Kontrolle an andere Threads mit NutSleep abgibt, kommt auch der Thread zur Ausführung, der seinerseits eine Endlosschleife ist. Wichtig ist auch hier, dass der Thread die Kontrolle mit NutSleep wieder abgibt, da sonst der Main-Thread nicht weiter ausgeführt würde. Jeder Thread muss also die Prozesskontrolle abgeben.

Timer Management

Heap Management

Event Management

Stream I/O

Typischerweise verwenden Programme die Standard-I/O-Bibliothek (z.B. printf) zwecks ausgabe. Da es bei Mikrocontrollern kein Standard-Ausgabesystem gibt, bedarf es einer Anpassung. Nut/OS bietet dazu Device-Treiber und eine eigene Bibliothek nutcrt, die die Standard-Funktionen der Laufzeitbibliothek des Compilers überschreibt:

  • void clearerr (FILE *stream) - Setzt den Fehlerstatus eines Streams zurück.
  • int fclose (FILE *stream) - Schließt einen Stream.
  • void fcloseall (void) - Schließt alle offene Streams.
  • int feof (FILE *stream) - Prüft, ob ein Stream das Dateiende erreicht hat.
  • int ferror (FILE *stream) - Prüft einen Fehler in einem Dateistream.
  • int fflush (FILE *stream) - Flush eines Streams.
  • int fgetc (FILE *stream) - Liest ein Zeichen von einem Stream.
  • char * fgets (char *buffer, int count, FILE *stream) - Liest eine Zeile von einem Stream.
  • FILE * fopen (CONST char *name, CONST char *mode) - Öffnen eines Streams.
  • int fprintf (FILE *stream, CONST char *fmt,...) - Gibt Daten formatiert in einem Stream aus.
  • int fpurge (FILE *stream) - Löscht einen Stream, z.B. die in einem Eingabepuffer gespeicherten werden gelöscht.
  • int fputc (int c, FILE *stream) - Schreibt ein Zeichen auf einen Stream.
  • int fputs (CONST char *string, FILE *stream) - Schreibe einen String in einen Stream.
  • size_t fread (void *buffer, size_t size, size_t count, FILE *stream) - Lese Daten von einem Stream.
  • FILE * freopen (CONST char *name, CONST char *mode, FILE *stream) - Einen Stream erneut öffnen.
  • int fscanf (FILE *stream, CONST char *fmt,...) - Liest formatierte Daten von einem Stream.
  • int fseek (FILE *stream, long offset, int origin) - Verschiebt die Lese-/Schreibposition eines Streams.
  • long ftell (FILE *stream) - Gibt die Lese-/Schreibposition eines Streams zurück.
  • size_t fwrite (CONST void *data, size_t size, size_t count, FILE *stream) - Schreibt Daten in einen Stream.
  • int getc (FILE *stream) - Liest ein Zeichen von einem Stream.
  • int getchar (void) - Zeichenweise Lesen von der Standardeingabe.
  • char * gets (char *buffer) - Zeilenweises Lesen von der Standardeingabe.
  • int printf (CONST char *fmt,...) - Formatierte Ausgabe auf dem Standardausgabestream.
  • int putc (int c, FILE *stream) - Schreibe ein Zeichen auf den Stream.
  • int putchar (int c) - Schreibe ein Zeichen auf die Standardausgabe.
  • int puts (CONST char *string) - Gibt einen Stream auf stdout aus.
  • int scanf (CONST char *fmt,...) - Lese formatierte Daten vom Standardeingabestream.
  • int sprintf (char *buffer, CONST char *fmt,...) - Schreibt formatiert Daten in einen Stream.
  • int sscanf (CONST char *string, CONST char *fmt,...) - Least formatierte Daten von einem string.
  • int ungetc (int c, FILE *stream) - Schiebt gelesenes Zeichen in Stream zurück.
  • int vfprintf (FILE *stream, CONST char *fmt, va_list ap) - Schreibt Argumentenliste in vorgegebenen Format in Stream.
  • int vfscanf (FILE *stream, CONST char *fmt, va_list ap) - Liest formatierte Daten von einem Stream.
  • int vsprintf (char *buffer, CONST char *fmt, va_list ap) - Schreibt eine Liste von Argumenten formatiert in einen String.
  • int vsscanf (CONST char *string, CONST char *fmt, va_list ap) - Liest formatiete Daten von einen string.

Darüber hinaus gibt es eine Reihe weiterer low-level I/O-Funktionen:

  • FILE * _fdopen (int fd, CONST char *mode) - Open a stream associated with a file, device or socket descriptor.
  • int _fileno (FILE *stream) - Get the file descriptor associated with a stream.
  • void _flushall (void) - Flushes all streams
  • int _fmode (CONST char *mode)
  • int _getf (int _getb(int, void *, size_t), int fd, CONST char *fmt, va_list ap) - Read formatted data using a given input function.
  • int _putf (int _putb(int, CONST void *, size_t), int fd, CONST char *fmt, va_list ap) - Write formatted data using a given output function.

Um bei Harvard Architekturen, z.B. Atmel AVR, RAM Speicher einzusparen, gibt es für bestimmte Funktionen eine Variante, bei der der konstante String direkt aus dem Flash Speicher gelesene wird:

  • int fprintf_P (FILE *stream, PGM_P fmt,...) - Print formatted data to a stream.
  • int fputs_P (PGM_P string, FILE *stream) - Write a string from progam memory to a stream.
  • int fscanf_P (FILE *stream, PGM_P fmt,...) - Read formatted data from a stream.
  • size_t fwrite_P (PGM_P data, size_t size, size_t count, FILE *stream) - Write data from program space to a stream.
  • int printf_P (PGM_P fmt,...) - Print formatted output to the standard output stream.
  • int puts_P (PGM_P string) - Write a string from program memory to stdout.
  • int scanf_P (PGM_P fmt,...) - Read formatted data from the standard input stream.
  • int sprintf_P (char *buffer, PGM_P fmt,...) - Write formatted data to a string.
  • int sscanf_P (CONST char *string, CONST char *fmt,...) - Read formatted data from a string.
  • int vfprintf_P (FILE *stream, PGM_P fmt, va_list ap) - Write argument list to a stream using a given format.
  • int vfscanf_P (FILE *stream, PGM_P fmt, va_list ap) - Read formatted data from a stream.
  • int vsprintf_P (char *buffer, PGM_P fmt, va_list ap) - Write argument list to a string using a given format.
  • int vsscanf_P (CONST char *string, PGM_P fmt, va_list ap) - Read formatted data from a string.


Aufgrund der Vielzahl der angepassten I/O-Funktionen ist es einfach, ein PC-Programm zu portieren. Wichtig dabei ist jedoch, dass vor der Nutzung eines Standarddevices wie z.B. stdin, stdout oder stderr, dieses Device per Devicetreiber zugewiesen wird.

#include <stdio.h>
#include <io.h>
#include <dev/board.h>

int main(void)
{
    /* Device für Ausgabe vorbereiten */
    u_long baud = 115200;
    NutRegisterDevice(&DEV_UART, 0, 0);
    freopen(DEV_UART_NAME, "w", stdout);
    _ioctl(_fileno(stdout), UART_SETSPEED, &baud);   

    printf("Hello world!\n");

    for(;;);
}

Eine Fehlerquelle bei der Verwendung der Nut/OS Ausgabefunktionen kann sein, dass man tatsächlich die Ausgaberoutinen der Standard-Bibliothek versehentlich verwendet hat. Ein Blick in die Mapping-Datei verschafft dann Klarheit.

Dateisystem

Device Treiber

Netzwerk-API