Froid, abandon, quitte...
C'est triste un démarrage de Forth... COLD, ABORT, QUIT. On aurait pu imaginer des mots comme WARMUP, READY, LOOP. Mais je n'ai pas prévu de renommer les mots standards de Forth. Et comme indiqué dans l'article précédent, il est temps de déplacer le code de démarrage vers les mots officiels.
Et à vrai dire, comme prévu, il n'y avait pas grand chose à faire.
Tout d'abord, le code de démarrage devient juste initialiser l'interpréteur avec le mot COLD :
boot_forth:
; Set the Work Register to the first word to execute:
lda #<COLD_word_cfa
sta REG_W
lda #>COLD_word_cfa
sta REG_W + 1
; And use it
jmp docol
Et pour faire simple, ABORT et QUIT appellent juste des mots cachés qui contiennent le code assembleur que j'avais déjà. Cela donne :
; COLD
DEFINE_FORTH_WORD COLD, 0, 0
.word RESET_ENV_word_cfa
.word ABORT_word_cfa
; ABORT never returns, no need for DO_SEMI
; ABORT
DEFINE_FORTH_WORD ABORT, COLD, 0
.word RESET_STACK_word_cfa
.word QUIT_word_cfa
; QUIT never returns, no need for DO_SEMI
Reste QUIT qui est la boucle infinie. J'avais déjà une boucle infinie sous forme de mot assembleur, je l'ai renommée et voilà, terminé !
Certes, ce n'est pas encore un vrai mot QUIT, qui doit traiter les entrées utilisateur et exécuter les commandes. Mais en tout cas, la structure est en place.
Lire le clavier
Et donc puisque tout cela n'était pas bien compliqué, et que QUIT va avoir besoin de lire le clavier, passons à la lecture du clavier.
Accès au clavier
Le clavier Family BASIC est connecté à la Famicom via le port d'extension. Celui qui est devant dans une Famicom classique. Avant de l'interroger, il faut écrire à l'adresse $4016 puis lire depuis l'adresse $4017.
L'écriture à l'adresse $4016 contrôle le balayage du clavier. En effet, le clavier est une matrice de touches sur 9 lignes et 2 colonnes. La matrice est visible sur NESDev.
L'écriture à $4016 utilise les 3 premiers bits pour contrôler le scan.
| Bit | Description |
|---|---|
| 0 | 0 = scan normal ; 1 = réinitialiser le scan ligne 0, colonne 0 |
| 1 | Sélection de colonne |
| 2 | Activer le clavier (1 = activé) |
| 3-7 | inutilisés |
La lecture depuis $4017 récupère les données du clavier.
| Bit | Description |
|---|---|
| 0 | Inutilisé |
| 1-4 | Données de colonne |
| 5-7 | Inutilisés |
Un délai doit être respecté entre l'écriture à $4016 et la lecture depuis $4017 pour permettre au clavier de préparer les lignes de données.
Scan dans le Forth
Pour le scan dans le Forth, j'utilise trois tampons en mémoire qui sont chacun de taille 9 octets (un par ligne). Pour chaque ligne les informations des deux colonnes sont regroupées dans un seul octet.
- Bits 0-3 : données de la colonne 0
- Bits 4-7 : données de la colonne 1
Le premier tampon sert au scan en cours. Un second sert à stocker le résultat du scan précédent. Le troisième sert à détecter les changements entre les deux scans (en faisant un XOR entre les deux autres tampons).
Une fois les tampons remplis, le tampon de changements est analysé pour détecter les changements d'état des touches. Si un bit a changé dans le tampon de changements, l'état pressé/relâché est déterminé à partir du tampon de matrice actuel. Ceci est fait pour l'octet entier, donc si plusieurs bits ont changé dans une ligne, ils sont tous traités.
Puis pour chaque bit modifié qui est aussi détecté comme un appui, une recherche dans une table de caractères fait la correspondance. Si le caractère est affichable, il est placé dans une file de caractères qui pourra être interrogée plus tard par le mot Forth KEY, qu'il reste à implémenter.
Pour chaque bit modifié, je calcule aussi un code de touche ((ligne << 4) | numéro_de_bit), mais je ne l'utilise pas. Cela vient d'une idée précédente où je voulais tout d'abord générer les évènements de touches puis les décoder dans un second temps. Au final, j'ai trouvé ça bien compliqué pour cette machine.
Ce qu'il reste à faire
Plein de choses. Même en se limitant au clavier. Mais je pense que le plus long a été fait : le principe général de scan et de décodage. Il me reste à traiter les touches spéciales (comme SHIFT, CTRL, etc) et à implémenter le mot Forth KEY.
J'ai prévu d'implémenter aussi le mot KEY? qui permet de savoir si une touche est disponible. Ainsi que KEYPRESSED? et KEYDOWN? pour interroger l'état de touches spécifiques.
On peut écrire !
En branchant rapidement la détection des touches avec un appel codé en dur à EMIT dans la boucle principale, je peux écrire à l'écran ! Le curseur n'avance pas, car lui aussi était un appel codé en dur, cependant, c'est assez satisfaisant.
D'ailleurs, je pense que la prochaine étape sera le traitement de l'avancée du curseur depuis la boucle principale en Forth. Cela va m'obliger à implémenter quelques nouveaux mots, comme 0BRANCH (afin de faire des tests conditionnels).
