NIC

Wechseln zu: Navigation, Suche

Von Frank M. (ukw)

NIC ist eine einfache strukturierte Programmiersprache, welche speziell für die Mikrocontroller-Familie STM32 entwickelt wurde. Da der NIC-Interpreter in C geschrieben ist, kann dieser auch auf andere 32-Bit-Mikrocontroller portiert werden.

NIC-Programme sind auch unter Linux und Windows lauffähig, wobei dies eher der Entwicklung und des Debuggings des NIC-Interpreters dient. Ein in NIC geschriebenes Programm wird zunächst von dem NIC-Compiler "nicc" auf dem Host in einen Objektcode übersetzt, welcher durch den NIC-Interpreter "nic" auf dem Mikrocontroller-System sehr effizient abgearbeitet werden kann. Dabei kann der NIC-Compiler auch selbst auf einem STM32 ausgeführt werden, wie das Projekte MINOS zeigt. Die üblichen Übersetzungsdauern liegen meist unter einer Sekunde, die Geschwindigkeit, mit der die Befehle dann vom Interpreter nic ausgeführt werden, liegen bei ca. 100.000 bis 250.000 Befehlen pro Sekunde - je nach verwendetem STM32.

Motivationen zur Entwicklung eines solchen Interpreters gibt es einige: Zum einen wird Einsteigern ein sehr einfaches System geboten, sich in die Welt von 32-Bit-Mikrocontrollern einzuarbeiten, zum anderen schrecken viele Umsteiger von 8-Bit-Systemen auf 32-Bit-Systeme vor der Komplexität der 32-Bitter zurück. Genau diese Komplexität wird in die NIC-Bibliotheksfunktionen verlegt, so dass man sich als NIC-Programmierer nicht damit im Detail befassen muss. Ein erfahrener Programmierer jedoch kann die NIC-Laufzeitbibliothek durch eigene C-Module erweitern, um diese dann im NIC-Programm unter Nutzung der NIC-API zu nutzen. So ist es dann zum Beispiel auf einfache Art und Weise möglich, eine Kette von WS2812-LEDs per DMA zu steuern, ohne sich im Detail mit der Programmierung einer DMA auseinandersetzen zu müssen.

NIC als strukturierte Programmiersprache ähnelt in manchen Teilen C, PHP, Pascal oder auch Basic. Datentypen müssen jedoch zum Beispiel im Gegensatz zu PHP stets angegeben werden. Trotzdem ist stets eine implizite Typumwandlung (zum Beispiel von Integer-Werten in Strings oder umgekehrt) möglich, die klaren Regeln unterworfen ist. Casts wie in C sind dabei nicht notwendig.

Natürlich ist NIC als Interpretersprache nicht so schnell wie ein C-Programm, kann sich aber trotzdem bezüglich Ausführungsgeschwindigkeit durchaus sehen lassen. Ein erfahrener C-Programmierer kann zeitkritische Programmteile in C erstellen, um sie dann als NIC-Bibliotheksfunktionen komfortabel zu nutzen. Der NIC-Interpreter ist ca. doppelt so schnell wie ein adäquates PHP-Programm und meist 3-5 mal schneller als vergleichbare Python-Programme, siehe auch Benchmarks.

Im folgenden werden lediglich die NIC-Syntax und die NIC-Standard-Funktionen erklärt. Zahlreiche Board-spezifische NIC-Erweiterungen, die immer hardware-spezifisch sind, findet man im MINOS-Projekt.

Inhaltsverzeichnis

Compiler nicc

Aufruf unter Windows

   nicc [-v] [-u COMx] datei.n

Die in eckigen Klammern angegebenen Parameter sind optional.

Optionen:

   -v : Verbose, gibt Statistiken, wie zum Beispiel Optimierungen von Ausdrücken aus
   -u COMx: Upload der compilierten Datei über die seriellen Schnittstelle COMx

Der NIC-Quelltext wird dabei in einen optimierten, maschinenunabhängigen Objekt-Code übersetzt. Das Kompilat legt nicc in der Datei datei.nico ab, welche (zur Zeit noch) eine ASCII-Datei ist. Gibt man per Upload-Parameter die serielle Schnittstelle an, wird das Kompilat direkt auf den µC geladen. Der Interpreter legt den Objekt-Code dann als binäre Datenstrukturen im RAM ab, um diese dann effizient abzuarbeiten.

Beispiel:

   nicc -u com5 lauflicht1.n

Das Upload-Feature wird zur Zeit jedoch nicht (mehr) genutzt, da das MINOS-Projekt den nicc-Compiler mittlerweile "On Board" hat. Das heisst, der STM32 kann NIC-Quelltexte nun selber übersetzen.

Aufruf unter Linux

   nicc [-v] datei.n

Hier fehlt (noch) die Upload-Funktion. Dafür ist es aber möglich, das Kompilat direkt unter Linux zu starten, siehe nächstes Kapitel.

Aufruf unter MINOS

   nicc [-v] datei.n

oder einfach:

   datei.n

Siehe auch: MINOS-Projekt


Optimierungen

Der NIC-Compiler bringt sämtliche arithmetische Ausdrücke wie "3 * 4 + x + 1 << 6" nach den Prioritätenregeln (Punkt- vor Strichrechnung usw.) in die UPN-Notation - auch Postfix genannt. Dabei werden konstante Teilausdrücke wie "3 * 4" direkt durch das Ergebnis "12" zur Compilezeit ersetzt. Dadurch muss der Interpreter sich nicht mehr um solche Regeln kümmern, sondern braucht nur noch einen Stack abzuarbeiten, auf dem alle Operanden und Operatoren schon fix und fertig in der richtigen Reihenfolge vorliegen.

Außerdem werden zur Compilezeit sämtliche Sprungadressen, die durch Schleifen wie loop, repeat, for, while, break und continue entstehen, vorher ausgerechnet, so dass der Interpreter je nach Bedingung nur noch Sprungadressen laden muss. Desweiteren sind bei Aufruf von Bibliotheksfunktion (wie zum Beispiel GPIO) die zu übergebenden Daten direkt zum Bedienen der API für Bibliotheksfunktionen vorbereitet. Diese können damit schnell und effektiv bedient werden.

Häufig vorkommende Konstrukte wie "n = n + 1" werden in spezielle Increment- und Decrement-Befehle gewandelt, die vom NIC-Interpreter ohne Evaluierung des arithmetischen Ausdrucks rechts vom Gleichheitszeichen direkt intern auf den Variableninhalt wirken.

Interpreter nic

Der NIC-Interpreter nic läuft direkt im Flash des STM32.

Für Entwickler:

Man kann den Interpreter auch unter Linux und Windows direkt von der Kommandozeile aus starten.

Übersetzen:

   nicc datei.n

Ausführen:

   nic datei.nic

STM32-spezifische Funktionen wie gpio.toggle() laufen dabei unter Linux und Windows über eine Stub-Funktion, welche die Laufzeitparameter lediglich als Info ausgibt. Alle anderen Standard-Funktionen werden jedoch ordnungsgemäß ausgeführt. Trotzdem sind nicc und nic unter Linux bzw. Windows eher zur Weiterentwicklung und Debugging des Compilers und des Interpreters gedacht und nicht dafür, Programme für Linux/Windows in der Programmiersprache NIC zu entwickeln - auch wenn das durchaus möglich ist.

main-Funktion

Jedes NIC-Programm muss eine Funktion namens "main()" definieren. Das ist der Startpunkt eines jeden NIC-Programms. Die Funktion main() liefert stets keinen Rückgabewert zurück und muss daher immer mit dem Funktionstyp "void" angegeben werden. Außerdem erhält sie meist keine Parameter, daher lassen wir zunächst den Inhalt zwischen den Klammern "(" und ")" leer.

Damit ergibt sich als Funktionsrumpf:

function void main ()
   ...
endfunction

Hello World

Als Einstiegsprogramm einer Programmiersprache wird oft ein einfaches Hello-World-Programm angegeben, welches die Zeichenkette "Hello, World" ausgibt. Für Mikrocontroller jedoch ist es oft das Blinken einer an den Mikrocontroller angeschlossenen LED.

Das folgende NIC-Programm lässt die LED auf einem STM32F4-Board blinken:

function void main ()
    gpio.init (GPIOA, 5, OUTPUT)
    loop
        gpio.set (GPIOA, 5)
        time.delay (500)
        gpio.reset (GPIOA, 5)
        time.delay (500)
    endloop
endfunction

Die LED wird dabei für 500msec ein- und dann für 500msec wieder abgeschaltet. Durch die Kontrollstruktur "loop" läuft das Programm in einer Endlosschleife und kann entweder durch Drücken der RESET-Taste auf dem STM32-Board oder durch Drücken der Taste STRG-C auf der Console gestoppt werden. Hier werden drei interne Bibliotheksfunktionen der Familie GPIO aufgerufen, nämlich gpio.init(), gpio.set() und gpio.reset(). Die erste Funktion gpio.init() initialisiert den Pin A5 des STM32 als Ausgabe-Pin durch Angabe des Schlüsselwortes "OUTPUT", die Funktion gpio.set() setzt dann den Pin auf HIGH-Pegel, die Funktion gpio.reset() wieder auf LOW-Pegel. Die GPIO-Befehle sind hardware-abhängig und werden deshalb im MINOS-Projekt erklärt. Die Bibliotheksfunktion time.delay() lässt den Mikrocontroller eine bestimmte Anzahl von Millisekunden warten - hier 500msec.

Am Pin A5 ist bei den Nucleo-Boards eine board-interne grüne LED angeschlossen. Diese blinkt dann bei der Ausführung des Programms. Um die board-interne blaue LED eines STM32F103-Mini-Development-Boards anzusprechen, muss der Port GPIOA durch GPIOC ersetzt und die Pinnummer auf 13 geändert werden, denn hier ist die LED an Pin C13 angeschlossen.

Durch Anwendung der Funktion gpio.toggle() kann der Code noch vereinfacht werden:

function void main ()
    gpio.init (GPIOA, 5, OUTPUT)
    loop
        gpio.toggle (GPIOA, 5)
        time.delay (500)
    endloop
endfunction

Hier wechselt die Funktion gpio.toggle() automatisch den jeweiligen LOW- bzw. HIGH-Pegel und lässt die LED mit einer Frequenz von einem Hertz blinken.

Wie wir später noch sehen werden, kann das Blinken der LED auch timergesteuert ausgeführt werden. Dann macht der NIC-Interpreter das "nebenbei", d.h. wir haben in der Hauptschleife Zeit für andere Dinge.

Hier schon mal das Programm, erklärt wird es dann später unten (alarm-Funktionen):

function void blink ()
    gpio.toggle (GPIOA, 5)
endfunction

function void main ()
    gpio.init (GPIOA, 5, OUTPUT)
    alarm.set (500, function.blink)                 // blink() alle 500msec automatisch aufrufen lassen

    loop
            // ...                                  // Hier koennen nun komplett andere Aufgaben erledigt werden!
    endloop
endfunction

Wie schnell ist nun der NIC-Interpreter? Wir können nun durch Entfernen des delay-Aufrufs die LED mit maximaler Geschwindigkeit blinken lassen und dann die Zeiten messen:

function void main ()
    gpio.init (GPIOA, 5, OUTPUT)            // Nucleo STM32F4xx: LED=A5, STM32F103-Mini-Board: LED=C13
    repeat 1000000
        gpio.toggle (GPIOA, 5)
    endrepeat
endfunction

Die Endlosschleife loop wurde hier durch ein repeat ersetzt. Hier können wir angeben, wie oft eine Schleife durchlaufen wird. In unserem Beispiel sind es 1.000.000 mal. Das Blinken selbst wird man aufgrund der Geschwindigkeit nicht mehr erkennen können, jedoch leuchtet die LED weiterhin deutlich sichtbar - bis das Programm nach einer Million Schleifendurchläufen beendet wird. Man wird dabei mit der Stoppuhr auf einem STM32F411RE-Nucleo-Board ungefähr 4 Sekunden messen.

Der Messvorgang geht aber auch genauer:

function void main ()
    int millis

    gpio.init (GPIOA, 5, OUTPUT)            // Nucleo STM32F4xx: LED=A5, STM32F103 Mini-Dev-Board: LED=C13
    time.start ()                           // Stoppuhr starten

    repeat 1000000
        gpio.toggle (GPIOA, 5)
    endrepeat

    millis = time.stop ()                   // Stoppuhr anhalten und vergangene Zeit in Variable millis ablegen
    console.println (millis)
endfunction

Mit den Funktionen time.start() und time.stop() messen wir die vergangene Zeit in Millisekunden. Diese wird am Ende auf der Console ausgegeben.

Bei einem mit 168MHz getakteten STM32F407VE dauert das Programm knapp 3000msec. Das entspricht etwas mehr als 333.000 Schleifendurchläufen pro Sekunde. Bei einem mit 100MHz getakteten STM32F411RE sind dies ca. 3900msec bzw. 256.000 Schleifendurchläufe pro Sekunde. Bei einem mit 85MHz getakteten STM32F401RE sind es noch knapp 200.000 Schleifendurchläufe, beim STM32F103 mit 72MHz-Takt ca. 180.000 Durchläufe.

Das Programm kann aber noch stärker vereinfacht werden. Jetzt verzichten wir auf die Variable "millis":

function void main ()
    gpio.init (GPIOA, 5, OUTPUT)            // Nucleo STM32F4xx: LED=A5, STM32F103 Mini-Dev-Board: LED=C13
    time.start ()

    repeat 1000000
        gpio.toggle (GPIOA, 5)
    endrepeat

    console.println (time.stop ())
endfunction

Als Funktionsparameter können also auch Rückgabewerte anderer Funktionen oder noch komplexere Ausdrücke angegeben werden.

Der Vollständigkeit halber noch das Hello-World-Programm im klassischen Sinne:

function void main ()
    console.println ("Hello, World!")
endfunction

Das sieht jetzt - nachdem wir bereits eine LED auf mehrere Art und Weise haben blinken lassen - viel zu einfach aus ;-)

Kommentare

Kommentare werden durch einen doppelten Schrägstrich eingeleitet, also durch '//'. Alle danach aufgeführten Zeichen gelten als Kommentar und werden vom NIC-Compiler nicc ignoriert.

Beispiel:

    gpio.init (GPIOA, 5, OUTPUT)            // Nucleo STM32F4xx: LED=A5, STM32F103 Mini-Dev-Board: LED=C13

Datentypen

Die Datentypen von Funktionen und Variablen müssen stets angegeben werden. Eine implizite Definition durch einfache Aufführung eines einer Funktion, eines Funktionsparameters oder einer Variablen wie in PHP oder Python ist nicht möglich.

Die Datentypen von Variablen und Funktionen sind Integer, Byte oder String, evtl. später auch noch "Float". Bei Funktionen ist zusätzlich der Datentyp "void" möglich, um anzugeben, dass die Funktion keinen Funktionswert zurückliefert.

  • int - Datentyp Integer, Breite 32 Bit, vorzeichenbehaftet
  • byte - Datentyp Byte, Breite 8 Bit, vorzeichenlos
  • string - Datentyp Zeichenkette
  • void - kein Returnwert (nur Funktionen)

Funktionen können als Rückgabewerte nicht nur die oben aufgeführten Typen zurückliefern, sondern auch vom Typ void sein, um anzuzeigen, dass sie keinen Rückgabewert liefern.

Beispiel:

function void errormessage (string message)
    console.println ("error: " : message)
endfunction

Bei allen anderen Datentypen muss eine Funktion einen Wert per return-Befehl zurückgeben.

Konstanten

Integer-Konstanten

Integer-Konstanten sind einfache Zahlenfolgen wie 1234 oder -1234. Ohne entsprechendes Prefix sind dies Dezimalzahlen. Möchte man einen numerischen Wert in hexadezimal angeben, ist 0x voranzustellen, also zum Beispiel 0x1234. Zahlen können auch im Binärsystem angegeben werden. In diesem Fall ist 0b voranzustellen, z.B. 0b10011100.

Beispiele:

  • 1234 ist eine Dezimalzahl
  • 0x23AF ist eine Hexadezimalzahl
  • 0b01010101 ist eine Binärzahl

String-Konstanten

String-Konstanten werden in Anführungszeichen angegeben.

Beispiel:

  • "Das ist eine String-Konstante"

Variablen

Variablen werden definiert durch die Angabe des Datentyps und des Namens.

Beispiel:

    int nummer
    byte data
    string zeichenkette

Es gibt lediglich drei verschiedene Datentypen: int oder byte oder string.

Der Datentyp int definiert einen 32-Bit-Integer, ist also vorzeichenbehaftet. Der Datentyp byte entspricht dem Byte, kennt also keinerlei Vorzeichen und ist 8 Bit breit. Der Typ string ist eine Zeichenkette.

Globale Variablen

Wird eine Variable außerhalb von Funktionen definiert, ist sie eine globale Variable. Auf globale Variablen kann funktionsübergreifend zugegriffen werden - und zwar ab der Zeile, wo sie definiert wird. Eine globale Variable kann jederzeit definiert werden, d.h. die Definition muss keinesfalls am Anfang des Quelltextes geschehen, jedoch spätestens dann, wenn auch auf sie zugegegriffen werden soll.

Beispiel:

int maxleds

function void main ()
    maxleds = 30
    console.println (maxleds)   // Ausgabe: 30
endfunction

Globale Variablen können auch direkt bei der Definition initialisiert werden. Dies muss dann - je nach Typ - eine numerische Konstante oder eine Stringkonstante sein.

Beispiel:

int maxleds = 30

function void main ()
    console.println (maxleds)   // Ausgabe: 30
    maxleds = 40
    console.println (maxleds)   // Ausgabe: 40
endfunction

Lokale Variablen

Wird eine Variable innerhalb einer Funktion definiert, dann gilt sie nur innerhalb dieser Funktion. Bei jedem Funktionsaufruf werden die lokalen Variablen neu erzeugt und (im Gegensatz z.B. zu C) neu mit 0 (für int) bzw. Leerstring "" (für string) initialisiert. Ruft die Funktion sich selbst rekursiv auf, wird jedesmal ein neuer lokaler Variablensatz erzeugt. Lokale Variablen werden nach dem Verlassen der jeweiligen Funktion wieder zerstört und sind dann nicht mehr verfügbar.

Eine lokale Variable kann jederzeit definiert werden, d.h. die Definition muss keinesfalls am Anfang einer Funktion stehen. Jedoch kann sie überall innerhalb der Funktion verwendet werden - frühestens aber unterhalb der erfolgen Definition.

Beispiel:

function void foo ()
    int var
    var = 3 * 2
    console.println (var)
endfunction

Auch lokale Variablen können direkt bei der Definition initialisiert werden. Dies muss dann - je nach Typ - eine numerische Konstante oder eine Stringkonstante sein.

Beispiel:

function void foo ()
    int var = 3
    string message = "Die Variable hat den Inhalt "
    console.println (message : var)
endfunction

Arithmetische Ausdrücke sind zur Zeit nicht erlaubt, die Initialisierungen müssen mit Konstanten vorgenommen werden. Wird die Initialisierung weggelassen, werden Integer-Variablen mit 0, String-Variablen mit "" (Leerstring) initialisiert.

Statische Variablen

Wird eine Variable innerhalb einer Funktion statisch definiert, dann gilt für sie erst einmal dasselbe wie für lokale Variablen. Der einzige Unterschied: Sie wird nur einmal beim Start des Programms erzeugt und beim Verlassen der Funktion auch nicht zerstört. Das heisst: Die Variable (und deren Inhalt) "lebt" während der ganzen Laufzeit des Programms. Außerhalb der Funktion, wo sie definiert wird, kann auf sie mit dem Namen "function.variable" zugegriffen werden, wenn es unbedingt nötig ist, siehe untenstehendes Beispiel.

Werden statische Variablen direkt bei der Definition initialisiert, so haben sie diesen Wert bereits beim Start des Programms.

Eine statische Variable kann jederzeit definiert werden, d.h. die Definition muss keinesfalls am Anfang einer Funktion stehen. Jedoch kann sie überall innerhalb der Funktion verwendet werden - frühestens unterhalb der erfolgen Definition.

Beispiel:

function void foo ()
    static int var = 4
    var = 3 * var
    console.println (var)         // Ausgabe: 12
endfunction

function void main ()
    console.println (foo.var)     // Ausgabe: 4
    foo ()
    console.println (foo.var)     // Ausgabe: 12
endfunction

Statische Variablen können auch außerhalb einer Funktion definiert werden. Da diese Definition semantisch keinen Unterschied zu der Definition einer globalen Variablen macht, werden globale statische Variaben identisch zu globalen Variablen behandelt.

Beim Compilieren erscheint dann eine Warnung:

 warning line 1: keyword 'static' takes no effect here.

Beispiel:

static int var = 3
function void main ()
    string message = "Die Variable hat den Inhalt "
    console.println (message : var)

Arithmetische Ausdrücke sind ebenso wie bei den nicht-statischen Variaben nicht erlaubt, die Initialisierungen müssen mit Konstanten vorgenommen werden. Wird die Initialisierung weggelassen, werden Integer-Variablen mit 0, String-Variablen mit "" (Leerstring) initialisiert.

Konstante Variablen

"Konstante Variablen" sind auf den ersten Blick ein Widerspruch in sich. Wie kann eine Konstante variabel sein?

Die Antwort: Eine konstante Variable wird von der Semantik wie eine normale Variable behandelt. Allerdings kann deren Wert nicht geändert werden. Daher sollte man direkt bei der Definition solche Variablen auch initialisieren. Unterlässt man dies, haben sie halt den Wert 0 bzw. "" (Leerstring) - je nach Typ.

Konstante Variablen sollte man nach Konvention ausschließlich in Großbuchstaben schreiben. Sie ähneln nämlich den Preprozessor-Konstanten in C. Mit der Großschreibung deutet man an: Dies ist eine Konstante!

Der Inhalt der Variablen wird auch nicht erst zur Laufzeit in arithmetischen Ausdrücken ermittelt, sondern bereits während des Compilierens in die entsprechenden Ausdrücke eingesetzt.

const-Variablen müssen vom Typ int oder string sein. byte ist hier nicht erlaubt - was aber keine Einschränkung bedeutet.

Beispiel:

const int MAXLEDS = 30            // 30 LEDs

function void foo ()
    int var = 4
    var = MAXLEDS * var
    console.println (var)         // Ausgabe: 120
endfunction

function void main ()
    foo ()
    console.println (MAXLEDS)     // Ausgabe: 30
endfunction

Arithmetische Ausdrücke sind ebenso wie bei den nicht-statischen Variaben nicht erlaubt, die Initialisierungen müssen mit Konstanten vorgenommen werden. Wird die Initialisierung weggelassen, werden konstante Variablen mit 0 bzw. mit "" (Leerstring) - je nach Typ - initialisiert.

Der Versuch, einer konstante Variablen später im Programm einen anderen Wert zuzuweisen, wird bereits beim Compilieren mit einer Fehlermeldung quittiert.

Beispiel:

const int MAXLEDS = 30            // 30 LEDs

function void main ()
    MAXLEDS = 40
    console.println (MAXLEDS)
endfunction

Die Fehlermeldung lautet dann:

 error line 4: variable 'MAXLEDS' is of type 'const'.

Arrays

Variablen können auch als Arrays verwendet werden. Dabei wird bei der Definition die Größe des Arrays in eckigen Klammern angegeben. Diese Größe muss entweder durch eine Zahl oder den Inhalt einer "konstanten Integer-Variablen" (const int) angegeben werden. Arrays beginnen immer mit dem Index 0 und enden bei der Arraygröße minus 1. Zugriffe außerhalb dieses Bereichs werden zur Laufzeit abgefangen. In diesem Fall wird eine entsprechende Fehlermeldung auf der Console ausgegeben und das Programm beendet.

Arrays können sowohl global als auch lokal angelegt werden. Auch static Arrays sind möglich. Konstante Arrays gibt es jedoch nicht.

Beispiel:

const int SIZE = 5
int nummer[7]

function void main ()
    string zeichenkette[SIZE]
    int i

    for i = 0 to 7 - 1
        nummer[i] = 2 * i
    endfor

    console.println (nummer[5])          // Ausgabe: 10

    for i = 0 to SIZE - 1
         zeichenkette[i] = "Hello " : i
    endfor

    console.println (zeichenkette[3])    // Ausgabe: "Hello 3"
endfunction

Funktionen

Bibliotheksfunktionen

Eigene NIC-Funktionen

Eine Funktion wird definiert durch folgenden Block:

function TYPE FUNCTIONNAME (TYP PARAMETER1, TYP PARAMETER2, ...)
   ...
endfunction

Dabei kann TYPE int, string oder void sein. FUNCTIONNAME ist einfach der Funktionsname. PARAMETER1 bis PARAMETERn sind die optionalen Funktionsparameter.

Der einfachste Fall ist die main-Funktion, die immer vorhanden sein muss:

function void main ()
   ...
endfunction

Möchte man das oben angegebene Blink-Programm als eigene Funktion einbauen, dann wäre das folgende Programm eine Lösung:

function void blink (int factor, int delay)
    repeat factor
        gpio.toggle (GPIOA, 5)
        time.delay (delay)
    endloop
endfunction

function void main ()
    gpio.init (GPIOA, 5, OUTPUT)
    blink (20, 200)
    blink (10, 500)
endfunction

Der erste Aufruf von blink() lässt die LED 20 mal schnell den Zustand wechseln (Verzögerungszeit 200 msec), der zweite Aufruf lässt die LED lediglich 10 mal den Zustand wechseln, dafür aber langsamer (Verzögerungszeit 500 msec).

Eigene NIC-Klassen

Später.

Typumwandlungen

String to Integer

Wird einer Integer-Variablen ein String zugewiesen, wird lediglich deren numerischer Inhalt zu Anfang des Strings als Integer-Wert übernommen. Enthält der String überhaupt keinen numerischen Wert zu Anfang, ist der Integer-Wert des Strings 0.

Beispiel:

function void main ()
    int zahl
    zahl = "1234abc"
    console.println (zahl)
endfunction

Die Variable Zahl enthält nach der Zuweisung den numerischen Wert 1234.

Dasselbe gilt für Funktionen, die einen Integer-Wert als Parameter erwarten, hier jedoch ein String übergeben wird.

Beispiel:

function int mult (int a, int b)
    return a * b
endfunction

function void main ()
    int ergebnis
    ergebnis = mult ("2", 3)
    console.println (ergebnis)
endfunction

Integer to String

Wird einer String-Variablen ein Integer-Wert zugewiesen, wird dieser automatisch in einen String umgewandelt. Dasselbe gilt für Funktionsparameter, wenn die Funktion einen String als Parameter erwartet.

function void main ()
    string zeichenkette
    zeichenkette = 1234
    console.println (zeichenkette)
endfunction

Ebenso gilt dies für den String-Operator ':', welcher zwei Strings zu einem String zusammensetzt:

function int mult (int a, int b)
    return a * b
endfunction

function void main ()
    int ergebnis
    string meldung
    ergebnis = mult ("2", 3)
    meldung = "Das Ergebnis ist " : ergebnis
    console.println (meldung)
endfunction

Bei dem String-Operator ':' können auch rein numerische Operanden zu einem String zusammengesetzt werden:

function void main ()
    string meldung
    meldung = 123 : 456
    console.println (meldung)
endfunction

Hier wird dann der String "123456" auf der Console ausgegeben.

Kombiniert man beide Typwandlungen (Integer->String und String->Integer), kann man den String-Operator auch auf rein numerische Ausdrücke anwenden, wobei die Zuweisungsvariable auch wieder eine Integer-Variable ist:

function void main ()
    int zahl
    zahl = 123 : 456
    console.println (zahl)
endfunction

Hier werden die Zahlen 123 und 456 zunächst in Strings umgewandelt, dann zu einem String zusammengesetzt und bei der Zuweisung wieder zurück in einen Integer-Wert gewandelt. Die Variable 'zahl' enthält nach der Zuweisung den Integer-Wert 123456.

Kontrollstrukturen

Zuweisung

Syntax: VARIABLE = AUSDRUCK

Beispiel:

function void main ()
    string zeichenkette
    int ergebnis

    zeichenkette = "Das Ergebnis lautet: "
    ergebnis = 1234 * 2
    console.println (zeichenkette : ergebnis)
endfunction

if

Syntax:

    if BEDINGUNG1
        ...
    elseif BEDINGUNG2
        ...
    elseif BEDINGUNG3
        ...
    else
        ...
    endif

Beispiel:

function void main ()
  int zahl

  zahl = 33

if zahl < 10
    console.println ("Die Zahl is kleiner 10")
elseif zahl < 100
    console.println ("Die Zahl is kleiner 100")
elseif zahl > 100
    console.println ("Die Zahl is groesser 100")
else
    console.println ("Die Zahl is gleich 100")
endif

loop

loop beginnt eine Endlosschleife. Diese kann lediglich mit dem Befehl 'break' abgebrochen werden. Der Befehl 'continue' innerhalb der Endlosschleife führt zu einem vorzeitigem Sprung zum Anfang der Schleife.

Syntax:

loop
   ...
endloop

Vorzeitiger Abbruch:

loop
   ...
   break
   ...
endloop

Vorzeitiger Sprung zum Anfang der Schleife

loop
   ...
   continue
   ...
endloop

Beispiel:

Das folgende Programm lässt die Board-LED auf einem Nucleo-Board leuchten, bis der User-Button gedrückt wird.

function void main ()
    gpio.init (GPIOC, 13, INPUT)            // set pin C13 (user button) to input, has extern pullup
    gpio.init (GPIOA, 5, OUTPUT)            // board LED = A5
    gpio.set (GPIOA, 5)                     // switch LED on

    loop
        if gpio.get (GPIOC, 13) = LOW       // button pressed?
            gpio.set (GPIOA, 5)             // switch LED off
            break                           // break loop
        endif
    endloop
endfunction

repeat

repeat beginnt eine Schleife, welche genau N-mal durchlaufen wird. Die repeat-Schleife kann vorzeitig mit dem Befehl 'break' abgebrochen werden. Der Befehl 'continue' innerhalb der Schleife führt zu einem vorzeitigem Sprung zum Anfang der Schleife.

Syntax:

repeat N
   ...
endrepeat

N kann dabei ein beliebiger arithmetischer Ausdruck sein.

Beispiel:

Das folgende Programm gibt die Zahlen 1 bis 9 auf der Console aus.

function void main ()
    int zahl
    zahl = 1
    repeat 9
        console.println (zahl)
        zahl = zahl + 1
    endrepeat
endfunction

while

Die while-Schleife wird durchlaufen, solange die angegebene Bedingung gegeben ist. Arithmeische Ausdrücke innerhalb der Bedingung werden bei jedem Schleifendurchlauf neu berechnet. Die while-Schleife kann vorzeitig mit dem Befehl 'break' abgebrochen werden. Der Befehl 'continue' innerhalb der Schleife führt zu einem vorzeitigem Sprung zum Anfang der Schleife.

Syntax:

while BEDINGUNG
   ...
endwhile

Beispiel:

Das folgende Programm gibt die Zahlen 1 bis 9 auf der Console aus.

function void main ()
    int zahl
    zahl = 1
    while zahl < 10
        console.println (zahl)
        zahl = zahl + 1
    endwhile
endfunction

for

Die for-Schleife wird für eine anzugebene Schleifenvariable solange durchlaufen, bis der angegebene Stopwert überschritten wird. Optional kann eine Schrittgröße für die Schleifenvariable angegeben werden. Standardmäßig ist diese 1, d.h. die Schleifenvariable wird mit jedem neuen Durchlauf inkrementiert. Ist der Stopwert kleiner als der Startwert, wird die Schleifenvariable mit jedem Durchlauf dekrementiert.

Die for-Schleife kann vorzeitig mit dem Befehl 'break' abgebrochen werden. Der Befehl 'continue' innerhalb der Schleife führt zu einem vorzeitigem Sprung zum Anfang der Schleife.

Syntax:

for VARIABLE = STARTWERT to STOPWERT [STEP x]
   ...
endfor

VARIABLE muss eine Integer-Variable sein, STARTWERT und STOPWERT ein beliebiger arithmetischer Ausdruck. Im Gegensatz zur while-Schleife, in welcher die Schleifen-Bedingung und damit die arithmetische Ausdrücke jeweils immer neu evaluiert werden, wird bei der for-Schleife STARTWERT und STOPWERT nur einmal berechnet, nämlich unmittelbar vor dem Betreten der Schleife.

Wird kein Step-Wert angegeben, wird die Laufvariable nach jedem Schleifendurchlauf um 1 erhöht. Wird ein abweichender Step-Wert angegeben, wird die Laufvariable um den angegebenen Wert erhöht. Ist der Step-Wert negativ, wird die Variable um den angegebenen Wert erniedrigt.

Beispiel:

Das folgende Programm gibt zunächst die Zahlen 1 bis 9 auf der Console aus. Anschließend werden die Werte von 9 bis 1 rückwärts zählend ausgegeben:

function void main ()
    int zahl
    for zahl = 1 to 9
        console.println (zahl)
    endfor
    for zahl = 9 to 1 step -1
        console.println (zahl)
    endfor
endfunction

return

  • return

Vergleichsoperatoren

Vergleichsoperator 'equal'

  • gleich: =

Vergleichsoperator 'not equal'

  • ungleich: !=

Vergleichsoperator 'less'

  • kleiner: <
  • kleiner oder gleich: <=

Vergleichsoperator 'greater'

  • größer: >
  • größer oder gleich: >=

Operatoren

Grundrechenarten

Addition

Syntax: AUSDRUCK + AUSDRUCK

Beispiel:

    int ergebnis
    ergebnis = 3 + 5
    console.println (ergebnis)

Subtraktion

Syntax: AUSDRUCK - AUSDRUCK

Beispiel:

    int ergebnis
    ergebnis = 5 - 3
    console.println (ergebnis)

Multiplikation

Syntax: AUSDRUCK * AUSDRUCK

Beispiel:

    int ergebnis
    ergebnis = 5 * 3
    console.println (ergebnis)

Division

Die Integer-Division behandelt nur den ganzzahligen Anteil der Division. Für den entstandenen Rest einer Division siehe Modulo.

Syntax: AUSDRUCK / AUSDRUCK

Beispiel:

    int ergebnis
    ergebnis = 5 / 3
    console.println (ergebnis)    // Ausdruck ist 1

Bei den Grundrechenarten gilt die Punkt-vor-Strich-Rechnung.

Modulo-Operator

Der Modulo-Operator berechnet den Rest einer Integer-Division.

Syntax: AUSDRUCK % AUSDRUCK

Beispiel:

    int ergebnis
    ergebnis = 5 % 3
    console.println (ergebnis)    // Ausdruck ist 2

Der Modulo-Operator bindet (im Gegensatz zu anderen Programmiersprachen!) stärker als die Grundrechenarten. Das Ergebnis von

    ergebnis = 2 * 5 % 3

ist identisch mit:

    ergebnis = 2 * (5 % 3)

Daher ist das Ergebnis hier 2 * 2 = 4.

Shift-Operatoren

Shift Left

Der Shift-Operator '<<' verschiebt die Bits einer Zahl nach links.

Syntax: AUSDRUCK << ANZAHL_BITS

Beispiel:

    int zahl
    int ergebnis
    zahl = 2
    ergebnis = zahl << 4  // Verschieben der Zahl 2 um 4 Stellen nach links
    console.println (ergebnis)    // Ausdruck ist 32

Shift Left

Der Shift-Operator '>>' verschiebt die Bits einer Zahl nach rechts.

Syntax: AUSDRUCK << ANZAHL_BITS

Beispiel:

    int zahl
    int ergebnis
    zahl = 32
    ergebnis = zahl >> 4  // Verschieben der Zahl 32 um 4 Stellen nach rechts
    console.println (ergebnis)    // Ausdruck ist 2

Die Shift-Operatoren binden stärker als die Grundrechenarten und der Modulo-Operator. Das Ergebnis von

3 * 1 << 4

ist also 3 * 16 = 48.

Wichtig: Beim Verschieben werden die Ausdrücke immer als unsigned-Werte angesehen, d.h. Vorzeichenbits werden nicht besonders behandelt.

Bitmask-Operatoren

Bitwise OR

Syntax: AUSDRUCK | AUSDRUCK

Beispiel:

    console.println (0x0F | 0xF0, HEX, 2)      // Ausdruck ist FF

Bitwise AND

Syntax: AUSDRUCK & AUSDRUCK

Beispiel:

    console.println (0xFFFF & 0x00F0, HEX, 4)    // Ausdruck ist 00F0

Bitwise XOR

Syntax: AUSDRUCK ^ AUSDRUCK

Beispiel:

    console.println (0xFFFF ^ 0x00F0, HEX, 4)    // Ausdruck ist FF0F

Bitwise Inversion

Syntax: ~AUSDRUCK

Beispiel:

    console.println (~0xFF00, HEX, 4)            // Ausdruck ist FFFF00FF
    console.println (~0xFF00 & 0xFFFF, HEX, 4)   // Ausdruck ist     00FF

Der And-Operator bindet stärker als der OR-Operator. Die Invertierung wirkt unmittelbar auf den nachfolgenden Ausdruck. Sämtliche Bitmask-Operatoren binden stärker als die vorangegangenen Operatoren.

Klammern

Um die obigen Prioritätenregelungen zu durchbrechen, können in den Ausdrücken Klammern gesetzt werden.

Beispiel:

    console.println (3 + 2 * 4)                  // Ausdruck ist 3 +  8 = 11
    console.println ((3 + 2) * 4)                // Ausdruck ist 5 *  4 = 20
    console.println (3 + 2 << 2)                 // Ausdruck ist 3 +  8 = 11
    console.println ((3 + 2) << 2)               // Ausdruck ist 5 << 2 = 20

String-Operator

Der String-Operator fasst zwei Zeichenketten zu einer Zeichenkette zusammen.

Syntax: STRING1 : STRING2

Beispiel:

function void main ()
    string str1
    string str2
    string str3
    str1 = "Hello, "
    str2 = "World"
    str3 = str1 : str2
    console.println (str3)  // Ausgabe: "Hello, World"
endfunction

Oder auch kürzer:

function void main ()
    string str
    str1 = "Hello, "
    console.println (str : "World")
endfunction

Oder noch kürzer:

function void main ()
    console.println ("Hello, " : "World")
endfunction

Der String-Operator wandelt numerische Ausdrücke automatisch in Strings.

Somit führt der Programmabschnitt:

    string str
    str = (3 * 5) : " ist das Ergebnis."
    console.println (str)

zu einem Ausdruck von "15 ist das Ergebnis.".

Wichtig: Der String-Operator bindet stärker als alle anderen nicht-unären Operatoren.

Fehlende Klammern in Abschnitt:

    string str
    str = 3 * 5 : " ist das Ergebnis."
    console.println (str)

führen also zum Ausdruck von lediglich "15", da der zwischendurch entstehende Ausdruck "5 ist das Ergebnis" für die Multiplikation in einen Integer gewandelt wird. Übrig bleibt daher lediglich 3 * 5. Das Ergebnis wird wegen der Zuweisung wieder zurück in einen String gewandelt. Übrig bleibt "15".


Bibliotheken

Im folgenden werden lediglich die NIC-Standard-Funktionen erklärt. Zahlreiche Board-spezifische NIC-Erweiterungen, die immer hardware-spezifisch sind, findet man im MINOS-Projekt, zum Beispiel:

Interessant könnten auch die Beispielprojekte sein.


STRING

string.length

Die length-Funktion gibt die Länge eines Strings zurück.

Syntax:

    int string.length (string str)

Parameter:

  • string: Die Eingabezeichenkette

Rückgabewert: Länge des Strings

Beispiel:

    int len
    len = string.length ("abcdef");            // gibt 6 zurück

string.substring

Die substring-Funktion kann beliebige Zeichenketten aus einem String extrahieren.

Syntax:

    string string.substring (string str, int start [, int length ])

Parameter:

  • string: Die Eingabezeichenkette
  • start: Die Startposition innerhalb des Strings
  • length: Die Länge des zu extrahierenden Strings. Der Parameter ist optional.

Die Startposition wird ab 0 gezählt. Ist der Startwert positiv oder 0, wird die Startposition innerhalb des Eingabestrings von links gezählt, anderenfalls von rechts.

Beispiel:

    string rest
    rest = string.substring ("abcdef", -1);    // gibt "f" zurück
    rest = string.substring ("abcdef", -2);    // gibt "ef" zurück
    rest = string.substring ("abcdef", -3, 1); // gibt "d" zurück

Ist length angegeben und positiv, enthält der zurückgegebene String höchstens length Zeichen ab start (abhängig von der Länge von string).

Ist length angegeben und negativ, werden genau so viele Zeichen vom Ende von string abgeschnitten (nachdem die Startposition ermittelt wurde, sollte start negativ sein). Gibt start die Position des abzuschneidenden Teiles oder dahinter an, wird ein leerer String zurückgegeben.

Beispiel:

    rest = string.substring ("abcdef", 0, -1);  // gibt "abcde" zurück
    rest = string.substring ("abcdef", 2, -1);  // gibt "cde" zurück
    rest = string.substring ("abcdef", 4, -4);  // gibt "" zurück
    rest = string.substring ("abcdef", -3, -1); // gibt "de" zurück

Die aus Basic bekannten Funktionen 'left', 'right' und 'mid' können alle mit der substring-Funktion abgebildet werden:

function string left (string str, int l)
    return string.substring (str, 0, l)
endfunction

function string right (string str, int l)
    return string.substring (str, -l)
endfunction

function string mid (string str, int s, int l)
    return string.substring (str, s, l)
endfunction

function void main ()
    string x
    x = "abcdef"

    console.println (left (x, 3))     // gibt "abc" aus
    console.println (right (x, 3))    // gibt "def" aus
    console.println (mid (x, 2, 2))   // gibt "cd" aus
endfunction

string.tokens

Ermittlung der Anzahl der Tokens in einer Zeichenkette.

Syntax:

    int string.tokens (string str, string delim)

Parameter:

  • str: Die zu durchsuchende Zeichenkette
  • delim: Das Trennzeichen oder die Trennzeichenkette, welche(s) die Token trennt.

Zurückgegeben wird die Anzahl der Token.

Beispiel:

    int tokens
    tokens = string.tokens ("Hello;World;This;is;NIC", ";");  // gibt 5 zurück

Siehe auch: string.get_token()


string.get_token

Extraktion eines Tokens aus einer Zeichenkette.

Syntax:

    string string.get_token (string str, string delim, int idx)

Parameter:

  • str: Die zu durchsuchende Zeichenkette
  • delim: Das Trennzeichen oder die Trennzeichenkette, welche(s) die Token trennt.
  • idx: Der Index, beginnend mit 0.

Zurückgegeben wird das jeweilige Token.

Beispiel:

function void main ()
    string str = "Hello;World;This;is;NIC"
    string token
    int    tokens
    int    idx

    tokens = string.tokens (str, ";");              // gibt 5 zurück

    for idx = 0 to tokens - 1
        token = string.get_token (str, ";", idx)
        console.println (token)                     // Gibt jedes Token in neuer Zeile aus
    endfor      
endfunction

Siehe auch: string.tokens()


INT

int.tochar

Umwandeln einer Integer-Zahl in ein Zeichen mit dem passenden ASCII-Wert.

Syntax:

    string int.tochar (int ch)

Beispiel:

function void main ()
    int    ch = 0x41
    string str

    str = int.tochar (ch)
    console.println (str)                       // Ausgabe ist 'A'
endfunction

POLAR

Umrechnung von Polarkoordinaten in kartesische Koordinaten.

polar.to_x

Umwandeln von Radius und Winkel in die dazugehörende x-Koordinate.

Syntax:

    int polar.to_x (int r, int angle)

Parameter:

  • r: Radius
  • angle: Winkel zwischen 0 und 360

Beispiel:

    x = polar.to_x (280, 0)             // x = 280
    x = polar.to_x (280, 45)            // x = 198
    x = polar.to_x (280, 90)            // x = 0
endfunction

Siehe auch: polar.to_y()


polar.to_y

Umwandeln von Radius und Winkel in die dazugehörende y-Koordinate.

Syntax:

    int polar.to_y (int r, int angle)

Parameter:

  • r: Radius
  • angle: Winkel zwischen 0 und 360

Beispiel:

    y = polar.to_y (280, 0)             // y = 0
    y = polar.to_y (280, 45)            // y = 198
    y = polar.to_y (280, 90)            // y = 280
endfunction

Siehe auch: polar.to_x()


TIME

time.delay

time.delay() wartet die angegebene Zeit in Millisekunden.

Syntax:

    void time.delay (int msec)

Beispiel:

function void main ()
    console.println ("Start")
    time.delay (2000)                    // das Programm wartet 2000 msec, also 2 Sekunden
    console.println ("Ende")
endfunction

time.start

Starten einer Stoppuhr.

Diese misst die Zeit in Millisekunden.

Syntax:

    void time.start ()

time.stop

Anhalten der Stoppuhr.

Die Stoppuhr wird angehalten und es wird die verstrichene Zeit seit dem Start in Millisekunden zurückgegeben.

Syntax:

    int time.stop ()

Beispiel:

function void main ()
    int millis
    time.start ()                                // Stoppuhr starten
    time.delay (500)                             // 500 msec warten
    millis = time.stop ()                        // Stoppuhr stoppen
    console.println ("Verstrichene Zeit: " : millis : " msec")
endfunction

ALARM

Mit den alarm-Funktionen können timergesteuerte Events verwaltet werden. Bis zu 8 Timer können damit verwaltet werden.

alarm.set

Setzen eines Alarm-Timers.

Syntax:

    int alarm.set (int msec)

oder

    int alarm.set (int msec, function)

Die Funktion gibt die Nummer des verwendeten Timers zurück. Diese wird dann bei der Anwendung der Funktion alarm.check() benutzt, siehe unten.

Beispiele:

Beispiel 1: Pollen des Timers:

function void main ()
    int timer1
    int seconds

    timer1 = alarm.set (1000)
    loop
        if alarm.check (timer1) = TRUE
            seconds = seconds + 1
            console.println (seconds)
        endif
        // In der Zwischenzeit etwas anderes tun
    endloop
endfunction

Beispiel 2: Automatischer Aufruf einer NIC-Funktion bei Ablauf eines Timers durch das Laufzeitsystem:

function void print_seconds ()
    static int seconds
    seconds = seconds + 1
    console.println (seconds)
endfunction

function void main ()
    int timer1
    int seconds

    timer1 = alarm.set (1000, function.print_seconds)

    loop
        // Hier irgendetwas anderes Sinnvolleres tun als zu warten.
    endloop
endfunction

alarm.check

Abfrage, ob ein Alarm-Timer abgelaufen ist.

Syntax:

    int alarm.check (int timer)

Beispiel: Pollen des Timers:

function void main ()
    int timer1
    int seconds

    timer1 = alarm.set (1000)
    loop
        if alarm.check (timer1) = TRUE
            seconds = seconds + 1
            console.println (seconds)
        endif
        // In der Zwischenzeit etwas anderes tun
    endloop
endfunction

CONSOLE

console.print

Syntax:

    int console.print (int/string EXPRESSION)

oder:

    int console.print (int/string EXPRESSION, const TYPE)

oder:

    int console.print (int/string EXPRESSION, const TYPE, int WIDTH)

Wird nur das erste Argument übergeben, wird anhand des Parameter-Typs entschieden, ob ein String oder ein Integer-Wert auf der Console ausgedruckt wird. Man kann aber auch steuern, wie der übergebene Parameter behandelt werden soll. Dabei kann TYPE die folgenden Werte haben:

  • STR - Der Parameter wird als String behandelt
  • DEC - Der Parameter wird als Dezimalzahl gedruckt
  • DEC0 - Der Parameter wird als Dezimalzahl gedruckt, jedoch wird das Ausgabefeld mit führenden Nullen aufgefüllt.
  • HEX - Der Parameter wird als Hexadezimalzahl gedruckt
  • BIN - Der Parameter wird als Binärzahl gedruckt

Mit einem weiteren numerischen Parameter WIDTH kann gesteuert werden, wie breit das Ausgabefeld sein soll. Dann wird der ausgegebene Text auf mindestens diese Anzahl von Zeichen tabuliert, das heißt mit Leerzeichen aufgefüllt. Ist WIDTH positiv, werden Leerzeichen von links beginnend eingefügt, ist WIDTH negativ, werden entsprechend viele Leerzeichen rechts angefügt, um auf die gewünschte Breite zu kommen. Eine Besonderheit ist das Drucken von HEX- oder BIN-Zahlen: Bei positivem Wert von WIDTH wird das Feld mit Nullen statt Leerzeichen aufgefüllt. Bei Dezimalzahlen geht dieses über den Typ DEC0.

Rückgabewert: Anzahl der gedruckten Zeichen.

Beispiele siehe console.println ().

console.println

Syntax:

    int console.println (int/string EXPRESSION)

oder:

    int console.println (int/string EXPRESSION, const TYPE)

oder:

    int console.println (int/string EXPRESSION, const TYPE, int WIDTH)

Das Verhalten entspricht exakt der Funktion console.print(). Jedoch wird noch nach dem Ausdruck ein CRLF-Paar angefügt, um eine neue Zeile zu beginnen.

Beispiele:

function void main ()
    int i

    i = 4711
    console.println (i)                        // 4711
    console.println (i, DEC)                   // 4711
    console.println (i, HEX)                   // 1267
    console.println (i, BIN)                   // 1001001100111

    i = 0xFFFFFFFF
    console.println (i, HEX)                   // FFFFFFFF
    console.println (i >> 1, HEX)              // 7FFFFFFF

    i = 0xFFFF
    console.println (i, HEX)                   // FFFF
    console.println (i, HEX, 8)                // 0000FFFF
    console.println (i >> 1, HEX, 8)           // 7FFF

    i = 0xFF
    console.println (i, HEX, 2)                // FF
    console.println (i >> 1, HEX, 2)           // 7F
    console.println (i >> 1, BIN, 16)          // 0000000001111111

    console.println (0xAA, BIN, 8)             // 10101010
    console.println (1, BIN, 32)               // 00000000000000000000000000000001

    console.println ("Hallo, Welt")            // Hallo, Welt
    console.println ("42 Hallo, Welt")         // 42 Hallo, Welt
    console.println ("42 Hallo, Welt", DEC)    // 42
    console.println ("42 Hallo, Welt", BIN)    // 101010
endfunction

console.putc

Ausgabe eines Zeichens auf der Console: Syntax:

    void console.putc (int ch)

Beispiel: Anzeige aller auf UART2 empfangenen Zeichen auf der Console:

function void main ()
    int ch

    loop
        ch = uart2.getc (ch)
        console.putc (ch)
    endloop
endfunction

MCURSES

Die MCURSES Bibliothek gibt es auch als C-Funktionsbibliothek. Die ausführliche Dokumentation findet man im Artikel MCURSES.

Hier werden die NIC-Funktionen der MCURSES-Bibliothek vorgestellt. Im Zweifel kann man im MCURSES-Artikel ausführlichere Informationen und Beispiele finden.

mcurses.initscr

Init Screen: Initialisierung.

Diese muss vor allen anderen MCURSES-Funktionsaufrufen aufgerufen werden.

Syntax:

    void mcurses.initscr ()

mcurses.move

Move: Setzen des Cursors auf eine Position (y,x).

Syntax:

    void mcurses.move (int y, int x)

Achtung: Die Cursorpositionen beginnen mit 0.

mcurses.attrset

Attribute Set: Bildschirmattribut setzen.

Syntax:

    void mcurses.attrset (int attr)

Mögliche Attribute:

   A_NORMAL                Normal
   A_UNDERLINE             Unterstrichen
   A_REVERSE               Invers
   A_BLINK                 Blinkend
   A_BOLD                  Fett
   A_DIM                   Dunkel
   A_STANDOUT              Hervorgehoben (i.d.R. wie A_BOLD)
   F_BLACK                 Vordergrund schwarz
   F_RED                   Vordergrund rot
   F_GREEN                 Vordergrund grün
   F_BROWN                 Vordergrund braun
   F_YELLOW                Vordergrund gelb
   F_BLUE                  Vordergrund blau
   F_MAGENTA               Vordergrund lila
   F_CYAN                  Vordergrund türkis
   F_WHITE                 Vordergrund weiß
   B_BLACK                 Hintergrund schwarz
   B_RED                   Hintergrund rot
   B_GREEN                 Hintergrund grün
   B_BROWN                 Hintergrund braun
   B_YELLOW                Hintergrund gelb
   B_BLUE                  Hintergrund blau
   B_MAGENTA               Hintergrund lila
   B_CYAN                  Hintergrund türkis
   B_WHITE                 Hintergrund weiß

Diese können mit dem "|"-Operator verknüpft werden, zum Beispiel:

    void mcurses.attrset (A_REVERSE | F_RED)

mcurses.addch

Add Character: Zeichen an aktueller Cursor-Position ausgeben.

Syntax:

    void mcurses.addch (int ch)

mcurses.mvaddch

Move Add Character: Cursor setzen, dann Zeichen ausgeben.

Syntax:

    void mcurses.mvaddch (int y, int x, int ch)

mcurses.addstr

Add String: String an aktueller Cursor-Position ausgeben.

Syntax:

    void mcurses.addstr (string str)

mcurses.mvaddstr

Move Add String: Cursor setzen, dann String ausgeben.

Syntax:

    void mcurses.mvaddstr (int y, int x, string str)

mcurses.printw

Print Window Formatted: Formatierte Ausgabe. Im Moment noch nicht implementiert. TODO.

Syntax:

    void mcurses.printw (string fmt, arg1, ...)

mcurses.mvprintw

Move Print Window Formatted: Formatierte Ausgabe mit vorheriger Cursorpositionierung.

Syntax:

    void mcurses.mvprintw (int y, int x, string fmt, arg1, ...)

Siehe auch mcurses.printw().

mcurses.getnstr

Get String: Abfrage einer Benutzereingabe.

Der Benutzer kann seine Eingabe mit den üblichen Tasten wie Cursor, Pos1, Ende, Entf, Backspace usw. editieren.

Syntax:

    string mcurses.getnstr (string default, int maxlen)

mcurses.mvgetnstr

Move Get String: Abfrage einer Benutzereingabe mit vorheriger Cursor-Positionierung.

Syntax:

    void mcurses.mvgetnstr (int y, int x, string default, int maxlen)

mcurses.setscrreg

Set Scrolling Region: Scrolling Region setzen.

Beim Start per initscr() geht die Standard Scrolling Region von der obersten bis zur untersten Zeile des Terminals.

Syntax:

    void mcurses.setscrreg (int top, int bottom)

mcurses.deleteln

Delete Line: Aktuelle Zeile löschen.

Befindet sich die Zeile innerhalb der Scrolling Region, werden nachfolgende Zeilen nach oben verschoben, also "gerollt".

Syntax:

    void mcurses.deleteln ()

mcurses.insertln

Insert Line: Zeile einfügen.

Befindet sich die Zeile innerhalb der Scrolling Region, werden nachfolgende Zeilen nach unten verschoben, also "gerollt".

Syntax:

    void mcurses.insertln ()

mcurses.scroll

Scroll: Rollen der Zeilen innerhalb der Scrolling Region um eine Zeile nach oben.

Syntax:

    void mcurses.scroll ()

mcurses.clear

Clear: Löschen des kompletten Bildschirminhaltes.

Syntax:

    void mcurses.clear ()

mcurses.erase

Erase: Löschen des Bildschirminhaltes - identisch mit mcurses.clear().

Syntax:

    void mcurses.erase ()

mcurses.clrtobot

Clear To Bottom: Löschen des Bildschirminhaltes ab der Cursor-Position bis unten.

Syntax:

    void mcurses.clrtobot ()

mcurses.clrtoeol

Clear To End Of Line: Löschen des Bildschirminhaltes ab der Cursor-Position bis zum Ende der Zeile.

Syntax:

    void mcurses.clrtoeol ()

mcurses.delch

Delete Character: Zeichen vorwärts löschen.

Nachfolgende Zeichen in derselben Zeile werden anschließend nach links verschoben.

Syntax:

    void mcurses.delch ()

mcurses.mvdelch

Move Delete Character: Zeichen vorwärts löschen mit vorheriger Cursorpositionierung.

Nachfolgende Zeichen in derselben Zeile werden anschließend nach links verschoben.

Syntax:

    void mcurses.mvdelch (int y, int x)

Siehe auch: mcurses.delch()

mcurses.insch

Insert Character: Zeichen einfügen.

Nachfolgende Zeichen in derselben Zeile werden vorher um eine Stelle nach rechts verschoben.

Syntax:

    void mcurses.insch (int ch)

mcurses.mvinsch

Move Insert Character: Zeichen einfügen - mit vorheriger Cursorpositionierung.

Nachfolgende Zeichen in derselben Zeile werden vorher um eine Stelle nach rechts verschoben.

Syntax:

    void mcurses.mvinsch (int y, int x, int ch)

Siehe auch: mcurses.insch()

mcurses.nodelay

Zur Zeit für STM32 nicht implementiert.

Syntax:

    void mcurses.nodelay ()

mcurses.halfdelay

Zur Zeit für STM32 nicht implementiert.

Syntax:

    void mcurses.halfdelay ()

mcurses.getch

Lesen eines Tastendrucks von der Console. Diese können alphanumerische Zeichen, aber auch Funktionstasten wie zum Beispiel KEY_DC (Entf), KEY_HOME (Pos1) oder KEY_END (Ende) sein.

TODO: KEY_XXX kann zur Zeit nicht als Konstante im Programm angegeben werden.

Syntax:

    int mcurses.getch ()

mcurses.curs_set

Sichtbarkeit des Cursors.

Syntax:

    void mcurses.curs_set (int visibility)

Beispiel:

    curses.curs_set (FALSE)                  // Cursor verstecken (unsichtbar)
    ....
    curses.curs_set (TRUE)                   // Cursor wieder zeigen (sichtbar)

mcurses.refresh

Ausgabe forcieren.

Syntax:

    void mcurses.refresh ()

Die serielle Ausgabe über UART kann langsamer laufen als das Programm selbst. Mit refresh() wird gewartet, bis die Ausgabe auf dem Terminal wieder synchron ist.

Anmerkung: Sobald eine Benutzerinteraktion mittels getch() abgefragt wird, wird vorher die Funktion refresh() automatisch von der MCURSES-Laufzeitbibliothek aufgerufen, damit der Bildschirminhalt aktuell ist.

mcurses.gety

Holen der aktuellen Zeilenposition.

Es wird die Zeilenposition - gerechnet ab 0 - zurückgegeben.

Syntax:

    int mcurses.gety ()

Beispiel:

    int line
    mcurses.addstr ("Hello")
    line = mcurses.gety ()
    move (line + 1, 0)              // An den Anfang der nächsten Zeile springen
    mcurses.addstr ("World")

mcurses.getx

Holen der aktuellen Cursor Spaltenposition.

Es wird die Spaltenposition - gerechnet ab 0 - zurückgegeben.

Syntax:

    int mcurses.getx ()

Beispiel:

    int line
    int column

    addstr ("Hello")
    line   = mcurses.gety ()
    column = mcurses.getx ()
    move (line + 1, column + 4)              // Eine Zeile nach unten, 4 Spalten nach rechts
    addstr ("World")

mcurses.endwin

MCURSES beenden.

Dieses sollte die LETZTE aufgerufene MCURSES-Funktion eines NIC-Programms sein.

Syntax:

    void mcurses.endwin ()

BIT

bit.set

bit.set() setzt ein Bit.

Syntax:

    int bit.set (int value, int bit)

Beispiel:

function void main ()
    int zahl
    zahl = 0b00000000
    zahl = bit.set (zahl, 5)
    console.println (zahl, BIN)     // Ausgabe: 00100000
endfunction

Alternativ kann man auch den Operator '|' nutzen, um ein Bit zu setzen:

function void main ()
    int zahl
    zahl = 0b00000000
    zahl = zahl | (1<<5)
    console.println (zahl, BIN)     // Ausgabe: 00100000
endfunction

bit.reset

bit.reset() setzt ein Bit zurück.

Syntax:

    int bit.reset (int value, int bit)

Beispiel:

function void main ()
    int zahl
    zahl = 0b11111111
    zahl = bit.reset (zahl, 5)
    console.println (zahl, BIN)     // Ausgabe: 11011111
endfunction

Alternativ kann man auch den Operator '&' zusammen mit '~' nutzen, um ein Bit zu zurückzusetzen:

function void main ()
    int zahl
    zahl = 0b11111111
    zahl = zahl & ~(1<<5)
    console.println (zahl, BIN)     // Ausgabe: 11011111
endfunction

bit.toggle

bit.toggle() wechselt den Zustand eines Bits.

Syntax:

    int bit.toggle (int value, int bit)

Beispiel:

function void main ()
    int zahl
    zahl = 0b00000000
    zahl = bit.toggle (zahl, 5)
    console.println (zahl, BIN)     // Ausgabe: 00100000
    zahl = bit.toggle (zahl, 5)
    console.println (zahl, BIN)     // Ausgabe: 00000000
endfunction

bit.isset

bit.isset() gibt den Zustand eines Bits zurück

Syntax:

    int bit.isset (int value, int bit)

Rückgabewert: TRUE, wenn das Bit gesetzt ist, sonst FALSE.

Beispiel:

function void main ()
    int bit
    bit = bit.isset (0b11111111, 5)
    if bit = TRUE
        console.println ("bit is set")
    else
        console.println ("bit is not set")
    endif
endfunction

BITMASK

Mit den Bitmask-Funktionen können zwei Bitmasken bitweise miteinander verknüpft werden.

bitmask.and

AND Verknüpfung.

Syntax:

    int bitmask.and (int mask1, int mask2)

Beispiel:

     int value1
     int value2

     value1 = bitmask.and (0b1110000, 0b10100000)
     value2 = bitmask.and (0xAA, 0x13)

bitmask.nand

NAND Verknüpfung.

Syntax:

    int bitmask.nand (int mask1, int mask2)

Beispiel:

     int value1
     int value2

     value1 = bitmask.nand (0b1110000, 0b10100000)
     value2 = bitmask.nand (0xAA, 0x13)

bitmask.or

Logische OR Verknüpfung.

Syntax:

    int bitmask.or (int mask1, int mask2)

Beispiel:

     int value1
     int value2

     value1 = bitmask.or (0b1110000, 0b10100011)
     value2 = bitmask.or (0xF0, 0x03)

bitmask.nor

Logische NOR Verknüpfung.

Syntax:

    int bitmask.nor (int mask1, int mask2)

Beispiel:

     int value1
     int value2

     value1 = bitmask.nor (0b1110000, 0b10100011)
     value2 = bitmask.nor (0xF0, 0x03)

bitmask.xor

Logische XOR Verknüpfung.

Syntax:

    int bitmask.xor (int mask1, int mask2)

Beispiel:

     int value1
     int value2

     value1 = bitmask.xor (0b1110000, 0b10100011)
     value2 = bitmask.xor (0xF0, 0x03)

bitmask.xnor

Logische XNOR Verknüpfung.

Syntax:

    int bitmask.xnor (int mask1, int mask2)

Beispiel:

     int value1
     int value2

     value1 = bitmask.xnor (0b1110000, 0b10100011)
     value2 = bitmask.xnor (0xF0, 0x03)

Benchmarks

Sieb des Eratosthenes

Es geht um die Ermittlung sämtlicher Primzahlen bis 100.000.

Sieb des Eratosthenes in NIC:

function void main ()
    int limit = 100000
    int zahl
    int zaehler
    int primzahl
    int letzte_primzahl

    for zahl = 2 to limit
        primzahl = TRUE

        for zaehler = 2 to zahl / 2
            if zahl % zaehler = 0
                primzahl = FALSE
                break
            endif
        endfor

        if primzahl = TRUE
            // console.println (zahl : " ist eine Primzahl")
            letzte_primzahl = zahl
        endif
    endfor
    console.println ("Die hoechste ermittelte Primzahl ist " : letzte_primzahl)
endfunction

Sieb des Eratosthenes in PHP:

<?php
$limit = 100000;

for ($zahl = 2; $zahl <= $limit; $zahl++)
{
    $primzahl = true;

    for ($zaehler = 2; $zaehler <= $zahl / 2; $zaehler++)
    {
        if ($zahl % $zaehler == 0)
        {
            $primzahl = false;
            break;
        }
    }

    if ($primzahl)
    {
        // print ("" . $zahl . " ist eine Primzahl\n");
        $letzte_primzahl = $zahl;
    }
}
print ("Die hoechste ermittelte Primzahl ist " . $letzte_primzahl . "\n");
?>

Sieb des Eratosthenes in Python:

def main():
    limit = 100000

    for zahl in range (2, limit):
        primzahl = True

        for zaehler in range (2, zahl / 2):
            if zahl % zaehler == 0:
                primzahl = False
                break

        if primzahl == True:
            letzte_primzahl = zahl
    print "Die hoechste ermittelte Primzahl ist %d" % (letzte_primzahl)
    return 0

if __name__ == '__main__':
    main()

Sieb des Eratosthenes in Lua:

local limit = 100000
local zahl
local zaehler
local primzahl
local letzte_primzahl

for zahl = 2, limit do
    primzahl = true

    for zaehler = 2, zahl / 2 do
        if zahl % zaehler == 0 then
            primzahl = false
            break
        end
    end

    if primzahl == true then
        -- print (zahl, "ist eine Primzahl")
        letzte_primzahl = zahl
    end
end
print ("Die hoechste ermittelte Primzahl ist ", letzte_primzahl)

Die Umsetzung in PHP, Python und Lua wurde durch 1:1-Ersetzung jeder Zeile durch die adäquate Formulierung in der jeweiligen Programmiersprache vorgenommen. Die Programme sind damit direkt vergleichbar.

Ausführungszeiten:

Ergebnisse: Sieb des Eratosthenes
Programmiersprache Version i7-2600 CPU @ 3.40GHz i7-6700 CPU @ 3.40GHz Intel T6500 @ 2.10GHz
NIC 0.9 5,3 sec 4,5 sec 17,3 sec
PHP 5.6.23 10,4 sec 7,9 sec
PHP 7.0 9,7 sec 7,3 sec
Python 2.7 25,6 sec 27,95 sec 118 sec
Lua 5.2 6,1 sec

Es empfiehlt sich nicht, obiges Programm auf dem STM32 mit dem oben verwendeten Startwert 100000 laufen zu lassen. Dabei muss man schon seeeehr lange warten, da der STM32 doch um einiges langsamer ist als ein moderner Intel-Prozessor. Für eigene Messungen auf dem STM32 empfiehlt sich ein Startwert von 10000, also eine Größenordnung geringer.

Anhang

Compiler-Warnungen

function 'foo' not used.

Die Funktion namens foo ist zwar definiert, wird aber nicht aufgerufen.

variable 'foo' not used.

Die Variable 'foo' wird zwar definiert, aber nicht verwendet.

variable 'foo' set but not used.

Die Variable 'foo' wird zwar zwischenzeitlich gesetzt, aber sonst nicht weiter verwendet.

local variable 'foo' shadows global variable 'foo' defined in line NN.

Die lokale Variable 'foo' überdeckt die globale gleichnamige Variable. Dies kann je nach Kontext zu unerwünschten Effekten führen: In der Funktion, wo die lokale Variable definiert wurde, wird dann der Wert dieser lokalen Variablen verwendet, in allen anderen Funktionen jedoch der Wert der globalen Variablen.

Compiler-Fehlermeldungen

return with a value, in function returning void.

Die definierte void-Funktion liefert unzulässigerweise einen Return-Wert zurück.

return with no value, in function returning non-void.

Die definierte Funktion mit Datentyp als Rückgabewert liefert unzulässigerweise keinen Return-Wert zurück.

keyword 'foo' unexpected.

Das Schlüsselwort 'foo' ist an der angegebenen Stelle unerwartet und damit falsch.

Variable 'foo' already defined.

Die Variable 'foo' wurde bereits definiert.

break stack overflow.

Die Schachtelungstiefe von Schleifen ist zu tief und somit kann die aktuelle Schleife nicht mehr durch einen Break-Befehl unterbrochen werden.

continue stack overflow.

Die Schachtelungstiefe von Schleifen ist zu tief und somit kann die aktuelle Schleife nicht mehr durch einen Continue-Befehl am Anfang der Schleife fortgesetzt werden.

double negation not allowed.

Eine doppelte Negation eines numerischen Wertes ist nicht erlaubt.

empty expression.

Der erwartete arithmetische Ausdruck ist leer.

invalid argument type 'foo' in function 'bar', argument #NN.

Der angegebene Parameter an NN-ter Stelle hat einen ungültigen Datentyp.

main must be defined as function returning void.

Die main-Funktion muss stets vom Datentyp void definiert werden.

no main function found.

Ein NIC-Programm muss immer eine main-Funktion als Startpunkt enthalten.

function 'foo' already defined.

Die Funktion namens 'foo' wurde bereits definiert.

function 'foo' returns void.

Es wird versucht, den Rückgabewert einer Funktion 'foo' weiterzuverwenden, obwohl der Datentyp dieser Funktion 'void' ist.

function 'foo' undefined.

Die aufgerufene Funktion 'foo' ist nicht definiert.

unterminated string.

Die angegebene Zeichenkette wurde nicht mit dem Anführungszeichen terminiert.

missing 'endfoo' at end of file.

Ein geöffneter Kontrollblock wurde nicht geschlossen.

missing arguments for function 'foo'.

Es wurden keine Parameter für die Funktion foo angegeben.

missing closing bracket.

Eine geöffnete Klammer wurde nicht geschlossen.

no matching ')' found.

Zu einer geschlossenen Klammer wurde die dazugehörende geöffnete Klammer nicht gefunden.

missing return before 'endfunction'.

Das Return-Statement unmittelbar vor dem Ende der Funktion fehlt.

no compare operator found.

Es wurde kein Vergleichsoperator im Ausdruck angegeben.

number of arguments wrong for call of function 'foo', expected NN.

Die Anzahl der Parameter für den Aufruf der Funktion 'foo' ist falsch, richtig ist NN.

syntax error. Use brackets around negative expression.

Ein Ausdruck mit einem negativen Operanden (wie z.B. "3 * -2") bedeutet einen Syntax-Error. In diesem Fall muss der zu negierende Operand geklammert werden, also: "3 * (-2)".

too many arguments for function 'foo'.

Zu viele Parameter für den Aufruf der Funktion namens 'foo'.

too many closing brackets.

Der Ausdruck enthält zu viele geschlossene Klammern.

too many open brackets.

Der Ausdruck enthält zu viele geöffnete Klammern.

variable 'foo' already defined.

Die Variable 'foo' wurde bereits definiert.

variable 'foo' not defined.

Die Variable 'foo' wurde vorher nicht definiert.