Build your OS – Der Bellatrix-Code – Seite 3

Eine zweite Zeile

Als nächstes machen wir folgendes Experiment: Im DAT-Block ganz am Ende des Programmes haben wir unseren String definiert. Diesen String vergrößern wir jetzt, so das er länger als 64 Zeichen ist. Schauen wir, was dabei passiert!

Irgendwie scheint es so, als ob unsere Printroutine in der neuen Zeile auf halber Höhe weiter schreibt. Nun, genau das ist der Fall! In der Routine verwenden wir die Variable „col“ als Index für den Zugriff auf unseren Screenpuffer. Am Zeilenende überschreiben wir aber diesem Index entsprechend nur die zweite Tile-Zeile. Wir müssen uns also mal genauer mit den Variablen für Zeilen und Spalten beschäftigen. Wenn wir das Zeilenende erreichen, müssen wir „row“ und „col“ neu berechnen und setzen. Erweitern wir unser Programm:

PRI print(c) | i, k
  case c
    $20..$FF:           'zeichen?
      k := color << 1 + c & 1
      i := $8000 + (c & $FE) << 6 + k
      array.word[row * cols + col] := i
      array.word[(row + 1) * cols + col] := i | $40
      if ++col == cols
        newline
    $100:               'screen löschen?
      wordfill(@array, spacetile, tiles)
      col := row := 0

PRI newline | i
  col := 0
  if (row += 2) == rows
    row -= 2
    repeat i from 0 to rows-3
      wordmove(@array.word[i*cols], @array.word[(i+2)*cols], cols)
    wordfill(@array.word[(rows-2)*cols], spacetile, cols<<1)

So erscheint nun unser Text in der Form wie er soll! In der neu eingefügten Routine „newline“ machen wir aber noch mehr: Erreicht unsere Bildausgabe den unteren Rand, so scrollt der gesamte Bildschirm eine Zeile nach oben und es wird in der frei werdenden Zeile weiter geschrieben. Das wollen wir testen, also geben wir als Test genug Text aus um unser Werk zu bewundern:

PUB main
  vga.start(vga_basport, @array, @vgacolors, 0, 0, 0)
  print($100)
  repeat 100
    print_string(@text)
  repeat

Nun wollen wir das ganze noch um einige weitere nützliche Steuerzeichen erweitern, die wir mit Sicherheit in unseren nächsten Experimenten brauchen werden.

PRI print(c) | i, k                                                             

  case c
    $0D:                'return
      newline
    $20..$FF:           'zeichen
      k := color << 1 + c & 1
      i := $8000 + (c & $FE) << 6 + k
      array.word[row * cols + col] := i
      array.word[(row + 1) * cols + col] := i | $40
      if ++col == cols
        newline
    $100:               'clear screen?
      wordfill(@array, spacetile, tiles)
      col := row := 0
    $101:               'home?
      col := row := 0
    $102:               'backspace?
      if col
        col--
    $110..$11F:         'select color?
      color := c & $F

Damit haben wir jetzt folgende Steuerzeichen zu unserer Verfügung:

$0D RET, Return
$20..$FF Textzeichen
$100 CLS, Bildschirm
löschen
$101 HOME, Cursor auf
erste Position im Screen
$102 BS, Backspace
$110..$11F COLOR, Farbe wählen

Zahlenausgabe

Was fehlt uns jetzt noch zum Glück? Richtig, wir wollen ja auch reine Zahlen ausgeben, nicht nur Zeichenketten! Also fügen wir in unser Programm noch zwei Routinen dafür ein und variieren zum testen noch die Hauptroutine.

PUB main | i
  vga.start(vga_basport, @array, @vgacolors, 0, 0, 0)
  print($100)
  repeat 3
    print_string(@text)
    print($0D)
  repeat i from 0 to 15
    printdec(i)
    print(" ")
  print($0D)
  repeat i from 0 to 15
    printhex(i,2)
    print(" ")
  repeat

PUB printdec(value) | i
  if value < 0                          'negativer zahlenwert
      -value
      print("-")
  i := 1_000_000_000
  repeat 10                             'zahl zerlegen
    if value => i
      print(value / i + "0")
      value //= i
      result~~
    elseif result or i == 1
      print("0")
    i /= 10                              'nächste stelle

PUB printhex(value, digits)
  value <<= (8 - digits) << 2
  repeat digits
    print(lookupz((value <-= 4) & $F : "0".."9", "A".."F"))

Das soll nun als Anfang zum VGA-Objekt genügen, wenden wir uns nun der zweiten Phase zu – der Tastatur.

Tastatur und VGA

[Testdatei 008-test-keyboard-1.spin] – Nun, ab jetzt möchte ich euch nicht weiter quälen, die folgenden Quelltexte liegen dem Downloadpaket bei. Die Änderungen werden nun doch zu umfangreich um sie hier erschöpfend schrittweise einzutippen. Als erstes starten wir das Programm „008-test-keyboard-1.spin“. In diesem Programm wird das Tastaturobjekt gestartet und in einer Endlosschleife auf dem Bildschirm ausgegeben. Im Quelltext sieht man das die Erweiterung zu unserem vorigen Experiment wirklich minimal ist.

PUB main | i
  vga.start(vga_basport, @array, @vgacolors, 0, 0, 0)
  key.start(keyb_dport, keyb_cport)
  print($100)
  print_string(@ver1)
  print($0D)
  repeat
    print(key.key)

Auch das Tastaturobjekt belegt mit dem Start eine COG. Damit laufen in Bellatrix nun vier RISC-Subsysteme parallel:

  • COG 0 – Testprogramm
  • COG 1+2 – VGA-Objekt
  • COG 3 – Tastatur-Objekt

Wie man sieht beansprucht auch das Tastaturobjekt eine eigene COG, die mit der Start-Routine belegt wird. Als Parameter werden die beiden Pins übergeben, an denen die PS/2-Schnittstelle angeschlossen ist.
Mit diesem einfachen Programm können wir schon problemlos einen Text eingeben, mit Return in die nächste Zeile wechseln und am Bildschirmende scrollt der Text nach oben. Was uns aber ganz augenscheinlich fehlt ist ein Cursor.

[Testdatei: 008-test-keyboard-cursor.spin] – In diesem Quelltext habe ich umfangreiche Änderungen vorgenommen, die wir nur noch grob im einzelnen anreißen wollen.

  1. Cursorverwaltung: Der Cursor wird im wesentlichen in der zentralen Zeichenausgabefunktion verwaltet. Dazu wird an der aktuellen Position ein Cursorzeichen ausgegeben. Bei jeder Ausgabeoperation wird dieses Zeichen wieder gelöscht, die Funktion ausgeführt und an der neuen Position wieder neu gezeichnet. Das Cursorzeichen wird in der Variable „curchar“ definiert und ist damit variabel. Der Cursor kann auch durch ein Steuerzeichen abgeschaltet werden.
  2. Initialisierung: Alle Initialisierungen, das Starten der Objekte, das Setzen bestimmter Variablen und bestimmte Funktionen, wie das Löschen des Screens beim Start, wurde aus „main“ in die Routine „init_subsysteme“ ausgelagert – diese Verfahrensweise ist übersichtlicher.
  3. Zusätzliche Funktionen: Die Zeichenausgabefunktion hat eine ganze Reihe zusätzlicher Funktionen erhalten. Diese mag jeder selbst ein wenig erkunden, mit dem jetzigen Wissen sollte es kein Problem sein die Funktionsweise zu verstehen.

Freestyle

Gut, die arbeitsame Drohne hat jetzt die Pflicht absolviert, für die leidenschaftliche Drohne wird es nun Zeit für die Kür! Einfach ein paar Spielereien mit Bellatrix:

[009-test-hive-4bit.spin] – Nix besonderes, nur ein paar Zahlen die als 4Bit-Band über den Bildschirm sausen.
[010-test-hive-matrix-1core] – Wie wäre es mit einer Hive-Matrix? Hier ein erster Test der Routine – noch auf einem Core laufend.
[011-test-hive-matrix-5core] – Matrix-Anzeige von 5 Cores. Um die Routine aus der Version mit 1 Core auf 4 Kerne zu verteilen, müssen alle Parameter reentrant mit Stackvariablen realisiert werden. Also nix mehr mit globalen Variablen. Da die Routine im Kern aber nur die Zeichenausgaberoutine „schar(c)“ verwendete, habe ich eine Kopie „mchar(c,mrow,mcol,mcolor)“ mit reinen Stackvariablen erstellt. Jede COG kann nun dank eigenem Stack diese Routine parallel aufrufen, ohne einer anderen COG in die Quere zu kommen. Theoretisch könnte man auch 6 COG’s verwenden, wenn man noch den Treiber für die Tastatur entfernt. Aber so als kleine Fingerübung sieht das doch schon ganz nett aus.

An sich haben wir jetzt die wesentlichen Funktionalitäten realisiert, welche wir für unser Mini-OS von Bellatrix erwarten, aber wir bewegen uns ja immer noch ausschließlich in einem einzigen Mikrocontroller. Unser Masterplan sieht aber vor, unsere eigentliche Anwendersoftware in Regnatix laufen zu lassen und Regnatix mit seinen ganzen Ressourcen einzig dem Benutzer zur Verfügung zu stellen. Bildschirmausgaben, Tastatureingaben und die Laufwerkszugriffe sollen vollständig Bellatrix und Administra übernehmen. Aber wie kann Regnatix unsere gerade realisierten Funktionalitäten in Bellatrix nutzen? Das Zauberwort heißt „Bus“ und wir werden uns im nächsten Teil damit beschäftigen – dem Regnatix-Code.
Abschließend für diesen Teil möchte ich bitten, bei Unklarheiten bitte unbedingt per Mail oder Forum anzufragen. Wichtige Punkte und genauere Erläuterungen werde ich dann zusätzlich nachträglich in das Tutorial aufnehmen. Wer Lust verspürt, kann auch einfach allein mit Bellatrix weiter experimentieren. So wäre es denkbar einen einfachen Kommandointerpreter und in diesem Testfunktionen, Speicheranzeige usw. zu realisieren. Ein einfaches Breakout oder Packman in Bellatrix? Dieser Bellatrix-Code kann der erste Schritt zu einem kleinen Programm sein, welches man im Verlauf der weiteren Tutorials noch mit Sound in Dateiarbeit erweitern kann – vielleicht sogar die Basis für ein eigenes kleines Tutorial! Wer da nette Ideen und Programme hat, kann mir diese auch gern zusenden, vielleicht auch mit ein paar Notizen um das Ganze dann als zusätzliches Experiment hier einzubauen.

Viel Spaß beim Experimentieren!

drohne235