Build your OS – Regnatix-Code

Regnatix-Code

  1. Der Bus
  2. Erste Zeichen
  3. Slave-Funktionen
  4. Kommandointerpreter
  5. Der Loader
  6. Dateiverwaltung
  7. Anwendungen

1. Der Bus

Im ersten Tutorial haben wir uns ausschließlich mit Bellatrix und ihren Funktionen beschäftigt. Nun wollen wir unseren Horizont erweitern und uns Regnatix zuwenden, denn schließlich sollen unsere Programme letztlich in Regnatix gestartet werden und die Funktionen von Bellatrix und Administra nur nutzen. Alle Ressourcen von Regnatix und auch der extern angeschlossene Arbeitsspeicher, sollen unseren Anwendungen frei zur Verfügung stehen. Um Administra kümmern wir uns später, wenn wir genauer verstehen, wie die Kommunikation zwischen den Propellerchips funktioniert. Das macht durchaus Sinn, da wir bei Administra ein wenig „Blindflug“ haben, so ganz ohne ein direkt angeschlossenes Display – diesen Luxus haben wir nur bei Bellatrix und bald indirekt bei Regnatix.

Doch wie koppeln wir unsere drei Propellerchips? Als erstes möchte ich die Entstehung kurz erläutern, um zu verdeutlichen wie sich die Bedingungen herausgebildet haben, welche wir momentan im Hive vorfinden.

Mein erster, noch sehr unscharfer Plan, sah für die Kopplung der drei Propeller einen seriellen Bus vor. Das schien mir am Anfang eine gute Lösung, um möglichst wenig Portleitungen zu verschwende. Etwas später aber fand ich die Idee sehr gut, Regnatix mit externem Arbeitsspeicher auszurüsten. Da Regnatix selbst ja keine Peripherie zu steuern hat, verwendete ich Arbeitsspeicher, der über einen 8Bit-Datenbus angeschlossen wird. Was lag da näher, als die zwei anderen Propellerchips auch über diesen parallelen Bus anzuschließen? Mittlerweile hat sich auch die Portbelegung von Administra und Bellatrix konkretisiert und dadurch wurde sichtbar, dass beide Chips noch genug Leitungen verfügbar hatten, um einen entsprechenden Bus zu bedienen. Ich glaube diese Lösung ist für unser Projekt als Kompromiss zwischen Ressourcenverbrauch und Leistung ganz gut, zumindest ist mir bisher keine einfachere und bessere Variante eingefallen.

Was brauchen wir also für einen 8Bit-Bus? Im allgemeinen werden alle Signale auf einem parallelen Bus in drei Funktionsgruppen unterteilt:

Link: Wikipedia „Bus“

Datenbus – Über diese Leitungen tauschen die Mikrocontroller und der Arbeitsspeicher untereinander die Daten aus.
Adressbus – Mit den Adressleitungen wird der Speicher und die anderen Bausteine am Bus adressiert.
Steuerbus – Diese Leitungen steuern im Detail den Datenaustausch, legen fest, wann Daten auf dem Bus gültig sind, oder legen fest, in welche Richtung die Daten übertragen werden sollen.

Schauen wir uns die konkreten Signale etwas genauer an. Zum Verständnis muß noch folgendes erwähnt werden: Alle Signale, um irgendwelche Bausteine auszuwählen, unabhängig von den Adresssignalen A0..18, sind ebenfalls dem Adressbus zuzurechnen. In einem klassischen Computer werden diese Signale meist mit der Hilfe eines Adressdekoders direkt aus den oberen Adressbits gebildet. Um den Hive einfacher und flexibler zu gestalten, dekodieren wir diese Signale intern per Software. Das erspart uns einen externen Schaltkreis und genau dieser Minimalismus war ja ein erklärtes Ziel bei der Entwicklung des Hive.

Pin Kategorie Master Slave Funktion
D0..7 Datenbus In/Out In/Out Über diese acht Leitungen werden die eigentlichen Daten zwischen den Baugruppen übertragen. An jedem Busteilnehmer sind diese Leitungen bidirektional.
A0..18 Adressbus Out Externer Adressbus; Regnatix hat nur A0..10 ausgeführt, A11..18 werden in einem Latch gespeichert. Die Slaves werden nicht über die externe Adresse angesprochen, sondern üben zwei durch die Software verwaltete Signale /Prop1+2
/RAM1 Adressbus Out Low selektiert die erste Rambank
/RAM2 Adressbus Out Low selektiert die zweite Rambank
/PROP1 Adressbus Out Dieses Signal adressiert mit einem Low den Administra-Chip
/PROP2 Adressbus Out Dieses Signal adressiert mit einem Low den Bellatrix-Chip
AL Steuerbus Out Mit einem High-Impuls wird A0..7 in das Adresslatch übernommen und bildet so A11..18.
BUSCLK Steuerbus Out In Mit diesem Bustakt steuert Regnatix den Datenaustausch mit den Slave-Propellern; der Takt wird dabei vom Master erzeugt.
/WR Steuerbus Out In Low zeigt an das Regnatix in den RAM schreiben will.
/HS Steuerbus In Out Handshake ist ein Quittungssignal von den Slaves zum Master. Die beiden Ausgänge an den Slaves sind per ODER-Gatter mit dem Eingang am Master verschaltet.
/CS Steuerbus In Ein Low an diesen Eingängen selektiert Bellatrix oder Administra

Werden wir noch konkreter und schauen uns an, wie der Bus elektrisch ausschaut. In dem folgenden Schaltplan sind alle elektrischen Leitungen zwischen Regnatix und Bellatrix dargestellt, die wir für die Realisierung des Busses benötigen. Folgende Punkte fehlen in der Schaltung, oder sind etwas vereinfacht, damit wir uns ganz ausschließlich auf den 8Bit Bus konzentrieren können:

  • Versorgungsspannungen, Quarze, Peripheriebauteile – also alles was nicht „Bus“ ist – fehlen völlig.
  • Einige Steuersignale haben einen sogenannten Pullup-Widerstand. Diese Widerstände sind nicht eingezeichnet. Sie dienen der Festlegung eines definierten Pegels dieser Signale, wenn kein Propeller zu einem bestimmten Zeitpunkt diese Signale belegt.
  • Die Handshake Signale der beiden Slave Propeller sind über zwei Dioden mit Regnatix verschaltet, um diese Signale zu entkoppeln. Die Funktion werden wir später im Zusammenhang mit Administra genauer betrachten.

Wer mag, kann diese Prinzipschaltung auch mit den Schaltplänen des Hives vergleichen, um sich etwas tiefer damit zu beschäftigen. Wie man an der Tabelle und am Schaltbild erkennen kann, sind einige Signale fest als Ausgang oder Eingang definiert, andere Signale werden bidirektional verwendet. So ist das Signal /PROP2 ein Ausgang an Regnatix und führt zu dem Eingang /CS an Bellatrix. Die Ports des Propellerchips sind universell, was bedeutet, dass die Arbeitsweise als Eingang oder Ausgang per Software festgelegt wird. Der Datenbus selbst (D0..7) wird je nach Bedarf und zeitlich begrenzt als Eingang oder Ausgang programmiert. Wenn Regnatix ein Byte zu Bellatrix sendet, wird also kurzfristig und zu einem genau definierten Zeitpunkt D0..7 an Regnatix auf Ausgabe geschaltet und D0..7 an Bellatrix arbeiten als Eingänge, um das Byte zu empfangen.  Das ist sehr flexibel, da wir so bei Bedarf durchaus aus einem 8Bit Bus per Software zwei 4Bit Busse machen können.

Wichtig: Bei Experimenten muß man genau aufpassen, nicht aus Versehen zwei Signale als Ausgang gegeneinander arbeiten zu lassen. Wenn das geschieht und beide Ausgänge unterschiedliche Pegel führen, so kommt es zwischen den Ausgangstreibern im Prinzip zu einem Kurzschluss.  Wer also statisch über einen längeren Zeitraum die Ports der Propeller gegeneinander arbeiten lässt, kann seinen Chip beschädigen – also aufpassen! Wer nicht so viel Erfahrung hat, sollte besser die Finger von den Busroutinen und von eigenen Experimenten am Bus lassen, oder sich einen Reservechip hinlegen… 😉

Ich selbst habe auch schon Fehler bei der Programmierung der Routinen gemacht und der Hive ist ziemlich robust was dynamische Buskonflikte angeht. Aber das waren alles dynamische und realtiv kurzzeitige Fehlerzustände. Passiert ist dabei nichts, aber ich wollte nur auf die Möglichkeit hinweisen und damit auf die Notwendigkeit, mit ausreichend Sorgfalt den Bus zu programmieren.

Ok, das wollte ich nur nochmal explizit erwähnt haben! Propeller grillen ist böse und I/O-Pins gegeneinander arbeiten lassen ist unanständig und unmoralisch und wird mit einer Strafe von 9,95€ bestraft! :twisted:

Link zum Hive-Bußgeldkatalog: 1 x Propeller quälen kostet…

2. Erste Zeichen

Als erstes Experiment wollen wir uns einer ganz einfachen Aufgabe widmen: Wir wollen in Regnatix eine kurze Programmschleife laufen lassen, welche kontinuierlich Zeichen über den Bus zu Bellatrix sendet. Bella soll diese Zeichen empfangen und auf dem Textbildschirm anzeigen. Im Downloadpaket sind alle nötigen Quelltexte enthalten, wir besprechen jetzt hier nur noch wichtige Routinen.

Wichtig: Da wir es ab jetzt meist mit zwei Propellern zu tun haben, genügt es nicht, den Code nur mit F10 zu übertragen. Ab jetzt muss der Code mit F11 in den EEProm dauerhaft gespeichert werden!

Übertragen wir nun folgende Programme in die beiden Propellerchips:

  • [001-bel-zeichen empfangen.spin] —> Bellatrix
  • [001-reg-zeichen senden.spin] —> Regnatix

Wenn alles richtig läuft sehen wir wie Regnatix ihre Zeichen an Bellatrix sendet. Die beiden Hauptroutinen sind dabei recht trivial. Viel interessanter für uns sind die Empfangs- und Senderoutine.

REGNATIX-CODE
PUB main | i
bus_init
repeat
  repeat i from 14 to 255
    bus_putchar2(i)
    waitcnt(cnt + 2_000_000)                            'zum mitlesen ;)

CON
'                            +------------------------- al
'                            |+------------------------ /prop2
'          hbeat   --------+ ||+----------------------- /prop1
'          clk     -------+| |||+---------------------- /ram2
'          /wr     ------+|| ||||+--------------------- /ram1
'          /hs     -----+||| |||||           +--------- a0..a10
'                       |||| |||||           |
'                       |||| |||||-----------+ -------- d0..d7
DB_IN            = %00000111_11111111_11111111_00000000 'maske: dbus-eingabe
DB_OUT           = %00000111_11111111_11111111_11111111 'maske: dbus-ausgabe

M1               = %00000000_00111000_00000000_00000000 'prop2=0, wr=0, busclk=0
M2               = %00000110_00111000_00000000_00000000 'prop2=0, wr=1, busclk=1
M3               = %00001100_01111000_00000000_00000000 'prop2=1, wr=1, busclk=0

M4               = %00000000_00000000_00000000_00000000
M5               = %00001000_00000000_00000000_00000000 '/hs=0?

PUB bus_putchar2(c)                                     'BUS: Byte an Prop1 (Bellatrix) senden
{{bus_putchar2(c) - bus: byte senden an prop2 (bellatrix)}}
  outa := M1                                            'prop2=0, wr=0, busclk=0
  dira := db_out                                        'datenbus auf ausgabe stellen
  outa[7..0] := c                                       'daten --> dbus
  outa[busclk] := 1                                     'busclk=1
  waitpeq(M4,M5,0)                                      'hs=0?
  dira := db_in                                         'bus freigeben
  outa := M3                                            'prop2=1, wr=1, busclk=0

BELLATRIX-CODE
PUB main | char                                         'Hauptroutine
  init_subsysteme
  repeat
    print_char(bus_getchar)

CON
'
'          hbeat   --------+
'          clk     -------+|
'          /wr     ------+||
'          /hs     -----+||| +------------------------- /cs
'                       |||| |                 -------- d0..d7
DB_IN            = %00001001_00000000_00000000_00000000 'maske: dbus-eingabe
DB_OUT           = %00001001_00000000_00000000_11111111 'maske: dbus-ausgabe

M1               = %00000010_00000000_00000000_00000000
M2               = %00000010_10000000_00000000_00000000 'busclk=1? & /cs=0?

M3               = %00000000_00000000_00000000_00000000
M4               = %00000010_00000000_00000000_00000000 'busclk=0?

PUB bus_getchar : zeichen                               'BUS: Ein Byte über BUS empfangen
{{ein byte über bus empfangen Regnatix --> Bellatrix}}
   waitpeq(M1,M2,0)                                     'busclk=1? & prop2=0?
   zeichen := ina[7..0]                                 'daten einlesen
   outa[bus_hs] := 0                                    'daten quittieren
   outa[bus_hs] := 1
   waitpeq(M3,M4,0)                                     'busclk=0?

Da unser minimalistisches SpinOS ganz streng nach einem Master-Slave Prinzip arbeiten soll, gehen alle Aktionen ursprünglich immer vom Master (Regnatix) aus. Die kleinste Dateneinheit die in einem Zyklus über den Bus übertragen werden kann ist ein Byte. Also kann man recht allgemein formulieren, was der Busmaster veranstalten muss um den Bus zum Leben zu erwecken:

  1. Adresse festlegen: Zeige mit wem du über den Bus kommunizieren möchtest!
  2. Datenrichtung festlegen: Ich will ein Byte lesen/schreiben!
  3. Datum austauschen: Byte schreiben!

Aus der Sicht von Belltrix läuft die Übertragung nach folgendem Schema ab:

  1. Warten auf Regnatix: Regnatix signalisiert mit einem Low an Signal /PROP2, dass ein Kommunikation startet.
  2. Warten auf Daten: Ist BUSCLK High liegen am Datenbus gültige Daten an.
  3. Datum austauschen: Datenbyte lesen.
  4. Quittierung: Wurde das Datenbyte gelesen, wird mit einem Low-Impuls der Erhalt quittiert.

Mit diesem Busdiagramm für den Buszyklus „Byte senden“ wird der Ablauf sicher klarer. Kurz formuliert passiert dabei folgendes:

  1. Regnatix: Mit „PROP2 = 0“ Bellatrix selektieren.
  2. Ragnatix: Datenbyte auf dem Datenbus ausgeben.
  3. Regnatix: Mit „BUSCLK = 1“ Signal an Bellatrix senden, dass gültige Daten anliegen.
  4. Regnatix: Warten auf „/HS = 0“.
  5. Bellatrix: Datenbyte vom Datenbus einlesen.
  6. Bellatrix: Empfang der Daten mit „/HS = 0“ quittieren.
  7. Regnatix: Bei „/HS = 0“ Buszyklus mit „BUSCLK = 0“ beenden.
  8. Bellatrix: Buszyklus mit „/HS = 1“ beenden.

Seite 1 – Seite 2Seite 3Fortsetzung