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

Геймпад от 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.
Но можно поступить еще проще, один добрый человек уже написал драйвер для работы с геймпадом, причем на очень низком уровне. Мне до такого еще далеко.
Спасибо за внимание

 
Источник

diy или сделай сам, gpio, raspberry pi, программирование

Читайте также