(et j’ai galéré à peu près à chaque étape)
Au départ, il y avait une idée un peu absurde.
Un clavier de Minitel.
Un vrai.
Lourd. Un peu jauni. Avec ses touches mythiques : ENVOI, SOMMAIRE, ANNULATION.
Et une question toute simple après avoir abandonné l’idée de “vraiment” refaire vivre ce vieux minitel qui refusait de me donner un affichage correct malgré des remplacements de composants “Est-ce que je peux encore m’en servir aujourd’hui ? Vraiment.”
Pas comme un objet déco.
Pas comme une émulation bancale.
Mais comme une vraie interface, branchée sur une machine moderne, pour faire tourner Colossus.
Spoiler : oui.
Mais pas sans galérer.
Le fantasme de départ (et la réalité)
Dans ma tête, c’était simple.
Un clavier → un câble → un ordinateur → ça marche.
Sauf que…
Un clavier de Minitel, ce n’est pas un clavier USB.
Il n’envoie pas de lettres.
Il ne “parle” pas.
On a une nappe avec 18 connecteurs, et aucune documentation n’explique ce que ça fait.
Les seuls indices sérigraphiés sur la nappe: 9 broches “X” et 9 “Y”
Seule forte présomption: quand tu appuies sur une touche, tu relies une ligne et une colonne dans une matrice.
Rien de plus.
Rien de lisible tel quel.
On a une nappe avec 18 connecteurs, et aucune documentation n’explique qui fait quoi.
À ce moment-là, j’ai compris un truc important:
👉 je n’allais pas “adapter” un clavier.
👉 j’allais devoir l’écouter.
Lire un clavier comme on lit une carte
J’ai branché la nappe du clavier sur un microcontrôleur, un Raspberry Pi Pico 2.
Pourquoi lui ?
- petit,
- fiable,
- capable de lire ou écrire sur des entrées électriques,
- et surtout capable de se faire passer pour un clavier USB.
- Mais avant d’envoyer quoi que ce soit à l’ordinateur, il fallait comprendre une chose essentielle:
➡️ Quelle touche correspond à quel contact électrique?
Donc j’ai fait un truc long. Très long.
J’ai écrit un programme qui ne faisait que:
- activer une broche,
- lire les autres,
- afficher les paires de contacts détectées,
- recommencer.
Touche par touche.
Sans tricher.
Sans deviner.
C’était lent.
Mais à la fin, j’avais une carte complète du clavier. Un fichier qui disait que par exemple quand on appuie sur espace, ça ferme les contacts entre les broches GP9 et GP16 de mon pico.
Et là, bizarrement, j’ai commencé à être serein.
Le clavier, lui, fonctionnait parfaitement.
Les bugs viendraient d’ailleurs.
L’électronique derrière (simple, mais essentielle)
Côté électronique, je suis resté volontairement sobre.
Chaque ligne et chaque colonne du clavier passe par une résistance de 10 kΩ en série.
Pourquoi ?
Pour éviter les courts-circuits. Pour dormir tranquille (évitons de cramer une entrée accidentellement. Un pico n’est pas cher, mais son remplacement demanderait du temps à commander et à rebrancher.
Ensuite :
- les entrées sont configurées avec des pull-up internes,
- une ligne est forcée à 0,
- les colonnes sont lues,
- puis on passe à la suivante.
Rien d’exotique.
Mais du fiable.
Faire croire à l’ordinateur que le Minitel est un clavier USB
Une fois les contacts compris, restait la partie magique.
Le Pico peut se présenter comme un clavier USB standard grâce à la librairie adafruit_hid
Il n’envoie pas des lettres.
Il envoie des codes de touches.
Et c’est l’ordinateur qui décide:
- si ce code devient un A,
- un Q,
- un ?,
- ou rien du tout.
Premier test.
J’appuie sur une touche.
Une lettre apparaît.
Pas la bonne.
QWERTY.
Moment de doute numéro 42.
Mais en réalité, tout fonctionnait parfaitement.
C’était juste une question de conventions.
Et c’est là que j’ai compris un point fondamental du projet :
👉 le Pico ne doit jamais envoyer des caractères.
👉 il doit envoyer des touches physiques.
Le reste, c’est le système qui gère.
Et tes touches ENVOI, CORRECTION, toussa?
Mes touches sont mappées pour être les touches de fonction d’un clavier PC standard.
J’ai eu une bonne intuition: c’est en effet ce qu’attendra plus tard minipavi.
Le déclic final
Après une douzaine d’heures, j’ai rebranché le pico au Raspberry.
J’ai ouvert un terminal.
J’ai appuyé sur :
- des lettres,
- des chiffres,
- ENVOI,
- SOMMAIRE,
- ANNULATION.
Et tout répondait.
Réellement.
À ce moment-là, ce n’était plus un bricolage.
C’était une interface fonctionnelle.
Un clavier des années 80 qui parlait à une machine moderne, sans tricher.
Ce que j’ai réellement construit
Techniquement:
- Je suis parti d’un clavier matriciel ancien,
- utilisé un microcontrôleur moderne,
- écrit du Python embarqué,
- exploité une interface USB HID standard.
Mais humainement ?
J’ai construit un pont.
Entre deux époques.
Entre deux façons de penser l’informatique.
Entre un monde où chaque touche avait un sens,
et un monde où tout est abstrait.
Et j’ai appris, encore une fois, que:
- comprendre prend du temps,
- douter fait partie du processus,
- et que quand ça marche enfin…
ce n’est jamais “juste technique”.
Et maintenant?
Ce clavier est devenu une base.
On peut :
- réaffecter les touches,
- recréer la logique Minitel,
- détourner ENVOI, SOMMAIRE, GUIDE,
- inventer de nouveaux usages.
Ce n’est plus un objet figé.
C’est une interface vivante.
Et franchement…
ça valait chaque moment de doute.
Ah et si ça peut aider…
Le code final qui fonctionne chez moi 🙂
import time
import board
import digitalio
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
# =========================
# CONFIG GPIO (nappe Minitel)
# =========================
PINS = [2,3,4,5,6,7,8,9,11,12,13,14,15,16,17,18,19]
GP = {n: getattr(board, f”GP{n}”) for n in PINS}
# GPIO uniques (évite les conflits “in use”)
io = {}
for n in PINS:
p = digitalio.DigitalInOut(GP[n])
p.direction = digitalio.Direction.INPUT
p.pull = digitalio.Pull.UP
io[n] = p
# =========================
# CLAVIER USB HID
# =========================
kbd = Keyboard(usb_hid.devices)
# =========================
# MAPPING MATRICE -> KEYCODE
# (basé sur ton dump JSON final + corrections terrain)
# =========================
MAP = {
# Modificateurs
(6,11): (Keycode.ESCAPE,),
(4,11): (Keycode.LEFT_CONTROL,),
(2,11): (Keycode.LEFT_SHIFT,),
(2,15): (Keycode.RIGHT_SHIFT,),
# Lettres
(5,11): (Keycode.A,),
(5,12): (Keycode.Z,),
(5,13): (Keycode.E,),
(5,14): (Keycode.R,),
(5,15): (Keycode.T,),
(5,19): (Keycode.Y,),
(5,18): (Keycode.U,),
(5,17): (Keycode.I,),
(4,17): (Keycode.O,),
(4,18): (Keycode.P,),
(3,11): (Keycode.Q,),
(4,12): (Keycode.S,),
(3,12): (Keycode.D,),
(4,13): (Keycode.F,),
(3,13): (Keycode.G,),
(4,14): (Keycode.H,),
(3,14): (Keycode.J,),
(4,15): (Keycode.K,),
(3,15): (Keycode.L,),
(4,19): (Keycode.M,),
(2,12): (Keycode.W,),
(2,17): (Keycode.X,),
(2,18): (Keycode.C,),
(2,19): (Keycode.V,),
(2,13): (Keycode.B,),
(2,14): (Keycode.N,),
# Chiffres
(8,19): (Keycode.ONE,),
(8,18): (Keycode.TWO,),
(8,17): (Keycode.THREE,),
(7,19): (Keycode.FOUR,),
(7,18): (Keycode.FIVE,),
(7,17): (Keycode.SIX,),
(3,19): (Keycode.SEVEN,),
(3,18): (Keycode.EIGHT,),
(3,17): (Keycode.NINE,),
(9,18): (Keycode.ZERO,),
# Navigation / édition
(9,15): (Keycode.ENTER,),
(8,14): (Keycode.BACKSPACE,),
(9,16): (Keycode.SPACE,),
# Flèches
(9,11): (Keycode.UP_ARROW,),
(9,12): (Keycode.DOWN_ARROW,),
(9,13): (Keycode.LEFT_ARROW,),
(9,14): (Keycode.RIGHT_ARROW,),
# Ponctuation (corrigée empiriquement)
(6,12): (Keycode.COMMA,), # ,
(6,13): (Keycode.PERIOD,), # .
(6,14): (Keycode.QUOTE,), # ‘
(6,15): (Keycode.SEMICOLON,), # ;
(6,17): (Keycode.SLASH,), # /
(6,18): (Keycode.GRAVE_ACCENT,),# :
(6,19): (Keycode.MINUS,), # –
# Touches spéciales
(9,19): (Keycode.KEYPAD_ASTERISK,),
(9,17): (Keycode.KEYPAD_HASH,),
# Touches Minitel → F-keys
(7,11): (Keycode.F1,), # CONNEXION / FIN
(8,11): (Keycode.F2,), # FNCT
(8,12): (Keycode.F3,), # SOMMAIRE
(8,13): (Keycode.F4,), # ANNULATION
(8,15): (Keycode.F5,), # REPETITION
(7,12): (Keycode.F6,), # GUIDE
(7,13): (Keycode.F7,), # CORRECTION
(7,14): (Keycode.F8,), # SUITE
(7,15): (Keycode.F9,), # ENVOI
}
# =========================
# SCAN MATRICE
# =========================
def scan_contacts():
now = set()
for d in PINS:
io[d].direction = digitalio.Direction.OUTPUT
io[d].value = False
time.sleep(0.00005)
for s in PINS:
if s != d and io[s].value is False:
a, b = sorted((d, s))
now.add((a, b))
io[d].direction = digitalio.Direction.INPUT
io[d].pull = digitalio.Pull.UP
return now
# =========================
# BOUCLE PRINCIPALE
# =========================
pressed_last = set()
print(“Clavier Minitel → USB HID prêt.”)
time.sleep(0.3)
while True:
contacts = scan_contacts()
pressed = contacts – pressed_last
released = pressed_last – contacts
# Relâchement
for pair in released:
keys = MAP.get(pair)
if keys:
for k in keys:
try:
kbd.release(k)
except Exception:
pass
# Appui
for pair in pressed:
keys = MAP.get(pair)
if keys:
try:
kbd.press(*keys)
except Exception:
pass
pressed_last = contacts
time.sleep(0.01)