Suivez-nous sur X
|
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
ALL
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
Programmation : Assembleur - Utilisation du clavier
(Article écrit par Loïc Far et extrait d'Amiga News Tech - décembre 1991)
|
|
Comme disait mon grand-père, après la théorie vient la pratique. Et puisque l'on sait maintenant tout
sur le fonctionnement du clavier, ne pensez-vous pas qu'il
est grand temps de mettre ces nouvelles connaissances en application ? No problemo.
Le programme qui suit peut paraître démesurément grand ou en tout cas, hors de proportion avec une gestion
simple du clavier. Il n'en est rien : la gestion elle-même s'effectue en quelques lignes de code à peine.
Ce qui prend de la place, c'est toute la partie d'initialisation. Car pour obtenir un résultat tangible,
il fallait que l'on puisse voir à l'écran ce qui se passe lorsque l'on appuie sur une touche. Aussi ai-je dû
initialiser un RastPort et une BitMap, ceci afin de pouvoir utiliser la fonction Text() de la graphics.library et
donner quelques indications visuelles. De même, la sauvegarde du contexte matériel actuel et sa restauration en
fin de programme prennent quelques lignes supplémentaires... Les fainéants, qui sont toujours débrouillards
lorsqu'il s'agit de ne pas travailler, pourront éviter de recopier ces lignes, pour ne plus se consacrer qu'à
l'essentiel.
L'essentiel
Pour la partie qui nous intéresse aujourd'hui, l'essentiel se limite à l'initialisation des deux CIA
(le premier pour l'interruption clavier, le second pour la minuterie), aux deux routines d'interruptions
elles-mêmes (NewIrq2 et NewIrq6), ainsi qu'aux routines GetKey et TestKey. S'il n'est même pas besoin de
rappeler pourquoi l'on utilise le premier CIA (Cf. article précédent),
on peut à juste titre se demander pourquoi l'on utilise également le second... Pour, une fois de plus,
une raison qui est vraiment toute bête.
En effet, si l'on utilise le premier CIA à la fois pour l'interruption clavier et l'interruption timer (minuterie)
(minuterie), deux cas peuvent se présenter : soit la gestion du clavier est suffisamment rapide et elle
se termine avant que l'interruption timer ne se déclenche, auquel cas tout ira bien, soit elle est trop lente
et l'interruption timer surviendra pendant l'interruption clavier. Les deux étant de même niveau pour le
68000 (niveau 2), l'interruption timer devra attendre que l'interruption clavier soit terminée avant d'être
traitée, d'où une possible perte d'information (le processeur clavier rentrant alors dans une phase de correction
d'erreur). Le CIA B, quant à lui, provoque une interruption de niveau 6 ; en l'utilisant, on est donc sûr
qu'elle sera de toute façon immédiatement prise en compte par le 68000, même si elle devait survenir pendant
celle de niveau 2. Notez au passage et tout à fait dans un autre domaine que pour ces mêmes raisons, il est
préférable d'utiliser le CIA B pour réguler le "stepping delay" du contrôleur de disquette, les interruptions
DSKBLK (Disk Block) et DSKSYNC (Disk Synchro) étant respectivement de niveaux 1 et 5...
La minuterie est programmée avec la valeur 120, ce qui correspond à (exactement) 85,125 microsecondes sur
un Amiga PAL et 85,90 microsecondes sur un Amiga NTSC. Plus précis, tu meurs. On atteint ainsi et quoiqu'il
arrive le délai minimum de 85 microsecondes recommandé par les concepteurs de l'Amiga, quelle que soit
la version du Kickstart et le microprocesseur utilisé (ce programme tourne sans aucun problème sur un A3000/25,
ce qui prouve bien qu'il est parfaitement compatible !).
Tiens, en parlant de clavier et de minuterie,
je ne peux m'empêcher d'ouvrir ici une petite parenthèse pour vous narrer une anecdote intéressante... Il
est écrit partout dans les RKM que le keyboard.device utilise le Timer A du CIA A pour gérer le
signal de contact du clavier, et tout le monde, y compris votre serviteur, l'a cru et rapporté dans
plusieurs articles. Or, il n'en est rien ! Comme on peut s'en rendre compte en traçant la routine d'interruption
de niveau 2, cette minuterie n'est absolument pas utilisée du tout par le système (tout au moins dans sa
version 1.3 ; qu'en est-il de la 2.0 ? Mystère et boules de gomme), lequel système se contente de compter
sur la vitesse d'exécution de sa routine pour être sûr de retomber sur ses pattes ! Peut-être d'ailleurs
est-ce là la raison d'une fourchette aussi importante (il y a loin de 85 microsecondes à 145 millisecondes...) : on s'assure
ainsi de la compatibilité avec tout type de microprocesseur... Je sais bien que tout le monde s'en fout, mais
il me semblait important de révéler la supercherie à la face du monde. Fin de la parenthèse et retour
à des considérations nettement plus matérielles.
La matrice clavier
Or donc, ce programme permet d'obtenir indifféremment soit le code de la dernière touche appuyée, soit l'état
d'une touche particulière (enfoncée ou non), ce qui permet, ô joie exquise, de gérer le multi-touche.
Le premier s'obtient en appelant la routine GetKey : l'interruption clavier engrange au fur et à mesure qu'elle
les récolte, tous les codes des touches appuyées dans un tampon mémoire dont la taille a été arbitrairement
fixée à 128 octets, et GetKey se contente de les en ressortir en ordre inverse, à raison d'un par appel. Si
le tampon mémoire est vide, GetKey renvoie -1.
Le remplissage du tampon mémoire est donc séquentiel et s'arrête dès que celui-ci est plein. Il existe évidemment
de bien meilleurs algorithmes (le "tampon mémoire tournant" pour ne citer que lui) mais celui-ci présente
l'avantage indéniable d'être à la fois rapide et simple à programmer. Jeu : amusez-vous à programmer une routine
Clearinput qui se charge de "vider" le tampon mémoire, sans altérer son contenu (une seule ligne de code
suffit amplement !).
L'état d'une touche donnée s'obtient en appelant la routine TestKey, le code de la touche à tester étant
placé dans d0.W. Au retour, le bit Z de CCR est positionné à 1 si la touche est actuellement appuyée
et effacé sinon. La méthode est tout aussi simple, bien que moins courante : on utilise un second tampon
mémoire, baptisé "matrice clavier", dont chaque bit représente l'état d'une touche (si le bit est mis,
la touche est effectivement enfoncée). Ce tampon mémoire est évidemment remis à jour à chaque
interruption en provenance du clavier.
Comment ça marche ? Les codes touches pouvant être compris entre $00 et $7F (les codes $80 à $FF
étant réservés pour des cas particuliers, comme le signalement d'une erreur de synchronisation),
128 bits sont nécessaires, soit seulement (128/8=16 octets pour représenter le clavier entier).
Le code de la touche concernée sert d'index lors de l'accès à la matrice. Le calcul est très simple :
(code/8) donne le numéro de l'octet dans la matrice et (code MOD 8) donne le numéro du bit dans cet
octet. Un exemple concret sera peut-être plus efficace.
Soit à accéder au bit représentant la touche "Esc" dans la matrice. Le code-clavier de "Esc" est $45.
On calcule $45/8=8 et $45 MOD 8=5. La touche "Esc" est donc représentée par le bit numéro 5 de
l'octet numéro 8 de la matrice.
Un autre exemple ? Bon, d'accord, mais c'est bien parce que c'est vous. Juste le temps de modifier
une variable, et je suis à vous... CODE=&H5F : GOTO Exemple. Voilà, ça y est, merci.
Soit à accéder au bit représentant la touche "Help" dans la matrice. Le code-clavier de "Help"
est $5F. On calcule $5F/8=11 et $5F MOD 8=7. La touche "Help" est donc représentée par le bit
numéro 7 de l'octet numéro 11 de la matrice.
Cette technique est très rapide en assembleur, puisqu'une division par 8 du code s'obtient par
un décalage logique de 3 bits vers la droite (instruction LSR) et que le modulo par 8 s'obtient par
une simple instruction AND 7 (de même que le modulo par 4 s'obtient par AND 3, que le modulo par
16 s'obtient par AND 15, etc. Essayez, vous verrez bien ! Ça marche avec toutes les puissances de 2).
La routine d'interruption clavier n'a donc plus qu'à positionner (respectivement effacer) le bit de
la matrice correspondant à la touche appuyée (respectivement relâchée). TestKey pour sa part se
contente de tester le bit correspondant au code touche passé en paramètre. Notez que l'instruction BTST
positionne Z à 1 si le bit testé était à 0 et inversement. Il faut donc inverser Z avant de revenir
au programme appelant, ce qui se fait très simplement avec l'instruction EORI.B #-1,CCR
(qui n'est pas une instruction privilégiée, c'est EOR to SR qui l'est). On aurait tout aussi bien
pu convenir depuis le début que TestKey positionnerait Z à 0 si la touche testée était appuyée,
mais ça me semblait beaucoup moins naturel...
Voilà, juste un mot pour finir : ne faîtes pas attention à la manière dont j'affiche le contenu de
la matrice entière dans la boucle principale, les premières inspirations ne sont pas forcément
toujours les meilleures... Hasta la vista, baby !
;
; Exemple de programmation du Clavier.
;
; © 1991, Loïc Far pour Amiga NewsTech. Aule Raillete Riserveude.
;
opt o+,ow-
incdir "include:"
include "graphics/rastport.i"
include "hardware/custom.i"
include "hardware/cia.i"
include "exec/exec_lib.i"
include "graphics/graphics_lib.i"
; ************************************
CALLSYS MACRO
jsr _LVO\1(a6)
ENDM
; ************************************
Custom EQU $dff000
CIAA EQU $bfe001
CIAB EQU $bfd000
DELAI EQU 120 ; Delai pour 85 µs (en PAL; NTSC=86 µs)
KBSIZE EQU 128 ; Taille du buffer clavier
; ************************************
rsreset
oldcop1 rs.l 1 ; Liste Copper Système 1
oldcop2 rs.l 1 ; Liste Copper Système 2
olddma rs.w 1 ; DMACON Système
oldint rs.w 1 ; INTENA Système
oldirq2 rs.l 1 ; IRQ Level 2 Système
oldirq6 rs.l 1 ; IRQ Level 6 Système
keymat rs.b 16 ; Matrice clavier
keybuf rs.b KBSIZE ; Buffer clavier
keypos rs.w 1 ; Position dans le buffer
VARSIZE rs.w 0
; ************************************
Start lea VARS(pc),a5
lea gfxname(pc),a1 ; Ouvre la graphics.library
moveq #0,d0
CALLEXEC OpenLibrary
move.l d0,_GfxBase
beq NoGfx
movea.l d0,a6
move.l $26(a6),oldcop1(a5)
move.l $32(a6),oldcop2(a5)
lea rport(pc),a1 ; Initialise le RastPort
CALLSYS InitRastPort
move.l #bitmap,rport+rp_BitMap
move.l #ecran,d0 ; Initialise la CopperList
move.w d0,CopBpl+6
swap d0
move.w d0,CopBpl+2
lea Custom,a6 ; Sauve la configuration système
move.w dmaconr(a6),olddma(a5)
ori.w #$8200,olddma(a5)
move.w intenar(a6),oldint(a5)
ori.w #$c000,oldint(a5)
move.l $68.w,oldirq2(a5)
move.l $78.w,oldirq6(a5)
move.w #$7fff,d0 ; Maintenant, on s'installe !
move.w d0,dmacon(a6)
move.w d0,intena(a6)
move.w d0,intreq(a6)
lea CIAA,a0 ; Prépare le CIA-A :
move.b #$1F,ciaicr(a0) ; Interruptions off
move.b #$88,ciaicr(a0) ; Interruption SP on
lea CIAB,a0 ; Prépare le CIA-B :
move.b ciacra(a0),d0
andi.b #%11000000,d0
ori.b #%00001000,d0 ; Timer A mode one-shot
move.b d0,ciacra(a0)
move.b #$1F,ciaicr(a0) ; Interruptions off
move.b #DELAI,ciatalo(a0) ; Pré-programme le délai :
move.b #0,ciatahi(a0) ; Le timer démarre aussitôt,
.wait btst #0,ciaicr(a0) ; donc on attend la fin
beq.s .wait ; de ce premier décompte.
move.b #$81,ciaicr(a0) ; Interruption TA on
move.l #NewCop,cop1lc(a6) ; Nouvelle CopperList on
move.w d0,copjmp1(a6)
move.l #NewIrq2,$68.w ; Irq 2 (CIA-A)
move.l #NewIrq6,$78.w ; Irq 6 (CIA-B)
move.w #$83c0,dmacon(a6) ; Copper, Bitplane, Blitter
move.w #$e008,intena(a6) ; IRQs 2 et 6
; ************************************
movea.l _GfxBase(pc),a6 ; Prépare a6
Wait btst #6,CIAA+ciapra
beq Fini
moveq #$63,d0 ; Code clavier de Ctrl
bsr TestKey ; La touche est appuyée ?
bne.s .noCtrl ; non
moveq #$45,d0 ; Code clavier de Esc
bsr TestKey ; La touche est appuyée ?
beq Fini ; Oui
.noCtrl bsr GetKey ; Touche appuyée ?
bmi.s .1
lea buf1+19(pc),a0 ; Oui, affiche son code
bsr Hex8
.1 lea rport(pc),a1
moveq #10,d0
moveq #20,d1
CALLSYS Move
lea rport(pc),a1
lea buf1(pc),a0
moveq #21,d0
CALLSYS Text
lea rport(pc),a1 ; Affiche la matrice
moveq #10,d0
moveq #40,d1
CALLSYS Move
lea rport(pc),a1
lea titre(pc),a0
moveq #9,d0
CALLSYS Text
lea keymat(a5),a2
moveq #0,d2
MatLoop lea rport(pc),a1
moveq #10,d0
move.w rp_cp_y(a1),d1
addq.w #8,d1
CALLSYS Move
lea buf2(pc),a0
move.w d2,d1
bsr.s Hex4
moveq #0,d3
moveq #0,d4
bset d2,d4
MatLop2 move.b 1(a2,d3.w),d5
lsl.w #8,d5
or.b 0(a2,d3.w),d5
moveq #'-',d0
and.w d4,d5
beq.s .non
moveq #'*',d0
.non move.b d0,(a0)+
addq.w #2,d3
cmpi.w #16,d3
blt.s MatLop2
lea buf2(pc),a0
lea rport(pc),a1
moveq #9,d0
CALLSYS Text
addq.w #1,d2
cmpi.w #16,d2
blt.s MatLoop
bra Wait
Hex8 move.w d0,d1
lsr.b #4,d1
bsr.s Hex4
move.w d0,d1
Hex4 andi.b #$f,d1
cmpi.b #9,d1
ble.s .1
addq.b #7,d1
.1 addi.b #'0',d1
move.b d1,(a0)+
rts
buf1 dc.b "Dernière touche : $--"
buf2 dc.b "x--------"
titre dc.b " 01234567"
; ************************************
Fini lea Custom,a6 ; Restaure le système
move.w #$7fff,d0
move.w d0,dmacon(a6)
move.w d0,intena(a6)
move.w d0,intreq(a6)
move.b #$1F,CIAA+ciaicr
move.b #$9F,CIAA+ciaicr
move.b #$1F,CIAB+ciaicr
move.b #$9F,CIAB+ciaicr
move.l oldirq2(a5),$68.w
move.l oldirq6(a5),$78.w
move.l oldcop1(a5),cop1lc(a6)
move.l oldcop2(a5),cop2lc(a6)
move.w d0,copjmp1(a6)
move.w olddma(a5),dmacon(a6)
move.w oldint(a5),intena(a6)
movea.l _GfxBase(pc),a1
CALLEXEC CloseLibrary
NoGfx moveq #0,d0
rts
; ************************************
; Renvoie dans d0.w le code de la dernière touche appuyée
; ou -1 si le buffer clavier est vide.
GetKey move.w keypos(a5),d0
subq.w #1,d0
bmi.s .nokey
move.w d0,keypos(a5)
lea keybuf(a5),a0
move.b (a0,d0.w),d0
ext.w d0
.nokey rts
; ************************************
; Teste si la touche dont le code est dans d0.w est appuyée
; et positionne Z en conséquence (Z=1 -> touche appuyée)
TestKey move.w d0,d1
lsr.w #3,d0
andi.w #7,d1
lea keymat(a5),a0
btst d1,(a0,d0.w)
eori.b #-1,ccr ; Inverse (entre autres) Z !
rts
; ************************************
; IRQ 2 (CIA-A, clavier)
NewIrq2 movem.l d0-d2/a0-a1,-(sp)
lea Custom,a0
move.w intenar(a0),d0
btst #14,d0 ; Bit INTEN mis ?
beq.s Irq2Ret
and.w intreqr(a0),d0
btst #3,d0 ; Interruption n°3 ?
beq.s Irq2Ret
lea CIAA,a0
move.b ciaicr(a0),d0
btst #3,d0 ; Interruption clavier ?
beq.s .ret
moveq #0,d0
move.b ciasdr(a0),d0 ; Lit la touche reçue
ori.b #$40,ciacra(a0) ; Met SP en sortie (hanshaking)
bset #0,CIAB+ciacra ; Démarre le Timer A du CIA-B
lea VARS(pc),a1
not.b d0
ror.b #1,d0
move.w d0,d1
andi.w #$7f,d1
lsr.w #3,d1 ; d1 = Code / 8
move.w d0,d2
andi.w #7,d2 ; d2 = Code MOD 8
lea keymat(a1),a0
bclr #7,d0
bne.s .KeyUp
.KeyDn bset d2,0(a0,d1.w) ; Touche enfoncée : met le bit...
move.w keypos(a1),d1 ; et insère le code-touche
cmpi.w #KBSIZE,d1 ; dans le buffer clavier.
bge.s .ret
addq.w #1,keypos(a1)
lea keybuf(a1),a0
move.b d0,(a0,d1.w)
bra.s .ret
.KeyUp bclr d2,0(a0,d1.w) ; Touche relâchée : efface le bit.
.ret move.w #$8,Custom+intreq
Irq2Ret movem.l (sp)+,d0-d2/a0-a1
rte
; ************************************
NewIrq6 movem.l d0/a0-a1,-(sp)
lea Custom,a0
move.w intenar(a0),d0
btst #14,d0 ; Bit INTEN mis ?
beq.s Irq6Ret
and.w intreqr(a0),d0
btst #13,d0 ; Interruption CIAB ?
beq.s Irq6Ret
lea CIAB,a1
move.b ciaicr(a1),d0
btst #0,d0 ; Interruption Timer A ?
beq.s .ret
andi.b #$bf,CIAA+ciacra ; Met SP du CIA-A en sortie
.ret move.w #$2000,intreq(a0)
Irq6Ret movem.l (sp)+,d0/a0-a1
rte
; ************************************
VARS ds.b VARSIZE
_GfxBase dc.l 0
gfxname dc.b "graphics.library",0
even
rport ds.b rp_SIZEOF ; RastPort pour Text()
bitmap dc.w 40,256,1,0
dc.l ecran,0,0,0,0,0,0,0
; ************************************
section CHIPS,DATA_C
NewCop dc.w diwstrt,$2981,diwstop,$29c1
dc.w ddfstrt,$0038,ddfstop,$00d0
dc.w bplcon0,$1200
dc.w bplcon1,$0000,bplcon2,$0000
dc.w bpl1mod,$0000,bpl2mod,$0000
CopBpl dc.w bplpt+0,$0000,bplpt+2,$0000
dc.w color+0,$0000,color+2,$0fff
CopEnd dc.l -2
ecran ds.b $2800 ; écran 320x200, 1 plan de bits
; ************************************
END
|
|