Obligement - L'Amiga au maximum

Dimanche 25 août 2019 - 18:56  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

 · Actualité (récente)
 · Actualité (archive)
 · Comparatifs
 · Dossiers
 · Entrevues
 · Matériel (tests)
 · Matériel (bidouilles)
 · Points de vue
 · En pratique
 · Programmation
 · Reportages
 · Quizz
 · Tests de jeux
 · Tests de logiciels
 · Tests de compilations
 · Trucs et astuces
 · Articles divers

 · Articles in english
 · Articles en d'autres langues


Twitter

Suivez-nous sur Twitter




Liste des jeux Amiga

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


Trucs et astuces

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


Glossaire

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


Galeries

 · Menu des galeries

 · BD d'Amiga Spécial
 · Caricatures Dudai
 · Caricatures Jet d'ail
 · Diagrammes de Jay Miner
 · Images insolites
 · Fin de jeux (de A à E)
 · Fin de Jeux (de F à O)
 · Fin de jeux (de P à Z)
 · Galerie de Mike Dafunk
 · Logos d'Obligement
 · Systèmes d'exploitation
 · Trombinoscope Alchimie 7
 · Vidéos


Téléchargement

 · Documents
 · Jeux
 · Logiciels
 · Magazines
 · Divers


Liens

 · Sites de téléchargements
 · Associations
 · Pages Personnelles
 · Matériel
 · Réparateurs
 · Revendeurs
 · Presse et médias
 · Programmation
 · Logiciels
 · Jeux
 · Scène démo
 · Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec

Hit Parade


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


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 par exemple 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 bitplan

; ************************************
	END


[Retour en haut] / [Retour aux articles]