Как стать автором
Обновить

Подключаем джойстик от Dendy к Raspberry pi

Время на прочтение5 мин
Количество просмотров19K
Однажды, насмотревшись всяких «Пока все играют», мне тоже захотелось поиграть на своём Raspberry pi. Да не просто поиграть, а поиграть используя реальное устройство. Для чего в переходе метрополитена за 150 рублей был куплен джойстик от Денди (ну не от денди, а Симбас Юниор). Те, кому интересно, что из этого получилось, могут ткнуть мышкой по кнопке ниже. В конце статьи будет ссылка на пруф.



Наши Китайские друзья, собрали его с девизом — качества нет, но вы держитесь. Сразу же был выпаян родной кабель с сечением жил в 2 кв мкм, и заменён на кабель от какого-то промышленного преобразователя интерфейсов, доставшегося после очередного ПНР, в нём как раз было 5 жил.

Прежде чем вносить правки в код, надо было разобраться с тем как работает сам геймпад. В геймпаде установлен сдвиговый регистр. У геймпада 5 проводов – 2 – питание, 3 информационных – Latch (Strobe), clock (Pulse) и data. При подаче логической единицы на Latch происходит сохранение состояния входов сдвигового регистра, при этом, на выходе – data сразу же доступно состояние кнопки «А», и при изменении логического уровня на линии clock на выходе – data появляются уровни напряжения, соответствующие состоянию остальных семи кнопок, в последовательном виде. Нажатой кнопке соответствует – 0, не нажатой – 1. Причём для игры всё с точностью до наоборот, необходимо делать инверсию. На рисунке ниже представлена диаграмма работы геймпада.

image

Далее следовал выбор эмулятора. Выбор пал на старенький 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 =

И в общем всё заработало. Я оставил задел, если вдруг решу прикупить второй джойстик, чтобы поиграть вдвоём в те же танчики.

» Ссылка на пруф
» Использовались данные с сайта
» Отдельное спасибо вот этому человеку, который помог разобраться в коде эмулятора
Теги:
Хабы:
Всего голосов 33: ↑32 и ↓1+31
Комментарии30

Публикации

Истории

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань