Emulando controle de GC

Sobre o autor:
Bem iniciante em tudo, vou aprendendo conforme a necessidade aparece.

Objetivos do projeto:
Controlar um Nintendo GameCube usando um software aberto.

Objetivos secundários do projeto:
Fazer o lance funcionar com um adaptador GC/WIIU também.

Histórico do projeto:
Eu comecei pensando em descascar um cabo USB, conectar os fios certos nos fios de um cabo de GCC (GameCube Controller) , plugar no GC e funcionar.
Descobri que o hardware da porta USB não funciona assim e não dá pra controlar que nem um arduino. Então, coloquei um arduino no meio. Pesquisando no google qualquer coisa relacionada a “gamecube” e “datasheet” o primeiro e mais relevante resultado é
http://www.int03.co.uk/crema/hardware/gamecube/gc-control.html
um artigo atualizado em 2004 sobre o projeto incompleto de um cara estudando as interações do GC e seu controle. Recomendo a leitura, o projeto quase todo está se baseando nesse artigo.
Entao, ao descobrir que a comunicação é feita a 1,000,000bps, descobri também que o arduino aguenta bem mais que isso. Então eu fiz uns códigos no arduino para apenas repassar os dados inalterados GC<->PC, mas eu encontrei um problema que acabou sendo mais solução do que problema: os pinos TX e RX do arduino são hard-wired com a porta usb serial, então eu posso simplesmente ligar o arduino rodando um código vazio no pc, ele vai ser reconhecido como um dispositivo USB, e eu posso ler e escrever (atualmente usando a biblioteca PySerial).
E também precisei de um conversor de nivel lógico, porque segundo o supracitado artigo, o controle de GC usa 3.43v e o arduino usa 5v.
Comprei esse coiso aqui
https://www.robocore.net/loja/produtos/conversor-de-nivel-logico.html
mas parece que o tempo de funcionamento dele é incompatível com o meu projeto, então ao ligar ele entre o arduino e a porta do GC, minha porta serial lê apenas um monte de bytes lixo sem significado nenhum.

Acho que o próximo passo é achar uma forma de converter um sinal de 5v para 3.43 com precisão de 1us. Se alguem puder dar uma idéia agradeço.

3 curtidas

liguei o controle na minha extensao aberta e li os fios de dados com o seguinte codigo:

def serial_monitor():
import serial

serialPort = serial.Serial(port='/dev/ttyUSB0',
                           baudrate=250000,
                           # bytesize=,
                           # parity=serial.PARITY_NONE,
                           # stopbits=serial.STOPBITS_ONE,
                           # timeout=0.013,
                           # xonxoff=False,
                           # rtscts=False,
                           # write_timeout=None,
                           # dsrdtr=False,
                           # inter_byte_timeout=None
                           )
print(f'\nCreated port {serialPort.name}')
input('\nWaiting. Send any key to continue')
while True:
    print(serialPort.read(9))
    serialPort.reset_input_buffer()
serialPort.close()  # todo

if name == ‘main’:
serial_monitor()

e o resultado foi

b’\x18\x08\x04!C0\x01\x18' b'\x01\x18\x08\x04!C0\x01’
b’\x18\x08\x04!C0\x01\x18' b'\x01\x18\x08\x04!C0\x01’
b’\x18\x08\x04!C0\x01\x18' b'\x01\x18\x08\x04!C0\x01’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x08\x04!C0\x01\x18’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x08\x04!C0\x01\x18’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x08\x04!C0\x01\x18’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x08\x04!C0\x01\x18’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x08\x04!C0\x01\x18’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x00\x02\x88C0\x01\x18’
b’\x01\x18\xf8\x01\x18\xf8\x01\x18\xf8’
b’\x80\x80\x80\x80\x80\x80\x80\x80\x80’
b’\x80\x80\x80\x80\x80\x80\x80\x80\x80’
b’\x80\x80\x80\x80\x80\x80\x80\x80\x80’
b’\x80\x80\x80\x80\x80\x80\x80\x80\x80’
b’\x80\x80\x80\x80\x80\x80\x80\x80\x80’
b’\x80\x80\x80\x80\x80\x80\x80\x80\x80’
b’\x80\x80\x80\x80\x80\x80\x80\x80\x80’
b’\x80\x80\x80\x80\x80\x80\x80\x80\x80’
b’\x80\x80\x80\x80\x80\x80\x80\x80\x80’
b’\x80\x80\x80\x80\x80\x80\x80$@’
b’\xc1@\x10|H\x84@\xf0' b'\xc1@\x10|H\x84@\xf0’
b’\xc1@\x10|H\x84@\xf0' b'\xc1@\x10|H\x84@\xf0’
b’\xc1@\x10|H\x84@\xf0' b'\xc1@\x10|H\x84@\xf0’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x08\x04!C0\x01\x18’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x08\x04!C0\x01\x18’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x08\x04!C0\x01\x18’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x08\x04!C0\x01\x18’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x08\x04!C0\x01\x18’
b’\x01\x18\x00\x02\x88C0\x01' b'\x18\x08\x04!C0\x01\x18’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x08\x04!C0\x01\x18’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x08\x04!C0\x01\x18’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x00\x02\x88C0\x01\x18’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x08\x04!C0\x01\x18’
b’\x01\x18\x08\x04!C0\x01' b'\x18\x00\x02\x88C0\x01\x18’
b’\x01\x18\xf8\x01\x18\xf8\x01\x18\xf8’
b’\x01\x18\xf8\x80\x80\x80\x80\x80\x80’
b’\x80\x80\x80\x80\x80\x80\x80\x80\x80’
b’\x80\x80\x80\x80\x80\x80\x80\x80\x80’
b’\x80\x80\x80\x80\x80\x80\x80\x80\x80’
b’\x80\x80\x80\x80\x80\x80\x80\x80\x80’

os \x80 foram os momentos que eu despluguei o controle. Estou no processo de interpretar isso

#!/usr/bin/env python

wipflag {}

def serial_monitor():
import serial
import time

is_reading = True
serialPort = serial.Serial(port='/dev/ttyUSB0',
                           baudrate=250000,
                           # bytesize=,
                           # parity=serial.PARITY_NONE,
                           # stopbits=serial.STOPBITS_ONE,
                            timeout=0.0024,
                           # xonxoff=False,
                           # rtscts=False,
                           # write_timeout=None,
                           # dsrdtr=False,
                           # inter_byte_timeout=None
                           )
print(f'\nCreated port {serialPort.name}')
input('\nWaiting. Send any key to continue')
time_sample_1 = time.time()
while is_reading:
    if serialPort.in_waiting > 0:
        msg = f'time since last input: {time.time()-time_sample_1}'
        time_sample_1 = time.time()
        print(f' ->{msg} -> {serialPort.read(72).hex()}')
serialPort.close()

if name == ‘main’:
serial_monitor()

novo codigo, produzindo o seguinte output:

->time since last input: 0.008018970489501953 → 80
->time since last input: 0.00849461555480957 → 80
->time since last input: 0.007468700408935547 → 80
->time since last input: 0.009088277816772461 → 80
->time since last input: 0.006919384002685547 → 80
->time since last input: 0.007945775985717773 → 80
->time since last input: 0.009104728698730469 → 80
->time since last input: 0.0068817138671875 → 80
->time since last input: 0.009634733200073242 → 80
->time since last input: 0.00635218620300293 → 80
->time since last input: 0.007982254028320312 → 80
->time since last input: 0.008000612258911133 → 80
->time since last input: 0.007985591888427734 → 80
->time since last input: 0.00798797607421875 → 80
->time since last input: 0.007996559143066406 → 80
->time since last input: 0.00909566879272461 → 80
->time since last input: 0.006880283355712891 → 80
->time since last input: 0.007997512817382812 → 80
->time since last input: 0.007983207702636719 → 80
->time since last input: 0.007992744445800781 → 80
->time since last input: 0.00909876823425293 → 80
->time since last input: 0.006901979446411133 → 80
->time since last input: 0.008030176162719727 → 80
->time since last input: 0.007901668548583984 → 80
->time since last input: 0.008546829223632812 → 80
->time since last input: 0.007443666458129883 → 80
->time since last input: 0.007991552352905273 → 80
->time since last input: 0.009099245071411133 → 80
->time since last input: 0.006877422332763672 → 802440fc
->time since last input: 0.008023738861083984 → c140107c48846280e0
->time since last input: 0.009081840515136719 → c140107c48846280e0
->time since last input: 0.00688934326171875 → c140107c48846280e0
->time since last input: 0.00799417495727539 → c140107c48846280e0
->time since last input: 0.007990360260009766 → c140107c48846280e0
->time since last input: 0.008012771606445312 → 4120087c48846280e0
->time since last input: 0.00955510139465332 → 0118080421433062
->time since last input: 0.008006811141967773 → 0118080421433062
->time since last input: 0.006881237030029297 → 0118080421433062
->time since last input: 0.007460117340087891 → 0118080421433062
->time since last input: 0.008004426956176758 → 0118080421433062
->time since last input: 0.008587837219238281 → 0118080421433062ff
->time since last input: 0.007320880889892578 → 0118080421433062
->time since last input: 0.008038997650146484 → 0118080421433062ff
->time since last input: 0.007998943328857422 → 0118080421433062
->time since last input: 0.009593486785888672 → 0118080421433062
->time since last input: 0.006429195404052734 → 0118080421433062
->time since last input: 0.009013652801513672 → 0118080421433062
->time since last input: 0.006896257400512695 → 0118080421433062
->time since last input: 0.009074211120605469 → 0118080421433062ff
->time since last input: 0.006884574890136719 → 0118080421433062
->time since last input: 0.008015155792236328 → 0118080421433062
->time since last input: 0.009646415710449219 → 0118080421433062ff
->time since last input: 0.0063130855560302734 → 0118080421433062

conseguimos os 80 com o controle desplugado, notamos que ao plugar o controle tem umas 5 interacoes diferentes, deve ser algum tipo de handshake, e o resto deve ser o estado do controle.

suponho que os primeiros 01 sejam um null byte enviado ao controle para pedir o estado.

notei que entre cada transmissao temos sempre um minimo de 0.006 segundo, entao vou setar o timeout para 0.004 (o maior valor possivel que ainda eh garantidamente menor que o intervalo entre frases) e ver se nao estamos perdendo bytes.

mapeando melhor o input, obtenho mais ou menos constantemente algo como

“01188804c1830860ff” -> start pressionado
“01180804c1830860ff” -> start solto

finalmente o resultado bate com o que foi especificado no artigo. Os dois primeiros bytes sao a pergunta, e o resto a resposta do controle.
esse 8 significa que o bit na posicao 3 (a contagem comeca do 0) do primeiro byte da resposta eh um 1.

“01180802b0020862” - >X pressionado
“01180804c1020862” -> X solto

agora isso nao faz muito sentido de acordo com o artigo. vou ver aqui

Bem bacana seu projeto!


Chegou a dar uma olhada nisso?

O codigo tava extremamente zoado, entao nesses ultimos dias eu melhorei um pouco, modularizando o tratamento de eventos do teclado e a conexao com o adaptador serial. Ler a 1/4 da velocidade nominal foi um erro, voltamos a 1000000 baud rate com exito. No ultimo teste conseguimos fazer o controle ativar o rumble, ou seja, ja podemos ler e escrever na linha sem queimar nenhum componente. porem ainda nao funciona.

vou dar uma olhada nos outros projetos assim que acabar de debugar isso aqui

Passei esse tempo todo estudando o projeto do Brownan e adaptando ao meu. Aprendemos que tudo o que eu fiz ate agora estava errado, e a forma certa de fazer envolve usar apenas o arduino, escrevendo 0 no pino digital para puxar a linha para 0v, e setando ele como input para deixar que a linha volte a subir, efetivamente escrevendo um 1 lógico. Estou no momento analisando o assembly gerado pelo código para contar certinho os ciclos usados por cada instrução do arduino e escrever os dados no tempo correto. 16 mhz = 16 ciclos por microsegundo, e a manipulação de porta do arduino leva 2 ciclos, eu não sei quantos ciclos levam os condicionais, loops e chamadas de função. Peguei o assembly de uma sketch bare minimum do arduino e já foi um negócio gigantesco, e parece que os comentários inseridos por asm volatile(;comentario) não aparecem lá.

link do projeto atualmente