Kami menghubungkan gamepad dari PS1 / PS2 ke Raspberry pi

Di halaman adalah liburan Mei. Dan itu berarti waktu untuk makan kebab dan minum berbagai minuman draft dan botol. Dan saya melakukan semua jenis omong kosong. Pikiran tentang proyek ini datang, sebelum tahun baru. Tetapi untuk waktu yang lama saya tidak bisa menyadarinya. Setelah membeli gamepad pertama (yang kemudian saya buang), dan setelah mencoba menginterogasinya, saya menyadari bahwa tidak cukup hanya membaca manual, meskipun ada cukup informasi. Untungnya, untuk tanggal 23 Februari ini, saya menerima hadiah bukan busa cukur yang dikemas dalam kaus kaki, tetapi 600 rubel. dengan keinginan untuk tidak menyangkal apa pun di aliexpress. Di mana salinan Cina dari penganalisa logika Saleae Logic dibeli. Mereka yang tertarik dengan apa yang datang dapat mengklik tombol di bawah ini. Dan mereka yang terlalu malas membaca dapat langsung melihat hasilnya di sini .



Setelah mengacaukan analisa logika ke desain yang tidak rumit, saya melihat bahwa slag eksplisit dikirim ke gamepad.

Momen teori.

Faktanya adalah konsol berkomunikasi dengan gamepad melalui antarmuka SPI. Dan ada mode operasi yang diatur dari antarmuka ini. Dalam hal ini, harus ada MODE 3, transmisi maju, dan level logika pada chip aktif atau Pilih slave ketika mengakses gamepad "0". Frekuensi pada garis clk adalah 250 kHz. Tapi saya melakukan 100 kHz, dan itu berfungsi dengan baik. Semuanya, pada prinsipnya, dijelaskan dengan jelas di sini . Dan sistem perintah untuk gamepad, dan transkrip jawaban di sini . Juga di portal Radiokot, ada publikasi, tetapi ada kesalahan di tim. Sederhananya, untuk menginterogasi gamepad standar, Anda perlu memberi perintah:

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

Di mana 0x41 adalah jenis gamepad, dan 2 byte terakhir adalah status tombol. Dengan analog, semuanya serupa, hanya Anda perlu menambahkan 4 byte nol lebih ke paket.

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

Di sini 0x73 ini berarti bahwa gamepad adalah analog, 4 byte terakhir adalah status analog.

Penting bahwa gamepad bekerja mulai dari 3.3 V., yang memungkinkannya ditenagai langsung dari Raspberry pi. Dalam mode standar (hanya tombol yang berfungsi), konsumsinya sekitar 2 mA, dalam mode analog 12 mA. Dan 10 mA ini menambahkan LED pada pengontrol. Dan pada jalur pi Raspberry 3,3 V, beban hingga 50 mA dapat ditanam.

Sebenarnya, untuk menghubungkan gamepad, Anda memerlukan 4 port GPIO dan daya. Hanya 6 kabel.

Jadi disini. Saya menyadari bahwa perpustakaan untuk bekerja dengan GPIO, bahwa bcm2835, bahwa wiringPi, bekerja sangat buruk dengan SPI. Pada prinsipnya, mereka tidak tahu bagaimana mengirimkan paket dengan bit forward yang paling tidak signifikan. Di dermaga ke salah satu dari mereka, ini dijelaskan dengan jujur, tetapi di suatu tempat yang sangat dalam. Dan mereka benar-benar tidak bisa mematuhi rezim.

Tetapi tidak ada yang mencegah untuk mereproduksi paket itu sendiri, dan membaca data dari gamepad. Tidak lebih cepat dikatakan daripada dilakukan. Ambil perpustakaan wiringPi dan tulis:

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

Semuanya klasik, kami mendeklarasikan port GPIO, variabel dan berbagai perintah. Selanjutnya, mode operasi port dikonfigurasikan, dan jalur clk, mosi, dan ce (chip aktif) diatur ulang. Kemudian 2 siklus terbentuk. Dalam yang pertama, byte dari perintah diurutkan, dan yang kedua, byte itu sendiri adalah bit-bijaksana, untuk memasok unit logis atau nol ke baris mosi, tergantung pada nilai bit. Dan pada saat yang sama kami membaca jawabannya. Ketika saya melihat jawaban yang berharga:

 0xff 0x41 0x5a 0xff 0xff 

Saya hampir melompat ke langit-langit. Berikut ini adalah contoh dari output program, dalam hal ini, saya bermain dengan analog kiri.

 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 

Nah, kalau begitu sudah mengintegrasikan kode ini ke dalam emulator. Dalam hal ini, pertanyaan dengan pilihan emulator tidak. Pasti pcsx_rearmed . Sama seperti waktu sebelumnya, perlu untuk menemukan fungsi yang diluncurkan salah satu yang pertama untuk menginisialisasi perpustakaan di dalamnya. PsxInit ternyata merupakan fungsi seperti itu, terletak di file r3000a.c, yang mengemulasi operasi prosesor pusat. Saya menambahkan file header perpustakaan ke file ini dan menyatakan port GPIO. Dalam fungsi psxInit, saya menginisialisasi perpustakaan dan mengatur level awal pada port GPIO. Di bawah ini adalah baris pertama file ini dengan perubahan.

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

Selanjutnya, saya harus mencari tahu bagaimana emulator mensimulasikan pengoperasian gamepad. Ada file header psemu_plugin_defs.h, terletak di direktori include, di sini berisi struktur tempat variabel disimpan di mana jenis gamepad standar, analog, variabel status tombol, variabel status analog, dan variabel kontrol getaran.

 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; 

Jadi tugas utama adalah menulis data yang diterima ke dalam variabel-variabel ini. Emulator ini memiliki plugin. Termasuk plugin gamepad, dalam direktori plugins ada subdirektori dfinput, di mana file pad.c yang diinginkan berada, di mana status gamepad dibaca, serta pengaturannya. Di sinilah kode dari program uji ditransfer. Port dan file header perpustakaan juga dideklarasikan. Ini juga memiliki variabel yang menyimpan kode untuk perintah yang dikirim ke gamepad. Variabel-variabel ini digunakan dalam fungsi mengakses gamepad. Di bawah ini adalah potongan kode ini:

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

Kami tertarik pada yang pertama, yang memiliki nilai 42. Di sini, ketika variabel ini dipanggil, kode akan dieksekusi. Selain itu, dalam file asli kosong. Dan pada fungsi enumerasi perintah do_cmd, saya memasukkan kode utama:

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

Kemudian, ketika perintah saat ini sedang membaca, gamepad disurvei dan data ditulis ke dalam variabel, dan kemudian emulator sendiri yang mengambilnya.

Itu saja, itu tetap hanya untuk mengkompilasi proyek. Agar kompiler mengambil pustaka wirigPi, Anda perlu memasukkan tautan ke dalam Makefile proyek. Ini cukup untuk dilakukan di awal.

 # 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 

Dan ini penting. Untuk menggunakan gamepad analog, emulator harus menentukan dalam pengaturan yang digunakannya.

Saya tidak punya banyak game untuk PS1, saya memeriksa di suatu tempat sekitar 7. Ini tidak berfungsi pada Tomb Raider2 dan Nuclear strike, tetapi ini terbukti karena game-game ini tidak tahu apa itu gamepad analog. Mungkin Anda perlu memilih standar di pengaturan dan mencoba.

PS Emulator itu sendiri lebih baik untuk berkumpul dengan flag optimasi. Dijelaskan dengan baik di sini .

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


All Articles