Systemaufrufe; Commodores Kernaltabelle benutzen ("Programmieren" lernen)

  • Bei dem Thread über die indirekte Adressierung ist schon einmal kurz etwas zum Aufbau einer Sprungtabelle geschrieben worden. Das Konzept einer solchen Tabelle ist dabei v.a., daß man die eigentlichen Routinen dadurch beliebig im Speicher platzieren kann, da man nur die Einträge in der Tabelle an die Speicheradresse anpassen muß, wohingegen Programme immer die gleiche Adresse - nämlich den Platz in der Tabelle - nutzen, um solch eine Funktion aufzurufen.


    Ob man diesen zweiten Sprung - nach dem ersten, den das Programm in die Tabelle (auf die gewünschte Funktion) ausführt - nun indirekt gestaltet oder nicht, ist ein bißchen Geschmackssache. Eine Indirektion hat den großen Vorteil, daß die aufzurufende Routine auch noch ergänzt oder gar ersetzt werden könnte. Die "Entkopplung" von Programm und realem Ort der Funktion wird davon nicht berührt.


    Besonders interessant wird soetwas, wenn es über mehrere Generationen einer bestimmen Maschine immer relativ konstant beibehalten wird - d.h. die Einsprungpunkte der Tabelle mit den gleichen Funktionen verknüpft bleiben.

    Sofern diese auch ihr Aufrufverhalten und die Rückgabewerte beibehalten, läßt sich so "uralte" Software von Gerät zu Gerät weiterverwenden - vorausgesetzt die Software nutzt ausschließlich die definierten Funktionen.


    Leider ist das manchmal gar nicht so einfach, insbesondere, wenn neue Hardwaremöglichkeiten auftauchen, die benutzt werden sollen. Spätestens dann wird Software zunehmend wenigstens rückwärts-INKOMPATIBEL, da eine Vorgängermaschine eben die neue Hardwarefunktion und die zugehörigen Routinen gar nicht bieten kann.


    Sehr konsequent wäre nun, daß solche Tabellen auch herstellerübergreifend angelegt würden und von irgendwelchen Firmenkonsortien durchgesetzt worden wären. Das hat es im Mikrocomputerbereich eigentlich nie wirklich gegeben. Die große Ausnahme ist vielleicht BASICODE, was versucht so ein System von gleichbenutzbaren Routinen mit definierten Einsprungpunkten zu definieren; als GOSUB Routinen für BASIC. Auch die MSX Geräte sind in der Form "normiert" (dort allerdings auch unter Vorgabe von Hardwaremindeststandards). Ansonsten funktioniert das gut nur innerhalb der Geräte eines Herstellers.


    Ähnliche Funktionalität findet sich im PC Bereich in Form des BIOS. Auch dieses definiert bestimmte immer wiederkehrende Grundfunktionen, "abstrahiert" zudem die eigentliche Hardware und stellt dazu auch fixe "Aufrufpunkte" für Software bereit. Dieses Konzept geht dort letztlich auf Gary Kildall (Intergalactic Digital Research) und seine Vorerfahrungen mit CP/M zurück.


    Recht konsequent war dies auch bei Acorn umgesetzt worden. Dort lassen sich auch auf dem aktuellsten RISC OS noch "gut" geschriebene und die Systemeinsprünge benutzende Programme für den BBC Micro direkt auf aktuellster Hardware ausführen.


    Für die Commodore Rechner gibt es auch eine gewisse Konsistenz in Form der sogenannten KERNAL-Tabelle.

    Diese findet sich bereits bei den ersten Modellen und ist über die 8-Bit Geräte kontinuierlich erweitert worden, bei relativ guter Konstanz der wichtigsten Routinen, sowohl in Bedienung wie bei ihrem jeweiligen "Tabellenplatz".


    Für die Spectrums und Ataris dieser Welt, wird es ähnliches vermutlich auch geben, immerhin gibt es auch dort mehrere Gerätegenerationen, die jeweils "alte" Software ausführen können.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • =6502= =(Commodore spezifisch)=

    Bei Commodore ist die Kernaltabelle der wichtigste Punkt, von dem aus man Routinen des Betriebssystems benutzen kann.


    https://commons.wikimedia.org/…bm_kernal_jmp_table3.jpeg


    https://www.c64-wiki.de/wiki/KERNAL#KERNAL-Routinen

    http://sta.c64.org/cbm64krnfunc.html


    Diese benutzt teils direkte JMPs, teils indirekte Sprünge, die über Vektoren springen, die im RAM liegen. Beim C16 z.B. findet man die veränderbaren Sprungvektoren ab $0300. Die Benutzung sollte aber IMMER über die Einträge der Kernaltabelle erfolgen, sonst könnte man ja auch gleich direkt auf die Routine springen.

    Die Kernaltabelle liegt bei allen Commodores im obersten ROM Bereich, z.B. ab $FF81.

    Allerdings kann sie unterschiedlich umfangreich sein. Und auch nicht alle Funktionen sind direkt zwischen einem PET und einem C128 "identisch", die wichtigsten aber schon.


    Daneben gibt es auch noch andere interessante Routinen in so einem Betriebssystem oder auch im BASIC Teil des ROMs, die man gut mitbenutzen kann. Allerdings sind diese dann eigentlich immer irgendwie gerätespezifisch und müssen daher für jedes Programm angepaßt werden, wenn man dieses auf ein anderes Gerät portieren will; z.B. VC20 nach C128.

    Sie sind eben nicht(!) über eine Sprungtabelle erreichbar, sondern nur direkt.



    Die Kernaltabelle enthält v.a. Funktionen, die sich mit Ein-/Ausgabe beschäftigen. Damit das nicht nur am Bildschirm funktioniert, gibt es die Möglichkeit Dateien (per Filenummer) und Geräte zu öffnen und zu schließen. Auf diese greift man dann, nach dem Öffnen, mit den gleichen Operationen zu, wie man es beim Bildschirm auch macht.

    Zudem gibt es Einträge, um die oben erwähnten Sprungvektoren zu initialisieren/zurückzusetzen oder auch um die Systemuhr auszulesen. Eine wichtige Gruppe sind die Routinen für die Bildschirmbearbeitung (Größe abfragen, Cursorposition lesen/setzen).



    Der eine Klassiker ist sicherlich die Adresse $FFD2.

    Das erste ist die Routine mit Namen "BSOUT" (oder früher CHROUT), die ein Byte an einen offenen Kanal schickt. Im Normalfall ist das der Bildschirm, zumindest bis man es ändert.

    Die zweite Klassiker ist die Abfrage der STOP Taste ($FFE1), weshalb sie den vielsagenden Namen "STOP" erhielt.


    Man sieht also:

    • die Routinen haben eine Adresse, nämlich die, worüber man sie aufruft
    • jede Routine hat eine relativ überschaubare, kleine, aber eindeutige Funktion
    • jede Routine bekommt einen besonderen Namen


    • zusätzlich ist für jede Routine definiert, wie man ihr bestimmte Werte übergeben/mitteilen kann


    So muß man der Ausgaberoutine natürlich schon sagen, was man ausgeben möchte. Da es sich dabei nur um ein einzelnes Byte handeln darf - mehr kann BSOUT nicht auf einmal verarbeiten - wird das in dem Fall einfach über den Akku gemacht. Man lädt das Byte in den Akku, ruft die Routine über die Kernaltabelle auf, diese gibt das Byte aus.

    Manche Routinen benutzen ausschließlich den Akku, andere zusätzlich X-Register oder Y-Register oder beide zur Datenübergabe.

    Je nach Routine wird auch was zurückgegeben, im Akku, in einem der beiden anderen Register, oder auch mal über ein gesetztes Flag - wie im Fall von STOP.


    Außerdem muß man beachten, daß manche Routinen z.B. X- oder Y-Register verwenden, um ihre Arbeit zu machen. Dadurch wird natürlich deren Inhalt zerstört. Wenn da nun vorher z.B. ein Index für eine Schleife oder einen RAM-Zugriff (LDA $5200,X) drinstand, wird das interessante Fehler im Hauptprogramm erzeugen.



    Beim Aufruf von BSOUT passiert nun:

    • der Akku wird mit dem Byte geladen, was ausgegeben werden soll
    • es wird in die Tabelle gesprungen, mit JSR $FFD2
    • in $FFD2 steht: JMP ($0324) - für den C16, woanders (PET) kann hier schon was anderes stehen
    • in Adresse $0324/$0325 steht der Sprungvektor für BSOUT: $4B/$EC
    • dorthin wird also nun weitergesprungen - $EC4B
    • hier wird zuerst geprüft, ob die Ausgabe auf den Bildschirm erfolgt
    • wenn nein, wird das Byte auf einen zuvor festgelegten Ausgabekanal geschickt (z.B. Drucker)
    • wenn ja, springt die Routine zur normalen BASIC Print Routine weiter
    • in der PRINT Routine wird auf CTRL-S getestet, auf RETURN und SHIFT-RETURN geprüft, eine ESC Sequenz ausgewertet, Sondercodes aussortiert und ausgeführt (z.B. für Cursortasten oder ClearHome), die Art des Zeichens ausgewertet (SHIFT ja/nein) und v.a. unterschieden zwischen ausgebbaren Zeichen und solchen die nur Sondereffekte bewirken, wie z.B. Anführungszeichenmodus; dann wird Zeile und Spalte ermittelt, wo der Cursor gerade steht, geprüft, ob der INSERT Modus aktiv ist und dieser beachtet, beim Bildschirmende ein Hochscrollen ausgelöst, und irgenwann auch mal das Zeichen in den Bildschirmspeicher geschrieben, wobei zusätzlich noch die aktuelle Zeichenfarbe ins Farbram kommt und die Info für ein blinkendes Zeichen mit geschrieben wird
    • all das endet mit einem RTS, worüber der Aufruf ins Hauptprogramm zurückkehrt

    Man stößt also mit so einer simplen Funktion potentiell jede Menge Code an !


    Der Vorteil ist:

    hohe Bequemlichkeit,

    relative Rechnerunabhängigkeit (!),

    hohe Funktionalität (Sonderzeichen, ESC etc.)


    Der Nachteil:

    ziemlich langsam im Vergleich zum direkten Plotten,

    evtl. völlig unnötige Bearbeitungen (z.B. wenn man weiß, daß man sowieso nur Zahlen von 0-9 ausgibt)



    Am Ende entspricht ein BSOUT ziemlich exakt dem BASIC Aufruf: PRINT CHR$(X)

    wobei "X" der im Akku übergebene Wert ist.


    Das bedeutet zudem auch, daß die Codierung der Zeichen anders ist, als bei direktem Schreiben in den Bildschirm. Hier wird der Commodore ASCII Zeichensatz benutzt, bei dem Zeichen völlig andere Werte haben können, als die, die letztlich im Bildschirmspeicher stehen. So ist das "A" für BSOUT eine 65 ($41) - wird aber im Bildschirmspeicher trotzdem natürlich als die bekannte $01 abgelegt. Das das klappt, ist der Umrechnerei innerhalb von BSOUT zu verdanken.


    Dafür kann man dann ASCII Zeichen somit auch ganz gut direkt auf Geräte ausgeben, die damit umgehen können (Drucker, Modem usf.).




    Eine kleine PRINT Routine.


    Eine sehr praktische Sache ist auch:

    LDA #$93 : JSR $FFD2

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • Das trifft sich ja sehr gut, ich habe mich heute Abend zufällig auch mit dem Thema beschäftigt und ein kleines Programm geschrieben mit Hilfe des Buchs. Hier das Listing:


    *=$0801

    start jsr $ffe1

    beq ende

    jsr $ffe4

    cmp #$30

    bcc start

    cmp #$3a

    bcs start

    jsr $ffd2

    ende rts




    Das Programm hab ich am C64 geschrieben. Der RAM beginnt hier bei $0800. Programme beginnt man allerdings bei $0801, da es sonst zu Problemen kommen kann. Wenn man das Programm startet, erwartet der Rechner eine Eingabe einer der Zifferntasten von 0-9, diese entsprechen ASCII 30-3A. Wird irgend etwas anderes eingegeben, bleibt die Schleife am laufen. Zu Anfang ist die STOP Kernal Routine $ffe1. Wird also die RUN/STOP-Taste gedrückt, wird das Programm beendet. Wird eine Zifferntaste gedrückt, wird diese Ziffer an der Cursorposition via Kernalroutine $ffd2 ausgegeben und ebenfalls das Programm beendet.


    Soweit so gut.



    BEQ bedeutet : verzweigt, falls das Zero Flag gelöscht wird. Das verstehe ich nicht, Wird zu Anfang des Programms das Zero Flag gesetzt und durch drücken der STOP-Taste gelöscht. Im Statusregister ist das Zero Flag nie gesetzt.


    Danke !


    EDIT:


    LDA #$93

    JSR $FFD2


    löscht den Bildschirm...


    Gruss Jan

    Edited 2 times, last by Jan1980 ().

  • Ok, hab die Lösung gefunden. Bei gedrückter STOP-Taste wird das Zero Flag auf 1 gesetzt.


    $FFE1

    STOP. Query Stop key indicator, at memory address $0091; if pressed, call CLRCHN and clear keyboard buffer.

    Input: –

    Output: Zero: 0 = Not pressed, 1 = Pressed; Carry: 1 = Pressed.

    Used registers: A, X.

    Real address: ($0328), $F6ED.



    Der Assembler-Befehl BEQ $hhll verzweigt zur Adresse $hhll (neuer Wert des Programmzählers PC), wenn das Ergebnis der letzten Operation gleich $00 ist, was mit Zero-Flag = 1 angezeigt wird.


    Es ist bei der relativen Adressierung darauf zu achten, dass sich das Sprungziel innerhalb des gültigen Adressbereiches befindet!


    Der Assembler-Befehl BNE $hhll verzweigt zur Adresse $hhll (als neuer Wert des Programmzählers PC), wenn das Ergebnis der letzten Operation ungleich $00 ist, was mit Zero-Flag = 0 angezeigt wird.


    Es ist bei der relativen Adressierung darauf zu achten, dass sich das Sprungziel innerhal
    b des gültigen Adressbereiches befindet!



    Da hat mich das mit BNE und BEQ durcheinander gebracht. Es ist genau umgekehrt, als ich dachte.. :-))



    Gruss Jan

  • =6502= =(Commodore spezifisch)=


    Ist ja lustig - da sollte jetzt ein Beispiel kommen und es ist schon da.

    Die STOP Funktion sollte nämlich als für eine eigenartige Form des Rückgabewertes herhalten. Eben dadurch, daß sie lediglich ein Flag setzt, wenn sie einen Tastendruck gefunden hat, ist sie schon etwas "speziell".


    Man sieht sicher, daß es eigentlich unabdingbar ist, sich so eine Kernaltabelle mit (!) Funktionsbeschreibung zu besorgen. Und zwar passend zur benutzten Maschine. In den Bücher ist oft nur eine Liste mit den Adressen und dann ein paar Beispielroutinen dazu. Eine deutsche umfassende Liste mit allen Infos, habe ich bisher nicht gefunden.



    Es gibt bei pagetable.com einen wirklich sehr schönen, empfehlenswerten Artikel zur Entwicklung der Kernaltabelle und einem Vergleichen der Systeme: Commodore KERNAL History.



    STOP : $FF8E1 :

    gibt gesetztes Zero-Flag bei gedrückter Stop-Taste zurück

    benutzt intern Akku und XR

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    Edited 3 times, last by ThoralfAsmussen ().

  • =6502= =(Commodore spezifisch)=


    Bei den Bildschirmfunktionen gibt es eine, die ich für sehr brauchbar halte: PLOT

    Keine Ahnung, wer die so genannt hat, sie macht was anderes ...


    PLOT : $FFF0 :

    setzt oder liest die Cursorposition , benutzt dazu XR für den Zeilenwert (0-24) und YR für die Spalte (0-39)

    . bei gesetztem Carry-Flag wird die aktuelle Position nach (YR,XR) gelesen

    . bei CarryFlag = 0 wird der Cursor auf die Position der Register (YR,XR) gesetzt

    benötigt intern Akku, XR und YR


    Man kann also mit

    SEC : JSR $FFF0

    die aktuelle Cursorposition in das Y-Register und das X-Register holen. Blöd ist halt, daß dabei gewissermaßen der X-Wert im Y-Register steht (Spalte) und der Y-Wert im X-Register (Zeile).


    Zum Setzen des Cursors nach (22,7) gilt dementsprechend

    LDY#$22 : LDX#$07 : CLC : JSR $FFF0


    Ein LDY#$22 : LDX#$07 : CLC : JSR $FFF0 : LDA#$41 : JSR $FFD2

    ist also das Gleiche wie ein PRINT TAB(22,7) "A"

    in BASIC, was es aber in dieser Form im Commodore BASIC nicht gibt. Dort gibt es CHAR sowie TAB(x) zum Setzen und POS(x) zum Abfragen der Spalte.





    Hier mal wieder was mit Sinus-Kurve. Dazu wird diese per BASIC vorberechnet und liegt dann ab $5200 mit 2mal 360 Grad bei 16 ($10) Schritten für 90 Grad (also 64 ($40) für 360°) im RAM. Die Amplitude beträgt 12 (also -12 bis +12) und ist in den positiven Bereich verschoben, durch +12 für alle Werte (darum nun 0 bis 24).


    Man kann daher die Werte aus der Tabelle direkt als Cursorpositionen benutzen - und mit PLOT setzen.


    Das Basicprogramm muß natürlich vorher mit RUN gestertet werden, um die Sinustabelle anzulegen.

    Bei $5000 wird der Tabellenstartplatz $5200 nach $D0/$D1 geschrieben.

    Dabei wird $D0 eine Doppelfunktion haben: es wird nämlich auch noch als Counter mißbraucht und runtergezählt - und darum steht dort nicht $5200, sondern $523F als Startwert ($3F in $D0).

    Der Bildschirm wird gelöscht.

    Eine Schleife mit $3F bis $00 (64) (360°) Durchläufen startet. Y-Register als Schleifenzähler. Dieser wird gleichmal gerettet nach $D8. Dann wird ein Wert aus der Sinustabelle ins X-Register geholt. Dies ist die Y-Position des Cursors. Dann wird ein Wert mit LDA ($D0),Y geladen und mit TAY ins Y-Register gebracht - das ist die X-Position des Cursors. Es wird das Carry-Flag gelöscht, um die PLOT Funktion auf "Cursor setzen" zu stellen; PLOT wird aufgerufen.

    An die nun gesetzte Cursorposition wird mit BSOUT ein Zeichen ausgegeben.

    Das Y-Register wird wieder hergestellt und die Schleife heruntergezählt - bis YR < $00 erreicht.


    Und nun kommt das, was evtl. schwierig sein könnte: $D0 wird heruntergezählt. Solange $D0 >= $00 ist wird wieder vorn begonnen.$D0 zählt also von $3F bis $00 den Verlauf mit und legt damit das Programmende fest (Counter).

    $D0 ist aber auch die Basisadresse für den Tabellenwert, der die X-Position des Cursors festlegt.

    Da in jeder Schleife für den Y-Wert immer der Gleiche geladen wird, ist also der X-Wert der, der in jeder Schleifenrunde einen leicht anderen Wert bekommt, da $D0 jedesmal um eine Position in der Tabelle nach vorn gesetzt wird.



    Wenn's zu unverständlich ist, macht nichts.

    Es geht primär darum zu sehen, daß man den Cursor mit PLOT beliebig auf dem Bildschirm positionieren kann und dort dann mit BSOUT was hinschreibt (auch wenns hier nur ein einzelnes Zeichen ist).

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    Edited 2 times, last by ThoralfAsmussen ().

  • Hier noch ein kleines Beispielprogramm:


    *=$0801 ; Startadresse HEX $801
    lda #$93
    ; Schreibt den Wert 93 in den Akku
    jsr $ffd2
    ; Kernal-Routine Bildschirm. Nimmt sich den Wert $93 aus dem Akku = löscht den Bildschirm.
    start lda #$00
    ; Schreibt den Wert $00 in den Akku. Resettet ihn quasi. Start ist ein Label.
    tax ; Kopiert den Wert $00 auch in das X-Register.
    main jsr $ffe1
    ; Kernal-Routine STOP-Taste. Wird die Stop-Taste gedrückt, wird das Zero-Flag auf 1 gesetzt.
    beq ende
    ; Das Zero-Flag wird abgefragt. Ist das Zero-Flag = 1, dann sprint er zum Label Ende. Andernfalls läuft die Schleife weiter.
    inc $d020 ; Erhöht die Speicherstelle $d020 um 1. Bewirkt in der Endlosschleife einen flackernden Hintergrund.
    lda message,x
    ; Lädt die bytes aus dem Label "message" nacheinander über den Akku in das X-Register.
    sta $05b8,x
    ; Speichert dann den Wert aus dem X-Register über den Akku ab Speicherstelle $05b8. Das ist die linke Spalte, mittlwere Zeile im Bildschirmspeicher.
    inx ; Erhöht den Wert im X-Register um 1.
    cpx #$1b
    ; Vergleicht den Wert im X-Register. $1b ist die länge des Textes hexadezimal inkl. Leerzeichen.
    bne main
    ; Ist der Wert $1b im X-Register noch nicht erreicht, sprint er zu main. Ist er erreicht, geht das Programm weiter.
    jmp start
    ; Sprungbefehl zu "start" für unsere Endlosschleife.
    rts ; Return from subroutine. Programmende.
    ende rts ; Return from subroutine. Das ist unser Sprunglabel von dem Befehl "beq ende" oben.
    message .text"Hallo Kollegen vom "
    ; Das ist der Ausgabetext auf der Bildschirmmitte.
    .text"vzekc !!"



    Wie man an dem Programm erkennen kann, flackert der Bildschirmrahmen und in der Mitte desselben steht geschrieben "Hallo Kollegen vom VZEKC !!". Mit einem Druck auf die RUN/STOP-Taste wird das Programm sauber beendet. Hier nutzen wir die Kernalroutinen für das Löschen vom Bildschirm und das Warten auf die STOP-Taste.


    Das Programm ist im Turboassembler geschrieben.


    Probiert doch mal aus, was passiert, wenn man sämtliche Funktionen in Subroutinen verfasst und jedes Mal Sprünge macht. Wird der Rechner dadurch spürbar langsamer ?


    Und, wie könnte man das Programm abändern, dass man statt dem STA $05b8,x die oben genannte Bildschirmausgabe-Kernal-Routine $ffd2 benutzen kann ?


    Anbei das Programm noch auf einem d64-Image. Man kann es im Basic laden und mit SYS 2049 (=hex $0801) starten...



    Gruss Jan

  • Sehr hübsch. Ein guter Anfang und ein sehr nettes "Hello World".


    Ich glaub nicht, daß man das langsamer werden durch SUBroutinen wirklich merken würde. Messen kann mans.

    Interessant ist, daß Du einen Text lesen kannst. Das bedeutet, daß der Assembler die Message schon als Bytes, wie für den Bildschirmspeicher benötigt, ablegt. Also $01 = "A". Wenn man das jetzt direkt per BSOUT ausgibt, wird man vermutlich gar nichts sehen, da BSOUT ASCII Daten erwartet.


    Warum 2 mal RTS auftaucht, erschließt sich mir nicht. Könnte einfach beides bei Label "ende" sein.


    Den Akku mit #$00 zu laden, könnte man hier auch weglassen. Es ist aber auch nicht verkehrt, weil man damit ja auch ansagt, daß man ihn benutzen wird. Das TAX wäre hier wahrscheinlich besser als LDX #$00 zu schreiben - dann wird es einfacher "lesbar". So wie es jetzt ist, ist es natürlich kürzer. 1 Byte gespart.

    Das ist halt immer schwierig - oft ist es angenehmer der guten Lesbarkeit den Vorzug zu geben.


    Und noch ein Extra: Die Akkuinitialisierung mit #$00 könnte man auch als Hinweis sehen, um noch etwas "Übliches" zu machen. Oft benutzt man für solche Texte eine Endemarkierung. Das kann z.B. $00 sein. Man liest dann einfach solange aus der Textmessage, bis die Endemarkierung erreicht ist. Diese muß dafür aber natürlich noch an den Text hinten angefügt werden; i.P. würde man dann schreiben

    LDA message,x

    BEQ ende

    STA $05b8,x

    d.h. wenn die $00 im Text kommt, ist sofort Schluß. Dafür kann man dann sogar das CPX#$1B "einsparen" und muß nur beim Schreiben der Message aufpassen, daß sie nicht länger als 256-1 Zeichen wird; -1, weil ja das Endezeichen auch mitzählt.

    Vorteil: Man kann dann den Text zwischen 0 und 255 Zeichen beliebig lang schreiben, wenn hinten die $00 dran hängt.


    Übrigens: Es ist gut "echte" Assembler zu benutzen ! Will man haben.

    Aber gerade für den absoluten Anfang, sollte man auch mal "echte" fixe Adresse angefaßt haben. Ob das jetzt auch noch HEXcodes sein müssen, weiß ich nicht.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • =6502= =( Commodore spezifisch )



    Die Routine SCREEN ist sowas eher fragwürdiges: Sie gibt die maximalen Bildschirmpositionen zurück. Dabei landet die Spalte im X-Register und die Zeile im Y-Register. Das wäre ja z.B. am +4 oder am C128 recht sinnvoll, weil diese ja bereits über Textwindows verfügen. Aber: im ROM steht für diese Routine


    LDX #$28

    LDY #$19

    RTS


    und das bekommt man ja evtl. auch selbst noch hin. OK, beim VC20 sind da die Zahlen anders.


    SCREEN : $FFED :

    maximale Textbildschirmgröße nach XR (Spalten), YR (Zeilen) laden

    benutzt XR, YR



    Wichtiger sind dagegen die INPUT Funktionen.


    Es gibt eine generelle - so wie es BSOUT für den OUTPUT gibt - das ist BASIN (bzw. CHRIN (für "CHAR IN")).

    Mit dieser kann man aus Dateien oder auch, wenn noch keine geöffnet sind, direkt vom Keyboard Zeichen einlesen. Dabei wird dann ein Cursor angezeigt. Da alle Zeichen zugelassen sind, ist die Funktion eigentlich nicht so gut für Tastenabfragen ggeignet, es sei denn, man baut gerade an einem Editor.


    .5000

    JSR $FFCF

    CMP #$0D ( Return als Abbruch )

    BNE $5000

    BRK


    BASIN oder CHRIN : $FFCF :

    liest ein Zeichen vom aktuellen Eingabekanal in den Akku, ist somit die nötige Ergänzung zu BSOUT

    bei Keyboardabfrage wird am Bildschirm zusätzlich ein Cursor gezeigt

    benutzt intern Akku, YR



    Speziell fürs Keyboard gibt es die beiden Funktionen SCNKEY und GETIN.

    Dabei liest SCNKEY (steht für ScanKey) den Wert einer gedrückten Taste als ASCII Wert in den Tastaturpuffer.

    Dieser kann einige wenige Zeichen aufnehmen (beim C16 z.B. 10). Wenn der Puffer bereits voll ist, wird das zuletzt geholte Zeichen einfach verworfen.

    Mit GETIN kann man die Zeichen wieder aus dem Tastaturpuffer herausholen, d.h. auslesen; man bekommt dann den ASCII Wert (natürlich immer die Commodore Version von ASCII) in den Akku.

    Eine $00 kommt zurück, wenn der Puffer leer war, was man gut abfragen kann (BEQ oder BNE).

    GETIN kann auch von anderen Eingabekanälen lesen, aber BASIN eignet sich dafür i.a. besser.


    Da SCNKEY innerhalb der Standard-Interruptroutine aufgerufen wird, kann man, solange diese läuft, immer mit GETIN nach Tastatureingaben schauen. Allerdings ist der Wert dann natürlich aus dem Tastaturpuffer entfernt und steht dem Kernal nicht mehr zu Bearbeitung zu Verfügung. Ansonsten muß SCNKEY separat vorher aufgerufen werden.


    SCNKEY : $FF9F :

    fragt das Keyboard ab, gedrückte Taste wird im Tastaturpuffer gespeichert

    benutzt Akku, XR, YR



    GETIN : $FFE4 :

    holt Zeichen vom Eingabekanal als ASCII Wert in den Akku, insbesondere zur Abfrage des Tastaturpuffers

    liefert $00, wenn nichts da ist

    benutzt Akku, XR, YR


    .5000

    JSR $FF9F

    JSR $FFE4

    BEQ $5000

    JSR $FFD2

    JMP$5000


    G5000 startet das direkte Tastenabfragen per SCNKEY/GETIN Kombi


    Wie man sieht ist das erste ganz schön schnell - zu schnell und zu gepuffert. Für Spiele o.ä. ist es darum möglicherweise sinnvoll sich die Stelle rauszusuchen, wo die letzte gedrückte Taste im RAM gespeichert wird ($C6 beim C16/+4). Das kann man dann direkt abfragen. Allerdings steht dort natürlich kein ASCII drin.



    Der Tastaturpuffer ist übrigens was Interessantes. Er wird einfach befüllt mit Daten, maximal bis er voll ist. Das Lesen geschieht dann aber nicht so wie beim Stack in umgekehrter Reihenfolge, sondern so, daß der zuerst in den Puffer geschriebene Wert auch als erster wieder aus dem Puffer gelesen wird.

    Wo der Stack ein LIFO Speicher ist - ist das hier ein FIFO Speicher, ein "First In First Out" Speicher.

  • =6502= =( Commodore spezifisch )


    Eine sehr wichtige Gruppe - und eigentlich neben den Initialisierungsroutinen sicher die wichtigste in der Kernaltabelle - ist die zur Dateibearbeitung.


    Bei Commodore läuft das ja auch im BASIC so ab, daß man ein Gerät öffnet, dabei über die Gerätenummer mitteilt, welches es denn sein soll und mit der Filenummer, die man dabei ebenfalls festgelegt hat, seine Daten an dieses geöffnete Gerät bzw. die Datei schickt. Für Dateien benötigt man noch einen Dateinamen.

    Die Filenummer ist dabei das "Handle" mit dem man alle späteren Operationen durchführt.


    In Assembler ist das weniger komfortabel, aber das Prinzip ist das Gleiche.

    • Man legt mit SETLFS die Parameter Filenummer, Gerätenummer und Sekundäradresse fest.
    • Für Dateien wird mit SETNAM ein Name vergeben, der zusätzlich Parameter enthalten kann.
    • Dann wird der Datenkanal geöffnet.
    • Da BASIN und BSOUT nur auf dem Defaultkanal arbeiten, erklärt man den gerade geöffneten noch dazu. Dann kann byteweise gelesen oder geschrieben werden.
    • Am Ende muß der Kanal wieder geschlossen und der normale Default (Keyboard/Screen) wiederhergestellt werden.


    SETLFS : $FFBA :

    im Akku wird die Filenummer übergeben; im X-Register die Gerätenummer, im YR die Sekundäradresse

    man kann bis zu 10 solche Kanäle öffnen (C16)


    Die Gerätenummern sind dabei

    0 - Keyboard, 3 - Bildschirm

    4 - Drucker

    5 - Drucker2

    1 - Recorder

    2 - RS232

    6 bis 15 andere , z.B. die 8 oder 9 für Floppies



    SETNAM : $FFBD :

    setzt den Dateinamen, dieser liegt als ASCII Text im Speicher, die Adresse wird übergeben als

    X-Register LowByte, Y-Regsiter HighByte; im Akku steht die Textlänge


    Kann man für Drucker etc. natürlich einfach weglassen.



    OPEN : $FFC0 :

    öffnet die mit SETLFS und evtl. SETNAM vorbereitete Datei - wird einfach nur so aufgerufen

    benutzt intern A,XR,YR


    Damit ist die Datei offen und kann benutzt werden. Weitere Befehle verwenden ab jetzt nur noch die Filenummer, was ab jetzt ausreicht, um dem KERNAL zu sagen, welches Gerät, welcher Namen etc.



    Um mit BSOUT bzw. BASIN Bytes zu verteilen, muß noch der Standard-Kanal festgelegt werden.


    CHKOUT : $FFC9 :

    das X-Register übergibt die Filenummer, die nach CHKOUT der Standard-Ausgabekanal ist

    benutzt intern Akku, XR


    CHKIN : $FFC6 :

    das X-Register übergibt die Filenummer, die nach CHKIN der Standard-Eingabekanal ist

    benutzt intern Akku, XR


    und ab da kann dann mit BSOUT/BASIN gearbeitet werden.




    Das Beenden ist einfacher:


    Man schließt den offenen Kanal / die Datei / Filenummer mit


    CLOSE : $FFC3 :

    im Akku (!) wird die Filenummer übergeben

    benutzt intern A,XR,YR


    und setzt die Standardgeräte zurück mit


    CLRCHN : $FFCC :

    keine Parameter - setzt Keyboard und Bildschirm wieder ein;

    benutzt intern Akku,XR


    da es "CLose and Restore" heißt, sollte es die vorherigen Standardsachen auch automatisch schließen.

    Es ist aber generell gut, sich bei Dateiarbeiten anzugewöhnen, die Dateien irgendwann explizit zu schließen. Daher besser mit CLOSE.



    Es gibt auch noch


    CLALL : $FFE7 :

    verwirft alle Filenummern und löscht die Tabelle, wo die zugehörigen Gerätenummern etc. stehen, ruft außerdem CLRCHN auf - schließt somit auch den letzten Standardkanal und setzt die normalen Standards

    benutzt intern Akku, XR


    Das ist so die Version für "ganz Faule". Am Besten ist die aber ganz am Anfang eines Programmes, um irgendwelche alten Parameter "per se" zu löschen. Sie macht "tabula rasa".



    Und hier als Beispiele alles zusammen, das erste zum Speichern der ersten $FF Bildschirmbytes, das zweite zum Wiedereinlesen an die gleiche Stelle.





    Der Dateiname muß jeweils an der Postion $5200 (SAVE) bzw. $5210 (LOAD) stehen. Als ASCII Text und mit den Parametern ",S" für Sequentiell und ",W" für Write. Bei der Laderoutine dann natürlich ",R" wie Read.

    Der Filenamen hat dadurch natürlich auch eine Länge von: Namenslänge + 4.


    Wie man sieht, ändern sich im zweiten Beispiel nur diese Namenszusätze und aus CHKOUT wird CHKIN und natürlich wird statt BSOUT das BASIN für den Input verwendet.

    Die Dateinamenlänge kann man sich vorher auch automatisch auszählen, mit

    LDX#$00 : LDA $5200,X : BNE (aufs LDA) : TXA


    Auf einer Diskette entstehen dadurch natürlich SEQ Files. Ist ganz praktisch für alle Daten, die man so ablegen mag. Allerdings werden "historisch" hier i.a. eher ganze Datenblöcke aus dem Speicher geschrieben bzw. eher noch die Daten direkt im PRG File mit geladen.


    Dafür gibt es die beiden Aufrufe LOAD : $FFD5 : bzw. SAVE : $FFD8 : für die man einfach die Adressbereiche angibt. Beschreibung gibt's in der oben verlinkten englischen Tabelle. Hier mal ohne Beispiel.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    Edited 2 times, last by ThoralfAsmussen ().

  • =6502= =( Commodore spezifisch )


    Nun sind da noch die "Uhren"kommandos.


    SETTIM : $FFDB :

    setzt die Zeit mittels Akku,XR,YR wobei das YR das höchstwertige Byte enthält. Die Uhr läuft dann mit 1/60tel Sekunde weiter.


    RDTIM : $FFDE :

    liest die Zeit aus. Akku,XR,YR kommen zurück - YR enthält das höchstwertige Byte.

    Die Adressen beim C16 sind $A3,$A4,$A5 - wobei $A3 das höchstwertige Byte ist -dort stehen dann die Uhrenwerte.


    UDTIM : $FFEA :

    Dies ist die Routine, die im Interrupt aufgerufen wird, um die Uhr weiterzuschalten. Update Time.

    Braucht man selbst i.a. eher nicht.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • =6502= =( Commodore spezifisch )


    Und dann gibt es noch gerätespezifische Routinen - die also nicht über die Kernaltabelle erreichbar sind.

    Davon sind einige durchaus nützlich. Manche existieren nur, um bestimmte Basic Befehle zu ermöglichen.


    Für den C16/+4 besonders nett sind


    ClearScreen : $D88B

    löscht das aktuelle Window


    Home : $D89A

    bringt den Cursor oben links ins aktuelle Window


    SCNCLR : $C567

    löscht den Bildschirm, auch Grafik


    SetPoint : $C1C3

    setzt einen Punkt in der HighRes Grafik

    dabei steht der Farbwert in $84; die Koordinaten sind beide 16Bit breit X: $02AD/$02AE und Y: $02AF/$02B0


    DrawLine : $C0DA

    Zeichnet eine Strecke in der GRafik. $84 übergibt denFarbwert. Der Anfangspunkt liegt bei $02Ac/$02AD X und $02AE/$02AF Y; der Endpunkt bei $02B0/$02B1 X und $02B2/$02B3 Y.


    INTOUT : $A45F

    Gibt eine Integerzahl am Bildschirm aus. Diese muß in Akku/XR übergeben werden.


    PUTHEX : $FB10

    Gibt eine zweistellige Hexzahl aus. Der Akku muß den Wert enthalten.


    PUTWRD : $FAFF

    Gibt ein Word, eine vierstellige Hexzahl aus. Der Wert muß dazu in Akku/XR vorliegen.



    Es gibt dann noch jede Menge andere Sachen, die hilfreich sein können, etwa Blockschiebebefehle, Informationsausgabe, und v.a. die Rechenbefehle des BASICs für gebrochene Zahlen. Grafikbefehle sind auch nett. Und Sound, etc.

    Diese alle sind aber nicht standardisiert verfügbar, weshalb es evtl. müßig ist, das hier aufzuschreiben. Je nach Gerät muß man das nachschlagen.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries