Наши Китайские друзья, собрали его с девизом — качества нет, но вы держитесь. Сразу же был выпаян родной кабель с сечением жил в 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 =
И в общем всё заработало. Я оставил задел, если вдруг решу прикупить второй джойстик, чтобы поиграть вдвоём в те же танчики.
» Ссылка на пруф
» Использовались данные с сайта
» Отдельное спасибо вот этому человеку, который помог разобраться в коде эмулятора
Комментарии (30)
RussDragon
15.10.2016 16:07+5Минусуйте меня полностью. Не джойстик. Геймпад.
Lertmind
15.10.2016 18:20Тут есть интересный момент, в английском языке правильное название controller, но как будет правильно в русском языке зависит от официального перевода. Не знаю на счёт NES (он официально не продавался в России), но замечу что Xbox 360 Controller — «геймпад», а контроллер от PS — «контроллер». Узнал из статьи на хабре: Локализация консольных игр: между контроллером и геймпадом.
Rumlin
15.10.2016 18:35С «джойстиками» вообще интересно, в тот период (90-е) когда они входили в оборот русского языка было еще одно конкурентное слово — «кнюппель», а кто пользовался ZX Spectrum знали слово «кемпстон».
Lertmind
15.10.2016 19:20«Кемпстон» получается было тогда нарицательным. Слово «кнюппель» похоже до сих пор используется в авиации.
Rumlin
15.10.2016 21:39+1Да «кнюппель» обитал в тех местах, где была авиация, ПВО либо оборонная промышленность этой специализации. Никогда не встречал это слова пока не увидел на Youtube интервью с водителем Лунохода В.Довгань, где он объяснял как помощью «кнюппеля» он управлял. Некоторое время гуглил это новое слово пока не нашел, что оно пришло от захваченных у немцев систем ПВО с дистанционно управляемыми ракетами. А параллельно выяснилось что «кнюппель» — это компьютерный джойстик и джойстик мобильных телефонов в некоторых регионах.
SADKO
15.10.2016 22:46+1«Кемпстон» было нарицательным для устройств на 31 порту, а были ещё «Синклер» джойстики, цеплявшиеся к цифрам клавиатуры…
Потом, когда по ползли приставки, геймпэды, народ по привычке называл джойстиками.Rumlin
16.10.2016 08:40На массовых Dendy порты были подписаны Joystick 1 и Joystick 2. Потому называть как-то по другому было бы нелогично.
FirsofMaxim
16.10.2016 19:20+1На пикабушке есть замечательный пост по теме — http://pikabu.ru/story/igrovaya_pristavka_svoimi_rukami_4546616
SargeT
16.10.2016 19:27+1Я прошу прощения, не граммар наци ни разу, но пунктуацию мозг подсознательно всё-таки использует во время чтения, и
пришлось обращаться к одному из celebrity из всё тех же «Пока все играют», с просьбой прокомментировать участки кода, и ткнуть носом
не могло не прочтись как «пришлось обращаться к celebrity и ткнуть его носом, чтобы заставить прокомментировать» :)
В личку написать не могу как рид энд коммент, так что можете просто не аппрувить этот комментарий, просто поправить или проигнорировать :)
lisovsky1
16.10.2016 19:30Я понимаю, когда мозг подсознательно переставляет или подставляет буквы в слова, но вот чтобы он подставлял или переставлял слова в предложении:). Сижу вот думаю, как исправить.
VBKesha
Мне кажется что сдесь можно использовать SPI для чтения байта. Разве что Latch надо будет через GPIO делать, хотя можно попробовать использовать CS.
NikitosZs
Проблему смотрите не в CS, а в бите кнопки А.
VBKesha
У SPI часто бывают по варианты по какому фрону вести захват данных по верхнему или по нижнему. Если поставить по нижнему, то вполне может получится. На днях попробую.
NikitosZs
Я не вижу никакого тактового фронта при бите кнопки А.
VBKesha
Ну потому что вы смотрите на эту картинку с этой датаграмой. А если отойти от картинки и поэкспериментировать, то выясняется вот что.
1. Пока Latch в верхнем уровне регистр не защёлкнут что хорошо видно по тому что наатие на A сразу попадает на линию Data(нажимаем кнопку там один отпускаем ноль)
2. Защёлкивание происходит когда латч переходит с верхнего уровня в нижний.
3. Смена бита на линии дата происходит при смене уровня линии CLK(Pulse) из нижнего в верхний.
(это всё проверено на реальном джойстике)
В итоге если SPI настроен так что хватает данные при переходе по CLK из верхнего уровня в нижний, то по идее можно получить вариант что на момент старта CLK в верхнем уровне CS уходит в нижний. SPI опускает CLK и хватает первый бит который как раз A.
NikitosZs
Вот оно как. Но думаю не выйдет, ибо наверняка будет по барабану джойстику 1 на LE или ноль. Он работает по переднему фронту. Т.е формально можно поставить режим cs и всё будет нормально, но без бита А.
VBKesha
Проверил только что, всё рабоатет. Правда проверял не на Raspberry а на DE0-Nano-SoC. Если интересно вот датаграммы.
Ничего не нажато:
Нажата A:
Нажата A+RIGHT:
Нажата B+RIGHT:
Данные снял только что на дендевском джойстике. В качестве Lacth использовал CS для SPI в программе тоже отлично прочиталось.
Джойстик нормально тянет скорость гед то до 2.2MHz потом начинает не успевать за клоком.
Andruwkoo
не подскажете, что за симулятор?
NikitosZs
Это копеечный логический анализатор saleae logic =) Наверное, копеечный. А может и оригинал за кучу денег.
Присмотрелся, 50МГц, это оригинал…
VBKesha
Это не симулятор это логический анализатор. Вот тут делал обзор.
https://geektimes.ru/post/280100/
NikitosZs
Интересно, супер. (Нафиг не нужно мне это, но реально интересно) Я думал о том, чтобы держать CLK в '1', но то ли отвлёкся, то ли не знаю что.
Почему на последних двух скриншотах Data выставляется невпопад с CLK? Я бы понял, если бы загнали частоту слишком большую и было запаздывание, но на последнем видно, что фронты как бы идут на опережение.
VBKesha
Там много веселых моментов, как идут данные очень зависит от количества нажатых кнопок. То есть когда нажата одна кнопка картинка идеальная если две всё чуть плывет, но считывание всё равно происходит как надо. Нужно было сразу аналог снять на всякий там может быть картина понятней бы стала. Но по частоте тут 2MHz снято, на 2.4 данные уже нормально не считывались. Так что по частоте возможно уже на пределе. Надо глянуть на какой реальная денди это делает.
NikitosZs
Спасибо, было интересно.
lisovsky1
Я немного не в курсе. У SPI регулируется частота на линии Clock? У сдвиговых регистров, которые применяются в оригинальных геймпадах есть ограничение по частоте, что-то вроде 5 МГц, если правильно помню.
NikitosZs
«Частота» тактового сигнала может быть даже нестабильной, сигнал может быть со скважностью отличной от 2.
VBKesha
У всех что я видел регулировалась делителем.
VBKesha
Проверил мой джойстик тянул где то 2.2MHz дальше начинает явно не успевать.