Triceraprog
La programmation depuis le Crétacé

VG5000µ, les hooks d'entrées/sorties ()

Pour ces quatre nouveaux hooks, je ne suis pas très inspirés. Il s'agit de hook destiné au traitement des entrées sorties. Trois d'entre eux sont appelés lors d'une impression de caractères, le quatrième pour de l'acquisition.

Routines en sorties

Voici les trois premières :

  • $47DF, prthk : début de commande PRINT.
    • Est appelé en tant que première instruction de l'exécution de l'instruction PRINT.
    • À ce moment là, HL pointe vers les arguments de PRINT et le flag Z est à 1 s'il n'y a rien dans ces arguments.
    • Si vous rendez la main à la routine, elle déroulement l'affichage.
  • $47E2, outhk : début d'impression de caractère -> pour rerouter vers de nouvelles sorties
    • Est appelé pour chaque caractère envoyé sur un périphérique de sortie (en $3bd0)
    • La variable système (prtflg) désigne le périphérique.
    • Le caractère à afficher est dans le registre A.
    • Attention, ce caractère est à comprendre par rapport aux modes d'affichage : est-ce qu'on est en caractères normaux ? semi-graphiques ? redéfinis par l'utilisateur ?
  • $47E5, crdhk : début de retour chariot -> pour rerouter vers de nouvelles sorties
    • Est appelé par chaque demande de retour à la ligne lors de l'émission des caractères sur le périphérique (en $3c57).
    • N'est pas appelé lors d'un retour chariot dans l'éditeur par contre.
    • La variable système (prtflg) désigne le périphérique.

La variable système (prtflg) désigne le périphérique en sortie selon les valeurs suivantes :

  • 0 - L'écran
  • 1 - L'imprimante
  • 255 - La cassette (en écriture)

Les appels aux hooks se situent avant le tri vers les trois routines. La sélection ne se fait pas systématiquement dans le même ordre, ce qui signifie que les valeurs 2 à 254 n'envoient pas au même endroit pour les différentes fonctions.

Le retour chariot enverra sur l'imprimante et la sortie de caractère sur la cassette. PRINT ne considère que deux cas, l'imprimante et l'écran, et s'occupera de la position du chariot ou du curseur suivant le cas. Pour l'affichage en lui-même, c'est la routine d'envoi de caractère et de retour chariot qui sont utilisés.

Ainsi, si vous voulez supporter un nouveau périphérique en sortie, il faudra probablement effectuer un premier traitement au niveau du PRINT suivant si vous voulez être traité comme un écran ou une imprimante ; puis rendre la main, ou bien tout faire seul et jeter la première adresse de retour de la pile.

Du côté de la sortie de caractère et du retour chariot, il vous faudra prendre la main et jeter la première adresse de retour de la pile quoi qu'il arrive.

N'ayant pas de périphérique à tester (on pourrait imaginer une sortie série), je n'ai pas essayé.

Précautions

Pendant l'exécution d'une routine de sortie, n'appelez-pas les routines de la ROM qui elles-mêmes font de l'affichage. Vous vous appelleriez vous-même...

De toute façon, les routines d'affichages ne sont pas ré-entrantes et ne supportent pas d'être exécutées dans deux contextes simultanément. Cela explique pourquoi, si vous avez essayé comment moi d'utiliser des routines d'affichage pendant l'interruption d'affichage, des choses étranges se produisent.

En effet, si l'interruption à lieu pendant que la ROM est en train d'exécuter une de ces routines (un PRINT tout simplement), il y a de bonnes chances que cela se passe mal. Même si vous sauvez tous les registres. Le contexte est beaucoup plus gros que ça, avec des buffers de constructions de chaînes.

Mieux vaut avoir vos routines d'affichages spécialisées.

Routine en entrée

Pour l'entrée, il n'y a qu'un hook, celui de l'instruction INPUT :

  • $47EB, inphk : début de commande INPUT
    • Appelé en second lors de l'exécution de l'instruction BASIC INPUT, la première instruction étant la vérification que INPUT n'a pas été appelée en direct, hors programme.
    • Comme pour PRINT, HL pointe sur les arguments.
    • La variable système (getflg) désigne le périphérique en entrée. 0 pour le clavier, 255 pour la cassette. Il n'y a pas de périphérique écran en entrée...

Il est donc possible d'aller router l'INPUT vers un nouveau périphérique externe.

Le manque d'idée...

Oui... décidément, comme je n'ai pas de nouveau périphérique à router, je manque d'idée. Le programme que j'écris se contente donc de compter les appels aux différents hooks puis d'offrir une routine pour afficher les compteurs.

Installation

    defc out_number = $0726
    defc out_str = $36aa

    defc xcursor = $4805

    defc prthk = $47df
    defc outhk = $47e2
    defc crdhk = $47e5
    defc inphk = $47eb
    defc inthk = $47D0

    org $7A00           ; Spécification de l'adresse mémoire d'implentation

    push AF             ; Sauvegarde des registres sur la pile
    push HL

    ld   hl, call_text  ; Affichage du CALL pour la routine d'Affichage
    call out_str

    ld   hl, call_routine
    call out_number

    ld A,$C3            ; Mise en place des JP
    ld (prthk), A
    ld (outhk), A
    ld (crdhk), A
    ld (inphk), A

    ld HL,prt_routine   ; Mise en place des adresses de saut
    ld (prthk+1),HL

    ld HL,out_routine
    ld (outhk+1),HL

    ld HL,cr_routine
    ld (crdhk+1),HL

    ld HL,inp_routine
    ld (inphk+1),HL

    pop HL              ; Restauration des registres depuis la pile
    pop AF

    ret                 ; Retour au programme appelant

call_text:
    defm "CALL ", $00

Dans cette première partie, on effectue comme d'habitude l'installation des routines. Comme il y a quatre routines à mettre en place, on me d'un coup les quatre JP , puis dans la foulée les quatre adresses.

Mais auparavant, on affiche à l'écran un message indiquant l'instruction CALL à lancer pour provoquer l'affichage des compteurs.

Comme indiqué ci-dessus, appeler les routines d'affichages textes et nombres pendant une interruption ne fonctionne pas sans de nombreuses précautions. J'ai donc préféré offrir une routine qui affiche les compteurs sur un appel explicite. Et plutôt que de placer cet appel à une adresse fixe, elle est à la suite des autres routines. Son adresse étant variable en fonction des modifications des autres routines, je m'aide en affichant l'adresse à appeler.

Comptage

Le comptage se résume à quatre fois la même routine, à l'adresse de la variable de comptage près.

prt_count:
    defw    $0000
out_count:
    defw    $0000
cr_count:
    defw    $0000
inp_count:
    defw    $0000

prt_routine:
    push hl

    ld   hl, (prt_count)
    inc  hl
    ld   (prt_count), hl

    pop  hl
    ret

out_routine:
    push hl

    ld   hl, (out_count)
    inc  hl
    ld   (out_count), hl

    pop  hl
    ret

cr_routine:
    push hl

    ld   hl, (cr_count)
    inc  hl
    ld   (cr_count), hl

    pop  hl
    ret

inp_routine:
    push hl

    ld   hl, (inp_count)
    inc  hl
    ld   (inp_count), hl

    pop  hl
    ret

Au tout début, je réserve quatre emplacements de 16 bits pour les compteurs. Puis viennent les quatre routines qui chacune va augmenter le compteur associer lors d'un appel.

Prenons prt_routine. Tout d'abord, HL est sauvegardé. Comme d'habitude au début d'une instruction, ce registre pointe vers la ligne en train d'être exécutée, il est essentiel de préserver sa valeur.

HL est ensuite chargé avec la valeur de compteur, cette valeur est incrémentée puis replacée dans le compteur.

On restaure HL et on revient. Vraiment simple.

L'affichage des compteurs

La dernière partie est une routine qui sera appelée explicitement par un CALL pour afficher la valeur des compteurs.

call_routine:
    push hl
    push bc
    push de
    push af

    ld   hl, (xcursor)
    push hl

    ld   hl, $0020
    ld   (xcursor), hl
    ld   hl, (prt_count)
    call out_number

    ld   hl, $0120
    ld   (xcursor), hl
    ld   hl, (out_count)
    call out_number

    ld   hl, $0220
    ld   (xcursor), hl
    ld   hl, (cr_count)
    call out_number

    ld   hl, $0320
    ld   (xcursor), hl
    ld   hl, (inp_count)
    call out_number

    pop  hl
    ld   (xcursor), hl

    pop  af
    pop  de
    pop  bc
    pop  hl

    ret

Vu qu'on appelle out_number, mieux vaut sauver pour commencer tous les registres (sauf IX et IY, mais ces registres sont particuliers pour la ROM). On les restaure en fin de routine.

Puis on récupère les coordonnées du curseur dans HL. Attention ici, l'adresse xcursor contient une valeur sur 8 bits. Elle est suivi par ycursor qui contient aussi une valeur sur 8 bits. Grâce au LD HL, (xcursor), c'est donc les coordonnées X et Y qui sont chargées dans HL en une seule instruction.

Ces coordonnées seront elles-aussi restaurées en fin de routine. Pour remettre le curseur là où il se situait avant le CALL.

Vient ensuite une répétition de quatre fois la même séquence, aux valeurs près.

Tout d'abord, HL prend les coordonnées d'affichages du nombre que l'on va écrire. Dans la valeur sur 16 bits, Y arrive en premier, suivi de X. Ainsi $0020 signifie ligne 0, colonne 32.

Cette position est placée dans la variable système (xcursor). HL prend ensuite la valeur du compteur à afficher, et enfin CALL out_number se charger d'afficher le nombre entier contenu dans HL à la position courante.

BASIC

Voici un programme BASIC qui va monter la routine en mémoire. Suivi d'un RUN pour l'exécuter puis du CALL pour lancer le hook.

10 CLEAR 50,&"79FF"
20 S=&"7A00"
30 READ A$
40 IF A$="FIN" THEN END
50 A$="&"+CHR$(34)+A$+CHR$(34):A=VAL(A$)
60 POKE S,A
70 S=S+1
80 GOTO 30
300 DATA F5,E5,21,37,7A,CD,AA,36,21,6D,7A,CD,26,7,3E,C3,32,DF,47,32
310 DATA E2,47,32,E5,47,32,EB,47,21,45,7A,22,E0,47,21,4F,7A,22,E3,47
320 DATA 21,59,7A,22,E6,47,21,63,7A,22,EC,47,E1,F1,C9,43,41,4C,4C,20
330 DATA 0,0,0,0,0,0,0,0,0,E5,2A,3D,7A,23,22,3D,7A,E1,C9,E5
340 DATA 2A,3F,7A,23,22,3F,7A,E1,C9,E5,2A,41,7A,23,22,41,7A,E1,C9,E5
350 DATA 2A,43,7A,23,22,43,7A,E1,C9,E5,C5,D5,F5,2A,5,48,E5,21,20,0
360 DATA 22,5,48,2A,3D,7A,CD,26,7,21,20,1,22,5,48,2A,3F,7A,CD,26
370 DATA 7,21,20,2,22,5,48,2A,41,7A,CD,26,7,21,20,3,22,5,48,2A
380 DATA 43,7A,CD,26,7,E1,22,5,48,F1,D1,C1,E1,C9
1000 DATA FIN
RUN
CALL &"7A00"

Pour éviter d'avoir à tout rentrer au clavier, voici le fichier .k7. À charger avec CLOAD, suivi d'un RUN et du CALL &"7A00".

Une fois le programme chargé et les routines installées via le CALL, vous pouvez regarder les effets sur les compteurs avec un programme similaire à celui qui suit (pensez à faire un NEW pour partir à vide) :

10 CALL xxxxx
20 PRINT "HELLO"
30 INPUT A$
40 INPUT A$
50 CALL xxxxx

En remplaçant bien entendu les xxxxx par la valeur indiquée lors du CALL &"7A00"

Le résultat

Et voici le résultat d'une session comme indiquée ci-dessus.

Un CALL avec paramètres