Однажды, насмотревшись всяких «Пока все играют», мне тоже захотелось поиграть на своём Raspberry pi. Да не просто поиграть, а поиграть используя реальное устройство. Для чего в переходе метрополитена за 150 рублей был куплен джойстик от Денди (ну не от денди, а Симбас Юниор). Те, кому интересно, что из этого получилось, могут ткнуть мышкой по кнопке ниже. В конце статьи будет ссылка на пруф.
Наши Китайские друзья, собрали его с девизом — качества нет, но вы держитесь. Сразу же был выпаян родной кабель с сечением жил в 2 кв мкм, и заменён на кабель от какого-то промышленного преобразователя интерфейсов, доставшегося после очередного ПНР, в нём как раз было 5 жил.
Прежде чем вносить правки в код, надо было разобраться с тем как работает сам геймпад. В геймпаде установлен сдвиговый регистр. У геймпада 5 проводов – 2 – питание, 3 информационных – Latch (Strobe), clock (Pulse) и data. При подаче логической единицы на Latch происходит сохранение состояния входов сдвигового регистра, при этом, на выходе – data сразу же доступно состояние кнопки «А», и при изменении логического уровня на линии clock на выходе – data появляются уровни напряжения, соответствующие состоянию остальных семи кнопок, в последовательном виде. Нажатой кнопке соответствует – 0, не нажатой – 1. Причём для игры всё с точностью до наоборот, необходимо делать инверсию. На рисунке ниже представлена диаграмма работы геймпада.
Далее следовал выбор эмулятора. Выбор пал на старенький fceu версии 0.98.12, так как он имеет прекрасную модульность и достаточно точно эмулирует архитектуру консоли, да и написан он на Си. Далее следовал выбор библиотеки для работы с GPIO, я выбрал bcm2835 от Майка Маккаулей, которая также написана на Си и имеет хорошее быстродействие.
Так как я нуб в программировании, то пришлось обращаться к одному из celebrity из всё тех же «Пока все играют», с просьбой прокомментировать участки кода, и ткнуть носом в те функции которые отвечают за передачу состояния кнопок игре. Мне доступным языком объяснили, что да как. И так, за эмуляцию ввода отвечает файл input.c, вот с ним и будут происходить основные изменения. Функций которые отвечают за имитацию геймпада несколько – FCEU_UpdateInput, ReadGP и DECLFW(4016), на самом деле больше, то это основные. По мимо input.c пришлось вносить изменения в файлы file.c и fceu.c. В первом случае, в файле file.c были ошибки, но эта проблема гуглится, имеется патч для этого файла, а в файле fceu.c я добавил инициализацию библиотеки bcm2835 в функции int FCEUI_Initialize(void):
bcm2835_init();
Предварительно добавив её заголовочный файл
#include <bcm2835.h>
Теперь input.c, я добавил также добавил заголовочный файл библиотеки bcm2835 (аналогично с fceu.c) и заголовочный файл библиотеки <unistd.h>, для работы с usleep. Далее я объявил порты GPIO которые будут задействованы:
#define LATCH RPI_V2_GPIO_P1_11
#define CLK RPI_V2_GPIO_P1_13
#define DATA RPI_V2_GPIO_P1_15
В функции void InitializeInput(void), я добавил код в котором прописал режим работы каждого порта GPIO, и сразу сбросил порты отвечающие за Latch (Strobe) и clock на 0.
bcm2835_gpio_fsel(LATCH, BCM2835_GPIO_FSEL_OUTP);
bcm2835_gpio_fsel(CLK, BCM2835_GPIO_FSEL_OUTP);
bcm2835_gpio_fsel(DATA, BCM2835_GPIO_FSEL_INPT);
bcm2835_gpio_set_pud(DATA, BCM2835_GPIO_PUD_UP);
bcm2835_gpio_write(CLK, LOW);
bcm2835_gpio_write(LATCH, LOW);
Теперь к функциям:
И так DECLFW(4016) — отвечает за имитацию сигнала Latch (Strobe). Как и было сказано, чтобы прочитать состояние кнопок надо подать на Latch – 1 на некоторое время. Есть переменная Laststrobe в которой записывается последнее значение записанное в данный регистр. Если Laststrobe был равен 0, то записывается логическая 1, соответственно и на контакт GPIO, который обозначен Latch, тоже подаётся 1 и через 1 мкс сбрасывается в 0. И если Laststrobe был равен 1, то этот участок кода игнорируется.
static DECLFW(B4016)
{
if (FCExp)
if (FCExp->Write)
FCExp->Write(V & 7);
if (JPorts[0]->Write)
JPorts[0]->Write(V & 1);
if(JPorts[1]->Write)
JPorts[1]->Write(V&1);
if((LastStrobe&1) && (!(V&1)))
{
/* This strobe code is just for convenience. If it were
with the code in input / *.c, it would more accurately represent
what's really going on. But who wants accuracy? ;)
Seriously, though, this shouldn't be a problem.
*/
if(JPorts[0]->Strobe)
JPorts[0]->Strobe(0);
if(JPorts[1]->Strobe)
JPorts[1]->Strobe(1);
if(FCExp)
if(FCExp->Strobe)
FCExp->Strobe();
}
if (LastStrobe==0)
{
bcm2835_gpio_write(LATCH, HIGH);
usleep(1);
bcm2835_gpio_write(LATCH, LOW);
}
LastStrobe=V&0x1;
}
Ну и теперь сам опрос джойстика, void FCEU_UpdateInput(void) — в этой функции происходит чтение данных из драйверов ввода, которые были выбраны при конфигурации эмулятора, или при его запуске вводом определённых ключей, например — геймпад, поверпад, световой пистолет и т. д., всё то, что можно было подключить к приставке. В ней формируются байты состояния кнопок геймпадов joy[0]...joy[3], в количестве от 2 до 4, так как можно включить эмуляцию приблуды для подключения ещё 2-х геймпадов. Вот в ней и произошли основные изменения. Так как мне не надо использовать возможность работы с 4 геймпадами и получать данные от других драйверов, то я выкинул весь код и вписал свой:
joy[0] = 0;
joy[1] = 0;
for (i = 0; i <= 7; i++)
{
joy[0] ^= bcm2835_gpio_lev(DATA) << i;
joy[0] ^= (1 << i);
joy[1] ^= bcm2835_gpio_lev(DATA) << i;
joy[1] ^= (1 << i);
bcm2835_gpio_write(CLK, HIGH);
usleep(1);
bcm2835_gpio_write(CLK, LOW);
usleep(1);
}
Причем я формирую сразу два байта, соответственно первого и второго джойстика. Так как многие игры считывают состояние кнопок с 2-х портов одновременно, для них нет понятия приоритетного порта. Но есть и игры для которых такое понятие существует – это например, все Марио, Кирби, Терминатор 2 и т.д. То есть они считывают состояние кнопок только с первого порта (в Марио для первого игрока, для второго только со второго), то есть с регистра 4016. Так же важно присвоить значение нуля при вызове этой функции, иначе в них будет сохраняться предыдущее значение, а новое уже будет накладываться на них. В принципе, можно было оставить байт для второго джойстика равным нулю, но я сделал так, чтобы была возможность играть в Марио вдвоём.
ReadGP — в ней уже происходит выделение битов из байтов joy[0]...joy[3], и переменная ret возвращает игре состояние конкретной кнопки в данный момент, номер кнопки задаётся переменной joy_readbit[w], где w — номер порта джойстика, первый или второй. Но в этой функции никаких изменений я не вносил. Оставил как есть.
Для успешной компиляции, в Makefile (формируется после выполнения команды Configure), который находится в каталоге src, нужно добавить -lbcm2835 -lm -lrt в то место, где прописываются зависимости от библиотек. Строчка:
LIBS =
И в общем всё заработало. Я оставил задел, если вдруг решу прикупить второй джойстик, чтобы поиграть вдвоём в те же танчики.
» Ссылка на пруф
» Использовались данные с сайта
» Отдельное спасибо вот этому человеку, который помог разобраться в коде эмулятора
Автор: lisovsky1