Controle de Super Nintendo no PC com Arduino Micro.

IMG_20151128_000144

O Super Nintendo, juntamente com o Megadrive (ou Gênesis) foram os consoles representantes da era 16-bits. Eu tive o Megadrive, mas joguei um bocado em Super Nintendo também.

Já joguei bastante em emuladores, um monte deles, incluindo os de Super Nintendo. Eu não acho que eu jogo mal no teclado, mas eu prefiro usar controles, ainda melhor se os controladores são os do console emulado. Por falar nisso, algum tempo atrás, eu construí um adaptador para ligar controladores SNES para PC a partir das instruções  nesse site de Raphaël Assénat’, usando um ATmega 8 e a biblioteca VUSB. Achei que a implementação USB difícil de entender, então eu apenas segui as instruções. O que posso dizer? Funcionou.

WP_20150310_11_05_13_Pro

Eu queria jogar Super Mario, mas não tinha esse adaptador mais. No entanto, eu tinha um Arduino Micro e placa perfurada. A coisa especial sobre o Arduino Micro, é que como o Leonardo, ele possui USB nativa, sendo capaz de ser reconhecido pelo PC como um teclado, um mouse, um joystick, ou o quer que seja. O Arduino Leonardo é provavelmente adequado para esse propósito também, como também possui uma porta USB nativa.

Nota: Os outros modelos Arduino geralmente usam outro ATmega para lidar com o protocolo USB, enquanto o chip maior é usad0 para o código. Normalmente, o usuário pode nem perceber que a placa tem dois processadores. Os modelos Micro e Leonardo tem uma porta USB no ATmega utilizado (32u4), e apenas um chip é usado para a comunicação USB e código em geral.

Hardware

Eu tive que conectar os controles no Arduino. Para isso eu usei uma veroboard e pinos macho, basicamente. Você pode ver os detalhes abaixo:

IMG_20151128_000010
Dois pinos machos foram usados pra connectar cada pino à veroboard.
IMG_20151127_235920
A minha desculpa é que a ponta desse ferro estava péssima.

A pinagem do controle de Super Nintendo é :

snes

Eu conectei da seguinte forma, mas os pinos podem ser editados em código.

Controle         Arduino

       5v                        5v

       data                      D7

       clock                     D4

        latch                     D5

        GND                 GND

Nota: Cada linha é compartilhada entre os controles, exceto a linha data, isto significa que latch, clock, 5v e GND são os mesmos pros controles conectados, enquanto as linhas de dado devem ser separadas, eu usei o pino D6 para a linha data do segundo joypad. A leitura do código pode esclarecer isso um pouco.

Biblioteca

Eu ainda não sei muito sobre o protocolo USB e sua forma de comunicação, e sempre digo a mim mesmo que vou estudar, mas nunca checo. A questão era que eu queria concluir o projeto em uma tarde. Então fui procurar uma biblioteca.

Opa, eu assumo que você tem a última versão do software Arduino, porque a biblioteca não funciona com a versão 1.6.5 ou abaixo. Se você não tiver a versão 1.6.6, por favor atualize para que possamos continuar. :)

Ok, voltando para o código. A biblioteca eu encontrei funciona muito bem, foi codificada por Matthew Heironimus, e pode ser encontrado aqui. Basta baixá-la e extrair na pasta bibliotecas dos arquivos da IDE do Arduino .

Com esta biblioteca, você pode fazer o Arduino aparecer no computador como até 3 controles. Assim, você pode programar a placa para receber os estados dos botões no controlade e enviá-los para o PC como um controlador USB HID. Eu optei por usar o código para dois controles, pois eu só tinha dois joypads de SNES de qualquer maneira.

Protocolo do controle de SNES

O protocolo de controle de SNES é bastante simples. No conector do controle, têm-se duas linhas de alimentação (5V e terra), e outras três linhas, uma para clock, um para a saida serial(data), e o outro é a linha latch. Para ler os botões, o procedimento é:

  1. Pulso em alto no pino de latch por 12uS. Isso instrui o controle a guardar o estado dos botões para a leitura.
  2. O primeiro bit(button B state) já vai estar no pino data, esse bit é lido.
  3. Troca-se o estado da linha de clock de baixo pra alto, lê-se o bit, e tráz-se a pra estado baixo novamente.
  4. Repete-se o passo 3 pra os outros 14 bits(16 bits são lidos, mas alguns sempre são ‘1’).

Como dito antes, muito simples. Mais informações podem ser encontradas nesse documento.

Código Arduino.

Finalmente. Se você já instalou a biblioteca e conectou tudo, só gravo o código abaixo e seu adaptador está pronto. Yay!

#include "Joystick.h"

int clk = 4, data1 = 7, data2 = 6, latch = 5; //two data lines, one for each controller.
uint16_t buttons1 = 0, buttons2 = 0;//the variables for each controller state
const bool testAutoSendMode = false;//we choose when to send the values over USB

#define JOYSTICK_COUNT 2
Joystick_ Joystick[JOYSTICK_COUNT] = {
 /*Joystick object structure:
  * (hidReportId, joystickType, buttonCount, hatSwitchCount, includeXAxis, includeYAxis, includeZAxis, includeRxAxis, 
  * includeRyAxis, includeRzAxis, includeRudder, includeThrottle, includeAccelerator, includeBrake, includeSteering)
  * please see the library README or repository for details
 */
  Joystick_(0x03, JOYSTICK_TYPE_JOYSTICK, 8, 0, true, true, false, false, false, false, false, false, false, false, false),
  Joystick_(0x04, JOYSTICK_TYPE_JOYSTICK, 8, 0, true, true, false, false, false, false, false, false, false, false, false)

};

void setup() {

    for (int index = 0; index < JOYSTICK_COUNT; index++)
      {
      Joystick[index].setXAxisRange(-127, 127);
      Joystick[index].setYAxisRange(-127, 127);
    
      if (testAutoSendMode)
      {
        Joystick[index].begin();
      }
      else
      {
        Joystick[index].begin(false);
      }
    }
    // Pin config
    pinMode(latch, OUTPUT);
    pinMode(clk, OUTPUT);
    pinMode(data1, INPUT);
    pinMode(data2, INPUT);

}
int readControllers() {
  digitalWrite(latch, HIGH);//latch pulse, lock the state of the buttons in the register
  delayMicroseconds(12);
  digitalWrite(latch, LOW);
  delayMicroseconds(6);
  buttons1 = 0;//zero the variables to receive new values.
  buttons2 = 0;
  buttons1 |= (digitalRead(data1) << 0); //first bit read before clock.
  buttons2 |= (digitalRead(data2) << 0);

  for (int i = 1; i < 16; i++) {//clock &read
    digitalWrite(clk, HIGH);
    delayMicroseconds(6);
    buttons1 |= (digitalRead(data1) << i);//the values are stored for each bit
    buttons2 |= (digitalRead(data2) << i);
    digitalWrite(clk, LOW);
    delayMicroseconds(6);
  }
  buttons1 = ~buttons1; //workaround, a button pressed is read as a '0'(pull ups), jus inversing them to my taste. 
  buttons2 = ~buttons2; 

}
void loop() {
  readControllers();
  //Controller 1
  if (buttons1 & (1 << 0)) {
    Joystick[0].pressButton(0); //B
  } else {
    Joystick[0].releaseButton(0);
  }
  if (buttons1 & (1 << 1)) {
    Joystick[0].pressButton(1); //Y
  } else {
    Joystick[0].releaseButton(1);
  }
  if (buttons1 & (1 << 2)) {
    Joystick[0].pressButton(2); //Select
  } else {
    Joystick[0].releaseButton(2);
  }
  if (buttons1 & (1 << 3)) {
    Joystick[0].pressButton(3); //Start
  } else {
    Joystick[0].releaseButton(3);
  }

  if (buttons1 & (1 << 4)) {
    Joystick[0].setYAxis(-127); //Up and Down, -127=total up, 127= total down
  } else if (buttons1 & (1 << 5)) {
    Joystick[0].setYAxis(127);
  } else {
    Joystick[0].setYAxis(0); //the value zero, means the stick is stopped
  }
  if (buttons1 & (1 << 6)) {
    Joystick[0].setXAxis(-127); //Right left
  } else if (buttons1 & (1 << 7)) {
    Joystick[0].setXAxis(127);
  } else {
    Joystick[0].setXAxis(0);
  }


  if (buttons1 & (1 << 8)) {
    Joystick[0].pressButton(4); //A
  } else {
    Joystick[0].releaseButton(4);
  }
  if (buttons1 & (1 << 9)) {
    Joystick[0].pressButton(5); //X
  } else {
    Joystick[0].releaseButton(5);
  }
  if (buttons1 & (1 << 10)) {
    Joystick[0].pressButton(6); //L
  } else {
    Joystick[0].releaseButton(6);
  }
  if (buttons1 & (1 << 11)) {
    Joystick[0].pressButton(7); //R
  } else {
    Joystick[0].releaseButton(7);
  }

  //Controller 2
  if (buttons2 & (1 << 0)) {
    Joystick[1].pressButton(0); //B
  } else {
    Joystick[1].releaseButton(0);
  }
  if (buttons2 & (1 << 1)) {
    Joystick[1].pressButton(1); //Y
  } else {
    Joystick[1].releaseButton(1);
  }
  if (buttons2 & (1 << 2)) {
    Joystick[1].pressButton(2); //Select
  } else {
    Joystick[1].releaseButton(2);
  }
  if (buttons2 & (1 << 3)) {
    Joystick[1].pressButton(3); //Start
  } else {
    Joystick[1].releaseButton(3);
  }

  if (buttons2 & (1 << 4)) {
    Joystick[1].setYAxis(-127); //Up Down
  } else if (buttons2 & (1 << 5)) {
    Joystick[1].setYAxis(127);
  } else {
    Joystick[1].setYAxis(0);
  }
  if (buttons2 & (1 << 6)) {
    Joystick[1].setXAxis(-127); //Right left
  } else if (buttons2 & (1 << 7)) {
    Joystick[1].setXAxis(127);
  } else {
    Joystick[1].setXAxis(0);
  }


  if (buttons2 & (1 << 8)) {
    Joystick[1].pressButton(4); //A
  } else {
    Joystick[1].releaseButton(4);
  }
  if (buttons2 & (1 << 9)) {
    Joystick[1].pressButton(5); //X
  } else {
    Joystick[1].releaseButton(5);
  }
  if (buttons2 & (1 << 10)) {
    Joystick[1].pressButton(6); //L
  } else {
    Joystick[1].releaseButton(6);
  }
  if (buttons2 & (1 << 11)) {
    Joystick[1].pressButton(7); //R
  } else {
    Joystick[1].releaseButton(7);
  }

  if (testAutoSendMode == false)
  {
    Joystick[0].sendState(); //send the state to the computer.
    Joystick[1].sendState();
  }


}

 Projeto completo!

É isso, espero que tenha gostado. Obrigado pela leitura e até o próximo artigo o/

 

 

Robson Couto

Recentemente graduado Engenheiro Eletricista. Gosto de dedicar meu tempo a aprender sobre computadores, eletrônica, programação e engenharia reversa. Documento meus projetos no Dragão quando possível.

32 thoughts to “Controle de Super Nintendo no PC com Arduino Micro.”

    1. Oi amigo, depende.
      1- Voce pode fazer o adapter do site do Rafael seguindo olink notexto, mas ainda precisando de um atmega8, o circuito minimo(cristal, capacitores e diodos). Mas ainda é necessário gravar o hex no micro, sendo necessario um gravador AVR.
      2-Voce pode usar o microcontrolador do Arduino Micro (Atmega32u4) e gravar esse programa que escrevi, mas ainda é necessario o crystal e os capacitores. Alem disso é necessario gravar o programa usando um gravador AVR ou outro Arduino.
      Obrigado pela leitura.

    1. Vi que usando o Arduino uno apresenta essa mensagem, usando o leonardo funciona perfeitamente. Qual Avr vc usou? qual procedimento para gravação?
      Estava querendo usa o uno pois era so remover o avr e montar o circuito breadboard.

  1. Olá! tudo bem Parabéns pelo projeto. tenho uma dúvida, vc usou um atmega 32u4 standalone na foto da pra ver, você conseguiu gravar o bootloader simplesmente usando um arduino uno??? eu não tava afim de comprar um micro arduino, queria comprar só o chip da atmega gravar o boot nele e depois o código. será que da certo?? to pensando certo??? valew!!!

  2. Olá Robson,

    Estou com um projeto interessante, estou transformando um raspberry em um game boy color, porém estou com dificuldades para usar o direcional original do console e estou a ideia de usar o nano v3.0 para controlar o gamepad e se comunicar com o raspberry pelo usb. Você tem ideia de como fazer isto? desde já.

    1. Oi Tiago.

      Bacana sua ideia, hoje estava tentando emular alguns jogos no meu Raspberry. Bom, controle usb não dá no Nano, porque o Atmega presente nesse modelo não se comunica diretamente com a USB, mas sim usa um conversor USB-Serial.
      Recomendo comprar um Arduino com um Atmega32u4(Tem que ser esse chip em especial) e seguir o tutorial. Exemplo:
      http://www.ebay.com/itm/New-Pro-Micro-ATmega32U4-5V-16MHz-Replace-ATmega328-Arduino-Pro-Mini-/131513277878?hash=item1e9ecd5db6:g:rUAAAOSwPgxVNhve.

      No entanto, talvez haja a possibilidade de se usar as próprias GPIOs do Raspberry pra ler os botoes, recomendo passar nesse link:
      https://github.com/recalbox/mk_arcade_joystick_rpi
      Boa sorte!

        1. Olá Robson.
          O Arduino nano usa o chip FTDI geralmente, que é um conversor USB-serial. Há alguns arduinos (UNO e MEGA e clones que usem atmega8u2 ou 16u2) que podem em tese ser reprogramados pra usar USB, mas o processo é doloroso e eu nunca fui atrás de entender.
          Outra forma, possível com praticamente qualquer micro, é utilizar V-USB, porém isso já vai bem por fora do Arduino e exige experiencia em c puro no AVR.
          Dê uma olhada nesse artigo (ingles)
          https://dragaosemchama.com/en/2017/06/usbasp-usb-adapter/
          Mas em suma, não é possível usar (facilmente) o Arduino Nano.

  3. Robson blz? Estou com mais uma dúvida. Vi que você renomeou a biblioteca Joystick.h para Joystick2.h. Você modificou alguma coisa? Na hora de carregar no meu Arduíno está dando erro : fatal error: Joystick2.h: No such file or directory #include “Joystick2.h”. Renomeei a biblioteca para a que tenho, mais erros aparecem também. Se modificou, poderia disponibilizar a biblioteca Joystick que você tem? Desde já, obrigado.

    1. Olá Robson. Usei a biblioteca do link no texto mesmo, porém já faz tempo que escrevi o post.
      Acredito que o autor da biblioteca pode ter atualizado a mesma e quebrado a compatibilidade.
      Você pode comentar aqui os novos erros que aparecem?

      Robson

      1. Xará, blz?! O erro é na biblioteca mesmo. Com certeza o autor deve ter atualizado, pois encontrei um código parecido com o seu na net, porém com um controle só, e ele é bem antigo. Minha dúvida a respeito era porque utilizei um outro código diferente que usa essa biblioteca joystick.h e funcionou no Windows de boa. Porém no linux, mais específico no Retrorange pi ele reconhece os dois joysticks como apenas um, como se tivesse ligado os dois em paralelo. É muito estranho o comportamento. Achei que tinha adaptado a biblioteca, pois o seu código parece casar melhor com o Arduíno micro/Leonardo para usar os dois controles na mesma placa. Obrigado pela atenção.

        1. Estou montando um Orange pi one na carcaça de um Snes que comprei sem conserto e o sistema ainda não tem suporte para usar a GPIO como o Raspberry pi e como não tenho conhecimento a ideia do seu post foi a saída para a adaptação dos controles originais. Vou tentando aqui e vamos ver no que dá :)

  4. Hello,

    I emailed you about potential 4 snes pad support :)

    anyway, now i see there is an issue with the #include “Joystick2.h” line.

    If you could look into this Joystick2.h issue, maybe you can also have a look at the 4 pad support?

    Thank you!
    alex

    1. Hello Alexander

      Thanks for your comment.
      Unfortunately I did not port the code to the 32u4, but to the Atmega8 present in the USBASP.
      worth mentioning the code here is Arduino, but the 4snes code is pure C.
      Anyways, it should be pretty straightforward getting up to 4 or even more controllers working in this Arduino code. At the time I was using two controllers.
      I believe the library was updated and the code here is not compatible with the library anymore.
      I will try to fix it and give a reply here.

      Rob

  5. Hello Robson, the code now compiles fine. two remarks:

    1) in line 14, the last colon , is not required. (compiles with or without, so maybe ncm).

    2) have you been considering switching to the GAMEPAD tape:

    Joystick_(0x03, JOYSTICK_TYPE_GAMEPAD, 12, 0, false, false, false, false, false, false, false, false, false, false, false)

    will now test the 2p version, and then try to expand to 4p version. thank you!

    1. Hello Alexander.

      1) That`s right. I forgot to erase that, before there were other two controller configurations there, When I erased them I forgot to delete the comma. You can have a look in the examples of the library, I copied that from there and adapted for my code. To add add more controllers you just have to add two more Joystick_ objects in the array and update their state in the loop the same way I did with the other two.
      2) Actually yes. But My code was for a joystick type, so I kept it that way. I just update the axis as completely moved (127 or -127) and any device understands that fine. Also, I had a lot of trouble trying to implement joypads when making a usb device myself in the USBASP post. It was actually easier making the computer understand a joystick than a joypad. Fortunately with Arduino and this library you don’t have to mess with any of that.

      Please leave a comment here if you get everything working (or not). Thanks!

  6. hey, i just expanded this all to 4 devices, and it compiles fine. will solder together my first version now, and let you know asap. thank you! Alex

  7. Ok, i have problems here. the code compiles well and flashes well to the pro-micro.
    but i really start to believe, this will not work because i use the Pro micro (ATmega32u4), and not the ATmega8.

    though, MHeironimus Library is supposed to work with 32u4 as well.

    in the end, after flashing and reconnecting the arduino, it should find 2 (or 4) joystick devices in joy.cpl in windows (but doesnt)… will check further what options i have. I originally came from 4nes4snes from Raphael Assenat, but that as well is for ATmega8 only:
    http://www.raphnet.net/electronique/4nes4snes/index_en.php

    will let you know if i come further. there also is this other project, where i am in touch with the maintainer:
    https://github.com/nvsofts/SNES-HID/issues

    have a good day ahead!

    1. Hello Alex.

      I guess you are still confused. This code is for the 32u4 and is not my port of the 4nes4snes code. In fact is actually older.
      Anyway, sorry about the code not working before. I did not try it on a board. Now I tested the new code on my arduino Micro, but did not wire controllers. May you try this new one please? I have made some changes and it was recognised in my computer.

      Thanks!

      1. You see, the atmega 32u4 has native USB so it can talk fine with a computer through USB. The atmega8 does not have USB, so we use this library V-USB that “emulates” USB, None of the Arduino boards use V-USB, they use boards with native USB or a USB-Serial converter chip as the FTDI232

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *