Homemade Pixels

Des pixels frais qui sortent du four le blog de MrHelmut.

Bidouille : communication Gameboy et Arduino

Il y a peu, un ami m’a demandé quelques détails sur la Gameboy et sa capacité de communication via son port Game Link (qui sert à brancher deux Gameboy entre elles pour jouer à deux). Derrière sa question se cache un projet farfelu mais hautement cool (je reviendrai plus tard sur la finalité de celui-ci) et le topo est simple : comment communiquer avec une Gameboy et autre chose qu’une seconde Gameboy ? (notamment un port 3 broches TTL, ou Transistor-to-Transistor Logic)

Ni une ni deux, je resors ma documentation sur la Gameboy. Au passage, un ami qui dispose d’un SDK officiel m’a passé la doc de chez Nintendo. Véritable perle car elle contient toutes les infos bas niveau (jusqu’aux signaux électriques) que je n’ai pas trouvé sur le net ou dans la quasi-exhaustive pandocs.

Bref, en apparence, le port Game Link de la console ressemble à un port série. Je me dis “chouette, un port TTL, les doigts dans le nez”. Sauf qu’en réalité, ce n’est pas tout à fait du TTL et il y a essentiellement deux contraintes :

  • La Gameboy n’a pas de bits de début et d’arrêt sur le signal de transfert, mais se base sur des impulsions d’horloge (il y a donc 3 fils au lieu de 2 en TTL, si on exclu la masse);
  • Pour envoyer / recevoir des données, la Gameboy n’a qu’un registre 8bit, ce qui implique de devoir traiter ces 8 bits avant de recevoir quoi que ce soit d’autre (donc si un appareil quelconque balance plus de 8 bits d’un coup, la Gameboy ratera tout ce qui arrive le temps de repasser en mode réception). Or, les trames TTL sont plus longues que 8 bits à cause du point précédent et le périphérique visé n’a pas de mécanisme de segmentation de trames (comme peut avoir un clavier PS/2).

La Gameboy ne peut donc tout simplement pas communiquer avec un port TTL (enfin, ça dépend du périphérique et du mode de réception de la Gameboy, mais dans mon cas précis, je suis coincé). D’où l’idée de coller une carte Arduino (qui a un port TTL) entre la Gameboy et l’autre appareil (qui lui est bien TTL). Ca permettra aussi de faire plein d’autres trucs marrants par la suite.

TTL versus Game Link

Un peu d’électronique histoire d’illustrer la différence. Le protocole TTL se base sur deux fils. Un pour l’envoi de donnée (TX) et un pour la réception (RX) et permet une communication asynchrone full-duplex. Les trames sont généralement de 10 bits (1 bit de début, 8 de donnée et 1 bit de fin), mais parfois sans bit de début. Si il n’y a pas de transfert, le signal est HIGH (+5V), les 1 sont codés en +5V et les 0 en 0V. Pour que la communication se fasse, il faut que les deux appareils connectés aient leur port TTL configurés à la même fréquence (sinon on ne peut pas déterminer combien de bits sont passés, à moins d’avoir un signal d’horloge mais ce n’est pas mon cas).

Imaginons que l’on veuille transmettre la série de bits 01001011. Voici le signal correspondant :

signal0

Notons au passage que les données sont transmises en partant du bit de poids faible.

Le Game Link de la Gameboy est différent. Tout d’abord les trames, qui font 8 bits et ne contiennent que des données. Pas de bits start/stop ou de parité (contrôle d’erreurs). Pour savoir si des données sont envoyées ou non, et à quelle fréquence, c’est un signal d’horloge qui donne le la. Ce signal d’horloge n’est donné que par un des deux interlocuteurs (il n’y a qu’un seul câble d’horloge, donc seule une machine peut l’émettre). Celui qui émet l’horloge est dit “master”, l’autre est son “slave”. Bien entendu ces rôles peuvent changer à chaque transaction, mais par simplicité on définit et on garde les rôles une fois pour toute (dans les jeux Gameboy, c’est le joueur qui appui en premier sur “start” qui est “master”). Le comportement électrique est le même que le TTL : 0-5V et idle à 5V.

Le signal d’horloge définit la vitesse à laquelle les données sont transmises et le signal de données doit être parfaitement synchronisé à l’horloge. La Gameboy originale (non color) a une fréquence d’horloge fixe, de 8192hz, soit une capacité de transmission de 1 Ko/sec (overhead de traitement des trames exclu). Mais en mode slave, la Gameboy accepte un signal pouvant aller jusqu’à 500Khz (~60 Ko/sec).

Dans la pratique, voici le même exemple qu’avant avec un envoi des données 01001011 fait par une Gameboy master :

signal

Le slave, qui est en mode écoute, attend le signal d’horloge de la part du master et enregistre tous les bits impulsés par l’horloge (chaque bit entrant est poussé dans le registre 8bit par la droite à chaque impulsion d’horloge).

Si le slave veut envoyer des données, il doit attendre que le master passe en mode écoute et qu’il lui envoi le signal d’horloge.

A l’inverse du TTL, c’est le bit de poids fort qui est envoyé / reçu en premier. Mais ce n’est pas grave en soi, puisqu’il suffit d’inverser la trame à réception ou avant l’envoi.

L’avantage d’être porté par un signal d’horloge, est que la fréquence de transmission peut être variable, même en pleine trame, pourvu que les signaux soient synchronisés. Et ça, c’est vachement pratique pour la bidouille visée avec l’Arduino.

En bref, la compatibilité entre TTL et Game Link se joue à un cheveu près (les bits start/stop). On pourrait ségmenter les trames ou se débrouiller pour ignorer les 2 derniers bits, mais suivant les cas et les périphériques, on ne peut pas.

Arduino to the rescue

Ne pouvant communiquer via le protocole TTL, l’idée est alors de mettre une carte Arduino entre la Gameboy et le périphérique afin de retraiter une entrée TTL pour la rendre compatible Game Link. Grosso modo on retire les bits de contrôle (fait automatique par l’Arduino) et on inverse la trame.

Les Arduino ont des ports séries standards (généralement les PIN 0 et 1), donc côté entrée il n’y a aucun problème. Pour la communication avec la Gameboy, il nous faut donc 3 fils : l’horloge, une entrée et une sortie.

Si l’on veut que l’Arduino puisse fonctionner en mode slave, il faut être capable de détecter le début précis du signal d’horloge et de lire avec un timing tout aussi précis les données sur le fil d’entrée. Or, les Arduino ne permettent pas de détecter le début exacte d’un signal. Juste si celui-ci est HIGH ou LOW, mais pas de savoir depuis quand il est HIGH avec la précision requise. De plus, lire l’horloge, puis le fil d’entrée, se fait en deux étapes (DigitalRead) et il n’est pas garantie que la lecture des deux ait été synchrone. En gros, un Arduino ne peut pas fonctionner en mode slave avec une Gameboy, par limitation technique.

Et le mode master alors ? Pour être master, il faut être capable de deux choses : balancer un signal d’horloge et balancer de manière synchrone un signal d’envoi (ou lire le signal de réception).

Pour ce qui est de l’horloge, les Arduino permettent d’émettre un signal carré facilement, mais pas de déterminer le nombre d’impulsions pour les 8 bits d’une trame. Mais le Game Link a un avantage : pas besoin d’avoir un signal régulier, seule la synchronisation des signaux est importante.

Pour un envoi, l’idée est donc d’émettre un pic de signal d’horloge en même temps que la valeur du bit qui doit être véhiculé (le pic d’horloge ordonne à la Gameboy d’envoyer/recevoir un bit). Mais le problème est le même qu’avec la lecture en mode slave. Faire deux DigitalWrite (un pour l’horloge et un pour l’envoi/réception) l’un après l’autre ne garanti pas la synchronisation des signaux.

Solution ? La programmation bas niveau de l’Arduino en allant directement taper dans les registres des signaux. Inconvénient ? Ce n’est pas toujours portable entre différents modèles d’Arduino et pas spécialement “propre”. Avantage ? Ca permet de gérer la synchronisation de nos signaux aux petits oignons.

Dans la pratique, toute la magie réside dans le code Arduino suivant (désolé pour la lisibilité, mais j’adore les opérations bitwise) :

byte GB_CLK = B00000100; // on définit la PIN 2 comme étant notre horloge
byte GB_IN = B00001000; // PIN 3 sera l'entrée
byte GB_OUT = B00010000; // PIN 4 la sortie
// ATTENTION : les fonctions suivantes ne gèrent que les I/O sur les PIN analogiques 0 à 5 (car
// on bosse uniquement sur le registre Arduino C)
// Par conséquence, tous les signaux GB doivent être associés à des PIN d'un même registre,
// sinon on ne peut pas assurer la synchronisation des signaux

void setup()
{
	// on initialise les PIN en input ou output
	DDRC |= GB_OUT | GB_CLK; // outputs
	DDRC &= ~GB_IN; // inputs
	PORTC |= GB_CLK; // on initialise l'horloge à HIGH conformément à la spec GB
}

void sendToGB(byte data)
{
	for (int currentBit = 0; currentBit < 8; currentBit++) // on émet un bit après l'autre
	{
		if (data & B10000000) // si le bit de poids fort est à 1, on envoi HIGH
		{
			// la génération de l'impulsion se fait en deux temps, une fois pour
			// générer la descente du signal, une fois pour la remontée
			PORTC &= ~GB_CLK; // CLK = LOW
			PORTC |= GB_CLK | GB_OUT; // CLK = HIGH, OUT = HIGH
		}
		else // sinon LOW
		{
			PORTC &= ~GB_CLK; // CLK = LOW
			PORTC |= GB_CLK; // CLK = HIGH, OUT = LOW
		}
		data <<= 1; // et on éjecte le bit de poids fort qu'on vient d'envoyer
	}
}

byte readFromGB()
{
	byte data;
	for (int currentBit = 0; currentBit < 8; currentBit++)
	{
		data <<= 1;
		PORTC &= ~GB_CLK; // CLK = LOW
		// lecture de la PIN
		if (PINC & GB_IN)
		{
			data |= B00000001;
		}
		PORTC |= GB_CLK; // CLK = HIGH
	}
	return data;
}

Et le tour est joué !

Il faudra juste s’assurer qu’entre chaque opération la Gameboy ait le temps de repasser en mode écoute / émission avec des delay côté Arduino.

Après, pour les transactions, il convient de créer son propre protocole.

Bonux 1 : schéma du câble Game Link

Pour brancher la Gameboy à l’Arduino, j’ai pris un câble Game Link que j’ai charcuté pour brancher directement aux PIN nécessaires. Voici le schéma du câble (les couleurs correspondent aux couleurs du câble officiel Nintendo) :

  • CLK, le signal d’horloge, à brancher sur la PIN GB_CLK
  • IN, l’entrée de la Gameboy, à brancher sur la PIN GB_OUT
  • +5V, pas utilisé dans le cas de la Gameboy, ignoré
  • GND, la terre, à brancher sur la terre de l’Arduino
  • SD, ignoré
  • OUT, la sortie de la Gameboy, à brancher sur la PIN GB_IN

Bonux 2 : programmes de test

Pour tester la communication, il faut deux programmes, un côté Gameboy et un côté Arduino. On va se contenter de faire un echo : l’Arduino envoie un octet à la Gameboy qui lui renvoie le même incrémenté de 1.

Programme Gameboy (rapide test en assembleur) :

; Pour l'appel du traitement des données à réception/envoi
SECTION "Interrupetion reception", HOME[$58]
INT_58:
	CALL SERIAL_VECT
	RET

; Fonction main du programme
SECTION "Main", HOME[$0150]
Start::
	LD ($FFFF), $8 ; on active l'interruption de réception (INT $58)
	EI ; on active les interruptions
	LD ($FF02), $81 ; on active l'envoi/réception en mode slave
LOOP:
	JP LOOP ; on boucle en attendant l'interruption de réception

; La fonction de traitement à réception
SERIAL_VECT:
	LD A, ($FF01) ; on récupère l'octet reçu
	INC A ; on incrémente la valeur de 1
	LD ($FF01), A ; on replace la valeur dans le registre de données du Game Link
	LD ($FF02), $81 ; on repasse en mode envoi/réception slave
	RET ; on retourne à la boucle pour attendre l'envoi (puis on ne fait plus rien)

Côté Arduino :

// à rajouter dans setup()
void setup()
{
	...
	Serial.begin(9600);
}

// globals
byte result = 41;

void loop()
{
	sendToGB(result);
	delay(10);
	result = readFromGB();
	Serial.println(result); // si 42 s'affiche dans la console de debug, c'est gagné
	while (1) {} // on "stop"
}

Pour tester cela, il faut allumer la Gameboy en premier pour être sur qu’elle attende le paquet avant que l’Arduino ne l’envoie.

Je reviendrai plus tard sur le but de cette bidouille, avec une démo je l’espère (comme ça, ça met la pression à la personne à l’initiative du projet ^^).

Si vous n’êtes pas membres wefrag, vos commentaires peuvent être recueillis par mail :

contact

8 commentaires pour “Bidouille : communication Gameboy et Arduino”

  1. mouito dit :

    Excellent

  2. skaven dit :

    Je ne capte rien aux machins electriques mais je trouve ca totalement cool.

  3. firekorn dit :

    J’ai l’impréssion d’être de retour en DUT!
    Très bien expliquée mais je suppose que tu le fait sous arduino car le système est facile d’accès et simple à obtenir?

    D’ailleurs je suis étonné qu’il n’y ai aucun “accusé réception” du point de vue de la gameboy.
    Dans un système ou le traitement de la donné doit être fait avant d’obtenir les données suivantes c’est pour moi ce qui me semble logique à faire dans le protocole de com car là on se retrouve avec une durée inconnue, bizarre.

    Pour l’histoire du front descendants sur l’horloge, il est possible avec un système d’interruption de venir lire la data lors du front descendants de l’horloge, si le signal d’horloge est propre (pas de rebonds) cela devrait fonctionner du feu de dieu (pas sur de la portabilité entre différents arduino vis à vis des registres à configurer).

  4. MrHelmut dit :

    @firekorn : comme tu le dis, il y a surement plus simple avec les fronts descendants, mais je ne m’y connais pas plus que ca en electronique et, effectivement, Arduino est ce qui me semblait le plus abordable et documenté (et populaire, il faut dire).

    La GB ne fait aucun ACK en dehors d’envoyer/recevoir 8 bits à la fois. En fait, la GB reçoit et émet en une seule fois. Le registre 8bit de donnée est en fait décalé à gauche à chaque tick d’horloge, le bit éjecté part sur le fil d’output, et le bit injecté à droite provient du fil d’input.
    Dans la doc GB, il y a un workflow de référence, qui indique la marche à suivre pour implémenter un protocole. En fait, c’est le master qui fait tout. Il envoie/reçoit en même temps (du coup le slave répond à t+1), et si le master reçoit “0″, ca veut dire que le slave n’est pas pret et donc le master fait un petit wait avant de recommencer.

    Ensuite, je pense que le câble est suffisamment court et la vitesse suffisamment faible pour qu’il n’y ait pas à faire de contrôle d’erreur (malgré le 0 à 5V, plus propice aux erreurs que du -3/+3). Rien n’empêche d’implémenter son propre contrôle, mais des jeux décompilés que j’ai pu voir, aucun contrôle n’est fait.

  5. firekorn dit :

    En faite l’ACK est simulé par le bit 7 si je suis la documentation Pandocs.

    Ce qui fait que lorsque l’on veut faire un nouvelle envoie de 8 bit, le premier bit que la GB nous envoie lors de la réception du bit de poids forts étant ce bit qui indique si le transfert est autorisée ou non, et si non on attend avant de renvoyer le même bit si je comprend bien?

  6. MrHelmut dit :

    Je pense que tu parles du registre de contrôle (FF02) qui a un bit “Transfer Start Flag”. Ce bit sert à activer/configurer le transfer programmatiquement. Si on le met à 1, la gameboy décale FF01 (envoi/réception) en fonction de l’horloge. Une fois que 8 bits sont décalés, le flag repasse à 0.
    Ca sert à activer un transfert et à savoir si 8 bits ont été envoyés, mais pas à savoir si ils ont bien été reçus de l’autre côté.
    Au mieux, le slave peut savoir si il a raté des bits si le flag reste trop longtemps à 1. Mais le master ne sait rien.

  7. firekorn dit :

    Autant pour moi je n’avais pas correctement lu la doc.

    Détaille pour le code :

    Tu fait ton front descendant sur CLK avant de changer ta donnée lors de l’écriture ce qui risque d’être problématique si la GB lis plus vite que ton arduino (peut probable) alors que si tu change l’ordre tu modifie ta donnée et une fois prêtes tu l’envoie avec ta mise aux niveau bas de CLK. C’est juste une sureté pour le système qui évitera à ta GB de lire une donnée erronée.

    Pour l’histoire du contrôle d’erreur dans ce genre de protocole avec un câble qui me semblait correctement blindée dans mon souvenir et dans un environnement bien moins polluée de fréquences quelconque qu’aujourd’hui et où la vitesse n’est pas un véritable critère, une vérif serait rajouter des bits pour un intérêts bien limitée.

    Sinon tu peux je pense simuler un ACK à travers la prog de la GB et de l’arduino pour “ajuster le protocole” mais cela risque de te rajouter pas mal de code pour un gain de temps minime.

  8. MrHelmut dit :

    Tu as tout à fait raison concernant mon code. En l’état ca marche, mais ca peut être une source d’erreur. Il faudrait que je fasse un stress test, pour mesurer le taux d’erreurs sur plusieurs milliers de paquets.

Laisser un commentaire

Vous devez être connecté avec votre compte Wefrag pour publier un commentaire.