MINOS

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Von Frank M. (ukw)

MINOS - Minos Is No Operating System

MINOS ist eine sehr einfache, trotzdem aber auch komfortable Firmware, welche auf einem STM32F4xx läuft - vorzugsweise dem STM32F407VET6 Black Board, welches für einen einstelligen Euro-Betrag zu haben ist.

MINOS

MINOS unterstützt dabei ein Dateisystem auf einer eingesetzten SD-Karte und verwendet eine UNIX/Linux ähnliche Mini-Shell mit Command Line Editing und Dateinamen-Expansion. Desweiteren ist sowohl der NIC-Compiler und der dazugehörende Objekt-Interpreter als auch ein kleiner Editor zum Erfassen von Shell-/Boot- und NIC-Scripts vorhanden.

Derzeit wird folgende auf dem Black Board integrierte Hardware unterstützt:

  • SD-Karte mit FAT32-Dateisystem
  • GPIO für jeden verfügbaren Pin
  • Board-LEDs
  • Buttons als Taster
  • UART1 zum Betrieb als VT100-kompatible Console - vorzugsweise PuTTY als Terminal-Emulation
  • Batteriegestützte Echtzeituhr des STM32F407
  • SPI Flash mit 2 MB
Aufbau MINOS

Als externe Hardware wird zur Zeit unterstützt:

  • UART2 zum Anschluss von externer Peripherie über serielle Schnittstelle
  • WS2812 LED Stripes
  • FSMC-Schnittstelle zum Anschluss von SSD1963-TFT-Displays, zum Beispiel 7" 800 x 480.
  • SPI - zum Beispiel Touch auf einem angeschlossenen SSD1963-Display
  • I2C - zum Beispiel externe RTC DS3231

Sämtliche verwendete Peripherie kann mit NIC-Programmen angesteuert werden.

Da der NIC-Compiler "nicc" bereits "On Board" ist und dieser den NIC-Quelltext meist in weniger als einer Sekunde übersetzt, ist der Zyklus:

  • Bearbeiten des Quelltextes
  • Übersetzen
  • Ausführen

extrem kurz, da das zeitaufwändige Flashen des Mikrocontrollers komplett entfallen kann. Nach dem Übersetzen kann ein NIC-Programm sofort gestartet und getestet werden, denn es wird im RAM ausgeführt. Außerdem muss sich der Anwender nicht mit kompliziert anmutenden STM32-typischen Peripherie-Initialisierungs-Schlachten herumschlagen, sondern kann direkt loslegen. Schon das erste LED-Blink-Programm besteht nur aus wenigen Zeilen und ist sehr einfach und verständlich gehalten, siehe auch: NIC#Hello_World.

Im folgenden wird das MINOS-System erklärt, welches eine konkrete Umsetzung der NIC-Laufzeitumgebung beinhaltet. Sämtliche Hardware auf dem STM32F407VET6 Black Board wird dabei von MINOS unterstützt - und vieles mehr, beispielsweise ist hier die Unterstützung von WS2812-LED-Stripes, TFT-Displays und anderen seriell anzuschließenden Zusatzgeräten zu nennen.

Weitere Hardware-Unterstützungen sind geplant und kontinuierlich ausgebaut.

Download

Sourcecode auf GitHub: https://github.com/ukw100/MINOS

Release 0.9.1 vom 16.05.2021 incl. fertige HEX-Files:

https://github.com/ukw100/MINOS/releases/tag/0.9.1

Console

Die Console ist über UART1 (PA9/PA10) mit 115200 Bd (8 Bit, no parity) erreichbar.

Empfohlen wird für Windows PuTTY, da dessen Copy & Paste Funktionen sehr praktisch sind.

Dateisystem

Für das Dateisystem wird eine FAT32-formatierte SD-Karte mit einer Kapazität von bis zu 32 GB eingesetzt. Ideal ist eine SD-Karte mit 2 oder 4 GB. Als Software wird dabei FatFS von ChaN verwendet. Das Dateisystem unterstützt zur Zeit nur Dateinamen im 8.3-Format. Die Erweiterung auf längere Dateinamen ist geplant.

WICHTIG: Bei der Eingabe von Dateinamen werden Groß-/Kleinschreibungen ignoriert.

Folgende Befehle sind also vollkommen gleichwertig:

   cat README.TXT
   cat readme.txt
   cat README.txt

Dassselbe gilt für sämtliche Dateioperationen sowohl in der MINOS-Shell als auch in NIC-Programmen.

MINOS-Shell

Shell

Die MINOS-Shell ist eine Linux-ähnliche, jedoch sehr rudimentäre Shell zur Abarbeitung bzw. Eingabe von Befehlen.

Eine Liste der Standardbefehle findet man im Kapitel Shell-Befehle.

Die MINOS-Shell beherrscht unter anderem auch Command Line Editing, Wildcard-Expansion und Ausgabe-Umlenkungen auf Dateien. Diese werden in den folgenden Unterkapiteln erklärt. Desweiteren kann die Shell auch die Dauer von Befehlen mit dem Zusatzbefehl "time" messen, siehe time.

Jedes Kommando, welches man aus der MINOS-Shell startet, kann auf der Console mit STRG-C vorzeitig abgebrochen werden. Dazu gehören auch eigens geschriebene NIC-Programme.

Command Line Editing

Für das Command-Line-Editing können folgende Tasten verwendet werden:

Tastenbefehle
Taste Alternative Taste Befehl
Cursortasten links/rechts Cursor nach links
Cursortaste oben Vorherigen Befehl aus der Historie aufrufen
Cursortaste unten Nächsten Befehl aus der Historie aufrufen
Pos1 (Home) STRG-A Zum Anfang der Zeile
Ende (End) STRG-E Zum Ende der Zeile
Entf (Del) STRG-D Löschen eines Zeichens - vorwärts
Backspace CTRL-H Löschen eines Zeichens - rückwärts
STRG-U Löschen der Zeile bis zum Zeilenanfang
STRG-K Löschen der Zeile bis zum Zeilenende
TAB Dateinamen Expansion, soweit eindeutig
TAB TAB Auflistung aller passenden Dateinamen
ENTER CR Absenden des Befehls

Wildcards

Als Wildcards (Joker-Zeichen) können der Stern (* = beliebige Zeichenfolge) als auch das Fragezeichen (? = genau ein beliebiges Zeichen) verwendet werden. Zur Zeit werden Wildcards nur im letzten Teil des angegebenen Pfades evaluiert, d.h:

   ls *.nic      - funktioniert
   ls bin/*.nic  - funktioniert
   ls bi*        - funktioniert
   ls bi*/*.nic  - funktioniert nicht

Die MINOS-Shell expandiert sämtliche Wildcards selbst und gibt das Ergebnis der zutreffenden Dateinamen als Liste an den gewählten Befehl weiter. Der gewählte Befehl wertet grundsätzlich selbst keine Wildcards aus. Das macht immer die aufrufende Shell.

Beispiel:

   ls -l *.nic

Die Shell findet dann blink.nic und sieve.nic als zutreffende Dateinamen und ruft anschließend das Kommando ls folgendermaßen auf:

   ls -l blink.nic sieve.nic

Vorsicht:

Gibt man zum Beispiel folgendes ein:

   cp *.txt

weil man das Zielverzeichnis vergessen hat und befinden sich auf der SD-Karte genau zwei TXT-Dateien, z.B. LIESMICH.TXT und README.TXT, so wird der obige Befehl von der MINOS-Shell expandiert zu:

   cp LIESMICH.TXT README.TXT

Nach der Ausführung ist dann die Datei README.TXT überschrieben - was höchstwahrscheinlich nicht gewollt ist.

Umlenkungen

Sowohl der Standardausgabe-Kanal (stdout) als auch der Fehlermeldungen-Kanal (stderr) können auf Dateien umgelenkt werden.

Umlenkung von stdout:

   Befehl >datei.out

Dabei wird die Datei datei.out neu erzeugt. Man kann die Standard-Ausgabe aber auch an eine Datei anhängen:

   Befehl >>datei.out

Existiert die Datei noch nicht, wird sie neu erzeugt. Anderenfalls wird die Ausgabe an die bereits vorhandene Datei angehängt.

Umlenkung von stderr:

   Befehl 2>datei.err

Dabei wird die Datei datei.err neu erzeugt. Man kann die Standard-Fehlerausgabe aber auch an eine Datei anhängen:

   Befehl 2>>datei.err

Existiert die Datei noch nicht, wird sie neu erzeugt. Anderenfalls wird die Fehlerausgabe an die bereits vorhandene Datei angehängt.

Kombinationen der Umlenkungen sind auch möglich, zum Beispiel:

   nicc -v sieve.nic >msg.out 2>msg.err

Die Ausgaben des Standardausgabe-Kanals stdout landen dann in der Datei msg.out, etwaige Fehlermeldungen über den Fehlerausgabe-Kanal stderr in der Datei msg.err.

Shell-Befehle

Anmerkung:

Jedem Shell-Befehl kann der Befehl "time" vorangestellt werden. Damit kann die Dauer der Abarbeitung des Befehls in Millisekunden gemessen werden.


cat

Der Befehl zeigt eine oder mehrere Dateien hintereinander auf der Console an. Wird keine Datei angegeben, wird die Standard-Eingabe auf die Standard-Ausgabe kopiert. Dies kann man für Dateiumlenkungen nutzen, um zum Beispiel Dateien vom PC über PuTTY auf MINOS zu kopieren. Beispiel siehe unten.

Aufruf:

    cat [-e] [file ...]

Optionen:

   -e Echo auf der Console unterdrücken

Beispiele:

Anzeige der Datei README.TXT:

    cat README.TXT

Kopieren der Datei src auf die Datei dest

    cat src >dest

Kopieren der Dateien file1 und file2 auf die Datei all:

    cat file1 file2 >all

Anhängen der Datei file3 an die Datei all:

    cat file3 >>all

Lesen des Inhalts von stdin und Speicherung in der Datei file.out:

    cat >file.out
Umlenkung stdin auf Datei per cat

Letzteres kann durchaus dafür genutzt werden, um Text-Inhalte vom PC auf MINOS-Dateien zu kopieren - z.B. NIC-Quelltexte. Dazu kopiert man auf dem PC den Text in den Zwischenspeicher, gibt anschließend im MINOS ein:

    cat >equinox.n

und drückt dann im PuTTY die rechte Maustaste, um den Text im Terminal-Fenster einzufügen. Steht der Cursor anschließend nicht direkt am Anfang einer neuen Zeile, drückt man danach noch einmal die ENTER-Taste, um die Zeile abzuschließen. Ein anschließendes Drücken von STRG-D (das ist das EOF-Zeichen der MINOS-Console) schließt die Datei und der cat-Befehl wird beendet.

Da es wegen des Console-Echos bei größeren Dateiübertragungen (ab ca. 4KB) zu Zeichenverlusten kommen kann, sollte man hier besser das Echo auf der Console abstellen:

    cat -e >equinox.n

Natürlich kann man auch die SD-Karte in den PC stecken, um darauf Dateien zu transferieren. Anschließend steckt man die Karte wieder ins STM32-Board und schon sind nach einem mount-Befehl die Dateien im MINOS verfügbar. Nicht vergessen, vor dem Entnehmen der Karte diese mit umount abzumelden!


cd

Wechseln des Arbeitsverzeichnisses.

Dabei kann das neue Verzeichnis mit absolutem oder relativem Pfad (unter berücksichtigung des aktuellen Arbeitsverzeichnisses) angegeben werden.

Aufruf:

    cd directory

Beispiele:

In das Unterverzeichnis src wechseln - relative Pfadangabe:

    cd src

In das "benachbarte" Unterverzeichnis bin wechseln - relative Pfadangabe:

    cd ../bin

In das direkt unter dem Root-Verzeichnis befindliche Verzeichnis /test wechseln - mit absoluter Pfadangabe:

    cd /test

Siehe auch: pwd


clocks

Anzeige der Clock-Einstellungen des STM32F407. Dieser Befehl hat reinen Informationscharacter.

Aufruf:

    clocks

cp

Kopieren von einer Datei auf eine andere Datei oder Kopieren von mehreren Dateien in ein Verzeichnis.

Aufruf:

    cp [-f] [-v] source dest
    cp [-f] [-v] source ... destdir

Optionen:

    -f - fast copy: Die Datei wird in größeren Blöcken kopiert, etwas schneller als über stdio File-I/O.
    -v - verbose: Die Dateinamen werden während des Kopierens angezeigt.

WICHTIG: Existiert die Zieldatei bereits, wird diese ohne Warnung überschrieben.

Beispiele:

Kopieren der Datei blink.n nach blinki.n:

    cp blink.n blinki.n

Kopieren aller Dateien mit Endung ".txt" in das Verzeichnis /doc - mit Ausgabe der zu kopierenden Dateien:

    cp -v *.txt /doc

Siehe auch: mv


date

Anzeige oder Setzen des aktuellen Datums zusammen mit der Uhrzeit. Dabei wird die interne batteriegestützte Echtzeituhr des STM32F407 verwendet.

Aufruf:

    date [YYYY-MM-DD hh:mm:ss]

Beispiele:

1. Ausgabe von Datum und Uhrzeit:

    date

2. Setzen Datum/Uhrzeit:

    date 2018-04-12 12:10:47

df

Anzeige des (freien) Speicherbereichs auf der SD-Karte.

Aufruf:

    df

echo

Meldung ausgeben.

Aufruf:

    echo [text]

Beispiele:

Ausgabe von "Hello World!":

    echo Hello World!

Ausgabe aller Dateien, die im Namen auf ".nic" enden:

    echo Alle NIC-Dateien: *.nic

Ausgabe aller Dateien, die im Namen auf ".txt" enden:

    echo *.txt

find

Rekursives Dateilisting erstellen:

Aufruf:

    find [directory]

Beispiele:

Rekursive Ausgabe aller Dateien im aktuellen Arbeitsverzeichnis:

    find

Rekursive Ausgabe aller Dateien im Unterverzeichnis src:

    find src

Rekursive Ausgabe mit Umlenkung in eine Text-Datei:

    find src >srclist.txt

Der Aufruf wird später auch mit Angabe eines Such-Pattern möglich sein.


fe

Fine Editor - Einfacher Text-Editor.

Aufruf:

    fe file

Beispiel:

    fe blink.n

Siehe auch: Editor

MINOS-Editor fe

led

Ansteuerung der primären Board-LED.

Aufruf:

    led off|on

Beispiel:

    led on
    led off

ls

Datei-Auflistung

Aufruf:

    ls [-alUStr] [files...]

Optionen:

    -a - all: Anzeige auch der versteckten Systemdateien
    -l - long: Ausführliche Liste mit Directory-Angabe, Berechtigungen und Dateigröße
    -U - unsorted: Unsortierte Ausgabe
    -S - sort by size: Ausgabe wird nach Dateigröße sortiert, größte Datei zuerst
    -t - sort by time: Ausgabe wird nach Datum/Zeit sortiert, neueste Datei zuerst
    -r - sort reverse: Gewünschte Sortierung wird umgedreht

Standardmäßig wird die Ausgabe nach den anzuzeigenden Dateinamen alphabetisch sortiert.

Die Optionen können auch kombiniert werden, also zum Beispiel:

   ls -a -l -t *.nic

oder auch kürzer:

   ls -alt *.nic

Die Reihenfolge der Optionen ist egal, daher funktioniert es auch umgekehrt:

   ls -lta *.nic

oder auch in beliebig zusammengemischten Gruppen:

   ls -la -tr *.nic

Beispiele:

Ausführliche Anzeige aller im Arbeitsverzeichnis befindlichen Dateien:

    ls -l

Anzeige von der Sortierung her umdrehen:

    ls -lr

Ausführliche Anzeige inkl. der versteckten Dateien:

    ls -al

Ausführliche Anzeige sortiert nach Zeit, neuest zuerst:

    ls -lt

Ausführliche Anzeige sortiert nach Zeit, älteste zuerst:

    ls -ltr

Anzeige aller Dateien, die sich im Unterverzeichnis src befinden:

    ls -l src

Anzeige aller Dateien, welche die Dateinamen-Endung ".nic" haben:

    ls -l *.nic

Anzeige aller Dateien im Unterverzeichnis src mit Dateiendung ".nic":

    ls -l src/*nic

mkdir

Erstellung eines neuen Unterverzeichnisses.

Aufruf:

    mkdir directory

Beispiel:

    cd /
    mkdir bin

Siehe auch: rmdir


mount

Anmelden der eingesetzten SD-Karte.

Dieses passiert bereits beim Booten von MINOS, kann aber jederzeit nach einem umount-Befehl manuell eingegeben werden - z.B. nach einem SD-Kartenwechsel.

Aufruf:

    mount

Beispiel:

    umount
    ... (Karte wird zum Beispiel gewechselt)
    mount

Siehe auch: umount


mv

Datei oder Dateien umbenennen bzw. verschieben.

Aufruf:

    mv [-v] source dest
    mv [-v] source ... destdir

Optionen:

    -v - verbose: Die Dateinamen werden während des Verschiebens angezeigt.

Existiert die Zieldatei bereits, wird das Umbenennen/Verschieben mit einer Fehlermeldung abgebrochen.

Beispiele:

Umbenennen der Datei blink.n nach blinki.n:

    mv blink.n blinki.n

Verschieben aller Dateien mit Endung ".txt" in das Verzeichnis /doc - mit Anzeige der Dateinamen:

    mv -v *.txt /doc

Siehe auch: cp


nicc

Aufruf des NIC-Compilers.

Zur Programmiersprache NIC gibt es einen eigenen Artikel mit der Referenz-Liste der Standardfunktionen. Alle Peripherie-abhängigen Funktionen werden weiter unten im Kapitel Peripherie aufgeführt.

Aufruf:

    nicc [-v] nic-source-file

Optionen:

    -v - verbose: Ausgabe von verschiedenen Statistiken

Beispiele:

    nicc blink.n
    nicc blink.n 2>blink.err

Die MINOS-Shell kann Dateien mit dem Suffix ".n" auch automatisch mit nicc übersetzen, wenn man den Aufruf des NIC-Compilers "nicc" weglässt:

Beispiele:

    blink.n

Siehe auch: Artikel NIC


nic

Aufruf des NIC-Objekt-Interpreters.

Zur Programmiersprache NIC gibt es einen eigenen Artikel mit der Referenz-Liste der Standardfunktionen. Alle Peripherie-abhängigen Funktionen werden weiter unten im Kapitel Peripherie aufgeführt.

Aufruf:

    nic nic-object-file

Beispiele:

Starten des Programms blink.nic:

    nic blink.nic

Starten des Programms sieve.nic mit Argument 10000, um alle Primzahlen bis 10000 zu ermitteln:

    nic sieve.nic 10000

Die MINOS-Shell kann Dateien mit dem Suffix ".nic" auch automatisch ausführen, so dass man den Aufruf des Ojekt-Interpreters "nic" weglassen kann:

Beispiele:

    blink.nic
    sieve.nic 10000

Siehe auch: Artikel NIC


pwd

Print Working Directory - Ausgabe des Arbeitsverzeichnisses.

Aufruf:

    pwd

Siehe auch: cd


rm

Remove files - Datei[en] löschen.

Aufruf:

    rm file ...

Beispiel:

    rm *.nic

rmdir

Remove Directory - Verzeichnis löschen.

Das gewählte Verzeichnis muss leer sein, damit es gelöscht werden kann. Andererseits wird der Befehl mit einer Fehlermeldung abgebrochen.

Aufruf:

    rmdir directory

Beispiel:

    rmdir bin

Siehe auch: mkdir


sleep

Für einen bestimmten Zeitraum die Augen zumachen.

Aufruf:

    sleep seconds

Beispiel:

Zwei Sekunden schlafen:

    sleep 2

time

Die Dauer eines Befehls in Millisekunden messen.

Aufruf:

    time command ...

Beispiele:

Nachmessen, ob sleep tatsächlich die angegebene Zeit schläft:

    time sleep 2
    time: 2000 msec

Zeitmessung, wie lange NIC zur Ermittlung aller Primzahlen unter 10000 braucht:

    time sieve.nic 10000
    Die hoechste ermittelte Primzahl ist 9973
    time: 8643 msec

umount

Abmelden der SD-Karte.

Dies sollte immer vor dem Herausziehen der SD-Karte geschehen.

Aufruf:

    umount

Siehe auch: mount


Boot Script

Liegt eine Datei namens BOOT auf der SD-Karte im Hauptverzeichnis vor, dann wird diese von der MINOS-Shell beim Booten automatisch ausgeführt.

Beispiel:

    echo Welcome to MINOS!
    date
    led on

Auf der Console wird dann nach der Willkommensmeldung das aktuelle Datum nebst Uhrzeit angezeigt und anschließend die primäre Board-LED eingeschaltet.


Editor

Der Editor fe ist ein leicht zu bediender Text-Editor.

Aufruf:

    fe dateiname

Liste der Tastenbefehle:

Tastenbefehle
Taste Alternative Taste Befehl
Cursortasten nach links / nach rechts / nach oben / nach unten
Bild rauf/runter Um (fast) eine Bildschirmseite nach oben/unten
Pos1 (Home) STRG-A Zum Anfang der Zeile
Ende (End) STRG-E Zum Ende der Zeile
STRG-G Zur Zeile mit der Nummer...
Entf (Del) STRG-D Löschen eines Zeichens - vorwärts
Backspace CTRL-H Löschen eines Zeichens - rückwärts
STRG-U Löschen der Zeile bis zum Zeilenanfang
STRG-K Löschen der Zeile bis zum Zeilenende
STRG-Leertaste (Space) Markierung setzen
STRG-C Markierten Bereich kopieren
STRG-X Markierten Bereich ausschneiden
STRG-V Bereich wieder einfügen
ESC ESC Editor beenden

Compiler NICC

Die Programmiersprache NIC wird in einem separaten Artikel erläutert, siehe auch: Artikel NIC

Der Compiler nicc übersetzt eine NIC-Quelldatei in ein Objekt-Format, welches der Objekt-Interpreter nic anschließend sehr schnell und effizient ausführen kann.

Aufruf:

   nicc file.n

Endet der Dateiname auf ".n", kann der Befehl "nicc" auch weggelassen werden. Die MINOS-Shell ruft dann bei Eingabe des Dateinamens automatisch den Compiler nicc auf.

Beispiel:

   blink.n

Das Resultat einer erfolgreichen Übersetzung endet dann auf ".nic" - Aus beispiel.n wird also die Datei beispiel.nic.

Interpreter NIC

Die Programmiersprache NIC wird in einem separaten Artikel erläutert, siehe auch: Artikel NIC

Der Objekt-Interpreter nic führt eine vom NIC-Compiler nicc übersetzte NIC-Datei aus.

Aufruf:

   nic file.nic

Endet der Dateiname auf ".nic", kann der Befehl "nic" auch weggelassen werden. Die MINOS-Shell ruft dann bei Eingabe des Dateinamens automatisch den Interpreter nic auf.

Beispiele:

   blink.nic
   sieve.nic 1000

Peripherie

Im folgenden werden die zum MINOS-Projekt gehörenden Hardware-abhängigen NIC-Funktionen dokumentiert.

Die Dokumentation zu den NIC-Standard-Funktionsbibliotheken findet man im Kapitel Bibliotheken des NIC-Artikels, beispielsweise für:


FILE

Die Bibliothek FILE verwaltet die Datei Ein- und Ausgabe auf der eingelegten SD-Karte.

file.open

Öffnen einer Datei.

Syntax:

    int file.open (string filename, string mode)

Mögliche Werte für mode:

   "r"  - read, nur Lesen.
   "w"  - write, nur Schreiben
   "r+" - read/write, Lesen und Schreiben, aber ohne create, wenn Datei nicht existent.
   "w+" - read/write, Lesen und Schreiben, aber mit create, wenn Datei noch nicht existent.
   "a"  - append, nur Schreiben mit Positionierung zum Datei-Ende.
   "a+"  - read/write/append/create, Lesen und Schreiben mit create und Positionierung zum Datei-Ende.

Siehe auch: file.close()

file.getc

Lesen eines Zeichens:

Syntax:

    int file.getc (int fhdl)

Liefert die Konstante EOF zurück, wenn das Ende der Datei erreicht ist.

Zum Test auf das Datei-Ende kann aber auch file.eof() verwendet werden.

Beispiel: Kopieren einer Datei mit getc() und putc():

function void main ()
    int     ch
    int     hdlfrom
    int     hdlto

    hdlfrom = file.open ("copy.n", "r")

    if hdlfrom >= 0
        hdlto   = file.open ("copy.1", "w")
        if hdlto >= 0
            ch = file.getc (hdlfrom)
            while ch != EOF
                file.putc (hdlto, ch)
                ch = file.getc (hdlfrom)
            endwhile
            file.close (hdlto)
        endif
        file.close (hdlfrom)
    endif
endfunction

Siehe auch: file.putc()

file.putc

Schreiben eines Zeichens:

Syntax:

    void file.putc (int fhdl, int ch)

Siehe auch: file.getc()

file.readln

Lesen einer Zeile bis zum Zeilenende:

Syntax:

    string file.readln (int fhdl)

file.readln() liefert am Datei-Ende einen Leerstring zurück. Das ist aber kein hinreichendes Kriterium für die Erkennung eines Zeilenendes, da in einer Datei durchaus Leerzeilen enthalten sein können.

Daher sollte man das Datei-Ende immer prüfen mit file.eof().

Beispiel: Ausgabe einer Datei auf der Console:

function void main (string filename)
    string  str
    int     hdl

    hdl = file.open (filename, "r")

    if hdl >= 0
        str = file.readln (hdl)
        while file.eof (hdl) = FALSE
            console.println (str)
            str = file.readln (hdl)
        endwhile
        file.close (hdl)
    else
        console.println ("cannot open " : filename)
    endif
endfunction

Siehe auch: file.writeln()

file.writeln

Schreiben eines Strings mit abschließendem LF (Newline):

Syntax:

    void file.writeln (int fhdl, string str)

Siehe auch: file.readln()

file.write

Schreiben eines Strings.

Syntax:

    void file.write (int fhdl, string str)

file.tell

Ermittlung der Position in einer Datei.

Syntax:

    int file.tell (int fhdl)

Siehe auch: file.seek()

file.seek

Setzen der Position in einer Datei.

Syntax:

    int file.seek (int fhdl, int offset, int whence)

Mögliche Werte für whence:

   SEEK_SET - Setzen der absoluten Position
   SEEK_CUR - Setzen der relativen Position - ausgehend von der aktuellen Position
   SEEK_END - Setzen einer Position relativ zum Dateiende

Beispiele:

Beispiel 1: Zweimaliges Lesen derselben Zeile:

function void main ()
    string  str
    int     hdl
    int     pos

    hdl = file.open ("cat.n", "r")          // Datei öffnen

    if hdl >= 0
        pos = file.tell (hdl)               // Aktuelle Position holen
        str = file.readln (hdl)             // Zeile lesen
        console.println (str)               // Zeile auf Console ausgeben

        file.seek (hdl, pos, SEEK_SET)      // Zurück auf gemerkte Position
        str = file.readln (hdl)             // Zeile lesen
        console.println (str)               // Ergebnis: Es wird 2x dieselbe Zeile ausgegeben
        file.close (hdl)                    // Datei schließen
    endif
endfunction

Beispiel 2: Datei zum Schreiben öffnen und dann an das Datei-Ende positionieren:

function void main ()
    string  str
    int     hdl
    int     pos

    hdl = file.open ("test.txt", "w")       // Datei zum Schreiben öffnen

    if hdl >= 0
        file.seek (hdl, 0, SEEK_END)        // An das Datei-Ende positionieren
        file.writeln (hdl, "Hello, World")  // Zeile schreiben
        file.close (hdl)                    // Datei schließen
    endif
endfunction

Derselbe Effekt ist auch einfacher zu haben. Öffnet man die Datei mit "a" (append) statt "w" (write), wird automatisch an das Datei-Ende posititioniert.

Siehe auch: file.tell()

file.eof

Abfrage, ob beim Lesen das Dateiende erreicht wurde.

Syntax:

    int file.eof (int fhdl)

Rückgabewerte:

   TRUE  - Dateiende erreicht
   FALSE - Dateiende noch nicht erreicht

Siehe auch: file.getc() und file.readln()

file.close

Schließen der Datei.

Syntax:

    void file.close (int fhdl)

Siehe auch: file.open()


GPIO

gpio.init

Initialisiert General Purpose I/O für einen Pin.

Syntax:

    void gpio.init (PORT, BIT, INPUT [, NOPULL | PULLDOWN | PULLUP ])

oder

    void gpio.init (PORT, BIT, OUTPUT [, PUSHPULL | OPENDRAIN ])

Als PORT kann GPIOA, GPIOB, GPIOC usw. gewählt werden.
BIT ist das jeweilige Bit am I/O-Port, beim STM32 also ein Wert von 0 bis 15.
Für die Datenrichtung muss entweder die Konstante INPUT oder die Konstante OUTPUT vorgegeben werden.

Bei Verwendung des Pins als INPUT können folgende Alternativen eingestellt werden:

  • NOPULL (Default): Der Eingang wird nicht durch einen internen Pullup oder Pulldown beeinflusst
  • PULLDOWN: Es wird der interne Pulldown-Widerstand hinzugeschaltet, d.h. bei offenem Eingang ist der Pegel LOW.
  • PULLUP: Es wird der interne Pullup-Widerstand hinzugeschaltet, d.h. bei offenem Eingang ist der Pegel HIGH.

Bei Verwendung des Pins als OUTPUT können folgende Alternativen eingestellt werden:

  • PUSHPULL (Default): Der Ausgang wird möglichst niederohmig geschaltet.
  • OPENDRAIN: Der Low-Pegel wird möglichst niederohmig geschaltet, der High-Pegel ist hochohmig.

Der letzte Parameter von gpio.init() ist optional. Wird er weggelassen, wird der jeweilige Standardwert (Default) eingestellt.

Beispiel:

function void main ()
    gpio.init (GPIOC, 13, INPUT)            // C13 als Input, kein Pulldown/Pullup
    gpio.init (GPIOC, 13, INPUT, PULLUP)    // C13 als Input mit Pullup
endfunction

gpio.set

Setzt den ausgewählten Pin auf High-Pegel.

Syntax:

    void gpio.set (PORT, BIT)

Als PORT kann GPIOA, GPIOB, GPIOC usw. gewählt werden.
BIT ist das jeweilige Bit am I/O-Port, beim STM32 also ein Wert von 0 bis 15.

Beispiel:

function void main ()
    gpio.init (GPIOA, 5, OUTPUT)           // A5 als Output
    gpio.set (GPIOA, 5)                    // Pin auf HIGH
endfunction

gpio.reset

Setzt den ausgewählten Pin auf Low-Pegel.

Syntax:

    void gpio.reset (PORT, BIT)

Als PORT kann GPIOA, GPIOB, GPIOC usw. gewählt werden.
BIT ist das jeweilige Bit am I/O-Port, beim STM32 also ein Wert von 0 bis 15.

Beispiel:

function void main ()
    gpio.init (GPIOA, 5, OUTPUT)           // A5 als Output
    gpio.reset (GPIOA, 5)                  // Pin auf Low
endfunction

gpio.toggle

Invertiert den Zustand des ausgewählten Pins.

Syntax:

    void gpio.toggle (PORT, BIT)

Als PORT kann GPIOA, GPIOB, GPIOC usw. gewählt werden.
BIT ist das jeweilige Bit am I/O-Port, beim STM32 also ein Wert von 0 bis 15.

Beispiel:

function void main ()
    gpio.init (GPIOA, 5, OUTPUT)           // A5 als Output
    loop
        gpio.toggle (GPIOA, 5)             // A5 invertieren
        time.delay (500)                   // eine halbe Sekunde warten
    endloop
endfunction

gpio.get

Liest den Zustand des ausgewählten Pins.

Syntax:

    int gpio.get (PORT, BIT)

Als PORT kann GPIOA, GPIOB, GPIOC usw. gewählt werden.
BIT ist das jeweilige Bit am I/O-Port, beim STM32 also ein Wert von 0 bis 15.

Der Rückgabewert ist entweder LOW oder HIGH.

Beispiel:

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

UART

Auf dem STM32F407VET6 BlackBoard können UART2-UART6 als freie Schnittstellen verwendet werden.

uart.init

Initialisiert UART2 bis UART6.

Syntax:

    void uart.init (int uart_number, int alternate, int baudrate)

Gültige Werte für uart_number:

  • UART1 (Console, nicht verwendbar)
  • UART2
  • UART3
  • UART4
  • UART5
  • UART6

Gültige Werte für alternate:

  • 0 - Standard-Pins
  • 1 - Alternative Pins
  • 2 - Weitere alternative Pins
Mögliche UARTs
UART ALTERNATE TX RX Bemerkungen
UART1 0 PA9 PA10 UART1 = Console, nicht verwendbar
UART1 1 PB6 PB7 UART1 = Console, nicht verwendbar
UART2 0 PA2 PA3
UART2 1 PD5 PD6
UART3 0 PB10 PB11
UART3 1 PC10 PC11
UART3 2 PD8 PD9
UART4 0 PA0 PA1
UART4 1 PC10 PC11
UART5 0 PC12 PD2
UART6 0 PC6 PC7
UART6 1 PG14 PG9 Nicht vorhanden auf BlackBoard

Die Geschwindigkeit der Schnittstelle wird über die Baudrate angegeben.

Gebräuchliche Werte sind:

  • 9600
  • 19200
  • 38400
  • 115200

Beispiel:

    void uart.init (UART2, 0, 115200)

Die NIC-UART-Funktionen verwenden sowohl für den Empfang und für das Senden jeweils 128 Byte große FIFO-Buffer. Eventuell kann man das später einstellen.

uart.rxchars

Gibt die Anzahl der empfangenen, aber noch nicht abgeholten Bytes im FIFO an.

Syntax:

    int uart.rxchars (int uart_number)

Beispiel:

function void main ()
    int ch

    uart.init (UART2, 0, 115200)

    loop
        while uart.rxchars (UART2) > 0                         // Wenn Zeichen verfügbar...
            ch = uart.getc ()                                  // Zeichen abholen
            console.putc (ch)                                  // Zeichen auf der Console ausgeben
        endwhile
        // Hier etwas anderes tun
     endloop
endfunction

Siehe auch: uart.getc()

uart.getc

Holt ein Zeichen aus dem Empfänger FIFO ab.

Wenn kein Zeichen vorhanden ist, wartet uart.getc() solange, bis eines empfangen wird. Soll dieses vermieden werden, so ist eine vorherige Prüfung mittels uart.rxchars() sinnvoll.

Syntax:

    int uart.getc (int uart_number)

Beispiel:

Beispiel: Protokollieren aller empfangenen Zeichen in der Datei uart.log:

function void main ()
    int ch
    int fhdl

    uart.init (UART2, 0, 115200)

    fhdl = file.open ("uart.log", "w")

    if fhdl >= 0
        loop
            ch = uart.getc (UART2)                             // Zeichen empfangen
            if ch = 0x1B                                       // Ist es ESCAPE?
                break                                          // Ja, Abbruch
            endif
            file.putc (ch, fhdl)                               // Zeichen in Datei speichern
        endloop
        file.close (fhdl)
    else
        console.println ("Datei uart.log konnte nicht geoeffnet werden")
    endif
endfunction

Siehe auch: uart.rxchars()

uart.putc

Sendet ein Zeichen.

Syntax:

    uart.putc (int uart_number, int ch)

Beispiel:

Beispiel: Senden des Inhalts der Datei uart.log:

function void main ()
    int ch
    int fhdl

    uart.init (115200)

    fhdl = file.open ("uart.log", "r")                         // Datei zum lesen öffnen

    if fhdl >= 0
        loop
            ch = file.getc (fhdl)                              // Zeichen aus Datei lesen
            if ch = EOF                                        // Ende der Datei erreicht?
                break                                          // Ja, Schleife abbrechen
            endif
            uart.putc (UART2, ch)                              // Zeichen senden
        endloop
        file.close (fhdl)
    else
        console.println ("Datei uart.log konnte nicht geoeffnet werden")
    endif
endfunction

uart.print

Sendet einen String.

Syntax:

    uart.print (int uart_number, string str)

Beispiel:

Beispiel:

    uart.print (UART2, "Hello, World")

uart.println

Sendet einen String mit anschließendem CRLF.

Syntax:

    uart.println (int uart_number, string str)

Beispiel:

Beispiel:

    uart.println (UART2, "Hello, World")

BUTTON

button.init

Definiert einen GPIO-Pin als Tastereingang. Der Taster wird automatisch vom NIC-Laufzeitsystem per Software entprellt.

Syntax:

   int button.init (PORT, BIT, MODE)

Als PORT kann GPIOA, GPIOB, GPIOC usw. gewählt werden.
BIT ist das jeweilige Bit am I/O-Port, beim STM32 also ein Wert von 0 bis 15.

Bei Verwendung des Tastereingangs können folgende Alternativen für MODE eingestellt werden:

  • PULLDOWN: Es wird der interne Pulldown-Widerstand hinzugeschaltet, d.h. bei offenem Eingang ist der Pegel LOW.
  • PULLUP: Es wird der interne Pullup-Widerstand hinzugeschaltet, d.h. bei offenem Eingang ist der Pegel HIGH.
  • NOPULLDOWN: Für den Tastereingang ist die Hinzuschaltung des internen Pulldowns nicht notwendig, Taster ist active high.
  • NOPULLUP: Für den Tastereingang ist die Hinzuschaltung des internen Pullups nicht notwendig, Taster ist active high.

Daraus ergibt sich, dass bei Einstellung PULLDOWN der Schalter gegen Vcc, bei Einstellung PULLUP der Schalter gegen GND schalten muss. Dasselbe gibt für NOPULLDOWN bzw. NOPULLUP. Hier muss die externe Beschaltung selbst für einen definierten Pegel sorgen, zum Beispiel bei NOPULLUP durch Anschluss eines externen Pullups.

Das NIC-Laufzeitsystem vergibt eine interne Tastennummer größer Null, die bei Abfragen durch button.pressed() dann angegeben werden muss. Bis zu 8 solcher Tasten können mittels button.init() definiert werden. Wenn button.init() eine 0 zurückliefert, sind bereits alle 8 Tasten definiert.

Beispiel:

Beim Nucleo hängt der blaue User-Taster an PC13 und schaltet gegen GND. Da hier bereits ein externer Pullup-Widerstand vorgesehen ist, muss kein interner Pullup-Widerstand aktiviert werden.

function void main ()
    int button

    button = button.init (GPIOC, 13, NOPULLUP)   // C13 als Input, kein interner Pullup nötig, active low
    ...
endfunction

Beim STM32F103 Mini-Development-Board gibt es bis auf den RESET-Taster keinen weiteren Taster auf dem Board. Wir können aber zum Beispiel einen Taster an PA6 anschließen, so dass er gegen GND schaltet. Den externen Pullup sparen wir uns und verwenden dafür den internen Pullup am Pin:

function void main ()
    int button

    button = button.init (GPIOA, 6, PULLUP)   // PA6 als Input mit internem Pullup, active low
    ...
endfunction

button.pressed

Abfrage des Tasters.

Syntax:

   int button.pressed (int button)

button ist dabei die Taster-Nummer, die von button.init() zurückgegeben wurde.

Rückgabewerte:

   TRUE: Taste ist gedrückt
   FALSE: Taste wurde nicht gedrückt.

Beispiel:

Einschalten der Board-LED, sobald die blaue User-Taste gedrückt wird, sonst ausschalten.

function void main ()
    int button
    gpio.init (GPIOA, 5, OUTPUT)                 // LED als Output
    gpio.reset (GPIOA, 5)                        // Pegel low: LED ist aus

    button = button.init (GPIOC, 13, NOPULLUP)   // C13 als Input, kein interner Pullup nötig, active low

    loop
        if button.pressed (button) = TRUE        // Taste gedrückt?
            gpio.set (GPIOA, 5)                  // ja, LED einschalten
        else
            gpio.reset (GPIOA, 5)                // nein, LED ausschalten
        endif
    endloop
endfunction

Beim STM32F103 Mini-Development-Board gibt es eine blaue LED auf dem Board. Sie ist an PC13 angeschlossen. Allerdings muss diese gegen GND geschaltet werden, damit sie leuchtet. Sie ist also "active low" - im Gegensatz zur Nucleo-LED. Die Set- und Reset-Befehl werden hier daher ausgetauscht:

function void main ()
    int button
    gpio.init (GPIOC, 13, OUTPUT)                // LED als Output
    gpio.set (GPIOC, 13)                         // Pegel high: LED ist aus

    button = button.init (GPIOA, 6, PULLUP)      // PA6 als Input mit internem Pullup, active low

    loop
        if button.pressed (button) = TRUE        // Taste gedrückt?
            gpio.reset (GPIOC, 13)               // ja, LED einschalten
        else
            gpio.set (GPIOC, 13)                 // nein, LED ausschalten
        endif
    endloop
endfunction

RTC

Der STM32F407 auf dem Black Board hat eine interne batteriegetützte Echtzeituhr.

Das aktuelle Datum und die aktuelle Uhrzeit können in der MINOS-Shell über den Befehl date angezeigt bzw. eingestellt werden.

Es gibt aber auch einen NIC-Befehl, um Datum/Uhrzeit auszulesen:

date.datetime

Die Funktion date.datetime() gibt das aktuelle Datum und die Uhzeit als String im Format "YYYY-MM-DD hh:mm:ss" zurück.

Syntax:

   string date.datetime()

Beispiel:

function void main ()
    string dt
    dt = date.datetime()
    console.println ("Datum:   " : string.substring (dt, 0, 10))
    console.println ("Uhrzeit: " : string.substring (dt, 11))
endfunction

WS2812

Den Dateneingang DI von WS2812-Stripes schließt man an den verwendeten STM32F4xx an den Pin PA8 an. Da die WS2812-LEDs einen Pegel erwarten, der von 3,3V-µCs wie dem STM32 gerade eben so erfüllt werden, ist es ratsam, zusätzlich noch einen 1K Pullup-Widerstand zwischen DI und +5V an der ersten LED anzuschließen. Die WS2812-Softwarebibliothek erkennt den PullUp-Widerstand automatisch und schaltet dann den Ausgang PA8 auf OpenDrain statt PushPull. In diesem Fall wird der High-Pegel dann vom PullUp mit einem Pegel von nahezu 5V erfüllt statt mit nur ca. 3V.

Der Datentransfer geschieht per DMA im Hintergrund, so dass der STM32 sich nahezu langweilt. Zusätzlich zu den unten aufgeführten Funktionen ist auch noch ein Fading geplant, um jede LED auf dem Streifen per Software sanft ein- und wieder auszublenden.

Hier aber schon mal die Standard-Funktionen:

ws2812.init

Mit ws2812.init() wird die WS2812-LED-Bibliothek initialisiert.

Syntax:

   void ws2812.init (int nleds)

Als Argument ist die maximale Anzahl der verwendeten LEDs in der Kette anzugeben.

Beispiel:

function void main ()
    ws2812.init (30)   // 30 LEDs auf dem Stripe
    ...
endfunction

ws2812.set

Damit wird die Farbe einer oder aller WS2812-LEDs gesetzt.

Syntax:

   void ws2812.set (int led, int red, int green, int blue)

led gibt die Nummer der LED an - beginnend mit 0. Wird als Nummer die Anzahl der verwendeten LEDs angegeben, werden die Farben sämtlicher verwendeter LEDs gesetzt. Die Farben können Werte zwischen 0 (aus) und 255 (volle Helligkeit) haben.

Der Set-Befehl setzt die angebenen Farben erst einmal nur im RAM. Erst mit dem ws2812.refresh()-Befehl werden dann sämtliche gesetzten Farben an die LEDs übertragen.

Beispiel:

const int MAXLEDS = 30

function void main ()
    int i

    ws2812.init (MAXLEDS)                 // 30 LEDs auf dem Stripe
    ws2812.set (MAXLEDS, 128, 128, 128)   // Alle LEDs auf weiß, halbe Helligkeit

    for i = 0 to MAXLEDS
        ws2812.set (i, 0, 128, 128)       // Eine LEDs auf türkis, halbe Helligkeit
        ws2812.refresh (MAXLEDS)          // Ausgabe
        delay (200)                       // 0,2 Sekunden warten
    endfor
endfunction

ws2812.refresh

ws2812.refresh() überträgt sämtliche zuvor gesetzte Farben an die LEDs.

Syntax:

   void ws2812.refresh (nleds)

Das Argument nleds bestimmt, wieviele der LEDs aktualisiert werden sollen. Der Maximalwert ist derjenige, der bei ws2812.init() angegeben wurde. Dann werden alle LEDs aktualisiert.

Beispiel:

const int MAXLEDS = 30

function void main ()
    ws2812.init (MAXLEDS)                 // 30 LEDs auf dem Stripe
    ...
    ws2812.refresh (MAXLEDS)              // Ausgabe: Alle LEDs aktualisieren
    ...
endfunction

Beispiele zur Ansteuerung der WS2812-LEDs findet man unten im Kapitel Beispielprojekte.


TFT

Es sind sowohl Text- als auch Graphik-Ausgabe möglich.

tft.init

Initialisierung des TFT Displays.

Syntax:

    void tft.init (int orientation)

Diese Funktion muss vor allen anderen TFT-Funktionen aufgerufen werden. Der Parameter orientation gibt an, wie der Inhalt des Diaplays gedreht bzw. gespiegelt werden muss, damit er "richtig" erscheint. Zum einen kann man das Display auf den Kopf stellen, zum anderen gibt es herstellerspezifische Unterschiede.

In den meisten Fällen ist für SSD1962 der Wert 0 für orientation korrekt, beim ILI9341 ist es meist der Wert 2.

tft.rgb_to_color565

Umwandlung einer Farbe (RGB) in einen Integer-Wert (COLOR565-Code). Mit diesem arbeiten alle anderen TFT-Funktionen.

Syntax:

    int tft.rgb_to_color565 (int red, int green, int blue)

Bereits voreingestellte Konstanten für color565-Werte sind:

   COLOR_BLACK         - schwarz
   COLOR_BLUE          - blau
   COLOR_DARKBLUE      - dunkelblau
   COLOR_RED           - rot
   COLOR_DARKRED       - dunkelrot
   COLOR_GREEN         - grün
   COLOR_DARKGREEN     - dunkelgrün
   COLOR_CYAN          - cyan
   COLOR_DARKCYAN      - dunkelcyan
   COLOR_MAGENTA       - magenta
   COLOR_DARKMAGENTA   - dunkelmagenta
   COLOR_YELLOW        - gelb
   COLOR_DARKYELLOW    - dunkelgelb
   COLOR_WHITE         - weiß
   COLOR_GRAY          - grau

tft.fadein_backlight

Fade-In der Hintergrundbeleuchtung. Diese Funktion hat momentan keinen Effekt und wäre auch nur für SSD1963-TFTs mit PWM-Eingang möglich.

Syntax:

    int tft.fadein_backlight (int msec)

tft.fadeout_backlight

Fade-Out der Hintergrundbeleuchtung. Diese Funktion hat momentan keinen Effekt und wäre auch nur für SSD1963-TFTs mit PWM-Eingang möglich.

Syntax:

    int tft.fadeout_backlight (int msec)

tft.draw_pixel

Pixel zeichnen.

Syntax:

    void tft.draw_pixel (int x, int y, int color565)

tft.draw_horizontal_line

Horizontale Linie zeichnen.

Syntax:

    void tft.draw_horizontal_line (int x0, int x1, int len, int color565)

tft.draw_vertical_line

Vertikale Linie zeichnen.

Syntax:

    void tft.draw_vertical_line (int x0, int x1, int height, int color565)

tft.draw_rectangle

Rechteck zeichnen.

Syntax:

    void tft.draw_rectangle (int x0, int y0, int x1, int y1, int color565)

tft.fill_rectangle

Rechteck ausfüllen.

Syntax:

    void tft.fill_rectangle (int x0, int y0, int x1, int y1, int color565)

tft.fill_screen

Komplettes Display mit einer Farbe füllen.

Syntax:

    void tft.fill_screen (int color565)

tft.draw_line

Linie zeichnen von Start-Punkt (x0, y0) bis End-Punkt (x1, y1):

Syntax:

    void tft.draw_line (int x0, int y0, int x1, int y1, int color565)

Siehe auch: tft.draw_thick_line()

tft.draw_thick_line

Dickere Linie zeichnen von Start-Punkt (x0, y0) bis End-Punkt (x1, y1):

Syntax:

    void tft.draw_line (int x0, int y0, int x1, int y1, int color565)

Siehe auch: tft.draw_line()

tft.draw_circle

Kreis zeichnen von Start-Punkt (x0, y0) mit vorgegebenem Radius:

Syntax:

    void tft.draw_circle (int x0, int x1, int radius, int color565)

Siehe auch: tft.draw_thick_circle()

tft.draw_thick_circle

Kreis (dickerer Strich) zeichnen von Start-Punkt (x0, y0) mit vorgegebenem Radius:

Syntax:

    void tft.draw_thick_circle (int x0, int x1, int radius, int color565)

Siehe auch: tft.draw_circle()

tft.draw_image

Bild anzeigen. Wird im Moment noch nicht unterstützt.

Syntax:

    void tft.draw_image (int x0, int y0, int len, int height, string filename)

tft.set_font

Syntax:

    void tft.set_font (int font)

Mögliche Werte für font:

  • FONT_05x08
  • FONT_05x12
  • FONT_06x08
  • FONT_06x10
  • FONT_08x08
  • FONT_08x12
  • FONT_08x14
  • FONT_10x16
  • FONT_12x16
  • FONT_12x20
  • FONT_16x26
  • FONT_22x36
  • FONT_24x40
  • FONT_32x53

Beispiel:

    void tft.set_font (FONT_08x12)

tft.reset_font

Syntax:

    void tft.reset_font ()

Setzt den Font auf den Standard - nämlich FONT_05x08.

tft.fonts

Syntax:

    void tft.fonts ()

Gibt eine Liste der verfügbaten Fonts aus.

tft.font_height

Syntax:

    int tft.font_height ()

Gibt die Höhe des eingestellten Fonts in Pixeln zurück.

tft.font_width

Syntax:

    int tft.font_width ()

Gibt die Breite des eingestellten Fonts in Pixeln zurück.

tft.draw_string

Syntax:

    void tft.draw_string (int x0, int y0, string str)

Gibt eine Zeichenkette auf dem TFT-Display an der vorgegebenen Position aus - mit dem zuletzt eingestellten Font.

Beispiel:

function void main ()
    tft.init (2)
    tft.fill_screen (COLOR_BLACK)
    tft.set_font (FONT_08x12)
    tft.draw_string (10, 10, "Hello World", COLOR_RED, COLOR_BLACK)
endfunction


Beispiele zur Ansteuerung des TFT-Displays findet man unten im Kapitel Beispielprojekte.


TOUCH

TODO


I2C

i2c.init

i2c.init () initialisiert die jeweils gewünschte I2C-Schnittstelle.

Syntax:

 void i2c.init (int channel, int alternate, int clockspeed)

Parameter:

  • channel: I2C-Channel. Erlaubte Werte: I2C1, I2C2 oder I2C3. Welche Werte davon möglich sind, ist prozessorabhängig.
  • alternate: Flag, ob die alternative Pinkonfiguration verwendet werden soll, siehe Tabelle.
  • clockspeed: Taktgeschwindigkeit. Standard ist 100000 (100k), möglich ist aber auch 400000 (400k) für schnelle Peripherie
Mögliche I2C-Channel
Channel ALTERNATE SCL SDA
I2C1 FALSE PB6 PB7
I2C1 TRUE PB8 PB9
I2C2 FALSE PB10 PB11
I2C3 FALSE PA8 PC9

ACHTUNG: I2C3 sollte nicht genutzt werden, da der Pin PC9 für die SD-Karte reserviert ist!

Beispiel:

function void main ()
    i2c.init (I2C1, FALSE, 100000)          // Channel 1, SCL=PB6, SDA=PB7, 100k
    ...
endfunction

Dies ist eine Low-Level-Funktion. Konkrete I2C-Anwendungen (LCD, RTC, EEPROM) findet man im Kapitel Beispielprojekte unter I2C LCD, I2C DS3231 und I2C EEPROM.

i2c.write

i2c.write() schreibt einen Datenpuffer auf die gewählte I2C-Schnittstelle.

Syntax:

   int i2c.write (int channel, int address, byte buffer[], int length)

Parameter:

  • channel: I2C-Channel, siehe i2c.init()
  • address: I2C-Adresse des anzusprechenden Moduls
  • buffer: Datenpuffer
  • length: Länge der Daten

Dies ist eine Low-Level-Funktion. Konkrete I2C-Anwendungen (LCD, RTC, EEPROM) findet man im Kapitel Beispielprojekte unter I2C LCD, I2C DS3231 und I2C EEPROM.

i2c.read

i2c.read() liest Daten von der gewählten I2C-Schnittstelle.

Syntax:

   int i2c.read (int channel, int address, byte buffer[], int length)

Parameter:

  • channel: I2C-Channel, siehe i2c.init()
  • address: I2C-Adresse des anzusprechenden Moduls
  • buffer: Datenpuffer
  • length: Länge der Daten

Dies ist eine Low-Level-Funktion. Konkrete I2C-Anwendungen (LCD, RTC, EEPROM) findet man im Kapitel Beispielprojekte unter I2C LCD, I2C DS3231 und I2C EEPROM.


Beispielprojekte

Im folgenden werden einige kleine NIC-Programme vorgestellt, welche nicht nur die Flexibilität und die Einfachheit der Anwendung der Sprache NIC zeigen, sondern auch für den Anwender als Tutorial dienen sollen.

Blinken einer LED

NIC-Programm, welches über time.delay() eine LED blinken lässt.

Für abweichende Hardware-Konstellationen müssen lediglich die Konstanten PORT und PIN angepasst werden.

const int PORT = GPIOA                              // Board-LED auf STM32F407 Black Board: GPIOA, Pin5
const int PIN  = 5

function void blink ()
endfunction

function void main ()
    gpio.init (PORT, PIN, OUTPUT)                   // Port initialisieren

    loop
        gpio.toggle (PORT, PIN)                     // Pin togglen
        time.delay (500)                            // 500 msec warten
    endloop
endfunction

NIC-Programm-Variante, welche dafür einen Alarm-Timer verwendet:

Der Vorteil ist hier, dass die Hauptschleife "gleichzeitig" noch andere Aufgaben übernehmen kann.

const int PORT = GPIOA                              // Board-LED auf STM32F407 Black Board: GPIOA, Pin5
const int PIN  = 5

function void blink ()
    gpio.toggle (PORT, PIN)
endfunction

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

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

Siehe auch: Kapitel ALARM im NIC-Artikel.


Ausgabe einer Text-Datei

NIC-Programm, welches den Inhalt einer Datei ausgibt:

function void main (string filename)
    string  str
    int     hdl

    hdl = file.open (filename, "r")

    if hdl >= 0
        str = file.readln (hdl)
        while file.eof (hdl) = 0
            console.println (str)
            str = file.readln (hdl)
        endwhile
        file.close (hdl)
    else
        console.println ("cannot open " : filename)
    endif
endfunction

Siehe auch: Kapitel FILE


Hexdump einer Datei

NIC-Programm, welches einen Hexdump einer beliebigen Datei erstellt:

function void main (string filename)
    int     hdl
    string  ch[16]
    int     cnt
    int     i

    hdl = file.open (filename, "r")

    if hdl >= 0
        ch[cnt] = file.getc (hdl)

        while file.eof (hdl) = FALSE
            console.print (ch[cnt], HEX, 2)
            console.print (" ")
            cnt = cnt + 1
            if cnt = 16
                for cnt = 0 to 15
                    if ch[cnt] >= 32
                        console.putc (ch[cnt])
                    else
                        console.print (".")
                    endif
                endfor
                console.println ("")
                cnt = 0
            endif
            ch[cnt] = file.getc (hdl)
        endwhile

        // Letzte Zeile auffuellen
        if cnt != 0
            for i = cnt + 1 to 16
                console.print ("   ")
            endfor

            for i = 0 to cnt - 1
                if ch[i] >= 32
                    console.putc (ch[i])
                else
                    console.print (".")
                endif
            endfor
        endif
        console.println ("")

        file.close (hdl)
    else
        console.println ("cannot open " : filename)
    endif
endfunction

Beispiel-Aufruf:

    hexdump.nic cat.n

Ausgabe:

66 75 6E 63 74 69 6F 6E 20 76 6F 69 64 20 6D 61 function void ma
69 6E 20 28 29 0A 20 20 20 20 73 74 72 69 6E 67 in ().    string
20 20 73 74 72 0A 20 20 20 20 69 6E 74 20 20 20   str.    int
20 20 68 64 6C 0A 0A 20 20 20 20 68 64 6C 20 3D   hdl..    hdl =
20 66 69 6C 65 2E 6F 70 65 6E 20 28 22 63 61 74  file.open ("cat
2E 6E 22 2C 20 22 72 22 29 0A 0A 20 20 20 20 69 .n", "r")..    i
66 20 68 64 6C 20 3E 3D 20 30 0A 20 20 20 20 20 f hdl >= 0.
20 20 20 73 74 72 20 3D 20 66 69 6C 65 2E 72 65    str = file.re
61 64 6C 6E 20 28 68 64 6C 29 0A 20 20 20 20 20 adln (hdl).
20 20 20 77 68 69 6C 65 20 66 69 6C 65 2E 65 6F    while file.eo
66 20 28 68 64 6C 29 20 3D 20 30 0A 20 20 20 20 f (hdl) = 0.
20 20 20 20 20 20 20 20 63 6F 6E 73 6F 6C 65 2E         console.
70 72 69 6E 74 6C 6E 20 28 73 74 72 29 0A 20 20 println (str).
20 20 20 20 20 20 20 20 20 20 73 74 72 20 3D 20           str =
66 69 6C 65 2E 72 65 61 64 6C 6E 20 28 68 64 6C file.readln (hdl
29 0A 20 20 20 20 20 20 20 20 65 6E 64 77 68 69 ).        endwhi
6C 65 0A 20 20 20 20 20 20 20 20 66 69 6C 65 2E le.        file.
63 6C 6F 73 65 20 28 68 64 6C 29 0A 20 20 20 20 close (hdl).
65 6E 64 69 66 0A 65 6E 64 66 75 6E 63 74 69 6F endif.endfunctio
6E 0A                                           n.

Siehe auch: Kapitel FILE


Lauflicht mit WS2812

Im folgenden wird ein Lauflicht mit 30 WS2812-LEDs realisiert. Die erste Variante läuft noch mit der delay-Funktion, welche keinen Spielraum mehr lässt für andere Sachen, die eventuell auch noch abgearbeitet werden sollen.

Die zweite Variante ist da schon wesentlich eleganter. Sie wird nur aktiv, wenn auch Änderungen auf unserem LED-Streifen vonnöten sind.

Die dritte Variante dagegen überlässt dem NIC-Laufzeitsystem die Zeitmessung bis zum nächsten LED-Update, so dass man sich darum nicht mehr kümmern muss. Desweiteren kann man bis zu 8 solcher Alarm-Timer gleichzeitig aktivieren, so dass man hier auch ganz verschiedene Aufgaben mit unterschiedlichen Aktivierungszeiten implementieren kann. Hier wird der Alarm-Timer noch gepollt, d.h. explizit abgefragt.

In der letzten und vierten Variante wird auch dieses noch dem NIC-Runtime-System überlassen. Hier geben wir einfach eine NIC-Funktion an, welche automatisch aufgerufen werden soll, wenn der Alarm eintrifft.


Lauflicht mit Delay

Die erste Variante arbeitet mit einem Delay, welches den Prozessor warten lässt. Währenddessen kann man keine anderen Aktionen im Programm ausführen.

int MAX_LEDS = 30                           // Anzahl der benutzten LEDs

function void main ()
    int red     = 128                       // Rot auf halbe Helligkeit
    int green   =  0                        // Gruen aus
    int blue    =  0                        // Blau aus
    int led                                 // Aktive LED

    ws2812.init (MAX_LEDS)                  // Initialisierung

    loop
        ws2812.set (MAX_LEDS, 0, 0, 0)      // Alle LEDs aus
        ws2812.set (led, red, green, blue)  // Eine LED leuchten lassen
        ws2812.refresh (MAX_LEDS)           // Zustand aktualisieren
        led = led + 1                       // Naechste LED
        if led >= MAX_LEDS                  // Ende erreicht?
            led = 0                         // Ja, zurueck zum Anfang
        endif
        time.delay (500)                    // Eine halbe Sekunde warten
    endloop
endfunction

Lauflicht mit Millis-Timer

Diese Variante ermittelt mittels time.millis(), wann es aktiv werden soll und kann währenddessen noch andere Aufgaben erledigen. Allerdings wird hier der millis-Timer verwendet, so dass dieser nicht mehr für andere Dinge zur Verfügung steht.

int MAX_LEDS = 30                                   // Anzahl der benutzten LEDs

function void main ()
    int red     = 128                               // Rot auf halbe Helligkeit
    int green   =  0                                // Gruen aus
    int blue    =  0                                // Blau aus
    int led                                         // Aktive LED

    ws2812.init (MAX_LEDS)                          // Initialisierung
    time.reset ()                                   // Setze "Uhrzeit" zurueck

    loop
        if time.millis() > 500                      // Eine halbe Sekunde erreicht?
            time.reset ()                           // Ja, Zeit wieder zuruecksetzen
            ws2812.set (MAX_LEDS, 0, 0, 0)          // Alle LEDs aus
            ws2812.set (led, red, green, blue)      // Eine LED leuchten lassen
            ws2812.refresh (MAX_LEDS)               // Zustand aktualisieren
            led = led + 1                           // Naechste LED
            if led >= MAX_LEDS                      // Ende erreicht?
                led = 0                             // Ja, zurueck zum Anfang
            endif
        else
            // ...                                  // Hier koennen nun andere Aufgaben erledigt werden!
        endif
    endloop
endfunction

Lauflicht mit Alarm

Die dritte Variante verwendet einen von acht Alarm-Timern. Das ist damit die Luxusversion. Der millis-Timer wird nicht verwendet. Neben diesem können noch sieben weitere Alarm-Timer für andere Aufgaben benutzt werden:

int MAX_LEDS = 30                                   // Anzahl der benutzten LEDs

function void main ()
    int red     = 128                               // Rot auf halbe Helligkeit
    int green   =  0                                // Gruen aus
    int blue    =  0                                // Blau aus
    int led                                         // Aktive LED
    int timer1                                      // alarm-timer

    ws2812.init (MAX_LEDS)                          // Initialisierung
    timer1 = alarm.set (500)                        // Alarm initialisieren

    loop
        if alarm.check (timer1) = TRUE              // Alarm gesetzt?
            ws2812.set (MAX_LEDS, 0, 0, 0)          // Alle LEDs aus
            ws2812.set (led, red, green, blue)      // Eine LED leuchten lassen
            ws2812.refresh (MAX_LEDS)               // Zustand aktualisieren
            led = led + 1                           // Naechste LED
            if led >= MAX_LEDS                      // Ende erreicht?
                led = 0                             // Ja, zurueck zum Anfang
            endif
        else
            // ...                                  // Hier koennen nun andere Aufgaben erledigt werden!
        endif
    endloop
endfunction

Lauflicht mit Alarm als Interrupt

In der letzten und vierten Variante wird das Pollen des Alarm-Timers dem NIC-Runtime-System überlassen. Hier geben wir einfach eine NIC-Funktion an, welche aufgerufen werden soll, wenn der Alarm eintrifft:

int MAX_LEDS = 30                                   // Anzahl der benutzten LEDs

function void lauflicht ()
    static int red      = 128                       // Rot
    static int green    = 0                         // Gruen
    static int blue     = 0                         // Blau
    static int led                                  // Aktive LED

    ws2812.set (MAX_LEDS, 0, 0, 0)                  // Alle LEDs aus
    ws2812.set (led, red, green, blue)              // Eine LED leuchten lassen
    ws2812.refresh (MAX_LEDS)                       // Zustand aktualisieren
    led = led + 1                                   // Naechste LED
    if led >= MAX_LEDS                              // Ende erreicht?
        led = 0                                     // Ja, zurueck zum Anfang
    endif
endfunction

function void main ()
    ws2812.init (MAX_LEDS)                          // Initialisierung
    alarm.set (500, function.lauflicht)             // lauflicht() alle 500msec automatisch aufrufen lassen

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

Die Variablen led, red, green und blue sind nun alle in die Funktion lauflicht() gewandert. Die Definition als static-Variable lassen sie über die ganze Laufzeit des Programms "leben", d.h. sie verlieren nicht ihren Wert, wenn die Funktion wieder verlassen wird. Alternativ können sie auch einfach als globale Variablen definiert werden. Der Vorteil der static-Definition ist jedoch, dass sie ausschließlich in der Funktion lauflicht() manipuliert werden können.

Siehe auch: Kapitel WS2812 und ALARM


Equinox Uhr mit WS2812

Dies ist ein einfaches NIC-Programm zur Anzeige der aktuellen Uhrzeit auf einer Equinox-Uhr.

Dabei geht es um einen LED-Ring mit 60 WS2812-RGB-LEDs. Auch hier wird ein Alarm-Interrupt verwendet, so dass die Hauptfunktion main() am Ende tatsächlich gar nichts mehr tun muss, weil die LED-Anzeige komplett vom NIC-Laufzeitsystem koordiniert und ausgeführt wird.

In der verbleibenden Schleife am Ende

    loop                                                 // Endlosschleife...
        // do nothing
    endloop

könnte man das Programm also noch etwas ganz anderes machen lassen.

Hier das vollständige Programm:

//----------------------------------------------------------------------------------------------
// equinox.n - Ausgabe der Uhrzeit auf Equinox-Anzeige (Ring mit 60 WS2812 LEDs)
//----------------------------------------------------------------------------------------------
const int MAX_LEDS = 60                       // 60 LEDs im Ring

const int HH_RED    = 255                     // Farbe fuer Stundenzeiger: rot
const int HH_GREEN  = 0
const int HH_BLUE   = 0

const int MM_RED    = 0                       // Farbe fuer Minutenzeiger: grün
const int MM_GREEN  = 255
const int MM_BLUE   = 0

const int SS_RED    = 0                       // Farbe fuer Sekundenzeiger: blau
const int SS_GREEN  = 0
const int SS_BLUE   = 255

int     hour                                  // aktuelle Stunde
int     minute                                // aktuelle Minute
int     second                                // aktuelle Sekunde

//----------------------------------------------------------------------------------------------
// display() - Ausgabe der aktuellen Uhrzeit
// Wird durch Alarm automatisch jede Sekunde aufgerufen
//----------------------------------------------------------------------------------------------
function void display ()
    int     hourled

    hourled = (hour % 12) * 5 + minute / 12              // Umrechnung Position Stundenzeiger

    ws2812.clear (MAX_LEDS)                              // alle LEDs löschen
    ws2812.set (hourled, HH_RED, HH_GREEN, HH_BLUE)      // LED für Stunde setzen
    ws2812.set (minute,  MM_RED, MM_GREEN, MM_BLUE)      // LED für Minute setzen
    ws2812.set (second,  SS_RED, SS_GREEN, SS_BLUE)      // LED für Sekunde setzen
    ws2812.refresh (MAX_LEDS)                            // Anzeige aktualisieren

    second = second + 1                                  // Eine Sekunde weiterzaehlen
    if second = 60                                       // Wenn 60 Sekunden erreicht...
        second = 0
        minute = minute + 1                              // Minute weiterzaehlen
        if minute = 60                                   // Wenn 60 Minuten erreicht...
            minute = 0
            hour = hour + 1                              // Stunde weiterzaehlen
            if hour = 24                                 // Wenn 24 Stunden erreicht...
                hour = 0                                 // Stunden zuruecksetzen
            endif
        endif
    endif
endfunction

//----------------------------------------------------------------------------------------------
// main() - Hauptprogramm
// Initialisiert LEDs, den Alarm-Timer und macht anschließend .... nichts!
//----------------------------------------------------------------------------------------------
function void main ()
    string  datetime

    ws2812.init (MAX_LEDS)                               // WS2812 initialisieren
    datetime = date.datetime ()                          // Aktuelles Datum/Uhrzeit holen

    // 0123456789012345678
    // YYYY-MM-DD hh:mm:ss
    hour    = string.substring (datetime, 11, 2)         // Stunde/Minute/Sekunde extrahieren
    minute  = string.substring (datetime, 14, 2)
    second  = string.substring (datetime, 17, 2)

    alarm.set (1000, function.display)                   // Alarm setzen: Jede Sekunde!

    loop                                                 // Endlosschleife...
        // do nothing
    endloop
endfunction

TFT-Analoguhr

Ein weiteres NIC-Programm, welches neben der Uhrzeit auch das Datum anzeigt - diesmal auf einem 7" TFT-Display mit 800 x 480 Bildpunkten.

Uhr mit Datumsanzeige auf 7" TFT-Display
//------------------------------------------------------------------------------------------
// clock.n - TFT Analoguhr
//------------------------------------------------------------------------------------------

const int   CENTER_X                = 240                       // Zentrum der Uhr
const int   CENTER_Y                = 240
const int   CLOCKFACE_RADIUS        = 200                       // Radius Ziffernblat
const int   SECHAND_RADIUS          = 180                       // Radius Sekundenzeiger
const int   MINHAND_RADIUS          = 160                       // Radius Minutenzeiger
const int   HOURHAND_RADIUS         = 120                       // Radius Stundenzeiger

const int   BG_COLOR                = COLOR_DARKBLUE            // Hintergrundfarbe
const int   SECHAND_COLOR           = COLOR_WHITE               // Farbe Sekundenzeiger
const int   MINHAND_COLOR           = COLOR_YELLOW              // Farbe Minutenzeiger
const int   HOURHAND_COLOR          = COLOR_RED                 // Farbe Stundenzeiger

//------------------------------------------------------------------------------------------
// drawhand - Zeiger zeichnen
//------------------------------------------------------------------------------------------
function void drawhand (int pos, int radius, int color565)
    int     angle
    int     x
    int     y

    angle   = ((360 + 90) - pos * 6) % 360
    x       = CENTER_X + polar.to_x (radius, angle)
    y       = CENTER_Y + polar.to_y (radius, angle)

    tft.draw_thick_line (CENTER_X, CENTER_Y, x, y, color565)
endfunction

//------------------------------------------------------------------------------------------
// display - Anzeige aktualisieren. Wird jede Sekunde 1x per Alarm-Timer aufgerufen
//------------------------------------------------------------------------------------------
function void display ()
    static int  already_called = FALSE
    static int  oldhourpos                                      // letzte    Stundenposition
    static int  oldminute                                       // letzte    Minute
    static int  oldsecond                                       // letzte    Sekunde
    string      year                                            // aktuelles Jahr
    string      month                                           // aktueller Monat
    string      day                                             // aktueller Tag
    int         hour                                            // aktuelle  Stunde
    int         hourpos                                         // aktuelle  Stundenposition
    int         minute                                          // aktuelle  Minute
    int         second                                          // aktuelle  Sekunde
    string      datetime
    string      date
    string      time

    datetime = date.datetime ()                                 // Aktuelles Datum/Uhrzeit holen

    // 0123456789012345678
    // YYYY-MM-DD hh:mm:ss
    year    = string.substring (datetime, 2, 2)
    month   = string.substring (datetime, 5, 2)
    day     = string.substring (datetime, 8, 2)
    hour    = string.substring (datetime, 11, 2)                // Stunde/Minute/Sekunde extrahieren
    minute  = string.substring (datetime, 14, 2)
    second  = string.substring (datetime, 17, 2)
    hourpos = (hour % 12) * 5 + minute / 12

    if already_called = TRUE                                    // Vormals gezeichnete Zeiger löschen:
        drawhand (oldsecond,  SECHAND_RADIUS,  BG_COLOR)
        drawhand (oldminute,  MINHAND_RADIUS,  BG_COLOR)
        drawhand (oldhourpos, HOURHAND_RADIUS, BG_COLOR)
    endif

    drawhand (hourpos, HOURHAND_RADIUS, HOURHAND_COLOR)         // Stundenzeiger zeichnen
    drawhand (minute,  MINHAND_RADIUS,  MINHAND_COLOR)          // Minutenzeiger zeichnen
    drawhand (second,  SECHAND_RADIUS,  SECHAND_COLOR)          // Sekundenzeiger zeichnen

    date = day : "." : month : "." : year
    time = string.substring (datetime, 11)

    tft.draw_string (520,  60, date, COLOR_WHITE, BG_COLOR)
    tft.draw_string (520, 120, time, COLOR_WHITE, BG_COLOR)

    oldhourpos  = hourpos
    oldminute   = minute
    oldsecond   = second

    already_called = TRUE
endfunction

//------------------------------------------------------------------------------------------
// main - Hauptfunktion: Initialisierung etc.
//------------------------------------------------------------------------------------------
function void main ()
    int     min
    int     angle
    int     x
    int     y

    tft.init ()
    tft.fill_screen (BG_COLOR)

    for min = 0 to 60 step 5                                    // Ziffernblatt zeichnen
        angle   = ((360 + 90) - min * 6) % 360
        x       = CENTER_X + polar.to_x (CLOCKFACE_RADIUS, angle)
        y       = CENTER_Y + polar.to_y (CLOCKFACE_RADIUS, angle)

        tft.draw_thick_circle (x, y, 4, COLOR_WHITE)
    endfor

    tft.set_font (FONT_32x53)
    tft.draw_string (520 + 3 * 32, 420, "MINOS", COLOR_YELLOW, BG_COLOR)

    alarm.set (1000, function.display)                          // Alarm setzen: Jede Sekunde!

    loop
        // do nothing
    endloop
endfunction

TFT-WordClock

Nachfolgend die Umsetzung der WordClock-Anzeige aus dem WordClock_mit_WS2812-Projekt:

Das Programm ist weniger als 270 Zeilen lang - Kommentare mit eingerechnet.

//----------------------------------------------------------------------------------------------------
// wc.n - TFT WordClock
//----------------------------------------------------------------------------------------------------
const int   BG_COLOR        = COLOR_DARKBLUE        // Hintergrundfarbe
const int   FG_COLOR        = COLOR_YELLOW          // Vordergrundfarbe aktiver Wörter
const int   FG_DARKCOLOR    = COLOR_GRAY            // Vordergrundfarbe inaktiver Wörter

int         font_height                             // Höhe des verwendeten Fonts
int         font_width = 32                         // Breite des Fonts: konstant auf 32

//----------------------------------------------------------------------------------------------------
// Liste aller möglichen Wörter
//----------------------------------------------------------------------------------------------------
const int   WP_NONE         = 0
const int   WP_ES           = 1
const int   WP_IST          = 2
const int   WP_FUENF_1      = 3
const int   WP_ZEHN_1       = 4
const int   WP_ZWANZIG      = 5
const int   WP_DREI         = 6
const int   WP_VIER         = 7
const int   WP_VIERTEL      = 8
const int   WP_DREIVIERTEL  = 9
const int   WP_NACH         = 10
const int   WP_VOR          = 11
const int   WP_HALB         = 12
const int   WP_ZWOELF       = 13
const int   WP_ZWEI         = 14
const int   WP_EIN          = 15
const int   WP_EINS         = 16
const int   WP_SIEBEN       = 17
const int   WP_DREI_2       = 18
const int   WP_FUENF_2      = 19
const int   WP_ELF          = 20
const int   WP_NEUN         = 21
const int   WP_VIER_2       = 22
const int   WP_ACHT         = 23
const int   WP_ZEHN_2       = 24
const int   WP_SECHS        = 25
const int   WP_UHR          = 26
const int   WP_ZIG          = 27
const int   N_WORDS         = 28

string      words[N_WORDS]
int         hours[12]
string      minutes[12]

//----------------------------------------------------------------------------------------------------
// draw_row - Eine Zeile ausgeben
//----------------------------------------------------------------------------------------------------
function void draw_row (int line, string str, int fg, int bg)
    string  letter
    int     i

    for i = 0 to string.length (str) - 1
        letter = string.substring (str, i, 1)
        tft.draw_string (40 + i * font_width, 40 + line * font_height, letter, fg, bg)
    endfor
endfunction

//----------------------------------------------------------------------------------------------------
// draw_word - Ein Wort ausgeben
//----------------------------------------------------------------------------------------------------
function void draw_word (int wordno, int fg, int bg)
    string  word
    int     line
    int     col
    int     i
    string  letter

    line    = string.substring (words[wordno], 0, 1)            // Zeile holen
    col     = string.substring (words[wordno], 1, 1)            // Spalte holen
    word    = string.substring (words[wordno], 2)               // Wort holen

    // Wort Buchstabe für Buchstabe (wegen geändertem Buchstabenabstand) ausgeben:
    for i = 0 to string.length (word) - 1
        letter = string.substring (word, i, 1)
        tft.draw_string (40 + (col + i) * font_width, 40 + line * font_height, letter, fg, bg)
    endfor
endfunction

//----------------------------------------------------------------------------------------------------
// draw_display - Komplette Anzeige ausgeben
//----------------------------------------------------------------------------------------------------
function void draw_display ()
    draw_row (0, "ESKISTLFÜNF", FG_DARKCOLOR, BG_COLOR)
    draw_row (1, "ZEHNZWANZIG", FG_DARKCOLOR, BG_COLOR)
    draw_row (2, "DREIVIERTEL", FG_DARKCOLOR, BG_COLOR)
    draw_row (3, "TGNACHVORJM", FG_DARKCOLOR, BG_COLOR)
    draw_row (4, "HALBXZWÖLFP", FG_DARKCOLOR, BG_COLOR)
    draw_row (5, "ZWEINSIEBEN", FG_DARKCOLOR, BG_COLOR)
    draw_row (6, "KDREIRHFÜNF", FG_DARKCOLOR, BG_COLOR)
    draw_row (7, "ELFNEUNVIER", FG_DARKCOLOR, BG_COLOR)
    draw_row (8, "WACHTZEHNRS", FG_DARKCOLOR, BG_COLOR)
    draw_row (9, "RSECHSFMUHR", FG_DARKCOLOR, BG_COLOR)
endfunction

//----------------------------------------------------------------------------------------------------
// wc_init - Initialisierung aller globalen Variablen
//----------------------------------------------------------------------------------------------------
function void wc_init ()
    // Alle Wörter, Format: Zeile Spalte Wort
    words[WP_NONE]          = ""                                                // end of words
    words[WP_ES]            = "00ES"
    words[WP_IST]           = "03IST"
    words[WP_FUENF_1]       = "07FÜNF"
    words[WP_ZEHN_1]        = "10ZEHN"
    words[WP_ZWANZIG]       = "14ZWANZIG"
    words[WP_DREI]          = "20DREI"
    words[WP_VIER]          = "24VIER"
    words[WP_VIERTEL]       = "24VIERTEL"
    words[WP_DREIVIERTEL]   = "20DREIVIERTEL"
    words[WP_NACH]          = "32NACH"
    words[WP_VOR]           = "36VOR"
    words[WP_HALB]          = "40HALB"
    words[WP_ZWOELF]        = "45ZWÖLF"
    words[WP_ZWEI]          = "50ZWEI"
    words[WP_EIN]           = "52EIN"
    words[WP_EINS]          = "52EINS"
    words[WP_SIEBEN]        = "55SIEBEN"
    words[WP_DREI_2]        = "61DREI"
    words[WP_FUENF_2]       = "67FÜNF"
    words[WP_ELF]           = "70ELF"
    words[WP_NEUN]          = "73NEUN"
    words[WP_VIER_2]        = "77VIER"
    words[WP_ACHT]          = "81ACHT"
    words[WP_ZEHN_2]        = "85ZEHN"
    words[WP_SECHS]         = "91SECHS"
    words[WP_UHR]           = "98UHR"
    words[WP_ZIG]           = "18ZIG"

    // Wörter für jede Stunde:
    hours[0]                = WP_ZWOELF
    hours[1]                = WP_EINS
    hours[2]                = WP_ZWEI
    hours[3]                = WP_DREI_2
    hours[4]                = WP_VIER_2
    hours[5]                = WP_FUENF_2
    hours[6]                = WP_SECHS
    hours[7]                = WP_SIEBEN
    hours[8]                = WP_ACHT
    hours[9]                = WP_NEUN
    hours[10]               = WP_ZEHN_2
    hours[11]               = WP_ELF

    // Wörter für jede Minute, Format: offset|Wort1|Wort2|...
    minutes[0]              = "0|" : WP_UHR
    minutes[1]              = "0|" : WP_FUENF_1 : "|" : WP_NACH
    minutes[2]              = "0|" : WP_ZEHN_1  : "|" : WP_NACH
    minutes[3]              = "0|" : WP_VIERTEL : "|" : WP_NACH
    minutes[4]              = "0|" : WP_ZWANZIG : "|" : WP_NACH
    minutes[5]              = "1|" : WP_FUENF_1 : "|" : WP_VOR  : "|" : WP_HALB
    minutes[6]              = "1|" : WP_HALB
    minutes[7]              = "1|" : WP_FUENF_1 : "|" : WP_NACH : "|" : WP_HALB
    minutes[8]              = "1|" : WP_ZWANZIG : "|" : WP_VOR
    minutes[9]              = "1|" : WP_VIERTEL : "|" : WP_VOR
    minutes[10]             = "1|" : WP_ZEHN_1  : "|" : WP_VOR
    minutes[11]             = "1|" : WP_FUENF_1 : "|" : WP_VOR
endfunction

//----------------------------------------------------------------------------------------------------
// draw_time - Aktuelle Uhrzeit in Worten ausgeben
//----------------------------------------------------------------------------------------------------
function void draw_time (int hour, int minute_5, int fg, int bg)
    string  token
    int     wordno
    int     i

    wordno = hours[hour]

    // Korrektur: ES IST EIN UHR (statt EINS):
    if hour = 1
        if minute_5 = 0
            wordno = WP_EIN
        endif
    endif

    // Wort für Stunde ausgeben:
    draw_word (wordno, fg, bg)

    // Wörter für Minute ausgeben:
    for i = 1 to string.tokens (minutes[minute_5], "|") - 1
        token = string.get_token (minutes[minute_5], "|", i)
        if token != ""
            draw_word (token, fg, bg)
        endif
    endfor
endfunction

//----------------------------------------------------------------------------------------------------
// update - Anzeige aktualisieren. Wird jede Sekunde per Alarm-Timer aufgerufen
//----------------------------------------------------------------------------------------------------
function void update ()
    static int  last_minute_5 = -1                                  // letzte Minute / 5
    static int  last_hour                                           // letzte Stunde
    int         minute_5                                            // aktuelle  Minute / 5
    string      year                                                // aktuelles Jahr
    string      month                                               // aktueller Monat
    string      day                                                 // aktueller Tag
    int         hour                                                // aktuelle  Stunde
    int         minute                                              // aktuelle  Minute
    string      datetime
    string      date
    string      time

    datetime = date.datetime ()                                     // Aktuelles Datum/Uhrzeit holen

    // 0123456789012345678
    // YYYY-MM-DD hh:mm:ss
    year    = string.substring (datetime, 2, 2)
    month   = string.substring (datetime, 5, 2)
    day     = string.substring (datetime, 8, 2)
    hour    = string.substring (datetime, 11, 2)                    // Stunde/Minute/Sekunde extrahieren
    minute  = string.substring (datetime, 14, 2)

    date = day : "." : month : "." : year
    time = string.substring (datetime, 11)

    tft.draw_string (520,  60, date, COLOR_WHITE, BG_COLOR)
    tft.draw_string (520, 120, time, COLOR_WHITE, BG_COLOR)

    minute_5 = minute / 5

    if last_minute_5 != minute_5                                    // Minute (5-Minutenraster) geändert?
        hour = hour + string.substring (minutes[minute_5], 0, 1)    // Evt. Offset addieren
        hour = hour % 12                                            // 24 Stunden -> 12 Stunden

        draw_word (WP_ES, FG_COLOR, BG_COLOR)                       // Anzeige: ES
        draw_word (WP_IST, FG_COLOR, BG_COLOR)                      // Anzeige: IST

        if last_minute_5 >= 0                                       // Löschen der alten Uhrzeit
            draw_time (last_hour, last_minute_5, FG_DARKCOLOR, BG_COLOR)
        endif

        draw_time (hour, minute_5, FG_COLOR, BG_COLOR)              // Anzeige der neuen Uhrzeit

        last_hour       = hour                                      // Letzte Uhrzeit merken
        last_minute_5   = minute_5
    endif

endfunction

//----------------------------------------------------------------------------------------------------
// main - Hauptfunktion: Initialisierung und Start der Uhr
//----------------------------------------------------------------------------------------------------
function void main ()
    tft.init ()                                                     // TFT initialisieren
    tft.fill_screen (BG_COLOR)                                      // Mit Hintergrundfarbe füllen

    tft.set_font (FONT_32x53)                                       // Font 32x53 auswählen
    tft.draw_string (520, 420, "MINOS", COLOR_YELLOW, BG_COLOR)     // Powered by ...

    tft.set_font (FONT_24x40)                                       // Font 24x40 wählen
    font_height = tft.font_height ()                                // Font-Höhe holen und speichern

    wc_init ()                                                      // Variablen initialisieren
    draw_display ()                                                 // Display anzeigen

    alarm.set (1000, function.update)                               // Alarm setzen: Jede Sekunde!

    loop                                                            // Endlosschleife: nichts zu tun
        // do nothing
    endloop
endfunction

I2C LCD

Als Text-LCDs sind häufig 4x20 I2C-LCDs üblich.

Funktionen:

  • int i2c.lcd.init (int i2c_hannel, int i2c_address, int i2c_alternate, int lines, int columns)
  • int i2c.lcd.clear ()
  • int i2c.lcd.home ()
  • int i2c.lcd.move (int line, int column)
  • int i2c.lcd.backlight (int on)
  • int i2c.lcd.define_char (int n, byte buffer)
  • int i2c.lcd.print (string str)
  • int i2c.lcd.print (int character)
  • int i2c.lcd.mvprint (int line, int column, string str)
  • int i2c.lcd.mvprint (int line, int column, int character)
  • int i2c.lcd.clrtoeol ()

LCD Beispiel

Beispiel: Versetzte Ausgabe von "Zeile N" in Zeile 1-4:

//-----------------------------------------------------------------------------------
// print "Zeile N" on lines 1-4
// I2C channel: I2C1
// I2C address: 0x27 (0x20 - 0x27 on PCF8574, 0x38 - 0x3F on PCF8574A)
//-----------------------------------------------------------------------------------
function void main ()
    if i2c.lcd.init (I2C1, FALSE, 0x27, 4, 20) = TRUE
        i2c.lcd.backlight (TRUE)
        i2c.lcd.mvprint (0, 0, "Zeile1")
        i2c.lcd.mvprint (1, 2, "Zeile2")
        i2c.lcd.mvprint (2, 4, "Zeile3")
        i2c.lcd.mvprint (3, 6, "Zeile4")
    endif
endfunction

Siehe auch: Kapitel I2C


I2C DS3231

Bei der DS3231 handelt es sich um eine externe Real Time Clock, die über I2C an das verwendete Board angebunden werden kann.

Schreiben DS3231

Beispiel 1: Schreiben des Datums "2021-05-15 18:35:30" in eine DS3231 Real Time Clock:

//-----------------------------------------------------------------------------------
// write date/time to DS3231
// I2C address of DS3231 is 0x68
//-----------------------------------------------------------------------------------
function void main ()
    string s

    i2c.ds3231.init (I2C2, FALSE, 0x68)
    i2c.ds3231.set ("2021-05-15 18:35:30")
    s = i2c.ds3231.get ()
    console.println (s)
    time.delay (2000)
    s = i2c.ds3231.get ()
    console.println (s)
endfunction

Lesen DS3231

Beispiel2: Lesen von Datum/Uhrzeit aus DS3231 Real Time Clock und Ausgabe auf der Console:

//-----------------------------------------------------------------------------------
// read date/time from DS3231 RTC
// I2C address of DS3231 is 0x68
// output format: "YYYY-MM-DD hh:mm:ss"
//-----------------------------------------------------------------------------------
function void main ()
    string s
    s = i2c.ds3231.get ()
    console.println (s)
endfunction

Siehe auch: Kapitel I2C


I2C EEPROM

Beispiel: Lesen/Schreiben eines über I2C angeschlossenen EEPROMs AT24C32:

//-----------------------------------------------------------------------------------
// main function: write some values into EEPROM, read them from EEPROM again
// I2C channel: I2C2 (PB10/PB11)
// I2C address of AT24C32 is 0x50 - 0x57
// If all jumpers are open, address is 0x57
//-----------------------------------------------------------------------------------
function void main ()
    byte buf1[3]
    byte buf2[3]

    buf1[0] = 0x11
    buf1[1] = 0x22
    buf1[2] = 0x33

    i2c.at24c32.init (I2C2, FALSE, 0x57)  // initialize I2C2
    i2c.at24c32.write (0x0000, buf1, 3)   // write 3 bytes at address 0x0000
    i2c.at24c32.read (0x0000, buf2, 3)    // read 3 bytes at address 0x0000

    console.println (buf2[0], HEX)
    console.println (buf2[1], HEX)
    console.println (buf2[2], HEX)
endfunction

Siehe auch: Kapitel I2C