Геймпад от Sega Mega Drive и Raspberry Pi Часть 2 (заключительная шестикнопочная)

Ну а теперь к самому сложному и интересному. Если лень читать, то ниже (ближе к концу статьи) будет ссылка на видео, с результатом и объяснением всего, в том числе и того, что описано в первой части. Кому интересно, то
В 6-и кнопочном режиме чтение происходит за 4 цикла или фазы (если выражаться языком эмулятора). То есть, раз в 16 мс происходит циклическое (4 цикла) изменение состояния выхода Select, и каждый четвертый цикл на выходе геймпада появляется состояние дополнительных кнопок. Ниже приведена диаграмма чтения, для наглядност, которую надо повторить:

Хорошо, что у меня есть логический анализатор, при помощи которого, я выловил баг, выражавшийся в том, что цикл не выходил из четвёртой фазы.
Не буду ходить вокруг да около, сразу приведу листинг этой функции:
static u32 read_pad_6btn(int i, u32 out_bits) { u32 pad = ~PicoIn.padInt[i]; // Get inverse of pad MXYZ SACB RLDU int phase = Pico.m.padTHPhase[i]; u32 value = 0; if (i == 0 && phase == 0 && (out_bits & 0x40)) // TH { digitalWrite (Select, HIGH); delayMicroseconds (30); value ^= digitalRead(Data0) << 0; //read UP button value ^= digitalRead(Data1) << 1; //read DOWN button value ^= digitalRead(Data2) << 2; //read LEFT button value ^= digitalRead(Data3) << 3; //read RIGHT button value ^= digitalRead(Data4) << 4; //read B button value ^= digitalRead(Data5) << 5; //read C button } if (i == 0 && phase == 0 && !(out_bits & 0x40)) // TH { digitalWrite (Select, LOW); delayMicroseconds (30); value ^= digitalRead(Data0) << 0; //read UP button value ^= digitalRead(Data1) << 1; //read DOWN button value ^= digitalRead(Data4) << 4; //read A button value ^= digitalRead(Data5) << 5; //read Start button digitalWrite (Select, HIGH); delayMicroseconds (10); } if (i == 0 && phase == 1 && (out_bits & 0x40)) // TH { digitalWrite (Select, HIGH); delayMicroseconds (20); value ^= digitalRead(Data0) << 0; //read UP button value ^= digitalRead(Data1) << 1; //read DOWN button value ^= digitalRead(Data2) << 2; //read LEFT button value ^= digitalRead(Data3) << 3; //read RIGHT button value ^= digitalRead(Data4) << 4; //read B button value ^= digitalRead(Data5) << 5; //read C button } if (i == 0 && phase == 1 && !(out_bits & 0x40)) // TH { digitalWrite (Select, LOW); delayMicroseconds (30); value ^= digitalRead(Data0) << 0; //read UP button value ^= digitalRead(Data1) << 1; //read DOWN button value ^= digitalRead(Data4) << 4; //read A button value ^= digitalRead(Data5) << 5; //read Start button digitalWrite (Select, HIGH); delayMicroseconds (10); } if (i == 0 && phase == 2 && (out_bits & 0x40)) // TH { digitalWrite (Select, HIGH); delayMicroseconds (20); value ^= digitalRead(Data0) << 0; //read UP button value ^= digitalRead(Data1) << 1; //read DOWN button value ^= digitalRead(Data2) << 2; //read LEFT button value ^= digitalRead(Data3) << 3; //read RIGHT button value ^= digitalRead(Data4) << 4; //read B button value ^= digitalRead(Data5) << 5; //read C button } if (i == 0 && phase == 2 && !(out_bits & 0x40)) { digitalWrite (Select, LOW); delayMicroseconds (30); value ^= digitalRead(Data4) << 4; //read A button value ^= digitalRead(Data5) << 5; //read Start button digitalWrite (Select, HIGH); delayMicroseconds (10); } if (i == 0 && phase == 3 && (out_bits & 0x40)) { digitalWrite (Select, HIGH); delayMicroseconds (20); value ^= digitalRead(Data0) << 0; //read Z button value ^= digitalRead(Data1) << 1; //read Y button value ^= digitalRead(Data2) << 2; //read X button value ^= digitalRead(Data3) << 3; //read MODE button value ^= digitalRead(Data4) << 4; //read B button value ^= digitalRead(Data5) << 5; //read C button } if (i == 0 && phase == 3 && !(out_bits & 0x40)) { digitalWrite (Select, LOW); delayMicroseconds (30); value ^= digitalRead(Data4) << 4; //read A button value ^= digitalRead(Data5) << 5; //read Start button digitalWrite (Select, HIGH); delayMicroseconds (10); value |= 0x0f; } if (i == 1 && phase == 0 && (out_bits & 0x40)) // TH { value = pad & 0x3f; // ?1CB RLDU } if (i == 1 && phase == 0 && !(out_bits & 0x40)) // TH { value = ((pad & 0xc0) >> 2) | (pad & 3); // ?0SA 00DU } if (i == 1 && phase == 1 && (out_bits & 0x40)) // TH { value = pad & 0x3f; // ?1CB RLDU } if (i == 1 && phase == 1 && !(out_bits & 0x40)) // TH { value = ((pad & 0xc0) >> 2) | (pad & 3); // ?0SA 00DU } if (i == 1 && phase == 2 && (out_bits & 0x40)) // TH { value = pad & 0x3f; // ?1CB RLDU } if (i == 1 && phase == 2 && !(out_bits & 0x40)) { value = (pad & 0xc0) >> 2; // ?0SA 0000 } if(i == 1 && phase == 3 && (out_bits & 0x40)) { return (pad & 0x30) | ((pad >> 8) & 0xf); // ?1CB MXYZ } if(i == 1 && phase == 3 && !(out_bits & 0x40)) { return ((pad & 0xc0) >> 2) | 0x0f; // ?0SA 1111 } return value; }
Разберём любое из условий например:
if (i == 0 && phase == 1 && !(out_bits & 0x40)) // TH
Здесь проверяется, что читаем с первого геймпада (i == 0), вторая фаза чтения (phase == 1), и вывод Select надо установить в 0 !(out_bits & 0x40). Чтобы понять как это устроено в эмуляторе, я скомпилировал код на Xubuntu, и Visual Studio Code, наставив кучу точек останова запускал код в режиме отладки. В результате получается вот такая красивая картинка:
Собственно результат работы вот:
Тут надо сказать пару слов про сам эмулятор. Или я в чём-то не разобрался, или это баг, но эмулятор изначально загружается в 3-х кнопочном режиме, даже если в глобальных настройках указано обратное. Для 99% игр этого достаточно. Для того, чтобы войти в режим работы с 6-и кнопочным геймпадом, надо выйти в настройки и зайти в игру назад, ничего не меняя.
Но есть одна игра, которая находится вне этого контекста, это Lost Vikings, в ней прекрасно работают кнопки X, Z, MODE без каких-либо плясок.
P.S.
Но можно поступить еще проще, один добрый человек уже написал драйвер для работы с геймпадом, причем на очень низком уровне. Мне до такого еще далеко.
Спасибо за внимание
Источник
Ubisoft открыла новые вакансии для продолжения разработки ремейка Splinter Cell
Главное за 29 сентября: противостояние американской чиновницы с геймерами и возвращение The Last of Us
Для Satisfactory анонсировали первое дополнение Core Values и показали его тизер
Моддер добавил в Minecraft новое измерение под названием Сифт
JoyToy показала распаковку фигурки Малогарста Кривого из легиона Сынов Хоруса
Iron Studios открыла предзаказ на статую Шанг Цунга из классической Mortal Kombat
Создатель оригинальной God of War раскритиковал авторов God of War Laufey за отсутствие страсти к играм
Бывший креативный директор Halo призвал Activision вернуть уволенных разработчиков для работы над серией