[Bidouilles et nostalgie] USB-ifions un clavier de PC/XT

Publié par cpb
Fév 19 2017

USB PC/XT keyboard

Voici un petit hack, simple et à la portée de tous, qui m’a été inspiré par un tweet de @Hackablemag de la semaine dernière.

Il s’agit de transformer un ancien clavier mécanique d’IBM PC/XT des années 1980 pour pouvoir le connecter en USB sur n’importe quel système actuel. Je n’ai pas pu résister à l’envie de tenter la même opération de mon côté, pour comparer nos approches ensuite.

PC/XT ?

Tout d’abord un IBM PC/XT, ça ressemble à ça :

IBM PC/XT de 1984

Ce vénérable ordinosaure pèse quand même plus de 28 kg poussière comprise (3 kg pour le clavier, 12.5 kg pour l’écran et 13 kg pour l’unité centrale) !

Il est organisé autour d’un processeur Intel 8088 (non, non, pas de faute de frappe), cadencé à 4.77 MHz (pas de faute de frappe non plus), muni de 512Ko de mémoire RAM (toujours pas de faute de frappe) et un écran CGA couleur avec une résolution de 640×200 (hem…). Celui-ci possède un disque dur de 20 Mo et un lecteur de disquette 5″1/4 (qui n’est pas le modèle d’origine).

Étudiant, j’ai acheté ce PC d’occasion auprès d’une entreprise en 1988, et je l’ai utilisé intensivement pendant quatre ans avec MS-DOS 3.2, Windows 1.0 (et oui !), puis – avec un succès mitigé – Minix.

Il paraît bien désuet aujourd’hui, mais j’ai appris grâce à lui la programmation en assembleur Intel (j’avais pratiqué les assembleurs Zilog et Motorola auparavant), en langage C et en Pascal. J’ai écrit dessus de nombreux petits programmes et même des applications de reconnaissance d’écriture manuscrite par réseau de neurones durant mes études.

Les jeux n’étaient pas son fort, mais j’en ai pratiqué un bon nombre, dont le Tetris original (celui d’Alexey Pajitnov).

Son clavier mécanique avait un toucher particulièrement agréable et un cliquetis très sympathique. C’est l’objet de mon hack de cette semaine.

Connexion

Le clavier (tout comme le reste du système) n’était pas en très bon état extérieur. Remisé pendant des années dans différents endroits plus humides et poussiéreux les uns que les autres, il avait besoin d’un bon nettoyage.

Berk !

Berk !

Pas question, évidemment de l’immerger tel quel dans la baignoire. Après l’avoir ouvert et démonté les cabochons des touches, j’ai fait tremper toutes les parties en plastique dans un bain de liquide vaisselle, puis épousseté, gratté et graissé toutes les parties métalliques.

Lors de l’ouverture du clavier, une étiquette de validation indique la date de mise en service : 1er octobre 1984. Pas moins de quatre techniciens l’ont contre-signée, c’est une marque de la qualité remarquable de sa fabrication. Après plus de trente ans, la sérigraphie des touches est encore impeccable, contrairement à tous les autres claviers qui m’environnent et dont les touches principales comme SHIFT, CTRL, ou TAB (complétion du shell) affichent quelques signes d’usure.

Connecteur DIN

Le branchement du clavier sur l’unité centrale se fait avec un gros connecteur DIN (dont le successeur fût la petite prise PS2 clavier/souris que l’on rencontre encore parfois sur des systèmes anciens).

Le gros inconvénient de ce connecteur, était qu’à chaque débranchement/rebranchement du clavier, il était nécessaire de redémarrer le PC pour pouvoir le réinitialiser !

En ouvrant le clavier on trouve quatre fils reliés à un petit connecteur amovible, et une carte de commande avec un microcontrôleur. Les ingénieurs d’IBM devaient être d’humeur facétieuse le jour où ils ont décrété que l’alimentation +5V de la carte se ferait sur le fil marron, et la masse sur le fil rouge ! Les deux autres broches sont une horloge (fil noir) et une ligne de données (fil blanc).

Connecteur DIN

Connecteur DIN

Le clavier communique avec un protocole synchrone assez simple. La communication étant prévue pour être bidirectionnelle, le clavier n’émet les codes des touches pressées (et relâchées) que si les deux lignes d’horloge et de données sont au niveau haut pendant une durée minimale d’une milliseconde. Lorsque les lignes sont à un autre niveau, c’est le PC qui peut envoyer une commande au clavier (réinitialisation, état des LEDs, etc.).

Test des signaux

Test des signaux

Le protocole du clavier du PC/XT est plus simple que celui de ses successeurs, car il n’a pas de LED (Numlock, Capslock, etc.) et donc le PC n’a pas besoin d’envoyer de messages vers le clavier.

Attention, je le répète : ce protocole est spécifique au PC/XT, celui des claviers pour PC/AT ultérieurs est un peu différent au niveau de la séquence Start et ajoute un bit de parité. Un document que j’ai trouvé sur  http://kbdbabel.sourceforge.net/doc/kbd_signaling_pcxt_ps2_adb.pdf décrit les deux formats.

Après avoir alimenté le clavier, nous pouvons observer à l’oscilloscope les signaux CLOCK (en haut en bleu) et DATA (en bas en rouge) lorsque l’on presse et relâche des touches. Des résistances de tirage pull-up permettent de forcer à l’état haut les lignes au repos, autorisant le clavier à émettre. Le signal commence par une séquence START immuable dont le timing est précisément défini, suivi de 8 bits de données valides sur les fronts descendants du signal d’horloge.

"P" key pressed

Le code de la touche pressée (nommée scan-code) est lu en comptant le bit de poids faible en premier. Le code ci-dessus est donc 00011001b soit 0x19. Le bit de poids fort (le dernier transmis) indique s’il s’agit d’une pression (à 0) ou d’un relâchement de la touche (à 1). Le code ci-dessous correspond à un relâchement, c’est 0x99.

"P" key released

On peut donc construire un graphique des scan-codes du clavier de l’IBM PC/XT. La table ci-dessous présente les codes de pression. Pour obtenir les codes de relâchement correspondants, il faut leurs ajouter la valeur 0x80.

Les scan-codes ne dépendent pas de l’internationalisation. L’affectation au scan-code 0x10 de la touche ‘A’ en disposition AZERTY ou ‘Q’ en disposition QWERTY est effectuée par l’OS du système en fonction des choix de l’utilisateur.

Programmation

Pour “USB-ifier” un tel clavier, rien de tel que l’ajout d’un petit microcontrôleur comme un Arduino. Un choix encore meilleur sera celui d’une carte Teensy car elle propose directement une émulation de périphérique USB (nous avons déjà vu son comportement comme souris dans cet article).

J’ai donc connecté une Teensy 3.2 au clavier en conservant la couleur des câbles d’origine.

Teensy 3.2

Câble Rôle Connexion Teensy
Rouge GND GND
Marron +5V VUSB
Noir Clock D0
Blanc Data D1
Teensy 3.2

Teensy 3.2

J’ai programmé la carte Teensy avec le petit fichier suivant. On voit que les affectations de touches sont modifiables assez facilement dans le tableau initial. J’ai fait plusieurs essais pour obtenir un comportement assez proche des sérigraphies des touches sans trop modifier la configuration de mon PC (et du clavier que j’utilise au quotidien), il ne faut pas hésiter à faire ses propres adaptations.

usb-pc-xt-keyboard.ino:
/// ------------------------------------------------------------------------------------------
/// \file USB PC/XT keyboard - Teensy program to "USBify" an old PC/XT keyboard.
///
/// \author 2017 Christophe BLAESS <christophe@blaess.fr>
///
/// \license GPL
/// ------------------------------------------------------------------------------------------

/// \note Remember to Select "Keyboard" in "Tools-> USB Type" menu.

// The key table: the index is the keyboard scan-code.
//
int key[0x80] = {
/* 00-07 */  0xFF,           KEY_ESC,        KEY_1,          KEY_2,          KEY_3,          KEY_4,          KEY_5,          KEY_6,
/* 08-0F */  KEY_7,          KEY_8,          KEY_9,          KEY_0,          KEY_MINUS,      KEY_EQUAL,      KEY_BACKSPACE,  KEY_TAB,
/* 10-17 */  KEY_Q,          KEY_W,          KEY_E,          KEY_R,          KEY_T,          KEY_Y,          KEY_U,          KEY_I,
/* 18-1F */  KEY_O,          KEY_P,          KEY_LEFT_BRACE, KEY_RIGHT_BRACE,KEY_ENTER,      KEY_LEFT_CTRL,  KEY_A,          KEY_S,
/* 20-27 */  KEY_D,          KEY_F,          KEY_G,          KEY_H,          KEY_J,          KEY_K,          KEY_L,          KEY_SEMICOLON,
/* 28-2F */  KEY_QUOTE,      KEY_BACKSLASH,  KEY_LEFT_SHIFT, '<',            KEY_Z,          KEY_X,          KEY_C,          KEY_V,
/* 30-37 */  KEY_B,          KEY_N,          KEY_M,          KEY_COMMA,      KEY_PERIOD,     KEY_EQUAL,      KEY_RIGHT_ALT,  KEYPAD_ASTERIX,
/* 38-3F */  KEY_LEFT_ALT,   KEY_SPACE,      KEY_CAPS_LOCK,  KEY_F1,         KEY_F2,         KEY_F3,         KEY_F4,         KEY_F5,
/* 40-47 */  KEY_F6,         KEY_F7,         KEY_F8,         KEY_F9,         KEY_F10,        KEY_NUM_LOCK,   KEY_PRINTSCREEN,KEYPAD_7,
/* 48-4F */  KEYPAD_8,       KEYPAD_9,       KEYPAD_MINUS,   KEYPAD_4,       KEYPAD_5,       KEYPAD_6,       KEYPAD_PLUS,    KEYPAD_1,
/* 50-57 */  KEYPAD_2,       KEYPAD_3,       KEYPAD_0,       KEYPAD_PERIOD,  0xFF,           0xFF,           0xFF,           0xFF,
/* 58-5F */  0xFF,           0xFF,           0xFF,           0xFF,           0xFF,           0xFF,           0xFF,           0xFF,
/* 60-67 */  0xFF,           0xFF,           0xFF,           0xFF,           0xFF,           0xFF,           0xFF,           0xFF,
/* 68-6F */  0xFF,           0xFF,           0xFF,           0xFF,           0xFF,           0xFF,           0xFF,           0xFF,
/* 70-77 */  0xFF,           0xFF,           0xFF,           0xFF,           0xFF,           0xFF,           0xFF,           0xFF,
/* 78-7F */  0xFF,           0xFF,           0xFF,           0xFF,           0xFF,           0xFF,           0xFF,           0xFF,
};

// The keyboard leds values (used in the keyboard_leds global var).
#define KEYBOARD_LED_NUM_LOCK    0x01
#define KEYBOARD_LED_CAPS_LOCK   0x02
// The D0 pin is connected to the CLOCK pin of the keyboard (black wire on the original PC/XT keyboard).
#define KEYBOARD_CLOCK   0

// The D1 pin is connected to the DATA pin of the keyboard (white wire on the original PC/XT keyboard).
#define KEYBOARD_DATA    1

// They both have to be pulled-up for the keyboard to start sending key events.

// The Teensy 3.2 led is connected to the D13 pin.
#define KEYBOARD_LED    13

void setup()
{
  pinMode(KEYBOARD_CLOCK, INPUT_PULLUP);
  pinMode(KEYBOARD_DATA,  INPUT_PULLUP);
  pinMode(KEYBOARD_LED,   OUTPUT);
}


void loop()
{
  // Here's the signal from the PC/XT keyboard (warning, it's different from PC/AT more recent keyboards).
  //
  //  Step:  1   2  3  4  F  R  F  R  F  R  F  R  F  R  F  R  F  R  F  R ...
  //  
  //       --+   +--+  +--+  +--+  +--+  +--+  +--+  +--+  +--+  +--+  +----
  // Clock:  |   |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
  //         +---+  +--+  +--+  +--+  +--+  +--+  +--+  +--+  +--+  +--+
  // 
  //  Data:  <----------> B0    B1    B2    B3    B4    B5    B6    B7  
  //             Start
  //

  unsigned char scan_code = 0;
  int bit;

  // --- Start sequence ---
  
  // Wait for step 1 (main wait loop):
  while(digitalRead(KEYBOARD_CLOCK) == HIGH) {
    digitalWrite(KEYBOARD_LED, (keyboard_leds & KEYBOARD_LED_NUM_LOCK) != 0 ? HIGH : LOW);
  }
  // Wait for step 2:
  while (digitalRead(KEYBOARD_CLOCK) == LOW)
    ;
  // Wait for step 3:
  while(digitalRead(KEYBOARD_CLOCK) == HIGH)
    ;
  // Wait for step 4:
  while (digitalRead(KEYBOARD_CLOCK) == LOW)
    ;

for (bit = 0;  bit < 8; bit ++) {

    // Wait for step F (falling edge):
    while (digitalRead(KEYBOARD_CLOCK) == HIGH)
      ;
   // Read the data bit:
   scan_code >>= 1;
   if (digitalRead(KEYBOARD_DATA) == HIGH)
    scan_code |= 0x80;

    // Wait for step R (raising edge):
    while (digitalRead(KEYBOARD_CLOCK) == LOW)
      ;
  }

  if (key[scan_code & 0x7F] != 0xFF) {
    if (scan_code & 0x80) {
      Keyboard.release(key[scan_code & 0x7F]);
    } else {
      Keyboard.press(key[scan_code & 0x7F]);
    }
  }
/* Debug mode to show the unaffected scan code:
   else {
      Keyboard.println(scan_code);
  }
*/
}

Le code est très simple et mériterait d’être un peu “durci”, par exemple en ajoutant des timeouts dans les boucles d’attente pour revenir à l’état initial en cas de décalage dans la séquence (signal parasite).

Vous noterez l’ajout d’une fonctionnalité par rapport au clavier d’origine : la LED intégrée sur la carte Teensy 3.2 implémente l’affichage de l’état du modificateur Num Lock. Je n’ai pas eu le cœur de percer le capot du clavier pour la laisser apparaître, mais ce serait envisageable. Tout comme on pourrait ajouter l’affichage de Caps Lock (voire celui de Scroll Lock mais ça n’a aucune utilité de nos jours) en connectant une LED sur une sortie numérique de la Teensy.

Conclusion

Mon vieux clavier XT a repris place sur mon bureau !

Pas en place principale – il lui manque quelques éléments de confort comme le pavé de flèches par exemple – mais juste à côté, pour se connecter aux cartes embarquées que je programme. J’en suis très satisfait. J’espère que ce petit hack pourra servir d’inspiration pour reprendre en main des périphériques oubliés.

Comme je l’indiquais au début de cet article, tout a débuté en lisant un tweet de @hackablemag. Je suppose que mon camarade Denis prépare un article sur le même sujet pour un numéro à venir. J’ai hâte de le lire !

Note de dernière minute : en préparant la mise en page de ce billet, je viens de voir passer un message de @hackablemag indiquant que l’article en question paraîtra cette semaine dans le nouveau numéro du magazine. Je vous encourage à le lire.

Suivant :

Précédent :

Une réponse

  1. Bravo pour ce travail et ce papier. C’est clairement largement au dessus mes capacités de réaliser un hack pareil, mais c’était très intéressant à lire. Merci

URL de trackback pour cette page