website/content/blog/minitel/index.fr.md

8.1 KiB
Raw Blame History

+++ title = "Le Minitel comme terminal GNU/Linux" date = 2022-07-02 description = "Encore une tentative d'utilisation d'un Minitel comme terminal." [taxonomies] tags = ["Minitel", "rétro", "Arduino"] +++

Objectif : utiliser un Minitel comme terminal d'un système GNU/Linux.

{{ float_img(alt="Minitel affichant le logo de Rust.", src="minitel_rust.jpg", style="max-height:100vh;max-width:min(800px,100%)") }}

Cet article est paru dans le magazine Programmez! #256, qui contient plusieurs autres articles concernant le Minitel et son interfaçage.

😎 L'ère de la péri-informatique

Le Minitel dispose d'une prise "péri-informatique", qui est un port série d'entrée-sortie. Il suffit pour communiquer avec d'une interface série. J'ai donc utilisé une Arduino comme pont entre le Minitel et l'ordinateur. Mais attention, il faut un Arduino possédant plusieurs interfaces série (comme le Mega). Cela nous évite d'avoir à coder un pilote série logiciel. Le standard péri-informatique utilise à peu près de l'ASCII 7 bits et un découpage en octets. Le bit de poids fort est la somme de contrôle des 7 autres.

🔌 Électronique

{{ float_img(alt="Schéma du branchement", src="minitel_wiring.svg", style="width:300px;background-color:#fff") }}

  • Connectez le GND du Minitel (pin 2, celui du milieu) au GND de l'Arduino;
  • Connectez le RX du Minitel (pin 1, tout à gauche du GND) au TX1 de l'Arduino;
  • Connectez le RX1 de l'Arduino au TX du Minitel (pin 3, tout à droite du GND) à travers une résistance de 220Ω, et au +5V à travers une résistance pull-up de 2200Ω (toute valeur entre 1kΩ et 20kΩ devrait marcher). La résistance est nécessaire car la sortie du Minitel est en +8,5V.

💻 Logiciel

J'ai d'abord codé un contrôleur en Python. C'est une sorte d'émulateur de terminal, avec le port USB comme entrée-sortie. Il prend une ligne de commande et l'exécute à l'appui de la touche Envoi.

{{ float_img(alt="Minitel affichant le logo de Rust.", src="minitel_fortune.jpg", style="max-height:100vh;max-width:min(800px,100%)") }}

Rien que ça demandait de surmonter plusieurs difficultés :

  • Il n'y a pas de touche Nouvelle Ligne. Seulement une touche Retour Chariot. Mais par miracle le caractère Nouvelle Ligne existe, on peut donc utiliser du CRLF. Ça veut dire qu'il faut interpréter la touche Envoi (qui a un code sur deux octets) comme LF, et transformer les LF venant de Linux en CRLF.
  • Les accents ne sont pas standard. Une lettre accentuée fait trois octets : un qui indique un caractère spécial, un qui spécifie l'accent, et la lettre. Et attention! On a droit aux majuscules accentuées (coucou Windows, le Minitel te surpasse), mais seulement les accents franchouillards. La piñata et le glacier würmien n'ont pas la nationalité.
  • Le clavier est en majuscule, et les minuscules demandent l'appui de Maj. Si on veut inverser ce comportement, on doit pour chaque lettre arrivant au contrôleur, renvoyer un retour arrière puis la lettre en casse inversée. On voit le clignotement à l'écran et ça oblige à taper assez lentement pour que la réécriture ait le temps de s'opérer.
  • Le port péri-informatique supporte 1200 bit/s, soit un rafraîchissement complet de l'écran en plus de 6 secondes. Certains modèles sont plus rapides, jusqu'à 9600 bit/s. Cette lenteur oblige à optimiser l'affichage pour plus d'immédiateté. On ne peut pas rafraîchir tout l'écran en permanence, il faut envoyer uniquement les caractères qui changent. Donc s'il y a un bug (un imprévu dans le programme, un problème de signal, un utilisateur qui tape trop vite), l'écran ne sera pas dans l'état attendu, et le programme, qui n'a aucun moyen de le savoir, continuera même s'il écrit n'importe quoi.
  • En mode semi-graphique, les caractères sont remplacés par une grille de 2×3 pixels. On peut alors dessiner, en convertissant les images correctement. On a même quelques nuances de gris! (pas 50 mais c'est déjà bien) L'inconvénient, c'est que le changement de couleur agit sur le caractère entier, soit 6 pixels, et non sur chaque pixel individuellement. La gestion de la couleur est donc un peu délicate.
  • Si l'affichage est modifié en permanence, par exemple dans un jeu de Snake pour déplacer le serpent, l'appui de n'importe quelle touche peut interférer avec un caractère spécial de plusieurs octets envoyé par le contrôleur. Il en résulte un comportement imprévisible, et souvent un caractère aléatoire qui s'affiche. C'est pourquoi dans le Snake et dans le Tetris que j'ai faits, l'écran se retrouve vite constellé de caractères. J'ai essayé de nettoyer des petites parties différentes de l'écran en permanence, par exemple ligne par ligne, mais cela prend trop de temps, réduit la réactivité du jeu et n'est pas très efficace.
  • L'écran est très petit, 40×24 caractères seulement. Aussi, il ne défile pas mais reboucle en haut quand le curseur arrive en bas. Il n'est donc pas vraiment adapté à un terminal de type Unix. Il faut soit créer un système de pagination, qui nettoie l'écran à chaque page, soit accepter de reboucler et nettoyer la fin de la ligne.
  • Certains caractères manquent, comme la barre verticale |. Cette dernière étant indispensable dans un terminal, j'ai décidé de la substituer aux accents circonflexes ^ qui sont bizarrement affichés sur le Minitel.
  • Côté Linux ce n'est pas facile non plus... Il faut pouvoir indiquer aux programmes la taille du terminal, qu'ils comprennent qu'ils sont dans un terminal et non dans un script, pour que par exemple bash écrive son invite de commande (prompt), gérer le retour arrière (qui n'est pas géré directement par bash), etc.

Toutes ces contraintes rendent la conception d'un terminal générique compliquée. Il faut que chaque programme adapte sa gestion du clavier et de l'affichage, car les besoins sont différents pour un utilitaire en ligne de commande, un éditeur de texte, une messagerie instantanée ou un jeu en temps réel.

On m'a donc donné l'idée d'une approche différente : plutôt que d'envoyer la sortie des programmes au Minitel, on garde un tampon de l'écran du Minitel en mémoire. C'est un tableau de l'état de chaque caractère, dont le mode texte ou semi-graphique, la couleur, le clignotement, le code du caractère, etc. Les programmes écrivent dedans, puis demandent au contrôleur de l'envoyer. Au prix de calculs plus lents (ce qui reste négligeable), l'algorithme cherche alors à envoyer le moins de données possibles. Les caractères inchangés ne sont pas renvoyés, on profite de l'instruction plaçant le curseur à la position donnée et de celle permettant de répéter les N derniers caractères.

J'ai implémenté ce nouveau contrôleur en Rust (un langage bas-niveau offrant des abstractions haut-niveau sans coût et la sécurité de la mémoire), ce qui permet un code plus propre, sûr et rapide qu'en Python. C'est une bibliothèque réutilisable (publiée sous licence libre GNU AGPL), que j'ai pu utiliser pour faire un terminal, un Tetris et un Snake.

Au prix de quelques nanosieverts et d'un ultrason désagréable, cette petite aventure rétro m'a plongé dans une époque que je n'ai pas connue, quoique j'y vis peut-être encore.

Ressources utiles :