|
ASM89
Ce tutorial a été écrit par Skip Jordan, et a été traduit par Thsqrxz. Il est publié avec leur autorisation.
Les Bases.
La TI 89 utilise un microprocesseur
MC 68000 de Motorola, c'est pourquoi
nous allons apprendre l'assembleur 68000. Il est recommandé d'utiliser
PlusShell, mais Doors OS est aussi très bien écrit.
Le processeur comprend seulement
le langage binaire (base 2). On utilisera
le symbole % pour un nombre binaire, par exemple : %00110101 pour 53. On utilisera
le symbole $ pour un nombre hexadécimal, par exemple : $A3 pour 163. Les nombres
sans symbole seront des nombres décimaux. Toutes les données qui sont enregistrées,
que ce soit le nombre 34 ou une chaîne de caractères, sont enregistrés
en binaire. Cela devient très utile dans l'affichage des graphiques.
Au cœur de la TI 89 bat un
microprocesseur 68000 à 10 MHz. Il y
a plusieurs aires importantes sur le processeur, les premières sont
les registres. Les registres sont des petits emplacement de mémoire
situés sur le processeur. Utiliser les registre comme emplacements
de données est quelques fois nécessaire et toujours plus rapide
que d'utiliser un emplacement de mémoire sur de la RAM (mémoire
vive). Ces registres sont tous d'une capacité de 32 bits, cela signifie qu'ils peuvent enregistrer 32
digits d'un mot binaire (un digit est un 0 ou un 1, un mot binaire
est une suite de 0 et de 1).
Il y a huit
registres de données (notés D0 à D7), que l'on peut utiliser
pour ce que l'on veut. Ils sont communément utilisés lors de l'appel
d'une librairie de fonctions pour fournir des arguments. Les registres
peuvent être utilisés indifféremment les uns des autres, il n'y
a pas de registre "principal" comme le registre A en assembleur
Z80. Il y aussi huit registres d'adresses (A0 à A7). Ils sont utilisés
principalement pour enregistrer les pointeurs à différentes données
de la RAM ou pour se diriger vers les routines de la RAM vers lesquelles
on veut aller. Il y a aussi un registre de statuts,
registre dont on n'a pas encore besoin. Il faut juste savoir que
lorsque l'on utilise les instructions bxx, on est en train d'utiliser
le registre de statuts.
Il y a trois longueurs de données
sur le 68000 : le byte, noté b et contenant
8 bits ; le word, noté w et contenant
2 bytes ; le longword, noté l et contenant
2 words.
Le 68000 enregistre les nombres
de la manière suivante : $1234 est enregistré sous $12 $34, non
$34 $12 comme le fait le Z80. Un ; est
employé pour écrire un commentaire. Les commentaires sont ignorés
par le compilateur, ainsi on peut ajouter des commentaires pour
aider celui qui lit le code source par la suite. Un ; signifie que
le reste de la ligne est un commentaire.
L'adressage.
En assembleur, on a besoin de garder
le chemin de l'adresse des données ou des fonctions dans la RAM.
Il y a deux types d'enregistrement de cette information : relatif
à la position actuelle ou absolu. Si on enregistre de manière relative,
on dit : "Ce morceau de donnée est placé 95 bytes avant ma position
actuelle." C'est évidemment limité, mais lors de l'exécution des
programmes, l'adresse peut être utile. Cependant, c'est plus court
que l'adressage absolu, alors on peut l'utiliser pour limiter la
taille du programme. La deuxième manière est dite absolue. Tout
dans la RAM a un nombre qui définit son emplacement. Ces nombres
sont usuellement écrits en hexadécimal. La mémoire LCD de la TI
92, par exemple, est gardée à l'adresse $4440. L'adressage absolu
permet de recevoir des données venant d'ailleurs que du programme,
de lire le VAT, ... Ces deux types d'adressage utilisent les registres
d'adresses.
Premières instructions.
Evidemment, on a besoin d'apprendre
quelques instructions simples pour pouvoir programmer quelque chose.
Si on a besoin de déplacer une donnée d'un endroit à l'autre, on
utilisera l'instruction move.x. Cette instruction est suivie de
deux arguments : une source et une destination. Le x remplace ici
la longueur, dans un programme on le substituera par b, w ou l suivant
que l'on veuille déplacer un byte, un word ou un longword.
Exemples :
move.b d0,d1
; déplace un byte de d0 vers d1
move.w #10,d2
; place le nombre décimal 10 dans d2
Mais attention, lorsque l'on déplace
une donnée dans un registre, par exemple un byte, celui-ci va remplir
le byte de poids le plus faible du registre et le reste du contenu
sera inchangé. Le # définit une valeur immédiate, c'est-à-dire le
nombre 10, non le contenu de l'adresse 10. Pour déplacer un nombre
hexadécimal, on écrira : move.b #$F,d0. Mais si on écrit : move.b
$F,d0, cela va déplacer le byte enregistré à l'emplacement de mémoire
$F dans d0.
Exemple :
si d0 contient
%10100100101001001010010010100100 et que l'on déplace le byte %00000000
dans d0, il devient %10100100101001001010010000000000.
Notre instruction suivante est
clr.x. Elle est suivi d'un seul argument et met tous les bits spécifiés
de l'argument à 0, x remplaçant bien sûr la longueur du mot binaire.
Exemple :
clr.l d0 met
tous les bits de d0 à 0.
Passons maintenant aux instructions
mathématiques. Le 68000 a d'assez puissantes instructions mathématiques.
Voici les plus simple : add.x, sub.x, mulu.x, muls.x, divu.x, et
divs.x. add.x et sub.x sont très simples, ce sont une addition et
une soustraction. Si on veut additionner d0 et d1 et enregistrer
le résultat dans d1, on écrit : add.l d0,d1.
Il faut être sûr de bien spécifier la longueur. Si la somme excède
les limites de la longueur, la réponse ne sera pas correcte. Si
on veut soustraire d1 de d0 et enregistrer la réponse dans d0, on
écrit : sub.l d1,d0. A nouveau, il faut
être sûr de bien spécifier la longueur.
Passons à la multiplication. Il
y a deux instructions, muls et mulu. mulu fait des nombres sans
signe, c'est-à-dire des nombres non négatifs. muls est capable de
faire des nombres négatifs et positifs. Il faut se référer au guide
68kguide.txt de Jimmy MARDELL pour savoir comment le 68000 enregistre
les nombres négatifs. Pour multiplier d0 par d1 et enregistrer la
réponse dans d1, on écrit : mulu.x d0,d1.
Notons qu'il faut être sûr que le nombre ne va pas dépasser les
limites de la longueur que l'on aura choisie. Pour cela, il faut
bien spécifier la longueur de la donnée.
Passons à la division. C'est presque
la même chose que pour la multiplication. On a les instructions
divu.x et divs.x. Pour diviser d0 par d1, en utilisant la division
sans signe, puis enregistrer la réponse dans d0, on écrit : divu.x
d1,d0.
Les
Variables.
En assembleur 68000, on peut utiliser
les variables et les enregistrer dans le programme. Ces variables
gardent leur dernière valeur même après avoir quitté le programme.
C'est utile pour les listes de scores. Voici comment on déclare
une variable : dans le code source on écrit nom_var
dc.x 0 où nom_var est le nom de la variable et x la longueur
de donnée. On peut mettre une autre valeur de départ à la place
de 0. Pour utiliser des chaînes de caractères, on écrit : nom_chaine:
dc.b "Ceci est une chaine",0. On doit utiliser dc.b parce
que chaque caractère prend un byte. Le ,0 ajoute un caractère nul
à la fin de la chaîne. Ceci est nécessaire pour que la calculatrice
sache où est la fin de la chaîne. On peut déplacer des valeurs dans
des variables de la manière suivante : move.x
#10,nom_var. Il faut vérifier que les longueur correspondent.
Les
Branches et les Sauts.
Tout d'abord, on a besoin de labels
auxquels on va aller. On définit un label de cette manière : nom_label:. La première manière pour aller vers un
autre emplacement de mémoire et exécuter son code est l'équivalent
du goto en basic, jmp. On a besoin d'avoir une adresse absolue pour
l'utiliser, mail il n'est pas recommandé de l'utiliser si on ne
peut pas retourner à l'adresse précédente par rts. Pour faire des
sauts inconditionnels à l'intérieur du programme, on utilise bsr.
On écrit simplement : bsr nom_label et la calculatrice va au label indiqué
et continue d'exécuter le code. bsr enregistre l'adresse de laquelle
le saut a été effectué. Si on utilise l'instruction rts, alors la
calculatrice va retourner à bsr et exécuter l'instruction suivante.
Exemple :
bsr nom_label
move.w d0,d1
...
...
nom_label
move.w #10,d0
rts
Pour faire un saut conditionnel,
c'est-à-dire aller au label seulement si certaines conditions existent,
on utilise les instructions cmp et bxx. On verra plus bas les lettres
à par lesquelles on va substituer les x.
cmp B,A
bxx label_destination
bxx va se brancher au label de
destination si :
en
travaillant avec des nombres sans signe
|
A > B
|
bhi
|
branch
if higher |
A ³ B
|
bcc
|
branch
if carry clear |
A = B
|
beq
|
branch
if equal |
A £ B
|
bls
|
branch
if lower or same |
A < B
|
bcs
|
branch
if carry set |
A ¹ B
|
bne
|
branch
if non equal |
en
travaillant avec des nombres signés
|
A > B
|
bgt
|
branch
if greater |
A ³ B
|
bge
|
branch
if greater or equal |
A = B
|
beq
|
branch
if equal |
A £ B
|
ble
|
branch
if lower or equal |
A < B
|
blt
|
branch
if lower |
A ¹ B
|
bne
|
branch
if non equal |
Exemple :
move.w #10,d0
move.w #14,d1
cmp d0,d1
beq branchement_impossible
Passons au saut extérieur. Pour
faire un saut extérieur, on utilise l'instruction jsr. Cela fonctionne
de la même manière que bsr. On l'utilise principalement pour appeler
des fonctions dans les librairies.
Appels
de Librairies.
L'appel de librairies permet aux
programmeurs de mettre des fonctions communes à plusieurs programmeurs
dans un seul emplacement de la calculatrice, appelé librairie, en
permettant à tous les programmes de les utiliser. Les deux librairies
les plus importantes sur la TI 89 sont util et tios. Tios n'est
pas réellement une librairie, elle contient les appels de ROM. Util
contient les mêmes fonctions que flib pour Fargo II. Il est recommandé
de lire le fichier util.htm pour connaître les fonctions contenues.
Il y a beaucoup de fonctions très utiles dans la librairie util.
Par exemple clr_scr qui efface entièrement l'écran et redessine
une ligne de division vers le bas de l'écran. L'exemple suivant
montre comment utiliser cette fonction : jsr
util::clr_scr. Un exemple général serait : jsr
nom_lib::nom_fonction.
Note : il faut inclure la librairie
au début du code source sinon le compilateur ne comprendra pas ce
que cela signifie. On peut écrire, par exemple : include
"util.h". Exemple général : include "librairie.h".
Il faut faire ceci pour chaque librairie utilisée et vérifier que
le fichier librairie.89z se trouve bien sur la calculatrice.
Les Piles.
Comme les registres, les piles
- ou tas - sont des aires de mémoire. Elles sont usuellement pointée
par le registre A7. Elles peuvent être utilisées pour donner des
arguments aux fonctions ROM et elles sont aussi utilisées dans les
instructions bsr, jsr et rts. Il faut se rappeler de mettre à jour
le pointeur de la pile une fois utilisé. Ce qui arrive en dernier
sur la pile en ressort en premier.
Exemple :
move.w d0,-(a7)
Ceci déplace le word enregistré
en d0 sur la pile après avoir décrémenté l'adresse pointée par A7
de 1 byte. Il faut décrémenter l'adresse sinon le word s'écrira
par dessus d'autres informations probablement importantes. Pour
ressortir le word on écrit :
move.w (a7),d0
lea 2(a7),a7
Les parenthèses autour de A7 signifient
la donnée se trouvant à l'adresse que A7 contient, non la donnée
contenue par A7. La deuxième ligne met à jour le pointeur de la
pile. lea signifie load effective address, charger l'adresse effective.
Cette ligne incrémente A7 de 2 bytes et l'enregistre dans A7. On
incrémente par 2 bytes parce qu'avant on a décrémenté par un word,
donc par 2 bytes. On utilise toujours les bytes pour mettre à jour
le pointeur et si on ne le met pas à jour, la calculatrice risque
de planter.
Appels de
ROM.
Les appels de ROM sont très utiles
sur la TI 89. TI a déjà écrit quelques routines très utiles et on
peut les utiliser. Des routines telles que l'affichage de texte,
le changement de police d'écriture et l'utilisation du port de transmission
peuvent être utilisées par le programmateur. Le fichier tios.fct
contient toutes les fonctions de la librairie tios, ce qu'elles
font et leurs arguments. On appelle les fonctions de tios de la
même manière que pour les autres librairies. Cependant, les arguments
sont toujours sur la pile et le dernier argument est poussé en premier.
Aussi, les fonctions ROM utilisent probablement les registres D0
à D2 et quelques registres d'adresses, ainsi il ne faut pas compter
sur eux pour avoir les mêmes valeurs que lorsque l'on a appelé des
fonctions ROM. Prenons pour exemple de fonction ROM l'instruction
DrawStrXY. Elle est contenue dans tios.fct :
;---------------------------------------------------------
; void DrawStrXY
(WORD x, WORD y, BYTE *string, WORD color
;
; Function:
prints {string} at {x,y} with current font
;---------------------------------------------------------
Couleurs :
0 = blanc sur noir
1 = noir sur noir (!)
2 = blanc sur noir (?)
3 = gris sur blanc
4 = noir sur blanc
L'étoile * devant string signifie
qu'il faut pointer la chaîne, on a donc besoin de connaître l'adresse
de la chaîne que l'on veut afficher. Voici un exemple de programme
qui utilise cette fonction :
include "tios.h"
include "util.h"
xdef _main
xdef _comment
xdef _ti89
_main:
jsr util::idle_loop
jsr util::clr_scr
move.w #4,-(a7)
pea _comment
move.w #10,-(a7)
move.w #10,-(a7)
jsr tios::DrawStrXY
lea 10(a7),a7
_comment:
dc.b "Bonjour
!",0
end
Le
Graphisme.
Si on veut programmer des jeux,
on a forcément besoin de savoir comment faire des graphiques. On
utilise les librairies linelib.h et graphlib.h, librairies qu'il
est conseillé de consulter. Au début, à l'adresse contenue dans
la variable LCD_MEM, il y a une aire de mémoire de 3840 bytes. Cette
aire de mémoire est reflétée sur l'écran LCD de la calculatrice.
Si un bit est mis à 1 dans cette aire de mémoire, le pixel correspondant
sera noir sur l'écran. Dans cette aire de mémoire, chaque rangée
de pixels est longue de 20 bytes, ensuite il y a un tampon de 10
bytes, utilisé sur la TI 92+, puis une nouvelle rangée de 20 bytes
et ainsi de suite. On peut déplacer des mots binaires dans cette
mémoire pour les afficher à l'écran. On peut aussi utiliser les
instructions de manipulation des bits. Ces instructions affectent
chaque bit séparément dans le byte. Toutes les instructions testent
le bit avant de l'affecter :
btst teste un bit,
bset teste un bit et le met à 1,
bclr teste un bit et le met à 0,
bchg teste un bit et l'inverse.
Après ces instructions, on peut
utiliser les instructions bxx. Toutes ces instructions prennent
deux arguments, le premier est le nombre du bit à tester et le second
est l'adresse à laquelle se trouve le bit. Il y a une fonction très
utile dans la librairie util appelée find_pixel. Pour utiliser find_pixel,
on pousse un word pour la coordonnée y puis x du pixel. Ensuite
il faut mettre à jour le pointeur de la pile. Puis D0 contient le
nombre du bit recherché et A0 l'adresse. On peut ensuite changer
le pixel. Ainsi, pour inverser le premier pixel de l'écran (en haut,
à gauche) et se brancher ensuite au label pixel_noir s'il était
blanc avant qu'on l'ait changé, on écrit :
move.w #0,-(a7)
move.w #0,-(a7)
jsr util::find_pixel
lea 4(a7),a7
bchg.b d0,a0
bne pixel_noir
Les instructions dbxx sont très
utiles pour les graphiques, elles permettent de faire des boucles.
dbxx quitte une boucle. L'instruction est très similaire à bxx excepté
que la première opérande est un registre de donnée qui sera décrémenté
de 1 jusqu'à que -1 soit atteint, alors la boucle s'arrête. Pour
cela on utilise souvent dbra. Si on veut que la boucle se répète
10 fois, on peut enregistrer 9 dans un registre de données. Voir
le fichier 68kguide pour les autres conditions.
Exemple extrait de Tunnel, par
S. Jordan :
scroll:
lea LCD_MEM+3810,a0
move.w #1904,d0
Rep_Scrl:
move.w -(a0),30(a0)
dbra d0,Rep_Scrl
rts
©Tous les programmes presents
sur ce site sont soumis à l'autorisation de leur programmeurs
respectifs pour toutes modifications. La mise en page a necessité
un certain travail, il serait donc plus correct de ne pas la plagier.
|
|