Voici un objet qui, dans mes souvenirs, était mythique : imaginez un bouton miraculeux sur lequel il suffisait d'appuyer pour prendre le contrôle de n'importe quel programme, et ce, sans que celui-ci puisse y faire quoique ce soit ! Et comme il suffisait de deux bouts de ficelle et d'un trombone (j'exagère à peine) pour faire soi-même son switch NMi, MacGyver lui-même n'aurait pas rêvé meilleure bidouille sur Apple II !

Mais reprenons depuis le début : NMi veut dire Non-Maskable Interrupt, c'est à dire une interruption que l'on ne peut inhiber. Pour générer ce type d'interruption, il suffit de mettre à la masse une patte du 6502, la 6 en l'occurence. Mais, on peut aussi utiliser les connecteurs d'un slot d'extension. On retrouve en effet sur chaque slot, une broche NMi (la 29 cette fois) et une broche Masse (la 26).

Que se passe-t'il quand survient une interruption non masquable ?

Il se passe notamment 3 choses intéressantes pour nous :

  • le 6502 empile PCH et PCB, qui sont les parties hautes et basses du compteur ordinal (le seul registre 16 bits du processeur).
  • le 6502 empile ensuite le registre d'état.
  • puis récupération du contenu des adresses $FFFA et $FFFB et saut à l'adresse en résultant ($3FB sur un Apple II).

Le très grand intérêt d'une interruption NMi par rapport à un Reset (et le fait qu'on s'y attarde aujourd'hui) tient en deux mots : compteur ordinal. En empilant cette valeur, le 6502 empile l'adresse exacte de l'instruction qui était exécutée au moment de l'interruption. Il suffit donc de récupérer ce compteur pour savoir à quelle adresse mémoire en était le programme interrompu. Et c'est un très gros avantage par rapport au Reset qui ne nous est d'aucune utilité à ce niveau là !

Imaginez un soft qui boucle sur sa routine de protection. Un coup de switch et vous localisez immédiatement l'endroit. Une musique à récupérer ? Attendez simplement qu'elle soit jouée et hop, interruption non masquable et vous vous retrouvez au cœur de la routine en question.

Bidouille ultime ou simple promesse sans lendemain ? C'est ce que nous allons voir dans la suite...

Les mains dans le cambouis !

Dans un premier temps, il va déjà falloir confectionner notre switch NMi personnel :

 

Pour se fabriquer un switch NMi, rien de plus simple : un bouton poussoir de type "normalement ouvert" (c'est à dire qu'en état non appuyé, il ne laisse pas passer le courant), du fil, une résistance de 100 ohms (de 0.5W) et deux petits connecteurs type fourche. Il faudra aussi un tube en plastique dans lequel on glissera le bouton poussoir et qui nous servira de poignée. Ici, deux stylos ont été sacrifiés pour l'occasion ! Un bleu pour les garçons et un rouge pour les filles !

N'hésitez pas à prendre une bonne longueur de fil (pas comme sur la photo). Il faudra pouvoir aller de l'arrière de l'Apple, sortir du boitier et revenir sur le devant de la machine pour la prise en main, donc ne soyez pas pingre ! Laissez du mou pour avoir de la souplesse lors de l'utilisation...

 

 

 

Pour le montage, on soude directement la résistance sur une des pattes du switch. Sur l'autre patte vient se fixer le premier fil. Le second vient se positionner dans la continuité de la résistance.

Bon par contre, pour les soudures, sans Troisième Main et à moins de s'appeler Shiva, vous allez galérer (comme moi...). Il faudra tenir le switch avec les dents, approcher le fer à souder d'une main et le fil de l'autre et éviter de se brûler un œil... Avec un peu de pratique, on y arrive sans trop de dégats !

 

 

 

On soude les deux connecteurs type fourche à l'autre extrémité de chacun des fils. On n'oublie pas avant de glisser le morceau de stylo.

On remarquera d'ailleurs ici le modèle spécial fête ! À n'utiliser que pour les grandes occasions...

 

 

 

 

Le même mais en finition pro cette fois :
- du chaterton pour cacher la misère.
- une touche de vernis à ongle rouge sur une des pattes de chaque fourche. Un peu de coquetterie ne peut pas faire de mal...

 

 

 

Bon là, ça ne rigole plus. Il va falloir brancher un des fils sur le connecteur 26 (la masse). C'est celui qui est tout en haut à gauche de chaque slot. On enfiche la fourche à cheval sur le bord du slot de façon à ce que le contact se fasse par l'intérieur de la patte.

On fait de même avec le second fil sur le connecteur 29. Il est du même côté, 4 positions plus bas que le 26.

 

Juste en face du connecteur 26, la masse, on a le +5v ! Il faut donc bien faire attention que la patte de la fourche ne soit pas en contact avec ce connecteur. Vous comprenez maintenant le pourquoi du vernis, pitoyable tentative d'isoler la patte en cas de contact malheureux.

Je vous conseille d'ailleurs d'effectuer le branchement sur un slot "usagé", c'est à dire un slot dont les contacts sont écartés à force d'y mettre des cartes. Cela donnera un peu de marge entre la masse et le +5v !



Le branchement final : on fait ressortir le câble par l'arrière de la machine. À propos vous pouvez parfaitement intervertir fil noir et rouge.

Vous remarquerez également que j'ai dégagé toutes les cartes d’extension pour la première utilisation : autant éviter la casse en cas de problèmes.
Vérifiez que les fourches sont bien enfoncées et tiennent correctement à cheval sur le slot. Quand vous manipulerez le switch, qu'elles ne se décrochent pas au premier mouvement du fil...

 

 

Ici je vous présente un branchement alternatif. On a été chercher la masse, non plus sur le pin 26, mais directement sur la Carte Mère. Cela évite la proximité flippante avec le +5v. À l'inverse, on y perd en facilité de montage/démontage.

 

 

Voilà tout est en place, il ne reste plus qu'à tester. Normalement, si tout se passe bien, en appuyant sur le bouton, vous devriez entendre un BIP et récupérer la main sous Monitor (dans une configuration sans carte qui boot sur le BASIC). Vous pouvez alors remettre vos cartes et voir les réactions de l'interruption avec différents softs...

Alors le Switch NMi, arme d'interruption massive ?

Un simple bricolage tel que décrit ci-dessus, pour l'utilisation que l'on veut en faire, pose rapidement trois problèmes :

  • un bouton poussoir n'est pas suffisamment précis et vous allez vous rendre compte qu'il arrive souvent que plusieurs NMi s’enchaînent (la patte NMi ayant été mise à la masse pendant un laps de temps trop long). Pour juste générer une interruption, cela n'est pas spécialement gênant. Mais pour récupérer un compteur ordinal qui correspond à quelque chose, ça l'est un peu plus, car plusieurs octets ont alors été empilés.
  • par défaut, après une NMi, on se retrouve en $3FB. Il est bien évident que certains programmes ne vont pas gentiment nous rendre la main et modifieront le vecteur à cet endroit. Si l'interruption, elle, est non-masquable, la routine d'interception est, par défaut, à la discrétion du programme en mémoire.


Pour ces deux problèmes, il existe des solutions relativement faciles à mettre en œuvre :

  • un montage plus élaboré peut être envisagé, éliminant le premier problème (effet rebond). Le principe est simple : le bouton poussoir ne fait plus directement le contact entre masse et patte NMi mais alimente un circuit qui va faire cette jonction pendant un temps très court (de l'ordre de la microseconde). Voici le schéma théorique d'un tel circuit :

Merci aux membres du forum Futura-Sciences.Com, section électronique et notamment à son modérateur, Tropique, pour l'aide apportée (le schéma est de lui !) et les réponses à mes questions d'idiot du village !

  • pour récupérer le compteur ordinal et nous affranchir du vecteur en $3FB, on peut utiliser une ROM custom ! On redirigera le contenu de $FFFA et $FFFB non plus vers $3FB mais vers une routine perso ! Un exemple est donné ci-après :

 

  FFFA : CE ; en cas de NMi
  FFFB : FE ; saut vers $FECE !
 
  FECD-   60          RTS           ; neutralisation SAVE
 
  FECE-   48          PHA           ; push A
  FECF-   8A          TXA           ;
  FED0-   48          PHA           ; push X
  FED1-   98          TYA           ;
  FED2-   48          PHA           ; push Y
 
  FED3-   20 E4 FB    JSR $FBE4     ; PING!
  FED6-   AD 00 C0    LDA $C000     ; attente de
  FED9-   10 FB       BPL $FED6     ; l'appui d'une touche
  FEDB-   8D 10 C0    STA $C010     ; réinitialise clavier
  FEDE-   C9 C3       CMP #$C3      ; touche "C" (Continue) ?
  FEE0-   D0 06       BNE $FEE8     ;
  FEE2-   68          PLA	    ;
  FEE3-   A8          TAY	    ; "dépile" Y
  FEE4-   68          PLA           ;
  FEE5-   AA          TAX           ; "dépile" X
  FEE6-   68          PLA           ; dépile A
  FEE7-   40          RTI           ; retour d'interruption (dépile P/PCB/PCH)
  FEE8-   A9 F0       LDA #$F0      ; met COUT1
  FEEA-   85 36       STA $36       ; DANS COUT
  FEEC-   A9 FD       LDA #$FD      ; (nécessaire car utilisé...
  FEEE-   85 37       STA $37       ;  ... par $FDDA)
  FEF0-   18          CLC           ; c=0 (pour addition suivante)
  FEF1-   90 0B       BCC $FEFE     ; et forcer le saut vers la suite !
  FEF3-   00 00 00    BRK           ; inutilisés
 
  FEF6-   20 00 FE    JSR   $FE00   ; ne pas toucher
  FEF9-   68          PLA           ; à cette partie
  FEFA-   68          PLA           ; sous peine
  FEFB-   D0 6C       BNE   $FF69   ; de gros problèmes
 
  FEFD-   60          RTS           ; neutralisation LOAD
  FEFE-   BA          TSX           ; S-> X
  FEFF-   8A          TXA           ; X-> A (S->A)
  FF00-   69 06       ADC #06       ; A+6
  FF02-   AA          TAX           ; A->X
  FF03-   BD 00 01    LDA $0100,X   ; pointe sur PCH
  FF06-   20 DA FD    JSR $FDDA     ; affiche PCH
  FF09-   BD FF 00    LDA $00FF,X   ; pointe sur PCL
  FF0C-   20 DA FD    JSR $FDDA     ; affiche PCL
  FF0F-   A9 A0       LDA #$A0      ; affiche un espace
  FF11-   20 F0 FD    JSR $FDF0     ; par COUT1
  FF14-   8A          TXA           ; X (S Prim) -> A
  FF15-   20 DA FD    JSR $FDDA     ; affiche S Prim
  FF18-   A0 04       LDY #$04      ;
  FF1A-   A9 A0       LDA #$A0      ; affiche un espace
  FF1C-   20 F0 FD    JSR $FDF0     ; par COUT1
  FF1F-   BD FE 00    LDA $00FE,X   ; puis dans l'ordre :
  FF22-   20 DA FD    JSR $FDDA     ; P, A, X, Y
  FF25-   CA          DEX
  FF26-   88          DEY
  FF27-   D0 F1       BNE $FF1A     ; on boucle
  FF29-   4C 59 FF    JMP $FF59     ; saut monitor (sortie)
  FF2C-   00          BRK           ; inutilisé
  FF2D-   ne pas toucher

Quelques remarques concernant ce code :

  • encore une fois, j'ai utilisé l'espace dans la ROM F8 en $FECD et $FEFD en lieu et place des routines WRITE et READ dédiées au port Cassette.
  • j'ai inclus la possibilité de continuer à l'endroit où l'interruption a eu lieu (par la touche "C").
  • toute autre touche entraîne un saut vers le Monitor avec affichage des registres y compris le compteur ordinal, tel qu'ils étaient au moment de l'interruption.
  • je repositionne en $36 et $37 l'adresse de COUT1 car elle est utilisée par la routine Monitor d'affichage hexadécimal, ces deux octets en page zéro pouvant très bien avoir été modifiés par le programme en cours.
  • ce code n'est qu'une ébauche. À la base était prévu un code plus complet s'affranchissant totalement de l'utilisation de la page zéro (et donc de certaines routines Monitor utilisées ici), ceci afin de préserver le plus possible la mémoire dans l'état exact dans lequel elle était avant l'interruption. Une telle routine n'aurait bien entendu pas pu tenir en $FECD et cie mais il existe d'autres emplacements disponibles offrant l'espace nécessaire (comme par exemple la routine d'auto-test d'un Apple IIe).


Mais tout ceci, c’était avant le drame ! Avant que je m'aperçoive du troisième problème dont je n'ai pas encore parlé jusqu'à présent :

Nous avons vu que le processeur récupérait le contenu de $FFFA et $FFFB pour sauter à une adresse donnée ($3FB par défaut ou autre si ROM Custom). Mais ces valeurs ne seront lues en ROM uniquement si celle-ci est accessible au moment de l'interruption. S'il y a eu commutation de l'espace $D000-$FFFF vers la mémoire vive ou même utilisation de la mémoire auxiliaire à ce moment là, c'est dans la RAM (principale ou auxiliaire suivant les cas) que le contenu de $FFFA et $FFFB sera récupéré et l'adresse résultante pourra donc être n'importe quoi !

A contrario, lors d'un Reset, certains softswitchs sont restaurés à leur valeur initiale, ce qui fait que l'espace dans lequel on récupère l'adresse où sauter sera toujours lu en ROM, donc connu et fixe. Ce n'est hélas pas le cas pour une NMi : aucun softswitch n'est redisposé après cette interruption.

Si un programme n'utilise pas la RAM dans l'espace $D000/$FFFF ni la mémoire auxiliaire, la NMi fonctionnera comme elle est censée le faire. Sinon, c'est le grand saut vers l'inconnu. Pour tous les programmes utilisant 48 Ko de mémoire seulement, pas de problème donc. C'est ce qui rendait le switch NMi aussi mythique à l'époque des premiers Apple II. Avec tous les softs nécessitant 64 Ko (ou plus), le résultat est nettement plus aléatoire...

Est-ce que ce problème est contournable ? Oui mais cela nécessite une mise en œuvre, au niveau circuit électronique, beaucoup plus lourde et qui dépasse largement mes compétences... On va donc se contenter, dans un premier temps, de faire mumuse avec le bouton mais je ne désespère pas de peaufiner le tout un jour (peut-être dans une autre vie).