Wir verbinden das Gamepad von PS1 / PS2 mit Raspberry Pi

Auf dem Hof ​​sind die Maiferien. Und das bedeutet Zeit, Kebabs zu essen und verschiedene gezapfte Getränke und Flaschengetränke zu trinken. Und ich mache alle Arten von Unsinn. Der Gedanke an dieses Projekt kam vor dem neuen Jahr. Aber lange konnte ich es nicht realisieren. Nachdem ich das erste Gamepad gekauft hatte (das ich später weggeworfen hatte) und versucht hatte, es abzufragen, wurde mir klar, dass es nicht ausreicht, nur die Handbücher zu lesen, obwohl es genügend Informationen gibt. Glücklicherweise erhielt ich für diesen 23. Februar als Geschenk keinen in Socken verpackten Rasierschaum, sondern 600 Rubel. mit dem Wunsch, sich auf aliexpress nichts zu verweigern. Wo die chinesische Kopie des Saleae Logic Logikanalysators gekauft wurde. Diejenigen, die daran interessiert sind, was daraus entstanden ist, können auf die Schaltfläche unten klicken. Und wer zu faul zum Lesen ist, kann das Ergebnis hier sofort sehen.



Nachdem ich den Logikanalysator auf ein unkompliziertes Design geschraubt hatte, sah ich, dass eine explizite Schlacke an das Gamepad gesendet wurde.

Ein Moment der Theorie.

Tatsache ist, dass die Konsole über die SPI-Schnittstelle mit dem Gamepad kommuniziert. Und es gibt geregelte Betriebsarten dieser Schnittstelle. In diesem Fall sollten MODE 3, Vorwärtsübertragung und der Logikpegel auf der Leitung Chip aktivieren oder Slave auswählen sein, wenn auf das Gamepad "0" zugegriffen wird. Die Frequenz auf der Clk-Leitung beträgt 250 kHz. Aber ich habe 100 kHz gemacht und es funktioniert gut. Grundsätzlich wird hier alles klar beschrieben. Und das Befehlssystem für das Gamepad und die Abschrift der Antworten hier . Auch auf dem Radiokot-Portal gibt es eine Veröffentlichung, aber es gibt Fehler in den Teams. Einfach ausgedrückt, um ein Standard-Gamepad abzufragen, müssen Sie ihm einen Befehl geben:

0x1 0x42 0x0 0x0 0x0   , -  0xff 0x41 0x5a 0xff 0xff 

Dabei ist 0x41 der Typ des Gamepads und die letzten 2 Bytes der Status der Schaltflächen. Mit Analog ist alles ähnlich, nur müssen Sie dem Paket 4 weitere Null-Bytes hinzufügen.

 0x1, 0x42, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0   , -  0xff 0x73 0x5a 0xff 0xff 0x80 0x80 0x80 0x80 

Hier 0x73 bedeutet dies, dass das Gamepad analog ist, die letzten 4 Bytes sind der Status der Analoga.

Es ist wichtig, dass das Gamepad mit 3,3 V arbeitet, damit es direkt über Raspberry Pi mit Strom versorgt werden kann. Im Standardmodus (nur Tasten funktionieren) beträgt der Verbrauch ca. 2 mA, im Analogmodus 12 mA. Und dann fügen diese 10 mA eine LED am Controller hinzu. Und auf der 3,3-V-Himbeer-Pi-Leitung kann eine Last von bis zu 50 mA gepflanzt werden.

Um ein Gamepad anzuschließen, benötigen Sie 4 GPIO-Anschlüsse und Strom. Nur 6 Drähte.

Also. Ich erkannte, dass die Bibliotheken für die Arbeit mit GPIO, das bcm2835, das wiringPi, mit SPI sehr schlecht funktionieren. Im Prinzip wissen sie nicht, wie Pakete mit dem niedrigstwertigen Bit vorwärts übertragen werden sollen. In den Docks zu einem von ihnen wird dies ehrlich beschrieben, aber irgendwo sehr tief. Und sie können sich wirklich nicht an die Regime halten.

Nichts hindert jedoch daran, das Paket selbst zu reproduzieren und Daten vom Gamepad zu lesen. Kaum gesagt als getan. Nehmen Sie die wiringPi-Bibliothek und schreiben Sie:

 #include <stdio.h> #include <unistd.h> #include <wiringPi.h> #define mosi 12 #define miso 13 #define clk 14 #define ce 5 unsigned char i, b; unsigned char cmd[9] = {0x1, 0x42, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; int main() { wiringPiSetup () ; //  pinMode (mosi, OUTPUT); pinMode (clk, OUTPUT); pinMode (ce, OUTPUT); pinMode (miso, INPUT); digitalWrite (clk, HIGH); digitalWrite (mosi, LOW); digitalWrite (ce, HIGH); while(1) { unsigned char data[9] = {0}; digitalWrite (ce, LOW); delayMicroseconds(20); for (i = 0; i < 9; i++) { for (b = 0; b < 8; b++) { if (((cmd[i] >> b)&1) == 1) { digitalWrite (mosi, HIGH); digitalWrite (clk, LOW); delayMicroseconds(5); digitalWrite (clk, HIGH); data[i] ^= digitalRead(miso) << b; delayMicroseconds(5); digitalWrite (mosi, LOW); } else { digitalWrite (clk, LOW); delayMicroseconds(5); digitalWrite (clk, HIGH); data[i] ^= digitalRead(miso) << b; delayMicroseconds(5); } } delayMicroseconds(40); } digitalWrite (ce, HIGH); printf("%x %x %x %x %x %x\n", data[3], data[4], data[5], data[6], data[7], data[8]); delayMicroseconds(100); } } 

Alles ist klassisch, wir deklarieren GPIO-Ports, Variablen und eine Reihe von Befehlen. Als nächstes werden die Port-Betriebsmodi konfiguriert und die Leitungen clk, mosi und ce (Chip Enable) zurückgesetzt. Dann werden 2 Zyklen gebildet. Im ersten Fall werden die Bytes des Befehls sortiert, und im zweiten Fall sind die Bytes selbst bitweise, um der Mosi-Zeile je nach Wert des Bits eine logische Einheit oder Null zu liefern. Gleichzeitig lesen wir die Antwort. Als ich die geschätzte Antwort sah:

 0xff 0x41 0x5a 0xff 0xff 

Ich wäre fast an die Decke gesprungen. Hier ist ein Beispiel für die Ausgabe des Programms, in diesem Fall habe ich mit dem linken Analog gespielt.

 ff ff 80 80 ff 80 ff ff 80 80 ff 80 ff ff 80 80 ff 80 ff ff 80 80 ff 80 ff ff 80 80 ff 80 ff ff 80 80 ff 80 ff ff 80 80 ff 40 ff ff 80 80 ff 4b ff ff 80 80 ff 4b ff ff 80 80 ff 4b ff ff 80 80 ff 4b ff ff 80 80 ff 4b ff ff 80 80 ff 1a ff ff 80 80 ff 1a ff ff 80 80 ff 1a ff ff 80 80 ff 1a ff ff 80 80 ff 1a ff ff 80 80 ff 0 

Nun, dann war es schon an der Zeit, diesen Code in den Emulator zu integrieren. In diesem Fall war die Frage bei der Wahl des Emulators nicht. Auf jeden Fall pcsx_rearmed . Genau wie beim vorherigen Mal war es notwendig, eine Funktion zu finden, die als eine der ersten gestartet wurde, um die Bibliothek darin zu initialisieren. PsxInit stellte sich als solche Funktion heraus: Es befindet sich in der Datei r3000a.c, die den Betrieb des Zentralprozessors emuliert. Ich habe die Bibliotheks-Header-Datei zu dieser Datei hinzugefügt und GPIO-Ports deklariert. In der psxInit-Funktion initialisiere ich die Bibliothek und stelle die Anfangspegel an den GPIO-Ports ein. Unten finden Sie die ersten Zeilen dieser Datei mit den Änderungen.

 #include "r3000a.h" #include "cdrom.h" #include "mdec.h" #include "gte.h" #include <wiringPi.h> #define mosi 12 #define miso 13 #define clk 14 #define ce 5 R3000Acpu *psxCpu = NULL; psxRegisters psxRegs; int psxInit() { SysPrintf(_("Running PCSX Version %s (%s).\n"), PACKAGE_VERSION, __DATE__); wiringPiSetup () ; //  pinMode (mosi, OUTPUT); pinMode (clk, OUTPUT); pinMode (ce, OUTPUT); pinMode (miso, INPUT); digitalWrite (clk, HIGH); digitalWrite (mosi, LOW); digitalWrite (ce, HIGH); #ifdef PSXREC if (Config.Cpu == CPU_INTERPRETER) { psxCpu = &psxInt; } else psxCpu = &psxRec; #else psxCpu = &psxInt; #endif Log = 0; if (psxMemInit() == -1) return -1; return psxCpu->Init(); } 

Als nächstes musste ich herausfinden, wie der Emulator den Betrieb eines Gamepads simuliert. Es gibt eine Header-Datei psemu_plugin_defs.h, die sich im Include-Verzeichnis befindet. Hier enthält sie die Struktur, in der die Variablen gespeichert sind, in der der Gamepad-Typ Standard-, Analog-, Tastenstatusvariable-, Analogstatusvariablen- und Vibrationssteuerungsvariablen ist.

 typedef struct { // controler type - fill it withe predefined values above unsigned char controllerType; // status of buttons - every controller fills this field unsigned short buttonStatus; // for analog pad fill those next 4 bytes // values are analog in range 0-255 where 127 is center position unsigned char rightJoyX, rightJoyY, leftJoyX, leftJoyY; // for mouse fill those next 2 bytes // values are in range -128 - 127 unsigned char moveX, moveY; unsigned char Vib[2]; unsigned char VibF[2]; unsigned char reserved[87]; } PadDataS; 

Die Hauptaufgabe besteht also darin, die empfangenen Daten in diese Variablen zu schreiben. Dieser Emulator hat Plugins. Mit dem Gamepad-Plugin befindet sich im Plugins-Verzeichnis ein Unterverzeichnis dfinput, in dem sich die gewünschte pad.c-Datei befindet, in der der Gamepad-Status sowie die Einstellungen gelesen werden. Hier wurde der Code aus dem Testprogramm übertragen. Ports und die Bibliotheksheaderdatei werden ebenfalls deklariert. Es enthält auch Variablen, die den Code für Befehle speichern, die an das Gamepad gesendet werden. Diese Variablen werden für den Zugriff auf das Gamepad verwendet. Unten ist dieser Code:

 #include <stdint.h> #include "../include/psemu_plugin_defs.h" #include "main.h" #include <wiringPi.h> #define mosi 12 #define miso 13 #define clk 14 #define ce 5 unsigned char a, b; unsigned char cmd[9] = {0x1, 0x42, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; enum { ANALOG_LEFT = 0, ANALOG_RIGHT, ANALOG_TOTAL }; enum { CMD_READ_DATA_AND_VIBRATE = 0x42, CMD_CONFIG_MODE = 0x43, CMD_SET_MODE_AND_LOCK = 0x44, CMD_QUERY_MODEL_AND_MODE = 0x45, CMD_QUERY_ACT = 0x46, // ?? CMD_QUERY_COMB = 0x47, // ?? CMD_QUERY_MODE = 0x4C, // QUERY_MODE ?? CMD_VIBRATION_TOGGLE = 0x4D, }; 

Wir sind an der allerersten interessiert, die einen Wert von 42 hat. Wenn diese Variable aufgerufen wird, wird hier der Code ausgeführt. Außerdem ist es in der Originaldatei leer. Und in die Aufzählungsfunktion des Befehls do_cmd habe ich den Hauptcode eingefügt:

 static uint8_t do_cmd(void) { PadDataS *pad = &padstate[CurPad].pad; int pad_num = CurPad; unsigned char data[9] = {0}; CmdLen = 8; switch (CurCmd) { case CMD_SET_MODE_AND_LOCK: buf = stdmode[pad_num]; return 0xF3; case CMD_QUERY_MODEL_AND_MODE: buf = stdmodel[pad_num]; buf[4] = padstate[pad_num].PadMode; return 0xF3; case CMD_QUERY_ACT: buf = unk46[pad_num]; return 0xF3; case CMD_QUERY_COMB: buf = unk47[pad_num]; return 0xF3; case CMD_QUERY_MODE: buf = unk4c[pad_num]; return 0xF3; case CMD_VIBRATION_TOGGLE: buf = unk4d[pad_num]; return 0xF3; case CMD_CONFIG_MODE: if (padstate[pad_num].ConfigMode) { buf = stdcfg[pad_num]; return 0xF3; } // else FALLTHROUGH case CMD_READ_DATA_AND_VIBRATE: digitalWrite (ce, LOW); delayMicroseconds(20); for (a = 0; a < 9; a++) { for (b = 0; b < 8; b++) { if (((cmd[a] >> b)&1) == 1) { digitalWrite (mosi, HIGH); digitalWrite (clk, LOW); delayMicroseconds(5); digitalWrite (clk, HIGH); data[a] ^= digitalRead(miso) << b; delayMicroseconds(5); digitalWrite (mosi, LOW); } else { digitalWrite (clk, LOW); delayMicroseconds(5); digitalWrite (clk, HIGH); data[a] ^= digitalRead(miso) << b; delayMicroseconds(5); } } delayMicroseconds(40); } digitalWrite (ce, HIGH); pad->buttonStatus = data[4]; pad->buttonStatus = pad->buttonStatus << 8; pad->buttonStatus |= data[3]; pad->rightJoyX = data[5]; pad->rightJoyY = data[6]; pad->leftJoyX = data[7]; pad->leftJoyY = data[8]; default: buf = stdpar[pad_num]; buf[2] = pad->buttonStatus; buf[3] = pad->buttonStatus >> 8; if (padstate[pad_num].PadMode == 1) { buf[4] = pad->rightJoyX; buf[5] = pad->rightJoyY; buf[6] = pad->leftJoyX; buf[7] = pad->leftJoyY; } else { CmdLen = 4; } return padstate[pad_num].PadID; } } 

Wenn der aktuelle Befehl gelesen wird, wird das Gamepad abgefragt und die Daten werden in Variablen geschrieben, und der Emulator selbst übernimmt sie.

Das ist alles, es bleibt nur das Projekt zu kompilieren. Damit der Compiler die wirigPi-Bibliothek abrufen kann, müssen Sie im Makefile des Projekts einen Link dazu einfügen. Dies reicht zu Beginn aus.

 # Makefile for PCSX ReARMed # default stuff goes here, so that config can override TARGET ?= pcsx CFLAGS += -Wall -ggdb -Ifrontend -ffast-math -I/usr/include -I/usr/include/SDL LDLIBS += -lpthread -lSDL -lpng -lwiringPi ifndef DEBUG CFLAGS += -DNDEBUG -g endif CXXFLAGS += $(CFLAGS) #DRC_DBG = 1 #PCNT = 1 

Und es ist wichtig. Um ein analoges Gamepad verwenden zu können, muss der Emulator in den verwendeten Einstellungen angeben.

Ich habe nicht viele Spiele für PS1, ich habe irgendwo nach 7 gesucht. Es hat bei Tomb Raider2 und Nuclear Strike nicht funktioniert, aber das ist offensichtlich, weil diese Spiele nicht wissen, was ein analoges Gamepad ist. Wahrscheinlich müssen Sie den Standard in den Einstellungen auswählen und versuchen.

PS Der Emulator selbst lässt sich besser mit Optimierungsflags zusammenbauen. Es ist hier gut beschrieben.

Source: https://habr.com/ru/post/de403753/


All Articles