Праздники подходят к концу, а значит пора пожалеть печень и включить голову. Вот и мне пришла в голову очередная идея. После того, как я подключил геймпад от Dendy (он же джойстик, он же контроллер, он же кнюппель, он же игровой пульт и т. д.) geektimes.ru/post/281520, я задумался о подключении второго к Raspberry pi. Второе барахло с заедающими кнопками покупать не хотелось, и тут как раз кстати вывалили на прилавки Nintendo Classic Mini, ну как вывалили — хрен купишь. Самой цели покупать эмулятор за 4К не было, а вот геймпад я и решил купить. Благо мне удалось его купить, был последний в магазине. Те, кому интересно, что из этого получилось, могут ткнуть мышкой по кнопке ниже.
Вот прямая ссылка на пруф, если не активируется штатная.

Нет никакого секрета в том, что данный геймпад можно подключить и к Wii и к Wii U, а значит опросить его можно по интерфейсу i2c. Что отрадно, то что геймпад питается от 3,3 V, а значит не надо заморачиваться с согласованием уровней напряжения. Для подключения геймпада к Raspberry pi, я решил купить на алиэкспресс коннекторы для wiimote, которые распаиваются на плате у него. Хоть в упаковке было 2 коннектора, и они были в общей пупырке, один из коннекторов пришел в аварийном состоянии. Без жестянки тут не обойтись. После того, как все было припаяно, было необходимо проверить, а работает ли это. Для этого я решил использовать утилиты из пакета i2c-tools. По идее должно было определиться устройство по адресу — 52. Запустив i2cdetect я увидел заветное:
На сердце сразу стало теплее (как будто весной на улице тебе улыбнулась незнакомая девушка), значит оно работает. Далее нужно было погуглить о подключении периферии к Wii. Я нашел пример того, как к Raspberry pi подключается нунчак от Wii. Из данного примера, я узнал, что геймпад надо проинициализировать, записав в регист 0x40 ноль, а потом перед каждым чтением надо просто записывать ноль и читать первые 6 байт.
В данном, случае я так же встал перед выбором библиотеки для работы i2c. Так как с библиотекой bcm2835 у меня ничего не получилось, и я решил использовать ту библиотеку, которая использована в примере — это библиотека WirinPi. С ней всё получилось. Опытным путём я выяснил, что информация о кнопках содержится в 4 и 5 байтах (если считать байты с нулевого). Информация о кнопках select, start, down, right — в четвертом байте, а информация о кнопках A, B, up, left — в пятом байте. Причем информация о кнопках select, down, right находится в старших 4-х битах байта, а информация о кнопке start — в младших. То же самое и 5-м байте, информация о кнопках A, B находится в старших 4-х битах байта, а информация о кнопках up, left — в младших. Вот коды кнопок: A — 0xcf, B — 0xbf, up — 0xf0, left — 0xf1, select — 0xcf, start — 0xf3, down — 0xbf, right — 0x7f. Результатом совместного нажатия кнопок, информация о которых находится в одном байте и в одних битах — это логическое «И» их кодов. Так например совместное нажатие кнопок А и В даёт значение 0x8f.
Вот ссылка на таблицу:

Далее я написал небольшую тестовую программку для считывания кнопок, ниже приведу её листинг полностью и объясню, что там с чем:
В самом начале объявляются переменные, при помощи которых будет производится определения состояния конкретной кнопки. Важно, чтобы переменные имели тип char, иначе при побитовом сдвиге влево, просто добавятся 4 нуля, и всё. Интерпритация будет неправильная. Как бы это смешно не звучало, но надо использовать чары.
Далее в основном теле программы происходит инициализация библиотеки WiringPi — wiringPiSetup();, а следом задаётся адрес i2c устройства — 0x52. Далее происходит инициализация геймпада:
Ну а далее уже в теле цикла происходит чтение 6 байт, а перед чтением, каждый раз записывается ноль. И после этого происходит определение нажатой кнопки. В общем весь этот код и перекочевал в файлы эмулятора.
Как и в прошлый раз инициализация библиотеки прописана в файле fceu.c:
Ну а далее все изменения касаются только файла input.c. Первоначально в функции int FCEUI_Initialize(void), задаются режимы работы gpio портов для работы со старым геймпадом (от Simba's), и производится инициализация геймпада.
В начале, нужно также объявить переменные, при помощи которых будет определяться нажатие кнопок.
В функции static DECLFW(B4016), происходит подача строба для старого геймпада, новому строб не нужен:
При создании тестовой программки, я не смог решить проблему с определения совместно нажатых кнопок, информация о которых находится в одном байте и в одних битах. В итоге я применил конструкцию switch case, вместо if else.
Ну и сама функция функция чтения состояния кнопок:
Я в одном цикле совместил чтение кнопок с обоих геймпадов, ну подумаешь, что считывается 2 лишних байта, это не имеет значения, зато происходит всё одновременно. И нет никаких задержек.
В общем всё хорошо.
P.S: Как вспомню, что завтра на работу, аж передёргивает.
P.P.S Забыл добавить, что для успешной компиляции, в Makefile (формируется после выполнения команды Configure), который находится в каталоге src, нужно добавить -lwiringPi -lm -lrt в то место, где прописываются зависимости от библиотек. Строчка:
Вот прямая ссылка на пруф, если не активируется штатная.

Нет никакого секрета в том, что данный геймпад можно подключить и к Wii и к Wii U, а значит опросить его можно по интерфейсу i2c. Что отрадно, то что геймпад питается от 3,3 V, а значит не надо заморачиваться с согласованием уровней напряжения. Для подключения геймпада к Raspberry pi, я решил купить на алиэкспресс коннекторы для wiimote, которые распаиваются на плате у него. Хоть в упаковке было 2 коннектора, и они были в общей пупырке, один из коннекторов пришел в аварийном состоянии. Без жестянки тут не обойтись. После того, как все было припаяно, было необходимо проверить, а работает ли это. Для этого я решил использовать утилиты из пакета i2c-tools. По идее должно было определиться устройство по адресу — 52. Запустив i2cdetect я увидел заветное:
i2cdetect -y 1 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: 03 04 05 06 07 -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- 52 -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --
На сердце сразу стало теплее (как будто весной на улице тебе улыбнулась незнакомая девушка), значит оно работает. Далее нужно было погуглить о подключении периферии к Wii. Я нашел пример того, как к Raspberry pi подключается нунчак от Wii. Из данного примера, я узнал, что геймпад надо проинициализировать, записав в регист 0x40 ноль, а потом перед каждым чтением надо просто записывать ноль и читать первые 6 байт.
В данном, случае я так же встал перед выбором библиотеки для работы i2c. Так как с библиотекой bcm2835 у меня ничего не получилось, и я решил использовать ту библиотеку, которая использована в примере — это библиотека WirinPi. С ней всё получилось. Опытным путём я выяснил, что информация о кнопках содержится в 4 и 5 байтах (если считать байты с нулевого). Информация о кнопках select, start, down, right — в четвертом байте, а информация о кнопках A, B, up, left — в пятом байте. Причем информация о кнопках select, down, right находится в старших 4-х битах байта, а информация о кнопке start — в младших. То же самое и 5-м байте, информация о кнопках A, B находится в старших 4-х битах байта, а информация о кнопках up, left — в младших. Вот коды кнопок: A — 0xcf, B — 0xbf, up — 0xf0, left — 0xf1, select — 0xcf, start — 0xf3, down — 0xbf, right — 0x7f. Результатом совместного нажатия кнопок, информация о которых находится в одном байте и в одних битах — это логическое «И» их кодов. Так например совместное нажатие кнопок А и В даёт значение 0x8f.
Вот ссылка на таблицу:

Далее я написал небольшую тестовую программку для считывания кнопок, ниже приведу её листинг полностью и объясню, что там с чем:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <wiringPi.h> #include <wiringPiI2C.h> #include <errno.h> char i, a, b, c, d, state; char bytes[6]; int main(void) { wiringPiSetup(); int fd = wiringPiI2CSetup(0x52); wiringPiI2CWriteReg8(fd, 0x40, 0x00); delayMicroseconds(20); while(1) { state = 0; wiringPiI2CWrite(fd, 0x00); delayMicroseconds(100); for (i=0; i<6; i++) { bytes[i] = wiringPiI2CRead(fd); } a = bytes[5] >> 4; b = bytes[5] << 4; c = bytes[4] >> 4; d = bytes[4] << 4; if (a == 0xc) state ^= (1 << 0); if (a == 0xb) state ^= (1 << 1); if (c == 0xc) state ^= (1 << 2); if (d == 0x30) state ^= (1 << 3); if (b == 0x00) state ^= (1 << 4); if (c == 0xb) state ^= (1 << 5); if (b == 0x10) state ^= (1 << 6); if (c == 0x7) state ^= (1 << 7); printf("%x \n", state); } return 0; }
В самом начале объявляются переменные, при помощи которых будет производится определения состояния конкретной кнопки. Важно, чтобы переменные имели тип char, иначе при побитовом сдвиге влево, просто добавятся 4 нуля, и всё. Интерпритация будет неправильная. Как бы это смешно не звучало, но надо использовать чары.
Далее в основном теле программы происходит инициализация библиотеки WiringPi — wiringPiSetup();, а следом задаётся адрес i2c устройства — 0x52. Далее происходит инициализация геймпада:
wiringPiI2CWriteReg8(fd, 0x40, 0x00); delayMicroseconds(20);
Ну а далее уже в теле цикла происходит чтение 6 байт, а перед чтением, каждый раз записывается ноль. И после этого происходит определение нажатой кнопки. В общем весь этот код и перекочевал в файлы эмулятора.
Как и в прошлый раз инициализация библиотеки прописана в файле fceu.c:
int FCEUI_Initialize(void) { if(!FCEU_InitVirtualVideo()) return 0; memset(&FSettings,0,sizeof(FSettings)); FSettings.UsrFirstSLine[0]=8; FSettings.UsrFirstSLine[1]=0; FSettings.UsrLastSLine[0]=231; FSettings.UsrLastSLine[1]=239; FSettings.SoundVolume=100; FCEUPPU_Init(); X6502_Init(); wiringPiSetup(); return 1; }
Ну а далее все изменения касаются только файла input.c. Первоначально в функции int FCEUI_Initialize(void), задаются режимы работы gpio портов для работы со старым геймпадом (от Simba's), и производится инициализация геймпада.
pinMode (0, OUTPUT); pinMode (2, OUTPUT); pinMode (3, INPUT); digitalWrite (0, HIGH); digitalWrite (2, LOW); fd = wiringPiI2CSetup(0x52); wiringPiI2CWriteReg8(fd, 0x40, 0x00); usleep(20);
В начале, нужно также объявить переменные, при помощи которых будет определяться нажатие кнопок.
В функции static DECLFW(B4016), происходит подача строба для старого геймпада, новому строб не нужен:
if (LastStrobe==0) { digitalWrite (0, LOW); }
При создании тестовой программки, я не смог решить проблему с определения совместно нажатых кнопок, информация о которых находится в одном байте и в одних битах. В итоге я применил конструкцию switch case, вместо if else.
Ну и сама функция функция чтения состояния кнопок:
void FCEU_UpdateInput(void) { joy[0] = 0; joy[1] = 0xff; wiringPiI2CWrite(fd, 0x00); usleep(100); for (i = 0; i <= 7; i++) { bytes[i] = wiringPiI2CRead(fd); joy[1] ^= digitalRead(3) << i; digitalWrite (2, HIGH); delayMicroseconds (20); digitalWrite (2, LOW); delayMicroseconds (20); } a = bytes[5] >> 4; b = bytes[5] << 4; c = bytes[4] >> 4; d = bytes[4] << 4; switch (a){ case 0xc: joy[0] ^= (1 << 0); break; case 0xb: joy[0] ^= (1 << 1); break; case 0x8: joy[0] ^= (1 << 0); joy[0] ^= (1 << 1); break; } switch (b){ case 0x00: joy[0] ^= (1 << 4); break; case 0x10: joy[0] ^= (1 << 6); break; case 0x20: joy[0] ^= (1 << 4); joy[0] ^= (1 << 6); break; } switch (c){ case 0xc: joy[0] ^= (1 << 2); break; case 0xb: joy[0] ^= (1 << 5); break; case 0x7: joy[0] ^= (1 << 7); break; case 0x8: joy[0] ^= (1 << 2); joy[0] ^= (1 << 5); break; case 0x4: joy[0] ^= (1 << 2); joy[0] ^= (1 << 7); break; case 0x3: joy[0] ^= (1 << 5); joy[0] ^= (1 << 7); break; } switch (d){ case 0x30: joy[0] ^= (1 << 3); break; } digitalWrite (0, HIGH); }
Я в одном цикле совместил чтение кнопок с обоих геймпадов, ну подумаешь, что считывается 2 лишних байта, это не имеет значения, зато происходит всё одновременно. И нет никаких задержек.
В общем всё хорошо.
P.S: Как вспомню, что завтра на работу, аж передёргивает.
P.P.S Забыл добавить, что для успешной компиляции, в Makefile (формируется после выполнения команды Configure), который находится в каталоге src, нужно добавить -lwiringPi -lm -lrt в то место, где прописываются зависимости от библиотек. Строчка:
LIBS =
