Однажды, насмотревшись всяких «Пока все играют», мне тоже захотелось поиграть на своём 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 =

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

» Ссылка на пруф
» Использовались данные с сайта
» Отдельное спасибо вот этому человеку, который помог разобраться в коде эмулятора
Поделиться с друзьями
-->

Комментарии (30)


  1. VBKesha
    15.10.2016 15:30
    +1

    Мне кажется что сдесь можно использовать SPI для чтения байта. Разве что Latch надо будет через GPIO делать, хотя можно попробовать использовать CS.


    1. NikitosZs
      15.10.2016 17:25

      Проблему смотрите не в CS, а в бите кнопки А.


      1. VBKesha
        16.10.2016 01:12

        У SPI часто бывают по варианты по какому фрону вести захват данных по верхнему или по нижнему. Если поставить по нижнему, то вполне может получится. На днях попробую.


        1. NikitosZs
          16.10.2016 12:03

          Я не вижу никакого тактового фронта при бите кнопки А.


          1. VBKesha
            16.10.2016 12:24

            Ну потому что вы смотрите на эту картинку с этой датаграмой. А если отойти от картинки и поэкспериментировать, то выясняется вот что.
            1. Пока Latch в верхнем уровне регистр не защёлкнут что хорошо видно по тому что наатие на A сразу попадает на линию Data(нажимаем кнопку там один отпускаем ноль)
            2. Защёлкивание происходит когда латч переходит с верхнего уровня в нижний.
            3. Смена бита на линии дата происходит при смене уровня линии CLK(Pulse) из нижнего в верхний.
            (это всё проверено на реальном джойстике)

            В итоге если SPI настроен так что хватает данные при переходе по CLK из верхнего уровня в нижний, то по идее можно получить вариант что на момент старта CLK в верхнем уровне CS уходит в нижний. SPI опускает CLK и хватает первый бит который как раз A.


            1. NikitosZs
              16.10.2016 20:53

              Вот оно как. Но думаю не выйдет, ибо наверняка будет по барабану джойстику 1 на LE или ноль. Он работает по переднему фронту. Т.е формально можно поставить режим cs и всё будет нормально, но без бита А.


              1. VBKesha
                17.10.2016 16:05
                +1

                Проверил только что, всё рабоатет. Правда проверял не на Raspberry а на DE0-Nano-SoC. Если интересно вот датаграммы.
                Ничего не нажато:

                Нажата A:

                Нажата A+RIGHT:

                Нажата B+RIGHT:


                Данные снял только что на дендевском джойстике. В качестве Lacth использовал CS для SPI в программе тоже отлично прочиталось.
                Джойстик нормально тянет скорость гед то до 2.2MHz потом начинает не успевать за клоком.


                1. Andruwkoo
                  18.10.2016 18:35

                  не подскажете, что за симулятор?


                  1. NikitosZs
                    19.10.2016 01:01
                    +1

                    Это копеечный логический анализатор saleae logic =) Наверное, копеечный. А может и оригинал за кучу денег.
                    Присмотрелся, 50МГц, это оригинал…


                  1. VBKesha
                    19.10.2016 09:45

                    Это не симулятор это логический анализатор. Вот тут делал обзор.
                    https://geektimes.ru/post/280100/


                1. NikitosZs
                  19.10.2016 01:11

                  Интересно, супер. (Нафиг не нужно мне это, но реально интересно) Я думал о том, чтобы держать CLK в '1', но то ли отвлёкся, то ли не знаю что.
                  Почему на последних двух скриншотах Data выставляется невпопад с CLK? Я бы понял, если бы загнали частоту слишком большую и было запаздывание, но на последнем видно, что фронты как бы идут на опережение.


                  1. VBKesha
                    19.10.2016 09:51

                    Там много веселых моментов, как идут данные очень зависит от количества нажатых кнопок. То есть когда нажата одна кнопка картинка идеальная если две всё чуть плывет, но считывание всё равно происходит как надо. Нужно было сразу аналог снять на всякий там может быть картина понятней бы стала. Но по частоте тут 2MHz снято, на 2.4 данные уже нормально не считывались. Так что по частоте возможно уже на пределе. Надо глянуть на какой реальная денди это делает.


                    1. NikitosZs
                      20.10.2016 11:18

                      Спасибо, было интересно.


    1. lisovsky1
      16.10.2016 20:57

      Я немного не в курсе. У SPI регулируется частота на линии Clock? У сдвиговых регистров, которые применяются в оригинальных геймпадах есть ограничение по частоте, что-то вроде 5 МГц, если правильно помню.


      1. NikitosZs
        16.10.2016 22:14

        «Частота» тактового сигнала может быть даже нестабильной, сигнал может быть со скважностью отличной от 2.


      1. VBKesha
        16.10.2016 23:07

        У всех что я видел регулировалась делителем.


      1. VBKesha
        17.10.2016 16:06

        Проверил мой джойстик тянул где то 2.2MHz дальше начинает явно не успевать.


  1. RussDragon
    15.10.2016 16:07
    +5

    Минусуйте меня полностью. Не джойстик. Геймпад.


    1. nikitosk
      15.10.2016 17:08
      -7

      полностью не получитлось


    1. Lertmind
      15.10.2016 18:20

      Тут есть интересный момент, в английском языке правильное название controller, но как будет правильно в русском языке зависит от официального перевода. Не знаю на счёт NES (он официально не продавался в России), но замечу что Xbox 360 Controller — «геймпад», а контроллер от PS — «контроллер». Узнал из статьи на хабре: Локализация консольных игр: между контроллером и геймпадом.


      1. Rumlin
        15.10.2016 18:35

        С «джойстиками» вообще интересно, в тот период (90-е) когда они входили в оборот русского языка было еще одно конкурентное слово — «кнюппель», а кто пользовался ZX Spectrum знали слово «кемпстон».


        1. Lertmind
          15.10.2016 19:20

          «Кемпстон» получается было тогда нарицательным. Слово «кнюппель» похоже до сих пор используется в авиации.


          1. Rumlin
            15.10.2016 21:39
            +1

            Да «кнюппель» обитал в тех местах, где была авиация, ПВО либо оборонная промышленность этой специализации. Никогда не встречал это слова пока не увидел на Youtube интервью с водителем Лунохода В.Довгань, где он объяснял как помощью «кнюппеля» он управлял. Некоторое время гуглил это новое слово пока не нашел, что оно пришло от захваченных у немцев систем ПВО с дистанционно управляемыми ракетами. А параллельно выяснилось что «кнюппель» — это компьютерный джойстик и джойстик мобильных телефонов в некоторых регионах.


          1. SADKO
            15.10.2016 22:46
            +1

            «Кемпстон» было нарицательным для устройств на 31 порту, а были ещё «Синклер» джойстики, цеплявшиеся к цифрам клавиатуры…

            Потом, когда по ползли приставки, геймпэды, народ по привычке называл джойстиками.


            1. Rumlin
              16.10.2016 08:40

              На массовых Dendy порты были подписаны Joystick 1 и Joystick 2. Потому называть как-то по другому было бы нелогично.


  1. Valery4
    16.10.2016 14:52

    Cluster есть и на GT
    ClusterM


  1. FirsofMaxim
    16.10.2016 19:20
    +1

    На пикабушке есть замечательный пост по теме — http://pikabu.ru/story/igrovaya_pristavka_svoimi_rukami_4546616


    1. lisovsky1
      16.10.2016 21:15

      Я думаю, что и на первом RPI можно такое организовать.


  1. SargeT
    16.10.2016 19:27
    +1

    Я прошу прощения, не граммар наци ни разу, но пунктуацию мозг подсознательно всё-таки использует во время чтения, и

    пришлось обращаться к одному из celebrity из всё тех же «Пока все играют», с просьбой прокомментировать участки кода, и ткнуть носом

    не могло не прочтись как «пришлось обращаться к celebrity и ткнуть его носом, чтобы заставить прокомментировать» :)

    В личку написать не могу как рид энд коммент, так что можете просто не аппрувить этот комментарий, просто поправить или проигнорировать :)


  1. lisovsky1
    16.10.2016 19:30

    Я понимаю, когда мозг подсознательно переставляет или подставляет буквы в слова, но вот чтобы он подставлял или переставлял слова в предложении:). Сижу вот думаю, как исправить.