Freitag, 18. September 2015

Wir sind umgezogen

Wir haben unsere Projektwebsite unter www.steckschwein.de runderneuert und bei dieser Gelegenheit das Blog dorthin umgezogen.
Weiter geht es also unter www.steckschwein.de

Samstag, 12. September 2015

Ein Spiel entsteht...

Im Chrome Browser gibt es einen netten Zeitvertreib in Form des Games "Dinosaur". Das Spiel wird immer dann eingeblendet, wenn keine Internet-Verbindung verfügbar ist.
Das Spiel ist sehr einfach aufgebaut, kann aber leicht süchtig machen und ist ein netter Zeitvertreib bis die Verbindung wieder verfügbar ist.
Genau diese Einfachheit der Grafik und des Gameplays brachte mich auf die Idee das Spiel für das Steckschwein umzusetzen. Wie ich dabei vorgegangen bin, möchte ich Euch hier schildern.

video


Vorbereitung

Zunächst habe ich einige Zeit mit "zocken" verbracht, um das Gameplay genau zu studieren. 

Grafik

Die Grafik habe ich direkt aus dem Spiel genommen. Was heißt das? Nichts besonderes, ich habe Screenshots gemacht, die Grafik vergrößert, den Farbraum reduziert und die Feinheiten mit einem IconMaker-Tool bearbeitet. Wichtig war mir, dass ich die Assets im Bild-Format XPM speichern konnte.
So habe ich nach und nach, den Dino, die Kakteen, den Hintergrund, die Wolken und den Pterodactyl in XPM gegossen. Vielleicht etwas umständlich, da die Sprites auch direkt auf https://chromium.googlesource.com/chromium/src.git/ verfügbar sind. 
Nimmt man die Sprites in Originalgröße wird der Dino auf dem TMS9918/29 riesig sein, denn wir haben ja nut 192 Pixel vertikal zur Verfügung. Ich habe die Grafik daher entsprechend skaliert, damit diese für das Steckschwein eine sinnvolle Größe hat.
Mit dem Icon-Tool ging das wunderbar und ich habe so entsprechende xpm-Dateien für die Grafik erstellt. Diese konnte ich dann leicht mittels ein paar einfacher Shell-Befehle in eine Acme-Assembler Source-Datei konvertieren. Die Grafik steht also zur Verfügung.
Für den Dino werden Sprites verwendet, es kommen dabei 2x2 Sprites mit 16x16px zum Einsatz.
Die vorbeiziehenden Wolken werden ebenfalls mit Sprites realisiert, wobei jede Wolke aus 2 Sprites nebeneinander liegenden Sprites besteht. Die Wolken werden mit 1px/frame bewegt.
Jetzt fehlt noch der Hintergrund, also die Wüste, Berge und natürlich die Hindernisse in Form der Kakteen.

Mit Sprites kann man den Hintergrund auch nicht realisieren, da man mit 4 Sprites keinen ganzen Bildschirm voll bekommt. Es sind ja nur 4 Sprites pro Scanline erlaubt.
Daher kann es nur Cursor-Grafik sein. Damit das ganze aber "smooth" scrollt muss man sich was überlegen.

Soft-Scrolling

Das größte Problem beim TMS9918/TMS9929 ist, dass dieser kein Soft-Scrolling für Cursor-Grafik (mode1/mode2) unterstützt. Damit das ganze also "smooth" scrollt müsste man pro Frame ein Zeichen verschieben. Bei 50Hz (PAL) Steckschwein, sind das 400 Pixel pro Sekunde. Das ist viel zu schnell und nicht spielbar! 
Nach ein paar Tests habe ich mich für 4px/frame entschieden, dass sind 200 Pixel pro Sekunde und damit gut 3/4 des Screens. Das ist nahezu optimal und macht das Game spannend.

Aber wie kann ich jetzt 4 Pixel Soft-Scrolling realisieren?

Für das 4px Soft-Scrolling werden 2 Paletten der Cursor-Grafik erstellt, wobei bei einer Palette die Grafik um exakt 4 Pixel nach links versetzt ist. Nach dem 1. Frame schalte ich einfach die 2. Palette ein. Das passiert über Video-Register 4 des TMS9929.

lda #(.A_GX_PAT_2 / $800) ;VRAM-Adresse schreiben
sta a_vreg

lda #$84               ;Gfx-Register 4 -> $80 für write, 4 für 4. Registersta a_vreg

Nach dem 2. Frame schalte ich wieder auf die 1. Palette und anschließend kopiere ich die Zeichen auf dem Bildschirm um eine Cursor-Position (8px) nach links. So erhält man ein butterweiches 4px/frame Scrolling, womit man arbeiten kann. Will man 3px/frame soft scrollen wird's schon eklig, da man hier 7 Paletten benötigt. Man würde ja eine Palette benötigen die 3px verschoben ist, dann eine mit 6px, dann eine mit 9px was also eine Palette mit 1px Versatz entspricht, denn die 8px werden umkopiert. Dann eine mit 4px - die haben wir ja schon - usw... 2px/frame ist wieder einfach, da braucht man wieder "nur" 4 Paletten ;)

Steuerung

Gespielt wird über Joystick in Port 2, der Code dafür die Abfrage ist einfach.

lda #PORT_SEL_2 ;port 2, Joystick-Port 2 einschalten
sta via1porta
lda via1porta ;Port lesen und entsprechend vergleichen
and #JOY_UP


Mit Joystick nach oben springt der Dino, mit Joystick nach unten duckt sich dieser ab. Der jeweilige Zustand des Dinos wird in einem ZP-Speicherplatz abgelegt und darauf reagiert dann die Animations-Routine.
In dieser wird auf den Status reagiert und die Sprite-Pointers des Dinos entsprechend verändert.

Gameplay

Nach starten des Spiels mit "Feuer" gehts also los, der Dino wird animiert, dabei werden alle paar Frames die Sprite-Pointer des Dinos geändert. Dadurch entsteht der Eindruck wie im Original, das der Dino durch die Wüste rennt. 
Das Scrolling erfolgt wie oben beschrieben, nach jedem 2. Frame sind genau 8 Pixel nach links verschoben. Am rechten Rand entsteht eine Lücke. Hier kommt der Level-Generator zum Einsatz.

Der Level-Generator geht eine konfigurierte Liste von 0 und 1 durch. Eine 0 in der Liste bedeutet, es soll eine Wüste- oder Berg-Gruppe ausgegeben werden. Eine 1 bedeutet, es soll eine Kakteen-Gruppe ausgegeben werden. Die Auswahl ob Wüsten- oder Berg-Gruppe erfolgt per Zufall. Die Ausgabe einer der 4 Kakteen-Gruppen erfolgt ebenfalls per Zufall.
Per Zufall wird lediglich der Offset berechnet, womit dann aus einer Adresstabelle die einzelnen Hintergrund-Gruppen selektiert werden kann. Da jede Hintergrund-Gruppe unterschiedlich lang sein kann, ist das 1. Byte reserviert und gibt die "Skript"-Länge an.

Scoreboard

Das Scoreboard besteht wie im Original aus dem 5-stelligen Highscore und dem aktuellem Score des Spiels.
Der aktuelle Score wird alle 5 Frames erhöht, so dass man nach 1 Sekunde spielen 10 Punkte/Meter zurückgelegt hat. Es werden 3 Byte pro Score verwendet. 

  sed                ;add in decimal mode
  lda .score_value+2
  clc
  adc #$01
  sta .score_value+2
  bcc +
  adc .score_value+1
  sta .score_value+1
  bcc +
  adc .score_value
  sta .score_value
+ cld

Gezählt wird einfach im BCD-Mode des 65c02 unter Berücksichtigung des Übertrags.

Die Ausgabe erfolgt des Score erfolgt über Abbilden des Dezimalwertes jeder Stelle auf numerische Zeichen der ASCII-Tabelle. Da ich beim Scrolling permanent die Palette umschalte liegt der Zeichensatz quasi in jeder Palette vor, hier natürlich ohne Verschiebung der Pixel!



lda .score_value
jsr .digit_out

        ...
.digit_out
and #$0f
ora #'0'
sta a_vram  
rts

Wie gehts weiter?

Nach dem Update meines Chromes, gab es plötzlich auch einen Pterodactylus der mir im Chrome nach einiger Zeit spielen entgegen geflogen kam. Das hab ich vorher noch nicht gehabt, muss also mit dem letzten Update zu tun gehabt haben. Ich bin dran...

Freitag, 24. Juli 2015

Schweinebauchanzeige

Als Abfallprodukt der letzten Debugging-Session bezüglich SD-Karten und dessen, was von ihnen gelesen wird, hat die Shell ein kleines Hexdump-Feature bekommen, mit dem sich Speicherinhalte ähnlich wie beim "M"-Kommando bei diversen Maschinensprachemonitoren ausgeben lassen. 
Die Handhabung ist entsprechend bekannt: "dump <startadresse> <endadresse>", abgebrochen werden kann mit Ctrl-C. Als Abfallprodukt hiervon gilt das jetzt auch für Directory-Listings.

Damit hat die SteckShell nunmehr die Versionsnummer 0.10.

Mehr Karten (UPDATE)

Unser "Standard"-Massenspeicher SD-Karte funktioniert zwar an und für sehr gut, Sorgenkind war aber immer die Initialisierungs-Routine. Bisher ließen sich damit nur günstige Class4-Karten initialisieren, bei "höherwertigen" Karten schlug die Initialisierung immer fehl, sodass nur etwa 3 von 5 Karten nutzbar waren.
Initialisierungs-Ablauf nach elm-chan.org

Das hat uns schon etwas gewurmt, denn irgendwie hatte dieser Stand ein Geschmäckle von "Funktioniert aus Versehen". Also mussten wir da nochmal ran. Der Initialisierungs-Flow entspricht im Wesentlichen dem, was auf der bekannten Seite http://elm-chan.org/docs/mmc/mmc_e.html dokumentiert ist. In den letzten Tagen haben wir diesen unter die Lupe genommen, und tatsächlich ist etwas aufgefallen. Vor dem Senden eines Kommandos muss sichergestellt werden, dass die Karte bereit ist. Hierzu sendet man solange $ff an die Karte, bis diese auch $ff zurücksendet. Dann ist die Karte bereit, ein Kommando zu empfangen. In unserer Initialisierungsroutine wurde dies zwischen CMD55 und ACMD41 (näheres bitte dem Link entnehmen) schlichtweg nicht gemacht. Plötzlich lassen sich fast alle vorhandenen Karten initialisieren. Dass dies mit den Class4-Karten trotzdem funktionierte, war also gewissermaßen tatsächlich aus Versehen.

Das nächste Problem war dann, dass von den jetzt nutzbaren Karten über die Shell zwar im Filesystem navigiert und Verzeichnisse aufgelistet werden konnten, das eigentliche Laden von Dateien bzw. Starten von Programmen funktionierte nicht. Die Blockleseroutinen liefen korrekt durch, die geladenen Daten jedoch waren fehlerhaft.
Dass die Dateisystemoperationen funktioniert haben, und nur das Einlesen von Dateien Probleme machte, war verdächtig. Noch im Mai verkündeten wir u.a. stolz, dass wir zum Dateien lesen schnelle SD-Multiblock-Transfers verwenden. Diese haben den Vorteil, dass man der Karte nur die Adresse des ersten zu lesenden Blocks übermittelt, und dann einfach liest bis der Arzt kommt oder man der Karte sagt, dass man fertig ist. Vorteil ist, dass man die Blockadresse des nächsten Blocks nicht immer selber ausrechnen muss (immerhin eine 32bit-Addition) und auch nicht immer wieder an die Karte schicken muss.

Gut, also die fat_read-Routine zurückgebaut, und schon ist das Steckschwein mit so ziemlich jeder handelsüblichen SD-Karte kompatibel.

[UPDATE:] Mittlerweile funktionieren auch wieder Multiblock-Transfers. Der Trick war, beim Lesen von der Karte die MOSI-Leitung nicht auf L, sondern auf H zu setzen, also nicht mehr $00 zu senden, sondern $ff. Die Idee kam, nachdem sämtliche Beispiele, die sich im Web finden lassen, $ff senden. Die Stelle in der SD-Karten-Spezifikation, in der das so spezifiziert ist, ist bis dato nicht gefunden worden.

Freitag, 26. Juni 2015

WDC und kein Ende

In der letzten Zeit war es hier etwas still ums Steckschwein, was aber nicht als Indiz für Untätigkeit gelten soll.
Hauptsächlich haben wir uns auf das Schreiben von Code konzentriert, die Shell wurde weiterentwickelt, etc. Darüberhinaus gab es erste Experimente mit CPLDs. Auf dieser Basis sollen ja zukünftige Verbesserungen der Hardware entstehen, begonnen bei einem eigenständigen SPI-Controller bis hin zur Zusammenfassung der bestehenden Glue-Logik rund um die Adressdekodierung.
Da ich mir zu diesem Zweck testhalber solche CPLD-Entwicklungsplatinchen auf Basis des XilinX XC9572XL habe kommen lassen, stellte sich also als erstes die Frage, wie sich dessen 3.3V-basierte Logik mit dem 5V-Steckschwein vertragen würde. Zum CPLD hin wären ja keine Probleme zu erwarten, denn die IO-Pins des XC9572XL sind 5V-tolerant. Die Richtung vom CPLD zum Steckschwein bedarf also besonderer Betrachtung, denn es muss sichergestellt werden, dass alle Bausteine am Bus, die mit dem CPLD verbunden sind, dessen 3.3V-Logikpegel zuverlässig erkennen.
Als einzige wirklich problematische Komponente stellte sich hier - wieder mal - der auf meinem Steckschwein eingesetzte (Marko nutzt einen 65c02 von Rockwell) WDC 65c02 heraus. Das Datenblatt gibt als "Input High Voltage", also die Spannung, ab der auf der entsprechenden Leitung (BE, D0 -D7, RDY, /SO, /IRQ, /NMI, PHI2, /RES) eine logische 1 erkannt wird, mit "VDD*0.7" an. Bei einer Betriebsspannung von 5V also 3,5V. Mit 3.3V-Pegeln also schonmal nicht kompatibel. Geschweige denn mit TTL-Pegeln. Die leider so ziemlich alle auf dem Datenbus liegenden Bausteine verwenden, mit Ausnahme der WDC 65c22 VIA. 
Alle anderen Bausteine geben im Datenblatt als "High Level Output Voltage" Werte von 2.4-2.7V an. 
Kann also gar nicht passen. Dass das Steckschwein trotzdem mit dem WDC funktioniert ist ganz offenbar Glück bzw. der Tatsache geschuldet, dass der Chip dann doch toleranter ist als das Datenblatt uns glauben machen will. 
Trotzdem nicht sauber. In zukünftigen Revisionen müssen wir also zwischen CPU und Datenbus einen 74HCT245-Buffer eindesignen, der durch TTL-kompatible Eingänge und CMOS-Ausgänge die Pegelunterschiede ausbügelt. Gleiches gilt auch für weitere Experimente mit dem 3.3V-CPLD. Oder auch mit dessen 5V-Vorgänger XC9572. 

Zusammenfassend also noch einmal die Besonderheiten des 65c02 von WDC:



  1. Unterschiede im Pinout
    Pin1 beim WDC ist nicht mehr GND, sondern der Ausgang /VP (Vector Pull), der low wird, wenn die CPU an einen Vektor springt (IRQ, NMI, RESET)
    Pin 36 ist nur bei WDC /BE, sonst N.C. Dieser muss auf High liegen, sonst ist die CPU vom Bus abgekoppelt.
    Statt Takt an PHI0 anzulegen und den Rest des Systems mit PHI2 zu takten, wird bei WDC vorgeschrieben, CPU und restliches System mit dem an PHI0 angeschlossenen Oszillator zu takten
  2. Strafferes Timing
    Die wesentlich schnelleren WDC-Chips haben wesentlich kürzere Setup/Hold-Zeiten (10ns statt 30ns bei Rockwell)
  3. Nicht TTL-kompatibel
    Der WDC 65c02 erwartet wesentlich höhere Signalpegel, die entschieden über den TTL-Pegeln liegen. Dies hat auch MrVossi bei der Entwicklung seines LC64 schon festgestellt. Bei ihm hat es sich allerdings deutlicher geäußert.
Die aktuelle Steckschwein-Revision ist somit trotz aller Bemühungen (Jumper für Takteingang, /BE, /VP) immer noch nicht mit dem WDC 65c02 kompatibel. 

Im Beitrag über das erste WDC-Abenteuer hatte ich abschließend die Frage gestellt, wie man dann einen 65(c)02 in einem vorhandenen alten System mit einem WDC 65c02 ersetzen soll. Die wäre damit dann zumindest beantwortet: überhaupt nicht!


Mittwoch, 20. Mai 2015

Filesystem und Shell

Vor kurzem haben wir ja schon von ersten Gehversuchen einer FAT32-Implementation berichtet, mit der wir in der Lage waren, beim Systemstart eine Datei von SD-Karte zu laden.

Was fehlt, ist eine Möglichkeit, innerhalb eines Filesystems einer SD-Karte zu navigieren, Programme zu laden oder Dateien anzuzeigen. Um diese Lücke zu füllen, ist die SteckShell entstanden. In der aktuellen Version 0.6 unterstützt die Shell folgende Funktionen:

  • Directory auflisten
  • Directory wechseln
  • Programm laden und starten
  • Datei anzeigen
  • Grafik (TMS9929-Rohdaten) anzeigen
Wer auf dem VCFe 16.0 anwesend war konnte diese Shell auch in Aktion erleben.

Dank Marko unterstützt die Shell inzwischen sogar den 40-Zeichen-Modus des TMS99xx!
Die SteckShell in 40 Zeichen. Hier noch mit einem kleinen Positionierungsfehler
Die Shell liegt nicht im ROM, sondern wird entweder seriell aufs Steckschwein geladen oder mit dem beschriebenen boot-Mechanismus von SD-Karte gestartet.
Mit ihr fühlt sich das Steckschwein schon wie ein "richtiger" Computer an, denn jetzt ist eine interaktive Bedienung möglich.
Die SteckShell dient uns sozusagen als Betriebsystem-Keimzelle, in der u.a. die FAT-Routinen reifen. Hier hat sich seit unserem rudimentären ROM-Bootloader auch einiges getan.
Die ersten Versuche mit FAT, die auch in den allerersten Versionen der Shell noch Verwendung fanden, bestanden darin, über das Verzeichnis zu iterieren, eine Datei nach Namen oder Attribut zu finden und dann mit ihr etwas zu machen.
Dieser Ansatz funktioniert jetzt immer noch als ROM-Bootloader, aber für die Shell sind die Anforderungen etwas andere. Man möchte eine Datei vielleicht auch nur erst in den Speicher laden, um dann zu entscheiden, was als nächstes passieren soll. Man möchte evtl. mehrere Dateien geöffnet haben, oder zumindest das aktuelle Verzeichnis und eine Datei geöffnet haben können.
Ehe man sich versieht, befindet man sich inmitten der gleichen Überlegung, die vor einigen Jahrzehnten schon mal jemand angestellt hat, und sich dann das klassische und bekannte Interface bestehend aus open(), close(), read() usw. ausgedacht hat.
Also wurde das Ganze in Subroutinen fat_mount, fat_open, etc. aufgedröselt.
fat_open und fat_close verwalten eine Filedeskriptortabelle. 
fat_open bekommt einen Dateinamen als Argument und sucht diesen im aktuellen Verzeichnis. Der Startcluster und die Größe der gefundenen Datei wird in die Deskriptortabelle geschrieben. Die Adresse dieses Eintrags ist der File-Handle.
Mit diesem kann nun fat_read die geöffnete Datei in den Speicher einlesen. Dies geschieht bei der Gelegenheit nun per SD-Multiblock-Transfer, sodass ein ganzer Cluster in einem Stück ohne Overhead durch Zwischenberechnungen der Sektornummern eingelesen werden kann.
Das gesamte "Interface" hantiert nur mit Clusteradressen, die Berechnung der LBA-Adressen passiert intern.
Damit haben wir eine für unsere Zwecke erst einmal ausreichende Filesystem-Implementation. 
Aktuell können wir noch keine eigentlichen "FAT"-Lookups, d.h. wir können nur mit Dateien und Verzeichnissen umgehen, die in einen Cluster passen, was jedoch bei einer Clustergröße von 32k bei einer 4GB-Karte auf einem 8-bit-System keine große Einschränkung darstellt. Spätestens wenn wir so etwas wie seek() und damit sequentiellen Zugriff auf Dateien implementieren werden wir uns auch daran setzen müssen. 

Die Shell verfügt außerdem über die Fähigkeit, die Textkonsole zu scrollen. In diesem Fall allerdings nicht über die Möglichkeiten des VDP, sondern das Textscrolling wird im regulären Arbeitsspeicher durchgeführt und der "Bildschirminhalt" während des VDP-Blank ins RAM des TMS9929 geschrieben. Dies würde sogar ermöglichen, verschiedene umschaltbare Textkonsolen a la Linux (Alt-F1..n) zu realisieren. Aber über diesen Mechanismus darf sich Marko gerne auslassen.

Sonntag, 3. Mai 2015

VCFe 16.0 - schon wieder vorbei

Das VCFe 16.0 ist schon wieder vorbei.
Vielen Dank an alle, die vorbeigekommen sind und/oder unsere Vorträge verfolgt haben für ihr Interesse an unserem kleinen Projekt.
Besonderer Dank gilt Marco Baye für spontane Patches für den ACME-Crossassembler und Udo Möller für weitere Lektionen im Datenblätter lesen.