
Dieser Beitrag ist eine Einführung in mein Projekt "hausgemachte" Konsolen-Videokonsolen, die von Grund auf neu erstellt wurden. Ich war sowohl von Retro-Konsolen als auch von modernen Mustern inspiriert, bekam aber meine eigene Architektur. Meine Freunde sagten mir ständig, ich solle über mein Projekt sprechen und nicht alles ausschließlich "für mich" tun, also veröffentliche ich hier diesen Beitrag.
Achtung, dies ist eine Übersetzung
Wie alles begann
Mein Name ist Sergio Vieira, ich bin in den 80ern und 90ern in Portugal aufgewachsen und habe eine lange Nostalgie für Retro-Spiele, insbesondere für Konsolen der dritten und vierten Generation.
Vor einigen Jahren habe ich beschlossen, die Elektronik besser zu verstehen und mein eigenes Präfix zu erstellen.
Von Beruf bin ich Programmierer und hatte keine Erfahrung als Elektronikingenieur, außer (und sollte nicht in Betracht gezogen werden) unabhängigen Upgrades meines Destkop.
Obwohl ich keine Erfahrung hatte, sagte ich mir "Warum nicht?", Kaufte mehrere Bücher, mehrere elektronische Kits und begann zu lernen, basierend auf meinen Gefühlen darüber, was es wert war, studiert zu werden.
Ich wollte ein Präfix machen, das denen ähnelt, die mich nostalgisch machen. Ich wollte etwas zwischen NES und Super Nintendo oder vielleicht zwischen dem Sega Master System und Mega Drive .
Diese Konsolen hatten eine CPU, einen Original-Videochip (damals wurden sie nicht als GPU bezeichnet) und einen Audio-Chip, manchmal eingebaut und manchmal extern.
Die Spiele wurden auf Kassetten verteilt, bei denen es sich im Allgemeinen um Eisenerweiterungen handelte, manchmal nur um ROM-Chips, und manchmal um zusätzliche Komponenten.
Der ursprüngliche Plan bestand darin, ein Präfix mit den folgenden Merkmalen zu erstellen:
- Ohne Emulation sollten Spiele und Programme auf echter Hardware funktionieren, nicht unbedingt auf der gleichen Zeit, aber schnell genug für die Aufgabe und nicht mehr.
- Mit einer echten Retro-CPU.
- Mit analogem TV-Ausgang.
- Mit Ton
- Mit Dual-Controller-Unterstützung
- Scrolling Becks und Animations-Sprites
- Mit Funktionen zur Unterstützung von Plattformspielen wie Mario und natürlich allen möglichen anderen Spielen.
- Mit dem Herunterladen von Spielen und Programmen von SD-Karten.
Warum SD-Karten und keine Kassetten? Im Grunde ist es so viel praktischer, dass Sie sie von Ihrem Computer kopieren können. Und Patronen würden zum einen mehr Eisen in der Set-Top-Box bedeuten und zum anderen Eisen für jedes Programm produzieren.
Produktion
Videosignal
Als erstes habe ich ein Videosignal erzeugt.
Jede Konsole aus der Zeit, die ich als Beispiel genommen habe, hatte verschiedene proprietäre Grafikchips, was bedeutet, dass sie alle unterschiedliche Spezifikationen hatten.
Aus diesem Grund wollte ich keinen vorgefertigten Grafikchip verwenden, sondern wollte, dass meine Konsole eindeutige Grafikspezifikationen aufweist. Und da ich keinen eigenen Grafikchip herstellen konnte und zu diesem Zeitpunkt noch kein FPGA verwenden konnte, beschloss ich, mich auf die durch Software generierte Erzeugung eines Grafiksignals mit einem 8-Bit-Mikrocontroller mit 20 Megahertz zu beschränken.
Dies ist nicht zu viel und nur eine ausreichend leistungsfähige Lösung für Grafiken des Niveaus, an dem ich interessiert war.
Und so begann ich, den Atmega644-Mikrocontroller mit einer Reinheit von 20 MHz zu verwenden, um ein PAL -Videosignal für den Fernseher zu erzeugen. Ich musste das PAL-Protokoll etwas übertreffen , weil der Chip selbst nicht weiß, wie es geht.


Der Mikrocontroller erzeugt eine 8-Bit-Farbe (RGB332, 3 Bit Rot, 3 Bit Grün und 2 Bit Blau) und der passive DAC konvertiert alles in RGB. Glücklicherweise sind in Portugal fast alle Fernseher mit einem SCART-Anschluss ausgestattet und unterstützen den RGB-Eingang.
Das richtige Grafik-Subsystem
Da der Mikrocontroller ziemlich leistungsfähig ist und ich mich entschied, ihn ausschließlich zur Erzeugung eines Videosignals zu verwenden (ich nannte ihn VPU - Video Processing Unit), entschied ich mich, gleichzeitig einen Doppelpuffer zu organisieren.
Es stellte sich heraus, dass der zweite Mikrocontroller (PPU, Bildverarbeitungseinheit, Atmega1284-Chip ebenfalls bei 20 MHz) ein Bild im RAM-Chip 1 (ich nannte es VRAM1) erzeugte und der erste gleichzeitig den Inhalt des zweiten Chips (VRAM2) an den Fernseher sendete.
Nach einem Bild und zwei Bildern im PAL-System von 1/25 Sekunde schaltet die VPU die VRAMs um und sie werden ausgetauscht, die PPU erzeugt ein Bild in VRAM2 und die VPU speichert VRAM1 auf dem TV-Ausgang.
Die Grafikkarte erwies sich als sehr kompliziert, da ich externe Hardware verwenden musste, damit beide Mikrocontroller beide Speichermodule verwenden konnten, und um den Zugriff auf den RAM zu beschleunigen, da sie auch Bit-Banging aufweist. Daher musste ich Chips der Serie 74 als Zähler, Leitungsselektoren, Transceiver usw. hinzufügen. .
Die Firmware für VPU und PPU erwies sich ebenfalls als umständlich, da ich viel Code schreiben musste, um die maximale Geschwindigkeit aus den Grafiken herauszuholen. Zuerst wurde alles in Assembler geschrieben, dann wurde ein Teil in C umgeschrieben.


Infolgedessen erzeugt die PPU ein Bild mit 224 x 192 Pixeln, das dann über die VPU an den Fernseher gesendet wird. Möglicherweise ist die Auflösung niedrig, aber tatsächlich ist sie fast so hoch wie die Konsolen dieser Zeit, normalerweise 256 x 224. Eine etwas niedrigere Auflösung, aber ich konnte weitere Funktionen hinzufügen, die das System in einem Frame berechnen kann.
Wie in früheren Zeiten verfügt die PPU über eine eigene starre Mechanik, die Sie verwenden können müssen. Der Hintergrund (Backing) wird aus 8x8-Pixel-Zeichen gerendert, die auch als Kacheln bezeichnet werden. Es stellt sich heraus, dass die Größe des Hintergrunds 28x24 Kacheln beträgt.
Damit der Hintergrund Pixel für Pixel reibungslos gescrollt werden kann, habe ich 4 virtuelle Bildschirme mit jeweils 28 x 24 Kacheln erstellt, die nacheinander im Speicher abgelegt und umeinander gewickelt werden. Auf dem Bild ist dies klarer.


Über dem Hintergrund kann die PPU 64 Sprites rendern, die 8 oder 16 Pixel hoch oder breit sein können, dh 1, 2 oder 4 Kacheln, und die auch horizontal und / oder vertikal gespiegelt werden können.
Oben auf der Rückseite können Sie auch eine Überlagerung mit einem Puffer von 28 x 6 Kacheln rendern. Dies war zum Rendern von HUDs und Scores gedacht, um die Haupt-Sprites und das Scrollen der Rückseite nicht zu beeinträchtigen.
Eine „erweiterte“ Funktion ist, dass der Hintergrund nicht vollständig, sondern jede Zeile einzeln gescrollt werden kann, was alle möglichen interessanten Effekte wie geteilte Bildschirme oder nahezu Parallaxe ermöglicht .
Es gibt auch eine Attributtabelle, in der Sie jede Kachel auf einen Wert von 0 bis 3 setzen können. Anschließend können Sie eine Kachelseite für alle Kacheln mit einem Attribut angeben oder ihren symbolischen Wert erhöhen. Dies ist praktisch, wenn Teile der Sicherung regelmäßig geändert werden müssen und die CPU nicht jede Kachel einzeln berechnen muss. Es reicht aus, nur Folgendes zu sagen: "Alle Kacheln mit Attribut 1 erhöhen den numerischen Wert Ihres Zeichens um 2". Ähnliche Dinge können mit verschiedenen Techniken implementiert werden Beobachten Sie zum Beispiel in Blockplättchen in Mario, in denen das Fragezeichen animiert ist, oder in Spielen, in denen es einen Wasserfall gibt, in dem sich alle Plättchen ständig ändern, wodurch der Effekt von fallendem Wasser entsteht.
CPU
Als meine Grafikkarte funktionierte, begann ich mit der CPU als Zilog 80 für meine Set-Top-Box zu arbeiten.
Einer der Gründe, warum der Z80 ausgewählt wurde, ist neben der Tatsache, dass es sich um eine coole Retro-CPU handelt, seine Fähigkeit, zwei 16-Bit-Speicherplätze zu adressieren, einen für den Speicher und einen für E / A-Ports, nicht weniger als den legendären 6502 Es kann nur 16-Bit-Speicherplatz adressieren und muss dem Speicher sowie verschiedenen externen Geräten, Video, Audio, Joysticks, Hardware-Zufallszahlengenerator usw. zugeordnet werden. Es ist bequemer, zwei Adressräume zu haben, von denen einer bis zu 64 Kilobyte Code und Daten vollständig speichert und der zweite für den Zugriff auf externe Geräte.
Zuerst habe ich die CPU mit dem EEPROM verbunden, in dem sich mein Testprogramm befand, und sie über den E / A-Bereich mit dem von mir installierten Mikrocontroller verbunden, damit ich über RS232 mit meinem Computer kommunizieren und überwachen konnte, wie die CPU und alles andere funktionierten. Ich nenne diesen Atmega324-Mikrocontroller, der mit 20 MHz arbeitet, die E / A-MCU - eine Eingangs- / Ausgangs-Mikrocontrollereinheit, die für die Steuerung des Zugriffs auf Gamecontroller (Joysticks), einen SD-Kartenleser, eine PS / 2- Tastatur und einen Kommunikator über RS232 verantwortlich ist.

Die CPU ist mit einem 128-Kilobyte-Speicherchip verbunden, von dem nur 56 Kilobyte verfügbar sind. Das ist natürlich Unsinn, aber ich könnte nur 128 oder 32 Kilobyte-Chips bekommen. Es stellte sich heraus, dass der Speicher aus 8 Kilobyte ROM und 56 Kilobyte RAM besteht.
Danach habe ich die IO-MCU-Firmware mithilfe dieser Bibliothek aktualisiert und Unterstützung für SD-Kartenleser erhalten.
Jetzt konnte die CPU die Verzeichnisse durchsuchen, sehen, was darin enthalten ist, Dateien öffnen und lesen. All dies erfolgt durch Schreiben und Lesen an bestimmte Adressen im E / A-Bereich.
CPU an PPU anschließen
Das nächste, was ich getan habe, ist die Verbindung zwischen der CPU und der PPU. Zu diesem Zweck habe ich eine "einfache Lösung" angewendet, die darin bestand, Dual-Port-RAM zu kaufen. Dies ist ein solcher RAM-Chip, der direkt an zwei verschiedene Busse angeschlossen werden kann. Dies ermöglicht es ihm, zusätzliche Chips wie Leitungsselektoren loszuwerden und darüber hinaus von beiden Chips aus fast gleichzeitig auf den Speicher zuzugreifen. Eine andere PPU kann in jedem Frame direkt auf die CPU zugreifen, indem sie ihre nicht maskierbaren Interrupts aktiviert. Es stellt sich heraus, dass die CPU in jedem Frame einen Interrupt empfängt, der für verschiedene Timing-Aufgaben und zum Verständnis, wann es Zeit ist, ein Grafik-Update durchzuführen, nützlich ist.
Jeder Interaktionsrahmen der CPU, PPU und VPU erfolgt nach folgendem Schema:
- Die PPU kopiert Informationen aus dem PPU-Speicher in den internen Speicher.
- Die PPU sendet ein Interrupt-Signal an die CPU.
- Zur gleichen Zeit:
- Die CPU springt zur Interrupt-Funktion und aktualisiert den PPU-Speicher mit einem neuen Grafikstatus. Das Programm muss vom Interrupt bis zum nächsten Block zurückkehren.
- Die PPU rendert ein Bild basierend auf Informationen, die zuvor in einen VRAM kopiert wurden.
- VPU sendet ein Bild von einem anderen VRAM an den TV-Ausgang.
Etwa zur gleichen Zeit begann ich, Gamecontroller zu unterstützen. Zuerst wollte ich Nintendo-Controller verwenden, aber ihre Sockel sind proprietär und im Allgemeinen schwer zu finden. Deshalb habe ich mich für 6-Tasten-Controller entschieden, die mit Mega Drive / Genesis kompatibel sind. Sie haben Standard-DB-9-Sockel die sind überall.

Das erste richtige Spiel schreiben
Zu diesem Zeitpunkt hatte ich bereits eine CPU, die in der Lage war, die PPU zu steuern, mit Joysticks zu arbeiten, SD-Karten zu lesen ... es war Zeit , das erste Spiel zu schreiben , natürlich in Z80 Assembler, es dauerte mehrere Tage aus der Freizeit.
Fügen Sie dynamische Grafiken hinzu
Alles war super, ich hatte meine eigene Spielekonsole, aber das war nicht genug für mich, weil ich im Spiel Grafiken verwenden musste, die in den PPU-Speicher gestickt waren, und es unmöglich war, Kacheln für ein bestimmtes Spiel zu zeichnen, und es konnte nur durch erneutes Flashen des ROM geändert werden. Ich begann darüber nachzudenken, wie man mehr Speicher hinzufügt, damit die CPU Zeichen für die Kacheln darin laden kann, und dann könnte die PPU alles von dort lesen und wie man es einfacher macht, weil sich das Präfix bereits als kompliziert und groß herausstellte.
Und ich habe mir Folgendes ausgedacht: Nur die PPU hat Zugriff auf diesen neuen Speicher, und die CPU lädt dort Daten über die PPU. Während dieses Ladevorgangs kann dieser Speicher nicht zum Zeichnen verwendet werden, es kann jedoch zu diesem Zeitpunkt aus dem ROM gezogen werden.
Nach dem Ende des Ladevorgangs schaltet die CPU den internen ROM-Speicher auf diesen neuen Speicher um, den ich als Zeichen-RAM (CHR-RAM) bezeichnet habe. In diesem Modus beginnt die PPU mit dem Zeichnen dynamischer Grafiken. Dies ist wahrscheinlich nicht die beste Lösung, funktioniert jedoch. Infolgedessen wurde ein neuer Speicher mit 128 Kilobyte installiert, der 1024 Zeichen mit jeweils 8 x 8 Pixel für den Hintergrund und die gleiche Anzahl von Zeichen für Sprites speichern kann.

Und zum Schluss der Sound
Die Hände erreichten zuletzt den Ton. Zuerst wollte ich einen Sound wie in Uzebox , das heißt, der Mikrocontroller erzeugt 4 Kanäle PWM-Sound.
Es stellte sich jedoch heraus, dass ich die Vintage-Chips leicht bekommen kann und ich bestellte mehrere FM-Synthese- Chips YM3438, diese Typen sind voll kompatibel mit dem in Mega Drive / Genesis verwendeten YM2612 . Durch die Installation erhalten Sie hochwertige Musik, Mega Drive und vom Mikrocontroller erzeugte Soundeffekte.
Ich habe einen anderen Mikrocontroller installiert und ihn SPU (Sound Processor Unit) genannt. Er steuert den YM3438 und kann selbst Sounds erzeugen. Die CPU steuert es über einen Dual-Port-Speicher, diesmal sind es nur 2 Kilobyte.
Wie bei der Grafikeinheit verfügt die Soundeinheit über 128 Kilobyte Speicher zum Speichern von PCM-Samples und Soundpatches. Die CPU lädt Daten in diesen Speicher, indem sie auf die SPU zugreift. Es stellte sich heraus, dass die CPU die SPU entweder anweist, Befehle aus diesem Speicher auszuführen, oder die Befehle für die SPU in jedem Frame aktualisiert.
Die CPU steuert vier PWM-Kanäle über vier kreisförmige Puffer im SPU-Speicher. Die SPU durchläuft diese Puffer und führt die in sie geschriebenen Befehle aus. Es gibt auch einen solchen Puffer für den FM-Synthesechip.
Insgesamt verläuft die Interaktion zwischen CPU und SPU wie in der Grafik nach dem Schema:
- Die SPU kopiert Daten von der SPU in den internen Speicher.
- Die SPU wartet auf einen Interrupt von der PPU (dies dient zur Synchronisation).
- Zur gleichen Zeit
- Die CPU aktualisiert die PWM-Kanalpuffer und die FM-Synthesizer-Puffer.
- Die SPU führt Befehle in Puffern gemäß den Daten im internen Speicher aus.
- Zusammen mit all dem aktualisiert die SPU PWM-Sounds mit einer Frequenz von 16 Kilohertz.

Was am Ende herauskam
Nachdem alle Blöcke fertig waren, gingen einige zum Steckbrett.
Für den CPU-Block konnte ich eine benutzerdefinierte Leiterplatte entwickeln und bestellen. Ich weiß nicht, ob es sich für die anderen Module lohnt. Ich hatte wirklich Glück, dass meine Leiterplatte sofort funktioniert hat.
Auf dem Steckbrett ist jetzt (bisher) nur Ton zu hören.
So sieht es heute aus:

Architektur
Das Diagramm zeigt die Komponenten in jedem Block und wie sie miteinander interagieren. Das einzige, was nicht angezeigt wird, ist das Signal von der PPU an die CPU in jedem Frame als Interrupt und das gleiche Signal, das an die SPU geht.

- CPU: Zilog Z80 bei 10 MHz
- CPU-ROM: 8 KB EEPROM, enthält Bootloader-Code
- CPU-RAM: 128 KB RAM (56 KB verfügbar), Code und Daten für Programme / Spiele
- IO-MCU: Atmega324 ist die Schnittstelle zwischen CPU und RS232, der PS / 2-Tastatur, den Joysticks und dem SD-Kartendateisystem
- PPU-RAM: 4 Kilobyte Dual-Port-Speicher, Zwischenspeicher zwischen CPU und PPU
- CHR-RAM: 128 KB RAM, speichert dynamische Kacheln für Backing (Substrat) und Sprites (in Zeichen von 8 x 8 Pixel).
- VRAM1, VRAM2: 128 KB RAM (43008 ist wirklich verfügbar), sie werden für den Framebuffer verwendet, sie schreiben PPU und lesen VPU von ihnen.
- PPU (Picture Processing Unit): Atmega1284 zeichnet einen Frame in den Framebuffer.
- VPU (Video Processing Unit): Atmega324, liest den Framebuffer und generiert RGB- und PAL-Signal und Synchronisation.
- SPU-RAM: 2 KB Dual-Port-RAM, dient als Schnittstelle zwischen CPU und SPU.
- SNDRAM: 128 KB RAM, speichert PWM-Patches, PCM-Samples und Befehlsblöcke für den FM-Synthesizer.
- YM3438: YM3438, FM-Synthesechip.
- SPU (Sound Processing Unit): Atmega644 erzeugt Töne nach dem Prinzip der Pulsweitenmodulation (PWM) und steuert den YM3438.
Endgültige Spezifikationen
CPU:
- 8-Bit-CPU Zilog Z80 mit einer Frequenz von 10 MHz.
- 8 KB ROM für den Bootloader.
- 56 KB RAM.
IO:
- Lesen von Daten vom SD-Kartenleser FAT16 / FAT32.
- Lesen / Schreiben auf den RS232-Port.
- 2 MegaDrive / Genesis-kompatible Gamecontroller.
- Tastatur PS2.
Video:
- Auflösung 224x192 Pixel.
- 25 Bilder pro Sekunde (halbe FPS von PAL).
- 256 Farben (RGB332).
- 2x2 virtueller Hintergrund (448x384 Pixel) mit bidirektionalem pixelbasiertem Scrollen, basierend auf vier Vollbildseiten.
- 64 Sprites mit einer Breite und Höhe von 8 oder 16 Pixel mit der Möglichkeit des vertikalen und horizontalen Flip.
- Der Hintergrund und die Sprites bestehen aus Zeichen mit jeweils 8 x 8 Pixel.
- Symbolischer Videospeicher mit 1024 Zeichen für den Hintergrund und 1024 für Sprites.
- 64 unabhängiges horizontales Scrollen entlang festgelegter Linien
- 8 unabhängiges vertikales Scrollen entlang festgelegter Linien
- 224x48 Pixel Overlay mit optionaler Farbschlüsseltransparenz.
- Hintergrundattributtabelle.
- RGB und Composite PAL über SCART-Anschluss.
Ton:
- PWM für 8 Bit und 4 Kanäle mit integrierten Wellenformen: Quadrat, Sinus, Säge, Rauschen usw.
- 8-Bit-8-kHz-Samples in einem der PWM-Kanäle.
- FM-Synthesechip YM3438 mit Anweisungen mit einer Frequenz von 50 Hertz geladen.
Entwicklung für die Konsole
Für die Konsole wurde ein Bootloader geschrieben. Der Bootloader befindet sich in der ROM-CPU und kann bis zu 8 Kilobyte aufnehmen. Es verwendet die ersten 256 Bytes RAM. Der Loader ist das erste, was die CPU ausführt. Es wird benötigt, um die Programme auf der SD-Karte anzuzeigen.
Diese Programme befinden sich in Dateien, die kompilierten Code enthalten und möglicherweise auch Grafiken und Sound enthalten.
Nach Auswahl eines Programms wird es in den CPU-Speicher, den CHR-Speicher und den SPU-Speicher geladen. Danach wird der Programmcode ausgeführt. Die maximale Größe des in die Konsole geladenen Codes beträgt zusätzlich zu den ersten 256 Byte 56 Kilobyte. Natürlich müssen Sie den Speicherplatz für den Stapel und die Daten berücksichtigen.
Dieser Bootloader und andere Programme, die für diese Konsole geschrieben wurden, wurden auf die unten beschriebene Weise erstellt.
Speicher- / E / A-Zuordnung
Bei der Entwicklung für dieses Präfix ist es wichtig zu berücksichtigen, wie die CPU auf die verschiedenen Blöcke zugreift, und den Adressraum für die Eingabe und den Speicheradressraum korrekt zuzuweisen.
Die CPU greift über den Adressraum des Speichers auf den Direktzugriffsspeicher des Bootloaders zu.
Speicheradressraum

Und zu PPU-RAM, SPU-RAM und IO-MCU über den E / A-Adressraum.
E / A-Adressraum

Wie Sie der Tabelle entnehmen können, werden Adressen für alle Geräte, E / A-MCU, PPU und SPU im E / A-Adressraum zugewiesen.
PPU-Verwaltung
Aus den Informationen in der Tabelle ist ersichtlich, dass für die PPU-Steuerung in den PPU-Speicher geschrieben werden muss, der unter den Adressen 1000h-1FFFh im E / A-Adressraum verfügbar ist.
Zuweisung des PPU-Adressraums

Der PPU-Status kann folgende Werte annehmen:
- Eingebetteter Grafikmodus
- Dynamischer Grafikmodus (CHR-RAM)
- Aufnahmemodus im CHR-Speicher
- Die Aufzeichnung ist abgeschlossen und wartet auf die Bestätigung des Modus durch die CPU
Hier zum Beispiel, wie Sie mit Sprites arbeiten können:
Das Präfix kann 64 Sprites gleichzeitig zeichnen. CPU - 1004h-1143h (320 ), 5 (5 * 64 = 320):
- , : Active, Flipped_X, Flipped_Y, PageBit0, PageBit1, AboveOverlay, Width16, Height16.
- , ( ).
- ( — )
- X
- Y
, , Active 1, X Y , 32/32 , .
.
Wenn wir beispielsweise die Sprite-Nummer 10 anzeigen müssen, lautet die Adresse 4145 (1004h + (5 x 9)), schreiben Sie den Wert 1 für die Aktivierung und die Koordinaten, z. B. x = 100 und y = 120, schreiben Sie den Wert 100 an die Adresse 4148 und Adresse 4149 Wert 120.
Assembler verwenden
Eine der Programmiermethoden für die Konsole ist Assembler.
Hier ist ein Beispiel, wie ein Sprite angezeigt und animiert wird, sodass es sich bewegt und von den Rändern des Bildschirms entfernt wird.
ORG 2100h PPU_SPRITES: EQU $1004 SPRITE_CHR: EQU 72 SPRITE_COLORKEY: EQU $1F SPRITE_INIT_POS_X: EQU 140 SPRITE_INIT_POS_Y: EQU 124 jp main DS $2166-$ nmi: ; (NMI) ld bc, PPU_SPRITES + 3 ld a, (sprite_dir) and a, 1 jr z, subX in a, (c) ; X inc a out (c), a cp 248 jr nz, updateY ld a, (sprite_dir) xor a, 1 ld (sprite_dir), a jp updateY subX: in a, (c) ; X dec a out (c), a cp 32 jr nz, updateY ld a, (sprite_dir) xor a, 1 ld (sprite_dir), a updateY: inc bc ld a, (sprite_dir) and a, 2 jr z, subY in a, (c) ; Y inc a out (c), a cp 216 jr nz, moveEnd ld a, (sprite_dir) xor a, 2 ld (sprite_dir), a jp moveEnd subY: in a, (c) ; Y dec a out (c), a cp 32 jr nz, moveEnd ld a, (sprite_dir) xor a, 2 ld (sprite_dir), a moveEnd: ret main: ld bc, PPU_SPRITES ld a, 1 out (c), a ; 0 inc bc ld a, SPRITE_CHR out (c), a ; 0 inc bc ld a, SPRITE_COLORKEY out (c), a ; 0 inc bc ld a, SPRITE_INIT_POS_X out (c), a ; 0 inc bc ld a, SPRITE_INIT_POS_Y out (c), a ; Y 0 mainLoop: jp mainLoop sprite_dir: DB 0
C-Sprache verwenden
Sie können auch die Sprache C verwenden. Dazu benötigen wir den SDCC-Compiler und einige zusätzliche Dienstprogramme.
C-Code ist zwar langsamer, aber das Schreiben ist schneller und einfacher.
Hier ist ein Beispiel für Code, der mit dem obigen Assembler-Code identisch ist. Er verwendet eine Bibliothek, mit deren Hilfe PPU aufgerufen werden kann:
#include <console.h> #define SPRITE_CHR 72 #define SPRITE_COLORKEY 0x1F #define SPRITE_INIT_POS_X 140 #define SPRITE_INIT_POS_Y 124 struct s_sprite sprite = { 1, SPRITE_CHR, SPRITE_COLORKEY, SPRITE_INIT_POS_X, SPRITE_INIT_POS_Y }; uint8_t sprite_dir = 0; void nmi() { if (sprite_dir & 1) { sprite.x++; if (sprite.x == 248) { sprite_dir ^= 1; } } else { sprite.x--; if (sprite.x == 32) { sprite_dir ^= 1; } } if (sprite_dir & 2) { sprite.y++; if (sprite.y == 216) { sprite_dir ^= 2; } } else { sprite.y--; if (sprite.x == 32) { sprite_dir ^= 2; } } set_sprite(0, sprite); } void main() { while(1) { } }
Dynamische Grafik
(In den originalen benutzerdefinierten Grafiken. Ca. Per.)
Im Präfix-ROM sind 1 Seite mit Kacheln zum Sichern und eine weitere Seite mit vorgefertigten Sprites vernäht. Standardmäßig können Sie nur diese festen Grafiken verwenden, aber Sie können zu dynamisch wechseln.
Mein Ziel war es, dass alle notwendigen Grafiken in binärer Form sofort in den CHR-RAM geladen wurden und der Code im Bootloader aus dem ROM dies kann. Zu diesem Zweck habe ich mehrere Bilder in der richtigen Größe mit verschiedenen nützlichen Symbolen erstellt:

Da der Speicher für dynamische Grafiken aus 4 Seiten mit 256 Zeichen zu je 8 x 8 Pixel und 4 Seiten mit denselben Zeichen für Sprites besteht, habe ich die Bilder in das PNG-Format konvertiert und doppelte gelöscht:

Und dann übersetzte er alles mit einem selbstgeschriebenen Tool in ein binäres RGB332-Format mit 8x8-Blöcken.

Als Ergebnis haben wir Dateien mit Zeichen, in denen alle Zeichen nacheinander ablaufen und jeweils 64 Bytes benötigen.
Ton
Wave-RAW-Samples werden in 8-Bit-8-Kilohertz-PCM-Samples konvertiert.
Patches für Soundeffekte auf PWM und Musik werden mit speziellen Anweisungen geschrieben.
Für den FM-Synthesechip YM3438 von Yamaha habe ich ein Programm namens DefleMask gefunden, das PAL-synchronisierte Musik für den Genesis YM2612-Chip erzeugt, der mit dem YM3438 kompatibel ist.
DefleMask exportiert Musik im VGM-Format und ich konvertiere sie mit einem anderen proprietären Dienstprogramm in mein eigenes Binärformat.
Alle Binärdateien aller drei Soundtypen werden zu einer Binärdatei zusammengefasst, die mein Bootloader lesen und in den SDN-RAM-Soundspeicher laden kann.

Link zur endgültigen Datei
Binärer ausführbarer Code, Grafiken und Sound werden in einer PRG-Datei kombiniert. Die PRG-Datei hat einen Header, in dem alles beschrieben wird, ob Audio- und Grafikdaten vorhanden sind, wie viel sie belegen und welche Daten selbst vorhanden sind.
Eine solche Datei kann auf eine SD-Karte geschrieben werden, und der Konsolen-Bootloader berücksichtigt sie und lädt alles an die entsprechenden Stellen herunter und startet den ausführbaren Programmcode.

Emulator
Ich habe mit wxWidgets einen Emulator meiner Konsole in C ++ geschrieben, um die Entwicklung zu vereinfachen.
Die CPU wird von der libz80- Bibliothek emuliert.
Dem Emulator wurden Funktionen zum Debuggen hinzugefügt. Ich kann ihn jederzeit stoppen und den Assembler schrittweise debuggen. Wenn diese Sprache für das Spiel verwendet wurde, erfolgt eine Zuordnung zum Quellcode in C.
Laut Grafik kann ich in den Videospeicher, in die Symboltabellen und in den CHR-Speicher selbst schauen.
Hier ist ein Beispiel für ein Programm, das auf einem Emulator mit aktivierten Debugging-Tools ausgeführt wird.

Demo programmieren
Diese Videos wurden mit einer Smartphone-Kamera aufgenommen, die auf den CRT-Bildschirm des Fernsehgeräts gerichtet ist. Ich entschuldige mich für die unvollständige Bildqualität.
Der über die PS / 2-Tastatur programmierbare BASIC-Interpreter zeigt nach dem ersten Programm, wie durch Aktivieren und Verschieben des Sprites direkt über den E / A-Adressraum in den PPU-Speicher geschrieben wird:
Eine Demo von Grafiken in diesem Video lädt programmgesteuert 64 16x16-Sprites vor dem Hintergrund eines Hintergrunds mit dynamischem Bildlauf und einer Überlagerung herunter, die sich unter und über den Sprites bewegt:
Die Sound-Demo zeigt die Funktionen von YM3438- und PWM-Sound. Die Sounddaten dieser Demo sowie FM-Musik und PWM-Sounds belegen zusammen fast alle verfügbaren 128 Kilobyte Soundspeicher.
Tetris, fast ausschließlich die Hintergrundfunktionen, Musik auf dem YM3438, Soundeffekte auf PWM-Patches wurden für Grafiken verwendet.
Fazit
Dieses Projekt ist wirklich ein wahr gewordener Traum. Ich arbeite seit mehreren Jahren daran. Mit Unterbrechungen und Blick auf meine Freizeit hätte ich nie gedacht, dass ich bei der Erstellung meiner eigenen Retro-Videospielkonsole so weit gehen würde. Natürlich ist es nicht perfekt, ich bin sicherlich kein Experte für Elektronik, es gab offensichtlich zu viele Elemente in der Set-Top-Box, und zweifellos könnte es besser gemacht werden, und wahrscheinlich denkt einer der Leser nur darüber nach.
Trotzdem habe ich bei der Arbeit an diesem Projekt viel über Elektronik, Spielekonsolen und Computerdesign, Assemblersprache und andere interessante Dinge gelernt, und vor allem war ich sehr zufrieden mit Spielen, die ich selbst auf von mir selbst entwickelter Hardware geschrieben habe und gesammelt.
Ich habe Pläne, Konsolen / Computer und mehr zu machen. Eigentlich mache ich bereits eine neue Set-Top-Box, sie ist fast fertig und es handelt sich um eine vereinfachte Retro-Set-Top-Box, die auf einer FPGA-Karte und mehreren zusätzlichen Komponenten basiert (sicherlich in einer viel geringeren Menge als in diesem Projekt). Die Idee ist, viel billiger und wiederholbarer zu sein.
Obwohl ich hier viel über dieses Projekt geschrieben habe, kann zweifellos noch viel mehr besprochen werden. Ich habe kaum erwähnt, wie die Sound-Engine funktioniert, wie die CPU mit ihr interagiert und wie viel mehr über das Grafiksystem und andere Ein- / Ausgänge und die gesamte Konsole getan werden kann wäre zu erzählen.
Wenn ich mir die Reaktion der Leser anschaue, kann ich weitere Artikel schreiben, die sich auf Aktualisierungen, Details zu einzelnen Präfixblöcken oder andere Projekte konzentrieren.
Projekte, Websites, Youtube-Kanäle, die mich inspiriert und mir mit technischem Wissen geholfen haben:
Diese Websites / Kanäle haben mich nicht nur inspiriert, sondern mir auch geholfen, Lösungen für komplexe Probleme zu finden, die während der Arbeit an diesem Projekt entstanden sind.
Vielen Dank für das Lesen bis hierher. :) :)
Wenn Sie Fragen oder Feedback haben, schreiben Sie bitte in die Kommentare unten (Originalartikel in englischer Sprache auf Github. Ca. Per.)