Programmation en assembleur sur HP49G
Les interruptions (2): gestion des touches par bufferisation
Version du 23/04/2001

Introduction :

Tout d'abord, comme pour la première partie, il n'est pas, je pense, nécessaire d'être un Dieu en assembleur pour comprendre (la preuve est que j'ai compris...). Il faut évidement des bases de programmation en assembleur et surtout savoir un peu Programmer au sens général du terme (algo, réfléxion...). Comme j'ai voulus que ce "cours" puisse être compris par le plus grand nombre, les plus expérimentés pourront trouver certaines remarques ridicules voir intutiles mais je pense qu'elles pourront servir à d'autres.

Cette documentation se décompose en plusieurs parties. Une première présentera le fonctionnement d'un buffer et l'utilité qu'on pourra en faire. Suivra un algo détaillé et commenté de la gestion des touches par bufferisation. Puis son équivalent en assembleur accompagné de fonctions utilisant ce principe de gestion des touches. Tous les programmes sont écris avec la syntaxe MASD.

Je voudrais enfin signaler que, comme pour la première partie, certains passage sont tirés ou inspirés de PowerPaint de dam's.

Buffer : principe, utilité

Un buffer est une "Zone-mémoire destinée au stockage temporaire d'informations en attente de traitement" (Voyage au centre de la HP48G/GX). Un buffer est une structure (une file, je pense) de type FIFO, c'est à dire que la première information entrée dans la file est la première à en sortir. On ne peut y appliquer que peu de traitement : ajouter un élément, prendre ou voir un élément et on ne connait que peu de choses : indice du sommet (ou adresse), nombre d'éléments,...

Sur les HP, le buffer clavier, comme le buffer de transmission, est un buffer circulaire de 32 quartets, ce qui permet de placer 16 touches codées chacune sur 2 quartets. C'est un buffer circulaire, ce qui veut dire qu'on ne fait qu'incrémenter les deux variables indiquant la première case utilisée et la première case vide. Comme ces deux variables sont utilisées sur un quartets (16 valeurs différentes), une fois arrivée la valeur #Fh, si on incrémente on retombe sur #0h. On a donc fait "un tour" de buffer.

Il existe enfin deux types de buffers. Pour le premier on n'écrit dans le buffer que s'il est non plein. C'est évident au premier abord mais il existe un deuxième type de buffer, appelé aussi overflow. Pour les buffer de ce type, on écrit toujours dans le buffer mais on incrémente l'indice de la prochaine case à écrire que si le buffer n'est pas plein. On peut donc écrire sur la dernière touche.

Maintenant, l'utilité. Tout d'abord un point intéressant est de tout simplement savoir et pouvoir le faire :), même si on ne l'utilise pas (ce qui serait dommage).

Plus sérieusement, tout dépend de la façon dont on le programme et utilise. On peut le programmer de deux façons différentes : entièrement intégré au gestionnaire d'interruption, ou en partie dans le gestionnaire d'interruption et dans le programme en lui-même.

Pour la première solution, le placement des touches dans le buffer ainsi que les exécutions qui leurs sont associées sont dans le gestionnaire d'interruption. Ceci implique qu'à aucun moment on ne test les touches dans le programme principal, tout étant géré lors des interruptions. On peut donc observer un gain de temps car on ne test plus les touches.

Dans la deuxième solution, on place les touches dans le buffer à partir du gestionnaire d'inter et, dans le programme principal, on test s'il y a des touches dans le buffer. Si aucune touche est pressée, on gagne donc du temps car on ne test pas toutes les touches à chaque tour de boucle mais seulement s'il y a réellement une touche de pressée.

Seule cette deuxième solution sera traitée par la suite.

Le fait de placer les touches dans un buffer peut également faciliter la gestion de la durée d'appui d'une touche, les combinaisons de touches (prochaine documentation) par exemple.

Algo de gestion des touches par bufferisation

On utilise un tableau TabClavier de #99h quartets, dans lequel est noté sur 3 quartets le temps d'appui d'une touche en fonction de la période des interruptions non liées au clavier (VBL pour le plus courant). On utilise également un tableau TabOutIn de #132h quartets contenant les codes OUT IN de chaque touche sur 6 quartets (autre solution : TabOutIn est un pointeur sur un tableau contenu dans le programme). Les touches sont évidement placées dans le même ordre.

Lors d'une interruption

On parcourt TabClavier
Si on rencontre une valeur différente de zéro alors on a pressé précédemment sur un touche
-> On test le code avec les test classiques de touches : OUT=C=IN

On compare le code retourné avec le code théorique contenu dans TabOutIn à l'indice de la touche.
Si les codes ne correspondent pas c'est qu'on a relaché la touche, mais on en a surement pressé une autre.
-> On met à zéro la valeur du temps d'appui pour cette touche.
On test toutes les touches.
Sinon
-> La touche reste appuyée
On incrémente sa valeur dans TabClavier.
Sinon
-> Aucune touche n'a été précédemment appuyée.
On test toutes les touches (on peut rajouter un test pour savoir si c'est une interruption clavier ou non).

 

Voila, c'était pas si compliqué !

On parcourt TabClavier

On pointe sur TabClavier
{ On lit les trois quartets.
Si la valeur des trois quartets est différente de zéro
-> On ressort l'indice de la case du tableau où on est

On quitte.
Sinon
-> On pointe trois quartets plus loin
On boucle tant qu'on a pas parcouru tous le tableau (ou qu'on ait une valeur différente de zéro) }

 

Test de la valeur renvoyée

Si la valeur est nulle
-> Aucune touche n'a été pressée

On test toutes les touches.
Sinon
-> On pointe sur TabOutIn + 6 * indice de la touche
On récupère les 3 quartets.
On les place dans OUT
On récupère IN
On compare par rapport au couple OUT/IN du tabeau
Si les codes ne correspondent pas
-> On met la valeur du temps d'appui de la touche à zéro
On test toutes les touches
Sinon
-> On incrémente sa valeur dans TabClavier

 

Test de toutes les touches

On pointe sur la premi&egrae;re case de TabClavier
On pointe sur la première case de TabOutIn
{ On lit les trois quartets
On les met dans OUT
On récupère IN
On lit les trois quartets suivant dans TabOutIn
On compare les deux valeurs
Si les deux valeurs sont identiques
-> On incrémente la aleur de la touche nouvellement pressée dans TabClavier

On écrit le code de la touche dans le buffer
On sort
Sinon
-> On met à zéro le temps d´appui de la touche
On pointe trois quartets après (pour les deux tableaux)
On boucle tant qu'on a pas parcouru tout le tableau }

 

Ecriture du buffer

On pointe sur la zone mémoire qui contient l'indice de la case dans l'adresse dans laquelle on doit écrire
On pointe sur cette case
On écrit le code de la touche (dans le cas d'un buffer non overflow il faudrait tester avant si la case est vide)
On lit l'indice de la première case du buffer
On incrémente l'indice de la case dans laquelle on vient d'écrire
On fait un test entre les deux valeurs
Si les deux valeurs sont identiques
-> On quitte car le buffer est plein
Sinon
-> On écrit le nouvel indice de la prochaine case à écrire

On incrémente le nombre de touches dans le buffer

 

Récupération d'une touche

On pointe sur le nombre de touches dans le buffer
Si ce nombre est nul on quitte (on peut retourner zéro comme code de touche pour indiquer qu'il n'y en a pas)
Sinon
On décrémente le nombre de touche dans le buffer et on l'écrit
On pointe sur la première case du buffer contenant une touche
On lit et on retourne le code de la touche
On incrémente l'indice de la première case pleine du buffer et on l'écrit (par facilité on peut le faire avant de retourner la touche)

Récupération du temps d'appui d'une touche

Cette durée dépend éidemment du temps mis par horloges à passer à zéro (intervalle entre deux interruptions horloges)

On pointe en TabClavier + 3 * indice de la touche
On lit la valeur et on la retourne

Un peu facile, non ?

Attente de l'appui d'une touche

On lance la fonction récupérant une touche dans le buffer
On la relance tant que la fonction retourne zéro

Ca tout le monde aurait pu le trouver...

Attente du relachement de toutes les touches

On pointe en TabClavier
{ On lit les trois quartets
Si c'est différent de zéro
-> On recommence depuis le début (on pointe en TabClavier)
Sinon
-> On pointe trois quartets plus loin et on boucle jusqu'à ce qu'on ait parcouru tout le tableau }

On ne doit pouvoir sortir que si tout le tableau est à zéro

Programme équivalent

J'ai choisi de ne pas commenter beaucoup les programmes pour deux raisons. La première est que c'est long et fastidieux :). La deuxième est que ça vous oblige à réfléchir, seul solution pour comprendre, et à comparer avec l'algo ce qui permet de savoir ce qu'on fait et surtout à quoi ça sert (je fournis également les sources d'un petit programme qui sont elles un peu plus commentées...(bientôt dispo))

Adresses en ROM

Buffer : #8066Bh (32d quartets = 20h)
KeyStart #80669h (1 quartet)
KeyEnd #8066Ah (1 quartet)

Variables nécessaires

NbTouchesBuffer sur 2 quartets
TabClavier sur 99h quartets
TabOutIn sur 132h quartets

TabClavier doit être initialisé à zéro, tout comme KeyStart et KeyEnd.
TabOutIn doit être initialisé aec les codes OUT/IN de chaque touche (sauf ON) dans un ordre commun avec TabClavier. L'ordre est choisi arbitrairement mais doit obligatoirement être le même pour les 2 tableaux.
Voici l'ordre que j'ai utilisé (je l'ai choisi de façon a tester les touches les plus utiles au début) :
gauche, droite, haut, bas, A, B, C, D, E, F, Enter, Drop, Alpha, Left Shift, Right Shift, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ., SPC, +, -, *, /, Apps, Mode, Tool, Var, Sto, Nxt, Hist, Cat, Eqw, Symb, Q, racine, Sin, Cos, Tan, EEX, +/-, X, Y
Voici donc la procédure initialisant TabOutIn :

D0=(5)TabOutIn
LC 040 DAT0=C.X D0+3
LC 004 DAT0=C.X D0+3 % gauche
LC 040 DAT0=C.X D0+3
LC 001 DAT0=C.X D0+3 % droite
LC 040 DAT0=C.X D0+3
LC 008 DAT0=C.X D0+3 % haut
LC 040 DAT0=C.X D0+3
LC 002 DAT0=C.X D0+3 % bas
LC 020 DAT0=C.X D0+3
LC 002 DAT0=C.X D0+3 % A
LC 001 DAT0=C.X D0+3
LC 008 DAT0=C.X D0+3 % B
LC 002 DAT0=C.X D0+3
LC 004 DAT0=C.X D0+3 % C
LC 004 DAT0=C.X D0+3
LC 004 DAT0=C.X D0+3 % D
LC 008 DAT0=C.X D0+3
LC 004 DAT0=C.X D0+3 % E
LC 020 DAT0=C.X D0+3
LC 080 DAT0=C.X D0+3 % F
LC 001 DAT0=C.X D0+3
LC 001 DAT0=C.X D0+3 % Enter
LC 001 DAT0=C.X D0+3
LC 040 DAT0=C.X D0+3 % Drop
LC 080 DAT0=C.X D0+3
LC 008 DAT0=C.X D0+3 % Alpha
LC 080 DAT0=C.X D0+3
LC 040 DAT0=C.X D0+3 % Left Shift
LC 080 DAT0=C.X D0+3
LC 002 DAT0=C.X D0+3 % Right Shift
LC 008 DAT0=C.X D0+3
LC 001 DAT0=C.X D0+3 % 0
LC 008 DAT0=C.X D0+3
LC 002 DAT0=C.X D0+3 % 1
LC 004 DAT0=C.X D0+3
LC 002 DAT0=C.X D0+3 % 2
LC 002 DAT0=C.X D0+3
LC 002 DAT0=C.X D0+3 % 3
LC 008 DAT0=C.X D0+3
LC 004 DAT0=C.X D0+3 % 4
LC 004 DAT0=C.X D0+3
LC 004 DAT0=C.X D0+3 % 5
LC 002 DAT0=C.X D0+3
LC 004 DAT0=C.X D0+3 % 6
LC 008 DAT0=C.X D0+3
LC 008 DAT0=C.X D0+3 % 7
LC 004 DAT0=C.X D0+3
LC 008 DAT0=C.X D0+3 % 8
LC 002 DAT0=C.X D0+3
LC 008 DAT0=C.X D0+3 % 9
LC 004 DAT0=C.X D0+3
LC 001 DAT0=C.X D0+3 % .
LC 002 DAT0=C.X D0+3
LC 001 DAT0=C.X D0+3 % SPC
LC 001 DAT0=C.X D0+3
LC 002 DAT0=C.X D0+3 % +
LC 001 DAT0=C.X D0+3
LC 004 DAT0=C.X D0+3 % -
LC 001 DAT0=C.X D0+3
LC 008 DAT0=C.X D0+3 % *
LC 001 DAT0=C.X D0+3
LC 010 DAT0=C.X D0+3 % /
LC 020 DAT0=C.X D0+3
LC 080 DAT0=C.X D0+3 % Apps
LC 010 DAT0=C.X D0+3
LC 080 DAT0=C.X D0+3 % Mode
LC 008 DAT0=C.X D0+3
LC 080 DAT0=C.X D0+3 % Tool
LC 004 DAT0=C.X D0+3
LC 080 DAT0=C.X D0+3 % Var
LC 002 DAT0=C.X D0+3
LC 080 DAT0=C.X D0+3 % STO
LC 001 DAT0=C.X D0+3
LC 080 DAT0=C.X D0+3 % NXT
LC 010 DAT0=C.X D0+3
LC 040 DAT0=C.X D0+3 % HIST
LC 001 DAT0=C.X D0+3
LC 040 DAT0=C.X D0+3 % CAT
LC 080 DAT0=C.X D0+3
LC 040 DAT0=C.X D0+3 % EQW
LC 002 DAT0=C.X D0+3
LC 040 DAT0=C.X D0+3 % SYMB
LC 010 DAT0=C.X D0+3
LC 020 DAT0=C.X D0+3 % Q
LC 008 DAT0=C.X D0+3
LC 020 DAT0=C.X D0+3 % racine
LC 004 DAT0=C.X D0+3
LC 020 DAT0=C.X D0+3 % Sin
LC 002 DAT0=C.X D0+3
LC 020 DAT0=C.X D0+3 % Cos
LC 001 DAT0=C.X D0+3
LC 020 DAT0=C.X D0+3 % Tan
LC 010 DAT0=C.X D0+3
LC 010 DAT0=C.X D0+3 % EEX
LC 008 DAT0=C.X D0+3
LC 010 DAT0=C.X D0+3 % +/-
LC 004 DAT0=C.X D0+3
LC 010 DAT0=C.X D0+3 % X
LC 002 DAT0=C.X D0+3
LC 010 DAT0=C.X D0+3 % Y

Cette représentation a été choisi pour une facilité de compréhension. Par contre dans les sources, vous trouverez non pas TabOutIn comme un Tableau de 132q mais comme un pointeur sur une chaine composée des code OUT/IN, présentée de la manière suivante : $code1code2...
Lorsqu'on voudra pointer sur le tableau, on ne fera plus DO=(5)TabOutIn mais D0=(5)TabOutIn C=DAT0.A D0=C

Partie spécifique du gestionnaire d'interruption

Le minimum nécessaire à savoir sur les gestionnaires d'interruption est "détaillé" dans la doc sur le détournement des inter.

Voici la partie qui nous intéresse dans le gestionnaire :

GOSUB Parc_TC
GOSUB Test_Touche

Cette partie n'est rien d'autre qu'un appel à des routines que je sépare pour plus de lisibilié
Cette partie peut suffir mais pour avoir le temps d'appui des touches il faut l'inclure dans la gestion des horloges, pour ne tester les touches qu'a des intervalles de temps précis.
Il faut évidemment que les différentes routines soient accessibles depuis le gestionnaire...

Parcourt TabClavier

*Parc_TC
A=0.A % On met A à 0, qui contiendra le code de la touche
D0=(5)TabClavier
LC 00031 % nombre de case de TabClavier B=C.A
*D_Parc_TC
C=0.A
C=DAT0.X
?C#0.X SKIPYES

{
D0+3
A+1.B
?A<B.B GOYES D_Parc_TC % Si on a pas tout parcouru
}
RTN

 

Test de la valeur mise dans C.B (temps d'appui)

 

*Test_Touche
?C#0.X SKIPYES % Si le temps est à zéro, aucune touche n'a été pressée
{
GOSUB
Test_Toutes_Touches
}
SKELSE
{
B=A.A
A+A.A
A+B.A
A+A.A
LC(5)TabOutIn
C+A.A
D1=C
C=0.A
C=DAT1.X
OUT=C=IN
D1+3
A=0.A
A=DAT1.X
C&A.X
?C#0.X SKIPYES % Si les codes sont différents
{
A=0.X
DAT0=A.X % D0 pointe sur la case de TabClavier
GOSUB Test_Toutes_Touches
}
SKELSE
{
A=DAT0.X
A+1.X
DAT0=A.X
}
}
RTN

 

On test toutes les touches

 

*Test_Toutes_Touches
LC 0031
D=C.A
A=0.B
D0=(5)TabClavier
D1=(5)TabOutIn
{
C=0.A
C=DAT1.X
OUT=C=IN
B=C.X
D1+3
C=DAT1.X
C&B.X
?C=0.X SKIPYES % Si les codes correspondent
{
C=DAT0.X
C+1.X
DAT0=C.X
GOSUB W_Buffer % On écrit dans le buffer
RTN
}
SKELSE
{
C=0.X
DAT0=C.X
}
D0+3 % On passe à la case suivante de TabClavier
D1+3 % On passe à la case suivante de TabOutIn
A+1.B % On incrémente l'indice de la cas en cours
C=RSTK
C=A.B
?C>D.B
{
RSTK=C
UP2
}

RSTK=C
}
RTN

 

Ecriture du buffer

*W_Buff
B=A.A
D1=(5)KeyEnd
A=0.A
A=DAT1.1
A+A.B
LC(5)Buffer
C+A.A
D1=C % Pointe sur la case à tester
A=B.B
A+1.B % Permet de ne pas avoir 0 comme code de touche car c'est le code réservé pour indiquer qu'il n'y a pas de touche
DAT1=A.B
D1=(5)KeyStart
C=DAT1.1
D1=(5)KeyEnd
A=0.B
A=DAT1.1
A+1.B
?A=C.1 RTNYES
DAT1=A.1
D1=(5)NbTouchesBuffer
C=DAT1.B
C+1.B
DAT1=C.B
RTN

Retourne le code de la première touche du buffer

*Retourne_Touche
D0=(5)NbTouchesBuffer
C=DAT0.B
?C=0.B RTNYES
C-1.B
DAT0=C.B
D0=(5)KeyStart
A=0.A
A=DAT0.1
A+1.B
DAT0=A.1
A-1.B
A+A.B
LC(5)Buffer
C+A.A
D0=C
C=DAT0.B
RTN

Attente d'une touche

*Attente_Touche
GOSUB
Retourne_Touche
?C=0.B GOYES Attente_Touche

RTN

Retourne le temps d'appui d'une touche

*Temps_Touche
D0=(5)TabClavier
C-1.B RTNC % Si Carry passe à un, le code était donc 0
ACEX.A
C=0.A
C=A.B
A=C.A
C+C.A
C+A.A
AD0EX
C+A.A
D0=C
C=DAT0.X
RTN


Voila, c'est fini !
Pour ceux qui veulent, je joint un petit programme gérant les touches par bufferisation.
Le fichier est près à être utilisé, il suffit de le télécharger et de le transférer sur la calculette.
Ci-joint égalemment toutes les sources commentées : sources
Indisponible pour le moment mais ça ne saurait tarder :)

Il existe d'autres façons pour gérer les touches par bufferisation selon les utilisations qu'on veut en faire. J'ai présenté ici la solution la plus complète, elle peut être utilisée avec n'importe quel programme. Son avantage est d'avoir en mémoire le temps d'appui de toutes les touches. On peut donc utiliser cette méthode pour gérer l'appui simultané de plusieurs touches (prochaine partie...).
Par contre, si les combinaisons de touhes ne vous intéressent pas, on peut supprimer TabClavier et le remplacer par deux variables :
- une variable de 2 quartets contenant le code de la dernière touche pressée
- une variable de 3 quartets contenant le temps d'appui de cette dernière touche.
La deuxième variable équivaut donc à la case de TabClavier d'indice contenu dans la première variable. Le reste est évidemment identique.

Pour ceux qui n'ont pas compris, si il y a des erreurs ou des modifications à faire ou pour tout autre chose : yves.brissaud@wanadoo.fr ainsi que le forum d'HP-Sources