Ethernut Home Hardware Firmware Tools Download Community
 
Ethernuts  /  Ethernut 3  /  No OS
Suche | Impressum | English

No OS

Nicht immer macht es Sinn, Anwendungen unter einem Betriebssystem laufen zu lassen. Dafür gibt es eine Reihe von Gründen:

Abgesehen vielleicht vom zuerst genannten Punkt, benötigt man für das Erstellen solcher Programme mindestens

Diese werden oft vom Hersteller des Mikrocontrollers zur Verfügung gestellt, allerdings nicht immer passend zu der verwendeten Toolchain und dem speziellen Board. Eine entsprechende Anpassung kann u.U. sehr aufwendig werden. Dieser Beitrag soll zeigen, wie man die in Nut/OS bereits für den GNU-Compiler und Ethernut 3 vorhandene Implementierung nutzen kann, ohne Nut/OS selbst zu verwenden.

Verzeichnis erstellen

Voraussetzung ist, dass man mit dem Konfigurator bereits ein Build-Verzeichnis (nutbld) und ein Sample-Verzeichnis (nutapp) erstellt hat.

Wie für normale Nut/OS-Anwendungen üblich, erstellt man im nutapp eine neues Unterverzeichnis für die neue Applikation. Darin enthalten sind mindestens der Quellcode der Anwendung und das Makefile. Beispielhaft erstellen wir noos.c für den Quellcode und folgenden Makefile:

PROJ = noos
include ../Makedefs
SRCS =  $(PROJ).c
OBJS =  $(SRCS:.c=.o)
LIBS =
TARG =  $(PROJ).hex
all: $(OBJS) $(TARG) $(ITARG) $(DTARG)
include ../Makerules

Dabei werden Makedefs und Makerules aus dem Nut/OS übernommen, was uns eine Menge Arbeit erspart. Man beachte, dass hier der Eintrag LIBS leer bleibt. Wir verwenden also keine Bibliotheksfunktion aus Nut/OS. Wenn Sie eigene Bibliotheken verwenden möchten, können Sie diese dort eintragen.

Was genau in die Quelldatei noos.c kommt, wird im folgenden Abschnitt behandelt.

Quellcode erstellen

Es soll ein möglichst allgemein gehaltenes Beispiel erstellt werden. Hier bietet sich natürlich das übliche "Hello world!" Programm an. Notwendigerweise benötigen wir dazu ein Ausgabe-Device, z.B. die serielle Schnittstelle. Zwar müssen wir dazu in die Tiefen der UART-Initialisierung eindringen, Sie können diese Funktion aber später nutzen, um an bestimmten Stellen in Ihrem Programm Meldungen auszugeben. Aber der Reihe nach...

Wie oben erwähnt, können wir die Registerdefinitions aus Nut/OS verwenden. Dazu wird einfach die Header-Datei toolchain.h eingebunden.

#include <toolchain.h>

Die Runtime-Initialisierung von Nut/OS wird automatisch beim Linken hinzugefügt, darum brauchen wir uns nicht weiter zu kümmern. Allerdings wird von dort nicht, wie sonst üblich, die Funktion main() sondern statt dessen die Funktion NutInit() aufgerufen, die dann den Idlethread startet, der wiederum den Systemtimer aktiviert und seinerseits main() als eigenen Thread startet. Um diesen Start des Betriebssystems zu verhindern, definieren wir NutInit() direkt in unserer Anwendung.

void NutInit(void)
{
    /* Set spurious interrupt vector. */
    outr(AIC_SPU, (unsigned int)SpuriousEntry);
    /* Enable UART0 clock. */
    outr(PS_PCER, _BV(US0_ID));
    /* Disable peripheral function at UART0 transmit pin. */
    outr(PIO_PDR, _BV(P14_TXD0));
    /* Reset UART. */
    outr(US0_CR, US_RSTRX | US_RSTTX | US_RXDIS | US_TXDIS);
    /* Disable all UART interrupts. */
    outr(US0_IDR, 0xFFFFFFFF);
    /* Set UART baud rate generator register. */
    outr(US0_BRGR, At91BaudRateDiv(BAUDRATE));
    /* Set UART mode to 8 data bits, no parity and 1 stop bit. */
    outr(US0_MR, US_CHMODE_NORMAL | US_CHRL_8 | US_PAR_NO | US_NBSTOP_1);
    /* Enable UART transmitter. */
    outr(US0_CR, US_TXEN);
    /* Print message in a loop. */
    for (;;) {
        PutString("Hello world!\n");
    }
}

Praktisch ist dies unsere main-Funktion. Sie enthält eine Reihe von UART-Registerzugriffen, um die serielle Schnittstelle zu konfigurieren. Wenn Sie damit nicht vertraut sind, können Sie diese im einzelnen an Hand des Datenblatts der CPU nachvollziehen.

Zu Beginn wird allerdings noch eine Handler für den sog. Spurious Interrupt gesetzt. Da wir in unserem Beispiel keine weiteren Interupts verwenden, ist dieser Handler eigentlich überflüssig. Sobald Ihre Anwendung aber eigene Interrupt-Routinen definiert, ist diese Funktion zwingend erforderlich. Zudem benötigt Sie wenig Code.

static void SpuriousEntry(void) __attribute__ ((naked));
void SpuriousEntry(void)
{
    IRQ_ENTRY();
    IRQ_EXIT();
}

Die beiden Makrofunktionen IRQ_ENTRY und IRQ_EXIT werden von Nut/OS zur Verfügung gestellt. Sie eignen sich auch für eigene, in C geschriebene Interrupt-Routinen. Setzen Sie dazu IRQ_ENTRY an den Anfang und IRQ_EXIT an das Ende Ihres Handlers. Sie sorgen dafür, dass der Zustand der CPU auf dem Stack zwischengespeichert wird.

Damit ist unser Beispiel schon fast komplett. Lediglich die Schleife am Ende, die fortwährend das berühmte "Hello world!" über die serielle Schnittstelle ausgibt, benötigt noch die Routine PutString() ...

static void PutString(char *str)
{
    while (*str) {
        PutChar(*str);
        str++;
    }
}

... welche ihrerseits die Routine PutChar verwendet:

static void PutChar(char ch)
{
    if (ch == '\n') {
        PutChar('\r');
    }
    while ((inr(US0_CSR) & US_TXRDY) == 0);
    outr(US0_THR, ch);
}

Wenn Sie schon einmal selbst UART-Registern per Programm angesprochen haben, werden Ihnen diese Programmzeilen vertraut sein. Andernfalls sei wieder auf das Datenblatt verwiesen.

Eine winzige Kleinigkeit fehlt allerdings noch, nämlich die Berechnung des Teilerfaktors für die Baudrate. Dieser ließe sich auch manuell berechnen und hart kodieren, aber möglicherweise möchten Sie eine andere Baudrate verwenden, und da ist folgende Funktion sicher nützlich.

#define CPUCLOCK    73728000    /* CPU clock in Hz */
#define BAUDRATE    115200      /* UART baud rate */
static uint32_t At91BaudRateDiv(uint32_t baud)
{
    return (CPUCLOCK / (8 * baud) + 1) / 2;
}

Binärcode erstellen

Dies geschieht in gleicher Weise wie normale Nut/OS-Anwendungen, nämlich durch den Aufruf von make. Das Ergebnis kann sich sehen lassen. Die Anwendung benötigt lediglich etwa 500 Bytes Codespeicher und, bis auf den Stack, kein RAM, da offensichtlich alle Variable in Registern gehalten werden können. Eine minimale Nut/OS Anwendung benötigt ca. 16kBytes allein für den Basiscode.

Das Archiv enut3_noos.zip enthält den kommentierten Quellcode inklusive Makefile und den lauffähigen Binärdateien.