Nous connectons la manette de jeu de PS1 / PS2 à Raspberry pi

Dans la cour sont les vacances de mai. Et cela signifie qu'il est temps de manger des brochettes et de boire diverses boissons en fût et en bouteille. Et je fais toutes sortes de bêtises. L'idée de ce projet est venue avant la nouvelle année. Mais pendant longtemps, je ne pouvais pas m'en rendre compte. Ayant acheté la première manette de jeu (que j'ai jetée plus tard) et ayant essayé de l'interroger, j'ai réalisé qu'il ne suffisait pas de lire les manuels, bien qu'il y ait suffisamment d'informations. Heureusement, pour ce 23 février, j'ai reçu en cadeau non pas de la mousse à raser emballée en chaussettes, mais 600 roubles. avec le désir de ne rien vous refuser sur aliexpress. Où la copie chinoise de l'analyseur logique Saleae Logic a été achetée. Ceux qui sont intéressés par ce qui en est arrivé peuvent cliquer sur le bouton ci-dessous. Et ceux qui sont trop paresseux pour lire peuvent immédiatement voir le résultat ici .



Après avoir vissé l'analyseur logique à une conception simple, j'ai vu qu'un laitier explicite a été envoyé à la manette de jeu.

Un moment de théorie.

Le fait est que la console communique avec la manette de jeu via l'interface SPI. Et il existe des modes de fonctionnement réglementés de cette interface. Dans ce cas, il devrait y avoir le MODE 3, la transmission directe et le niveau logique sur la ligne Chip enable ou Select slave lors de l'accès au gamepad "0". La fréquence sur la ligne clk est de 250 kHz. Mais j'ai fait 100 kHz, et ça marche bien. Tout, en principe, est clairement décrit ici . Et le système de commandes pour la manette de jeu, et la transcription des réponses ici . Toujours sur le portail Radiokot, il y a une publication, mais il y a des erreurs dans les équipes. Autrement dit, pour interroger une manette de jeu standard, vous devez lui donner une commande:

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

Où 0x41 est le type de manette de jeu et les 2 derniers octets est l'état des boutons. Avec l'analogique, tout est similaire, seulement vous devez ajouter 4 octets de zéro supplémentaires au package.

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

Ici 0x73, cela signifie que la manette de jeu est analogique, les 4 derniers octets sont le statut des analogues.

Il est important que la manette de jeu fonctionne à partir de 3,3 V., ce qui lui permet d'être alimenté directement à partir de Raspberry pi. En mode standard (seuls les boutons fonctionnent), la consommation est d'environ 2 mA, en mode analogique 12 mA. Et puis ces 10 mA ajoutent une LED sur le contrôleur. Et sur la ligne 3,3 V Raspberry pi, une charge allant jusqu'à 50 mA peut être plantée.

En fait, pour connecter une manette de jeu, vous avez besoin de 4 ports GPIO et d'alimentation. Seulement 6 fils.

Alors voilà. J'ai réalisé que les bibliothèques pour travailler avec GPIO, que bcm2835, ce câblagePi, fonctionnent très mal avec SPI. En principe, ils ne savent pas transmettre de paquets avec le bit le moins significatif en avant. Dans les quais de l'un d'eux, cela est honnêtement décrit, mais quelque part très profondément. Et ils ne peuvent vraiment pas respecter les régimes.

Mais rien n'empêche de reproduire le package lui-même et de lire les données de la manette de jeu. Aussitôt dit, aussitôt fait. Prenez la bibliothèque de câblagePi et écrivez:

 #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); } } 

Tout est classique, nous déclarons les ports GPIO, les variables et un tableau de commandes. Ensuite, les modes de fonctionnement du port sont configurés et les lignes clk, mosi et ce (activation de la puce) sont réinitialisées. Ensuite, 2 cycles se forment. Dans le premier, les octets de la commande sont triés, et dans le second, les octets eux-mêmes sont bit par bit, afin de fournir une unité logique ou zéro à la ligne mosi, en fonction de la valeur du bit. Et en même temps, nous lisons la réponse. Quand j'ai vu la précieuse réponse:

 0xff 0x41 0x5a 0xff 0xff 

J'ai presque sauté au plafond. Voici un exemple de sortie du programme, dans ce cas, je jouais avec l'analogue gauche.

 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 

Eh bien, il était déjà possible d'intégrer ce code dans l'émulateur. Dans ce cas, la question du choix d'un émulateur ne résiste pas. Certainement pcsx_rearmed . Tout comme la fois précédente, il a fallu trouver une fonction lancée l'une des premières pour y initialiser la bibliothèque. Cette fonction s'est avérée être psxInit; elle se trouve dans le fichier r3000a.c, qui émule le fonctionnement du processeur central. J'ai ajouté le fichier d'en-tête de bibliothèque à ce fichier et déclaré les ports GPIO. Dans la fonction psxInit, j'initialise la bibliothèque et définit les niveaux initiaux sur les ports GPIO. Voici les premières lignes de ce fichier avec les modifications.

 #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(); } 

Ensuite, j'ai dû comprendre comment l'émulateur simule le fonctionnement d'une manette de jeu. Il y a un fichier d'en-tête psemu_plugin_defs.h, il est situé dans le répertoire include, ici il contient la structure dans laquelle les variables sont stockées dans lesquelles le type de gamepad est standard, analogique, variable d'état du bouton, variable d'état analogique et variables de contrôle des vibrations.

 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; 

La tâche principale consiste donc à écrire les données reçues dans ces variables. Cet émulateur a des plugins. Y compris le plugin gamepad, dans le répertoire plugins il y a un sous-répertoire dfinput, dans lequel se trouve le fichier pad.c souhaité, dans lequel l'état du gamepad est lu, ainsi que ses paramètres. C'est là que le code du programme de test a été transféré. Les ports et le fichier d'en-tête de bibliothèque sont également déclarés. Il contient également des variables qui stockent le code des commandes envoyées à la manette de jeu. Ces variables sont utilisées dans les fonctions d'accès à la manette de jeu. Voici ce morceau de 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, }; 

Nous nous intéressons à la toute première, qui a une valeur de 42. Ici, lorsque cette variable est appelée, le code sera exécuté. De plus, dans le fichier d'origine, il est vide. Et dans la fonction d'énumération de la commande do_cmd, j'ai inséré le code principal:

 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; } } 

Ensuite, lorsque la commande actuelle est en cours de lecture, la manette de jeu est interrogée et les données sont écrites dans des variables, puis l'émulateur lui-même les prend.

C'est tout, il ne reste plus qu'à compiler le projet. Pour que le compilateur récupère la bibliothèque wirigPi, vous devez insérer un lien vers celle-ci dans le Makefile du projet. Cela suffit au tout début.

 # 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 

Et c'est important. Pour utiliser une manette de jeu analogique, l'émulateur doit spécifier dans les paramètres qu'il est utilisé.

Je n'ai pas beaucoup de jeux pour PS1, j'ai vérifié quelque part vers 7. Cela n'a pas fonctionné sur Tomb Raider2 et Nuclear strike, mais cela est évident parce que ces jeux ne savent pas ce qu'est une manette de jeu analogique. Vous devez probablement sélectionner la norme dans les paramètres et essayer.

PS L'émulateur lui-même est préférable d'assembler avec des drapeaux d'optimisation. Il est bien décrit ici .

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


All Articles