Homemade Pixels

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

Archive pour septembre 2012

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