Продолжаем, самое легкое позади! А теперь к самому сложному и интересному. Если лень читать, то ниже (ближе к концу статьи) будет ссылка на видео, с результатом и объяснением всего, в том числе и того, что описано в первой части. Кому интересно, то прошу следовать далее.

В 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.

Но можно поступить еще проще, один добрый человек уже написал драйвер для работы с геймпадом, причем на очень низком уровне. Мне до такого еще далеко.

Спасибо за внимание!

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


  1. Alexufo
    04.01.2019 17:14

    Эх были времена, когда все это делалось через LTP или COM порты)


    1. lisovsky1 Автор
      04.01.2019 17:32

      Да, были). Жаль, что тогда у меня не было ПК. Хотя стоп, у меня же сейчас ПК с LPT. А вот про COM-порт можно подробнее?


      1. Alexufo
        04.01.2019 17:52

        Как-то так. Для джойстика от PS я помню еще 9 вольт заводил для обратной отдачи.
        pinouts.ru/Game/playstation_9.shtml


        1. DeeZ
          05.01.2019 21:50

          Да, было дело. Больше всего нравилось что с этим драйвером кнопки можно ремапить как вздумается. Простое вроде действие но в серийных геймпадах не делают такого функционала.


          1. lisovsky1 Автор
            04.01.2019 23:47

            Так кнопки наверно в драйвере ремапились? Насколько помню, геймпад от соньки отдает всего лишь 2 байта с состоянием кнопок. Или я не прав?


            1. DeeZ
              05.01.2019 00:15

              Да, в драйвере, я про это и написал.


              1. lisovsky1 Автор
                05.01.2019 09:25

                Значит не так понял. Я подумал, что сам геймпад начнет в другой последовательности отдавать данные)


  1. gr33tx
    04.01.2019 23:43

    Как же так, в детстве в МК не играл автор? Хоть бы потренировался перед записью)


    1. lisovsky1 Автор
      04.01.2019 23:48

      Что не так с МК?)


    1. kprohorow
      06.01.2019 16:43

      Зачем на демонстрации показывать свои навыки?


      1. lisovsky1 Автор
        06.01.2019 22:51

        А-а-а. Да, во первых с детства уже прошло немало времени, разучился. Да и когда пишешь, больше беспокоишься о картинке, а не о игровом процессе)


  1. pixellized
    06.01.2019 22:55

    этот код
    *вырвал глаза и бросил в лес*