في الفناء هي عطلة مايو. وهذا يعني الوقت لتناول الكباب وشرب مختلف أنواع المشروبات الغازية والمشروبات المعبأة في زجاجات. وأنا أفعل كل أنواع الهراء. جاءت الفكرة حول هذا المشروع قبل العام الجديد. لكن لفترة طويلة لم أتمكن من إدراك ذلك. بعد أن اشتريت لوحة الألعاب الأولى (التي طردتها لاحقًا) ، وحاولت استجوابها ، أدركت أنه لا يكفي مجرد قراءة الكتيبات ، على الرغم من وجود معلومات كافية. لحسن الحظ ، في 23 فبراير ، تلقيت كهدية ليست رغوة حلاقة معبأة في الجوارب ، ولكن 600 روبل. مع الرغبة في عدم إنكار أي شيء على aliexpress. حيث تم شراء النسخة الصينية من محلل المنطق Saleae. أولئك الذين يهتمون بما جاء منه يمكنهم النقر على الزر أدناه. وأولئك الذين كسالى جدًا في القراءة يمكنهم رؤية النتيجة
هنا على الفور.

بعد أن ربطت محلل المنطق بتصميم غير معقد ، رأيت أن خبثًا صريحًا تم إرساله إلى لوحة الألعاب.
لحظة نظرية.
والحقيقة هي أن وحدة التحكم تتواصل مع لوحة الألعاب عبر واجهة SPI. وهناك طرق تشغيل منظمة لهذه الواجهة. في هذه الحالة ، يجب أن يكون هناك MODE 3 ، ناقل حركة أمامي ، ومستوى المنطق على الخط رقاقة تمكين أو تحديد الرقيق عند الوصول إلى لوحة الألعاب "0". التردد على خط clk هو 250 كيلو هرتز. لكني فعلت 100 كيلو هرتز ، وهو يعمل بشكل جيد. كل شيء ، من حيث المبدأ ، موصوف بوضوح
هنا . ونظام أوامر لوحة الألعاب ، ونص الإجابات
هنا . يوجد أيضًا على بوابة Radiokot منشور ، ولكن هناك أخطاء في الفرق. ببساطة ، لاستجواب لوحة ألعاب قياسية ، تحتاج إلى إصدار أمر لها:
0x1 0x42 0x0 0x0 0x0 , - 0xff 0x41 0x5a 0xff 0xff
حيث 0x41 هو نوع لوحة الألعاب ، والبايت 2 الأخير هو حالة الأزرار. مع التناظرية ، كل شيء مشابه ، ما عليك سوى إضافة 4 بايت إضافية إضافية إلى الحزمة.
0x1, 0x42, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 , - 0xff 0x73 0x5a 0xff 0xff 0x80 0x80 0x80 0x80
هنا 0x73 هذا يعني أن لوحة الألعاب تناظرية ، 4 بايت الأخيرة هي حالة النظير.
من المهم أن تعمل لوحة الألعاب من 3.3 V. ، مما يسمح بتشغيلها مباشرة من Raspberry pi. في الوضع القياسي (تعمل الأزرار فقط) ، يكون الاستهلاك حوالي 2 مللي أمبير ، في الوضع التمثيلي 12 مللي أمبير. ثم تضيف هذه 10 مللي أمبير LED على وحدة التحكم. وعلى خط بي بي توت العليق 3.3 فولت ، يمكن زراعة حمولة تصل إلى 50 مللي أمبير.
في الواقع ، لتوصيل لوحة ألعاب ، تحتاج إلى أي 4 منافذ GPIO وقوة. 6 أسلاك فقط.
حتى هنا. أدركت أن المكتبات للعمل مع GPIO ، bcm2835 ، التي تعمل على wiringPi ، تعمل بشكل سيء للغاية مع SPI. من حيث المبدأ ، لا يعرفون كيفية إرسال الحزم ذات البت الأقل أهمية إلى الأمام. في أرصفة أحدهم ، يتم وصف هذا بصدق ، ولكن في مكان ما بعمق شديد. وهم لا يستطيعون الالتزام بالنظم.
لكن لا شيء يمنع إعادة إنتاج الحزمة نفسها ، وقراءة البيانات من لوحة الألعاب. لم يقل من فعله. خذ مكتبة wiringPi واكتب:
#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); } }
كل شيء كلاسيكي ، نعلن عن منافذ GPIO ومتغيرات ومجموعة من الأوامر. بعد ذلك ، يتم تكوين أوضاع تشغيل المنفذ ، وتتم إعادة تعيين خطوط clk و mosi و ce (تمكين الشريحة). ثم يتم تشكيل دورتين. في الأول ، يتم فرز بايتات الأمر ، وفي الثانية ، تكون البايتات نفسها حكيمة بعض الشيء ، من أجل توفير وحدة منطقية أو صفر لخط mosi ، اعتمادًا على قيمة البت. وفي نفس الوقت نقرأ الجواب. عندما رأيت الجواب الثمين:
0xff 0x41 0x5a 0xff 0xff
قفزت تقريبا إلى السقف. هنا مثال على إخراج البرنامج ، في هذه الحالة ، لعبت مع التناظرية اليسرى.
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
حسنًا ، كان علينا دمج هذا الرمز في المحاكي. في هذه الحالة ، لم يكن السؤال مع اختيار المحاكي. بالتأكيد
pcsx_rearmed . تمامًا مثل المرة السابقة ، كان من الضروري العثور على وظيفة تم إطلاقها من إحدى الوظائف الأولى من أجل تهيئة المكتبة فيها. تبين أن PsxInit هي مثل هذه الوظيفة ؛ فهي تقع في ملف r3000a.c ، الذي يحاكي تشغيل المعالج المركزي. أضفت ملف رأس المكتبة إلى هذا الملف وأعلنت منافذ GPIO. في وظيفة psxInit ، أقوم بتهيئة المكتبة وتعيين المستويات الأولية على منافذ GPIO. فيما يلي الأسطر الأولى من هذا الملف مع التغييرات.
#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(); }
بعد ذلك ، كان عليّ معرفة كيف يحاكي المحاكي تشغيل لوحة الألعاب. يوجد ملف رأس psemu_plugin_defs.h ، وهو موجود في دليل التضمين ، هنا يحتوي على الهيكل الذي يتم فيه تخزين المتغيرات التي يكون فيها نوع لوحة الألعاب قياسيًا ، تناظريًا ، متغير حالة الزر ، متغيرات الحالة التناظرية ، ومتغيرات التحكم في الاهتزاز.
typedef struct {
لذا فإن المهمة الرئيسية هي كتابة البيانات المستلمة في هذه المتغيرات. يحتوي هذا المحاكي على مكونات إضافية. بما في ذلك البرنامج المساعد gamepad ، في دليل الإضافات هناك دليل فرعي dfinput ، حيث يوجد ملف pad.c المطلوب ، والذي تتم فيه قراءة حالة لوحة الألعاب ، بالإضافة إلى إعداداته. هذا هو المكان الذي تم فيه نقل الرمز من برنامج الاختبار. يتم أيضًا تعريف المنافذ وملف رأس المكتبة. كما أن لديها متغيرات تخزن التعليمات البرمجية للأوامر المرسلة إلى لوحة الألعاب. يتم استخدام هذه المتغيرات في وظائف الوصول إلى لوحة الألعاب. يوجد أدناه هذا الرمز:
#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, };
نحن مهتمون بالمتغير الأول ، الذي تبلغ قيمته 42. هنا ، عندما يتم استدعاء هذا المتغير ، سيتم تنفيذ الرمز. علاوة على ذلك ، في الملف الأصلي فارغ. وفي دالة تعداد أوامر do_cmd ، أدرجت الكود الرئيسي:
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; }
ثم ، عندما يقرأ الأمر الحالي ، يتم تمرير لوحة الألعاب ويتم كتابة البيانات في متغيرات ، ثم يأخذها المحاكي نفسه.
هذا كل شيء ، يبقى فقط لتجميع المشروع. من أجل أن يلتقط المترجم مكتبة wirigPi ، تحتاج إلى إدراج ارتباط لها في Makefile للمشروع. هذا يكفي للقيام به في البداية.
# 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
وهذا مهم. لاستخدام لوحة الألعاب التناظرية ، يجب أن يحدد المحاكي في الإعدادات التي يتم استخدامها.
ليس لدي العديد من الألعاب لـ PS1 ، لقد راجعت في مكان ما حوالي 7. لم يعمل على Tomb Raider2 والضربة النووية ، ولكن هذا واضح لأن هذه الألعاب لا تعرف ما هي لوحة الألعاب التناظرية. ربما تحتاج إلى تحديد المعيار في الإعدادات والمحاولة.
PS المحاكي نفسه الأفضل للتجميع مع أعلام التحسين. موصوف جيدا
هنا .