MINOS
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:
|
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. |
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:
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
|
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 |
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:
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
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
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. |
//------------------------------------------------------------------------------------------
// 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