Site

. Les News
. Le Forum
. L'équipe et les projets
. Les Tutoriaux
. IRC
. La boite à outils
. Les routines et algos indispensables
. Les liens


Les News de TI-FR








 

 



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.