La RAM écran c'est simplement la portion de RAM qui sert de données pour l'affichage sur votre moniteur.
Cette portion de RAM est traitée par le composant ULA (le coeur même du Spectrum) pour être envoyée ensuite dans un certain ordre sous forme d'une image affichable par le moniteur.
La structure de la RAM écran sur spectrum est comme sur toutes les machines de l'époque assez particulière.
Pour vous en rendre compte je vous invite à taper ceci sous Basic:
CLS:FOR I=16384 to 22527:POKE I,255:NEXT I
Nous constatons plusieurs choses:
- La RAM écran est divisée en 3 gros blocs.
- Chaque bloc contient 8 blocs de ligne.
- A la fin d'une ligne l'adresse suivante se retrouve 8 lignes plus bas lorsqu'on est dans un des 3 gros blocs.
C'est ce que nous appelerons la structure de l'écran.
Concrètement, pour comprendre le fonctionnement de cette structure, il faut regarder cela en hexadécimal:
- #4000 est l'adresse du premier octet de la RAM.
- #4100 est l'adresse de la deuxième ligne de l'écran.
- #4200 est l'adresse de la troisième ligne de l'écran.
- #4300 est l'adresse de la quatrième ligne de l'écran.
- Les lignes suivantes commencent en #4400 ; #4500 ; #4600 ; #4700.
On remarque tout de suite que pour descendre d'une ligne dans un bloc ligne (1 caractère de 8 pixels), il suffit d'ajouter #100 à l'adresse.
Commençons à faire un peu d'assembleur:
Allez dans l'éditeur et commençons:
ORG &8000
ORG adresse: correspond à l'adresse de début de notre programme.
Cette instruction n'est pas une instruction du Z80, mais une instruction de l'assembleur.
En définissant le ORG, vous signifiez à votre assembleur qu'il devra commencer à assembler votre programme à partir de l'adresse donnée.
Celui-ci lors de l'assemblage transformera vos instructions en octets de différentes valeurs que l'on nomme opcode.
Chaque opcode correspond à une instruction du Z80.
Votre code assemblé contiendra des opcodes et des valeurs (valeurs 8/16 bits et adresses.).
Notez que sous l'émulateur ZXSpin, on utilisera le symbole "&" pour donner une valeur en hexadécimal bien que dans mes arcticles j'utilise le "#" ce qui est le cas dans d'autres assembleurs.
Entrons nos premières instructions:
ORG &8000 LD A,255 ;Notre Octet à envoyer LD (&4000),A ;on envoie la valeur contenue dans le registre A à l'adresse entre parenthèse RET
Assemblez ("File" ; "Assemble" ; "OK") puis lancez votre programme sous Basic par:
RANDOMIZE USR 32768
32768 correspondant en décimal à l'adresse #8000.
Voyons maintenant les différentes instructions que nous venons d'utiliser:
LD A,val8: LD est l'abréviation de LOAD qui signifie "charger".
Il faut comprendre ici que "charger" signifie: mettre une valeur dans un registre. Le registre en question est le registre A.
val8 est un nombre 8bits (donc ayant pour valeur 0 à 255). Si vous ne comprenez par pourquoi, revoyez les premiers cours.
Notre instruction met donc une valeur (255)dans le registre A. Cela reviendrait à écrite: A=255.
Le registre A a pour le Z80 une fonction particulière.
On nomme celui-ci "accumulateur".
Toute opération sur une valeur 8bits passera par lui et il contiendra le résultat.
Le registre A ne peut être couplé à un autre registre pour obtenir une valeur 16bits contrairement à la plupart des autres registres 8bits.
Comme nous venons de le voir LD signifie que l'on met une valeur dans quelque chose.
LD (adr),A envoie la valeur contenue dans le registre A à l'adresse contenue entre parenthèse.
Ainsi LD (&4000),A envoie la valeur contenue dans A (donc 255) à l'adresse &4000 (donc sur le premier octet de l'écran).
La dernière instruction: RET signifie RETURN. Ou "retour" en français si vous préférez.
Après un RET, le Z80 retourne d'ou il est venu. Dans le cas présent vous retournerez au basic, mais retenez que si un saut de type CALL a eu lieu avant, vous retournerez après cette instruction lorsque le Z80 rencontrera un RET.
Nous verrons cela plus en détail plus tard.
Vous vous demandez peut-être pourquoi j'envoie une valeur de 255.
Si c'est le cas c'est que vous n'avez pas suivi mon conseil de faire des tests au chapitre précédent...
Mais ce n'est pas bien grave, c'est l'occasion de voir comment un octet est composé lorsqu'il est envoyé à l'écran.
Lorsqu'un octet est mis dans la RAM écran, celui-ci est interprété par la machine et son composant graphique pour le retranscrire en pixels.
Sur ZX Spectrum, le codage des pixels est on ne peut plus simple.
Comme l'image est composée de 2 couleurs, il a été simplement décidé qu'un bit à 1 donnerait un pixel de "crayon" pendant qu'un bit à 0 donnerait un pixel de fond.
Quand j'envoie la valeur 255, cela remplis donc tous les bits à 1. J'envoie donc directement 8 pixels de "crayon".
Maintenant que nous avons envoyé 1 octet à l'écran, pourquoi ne pas envoyer un caractère complet ?
Pour ce premier essai, nous allons Simplement essayer de faire un caractère plein sur le premier bloc de ligne.
Notre caractère sera donc simplement composé de 8 octets pleins affichés les uns en dessous des autres.
Nous pourrions faire les choses de façon simple et sans réflechir beaucoup en donnant directement les adresses ou envoyer la valeur (j'en mettrai 4 pour l'exemple):
ORG &8000 LD A,255 ;Notre Octet à envoyer LD (&4000),A ;on envoie la valeur contenue dans le registre A à l'adresse entre parenthèse LD (&4100),A ;deuxième ligne LD (&4200),A ;troisième ligne LD (&4300),A ;quatrième ligne RET
Cette méthode fonctionne, mais imaginez que vous voulez ensuite déplacer votre caractère un peu plus à droite... Cela vous obligerait à changer toutes les adresses.
Comme nous l'avons vu, pour passer à la ligne suivante dans un même bloc de ligne il suffit simplement d'ajouter #100 à l'adresse.
Une adresse est une valeur 16bits.
Celle-ci est stockée sur 2 octets.
Par exemple pour #4000 vous aurez un octet #40 et un octet #00.
Le #40 est dit "poids fort" tandis que le #00 est le "poids faible" de l'adresse.
Si l'on réflechit un peu, on se rend compte que finalement, ajouter #100 à l'adresse c'est identique à ajouter #01 au poids fort.
Nous allons donc utiliser cette "astuce" pour calculer l'adresse de la ligne inférieure.
Commençons donc notre programme:
ORG &8000 ;Notre programme commencera en &8000 LD C,255 ;A=255 LD HL,&4000 ;HL=&4000 LD B,8
Pour le moment je n'ai fait qu'initialiser des registres. Mais cela va déjà nous permettre d'en parler un peu.
Je vous ai parlé de l'accumulateur: le registre A.
Heureusement pour nous ce n'est pas le seul registre du Z80.
Un registre peut être considéré comme une case d'1 octet pouvant contenir une valeur.
Certains registres pouvant-être couplés par deux, nous parlons alors de registres 16bits. Ceux-ci peuvent donc bien entendu contenir une valeur 16bits (0 à 65535).
Le Z80 dispose des registres suivants:
A
B
C
D
E
F
IX
IY
PC
SP
I
R
Les registres 8 bits B et C peuvent être associés pour former le registre 16 bits BC.
Les registres 8 bits D et E peuvent être associés pour former le registre 16 bits DE.
Les registres 8 bits H et L peuvent être associés pour former le registre 16 bits HL.
Quelques registres parmis ceux-ci ont des rôles particuliers. Voyons juste ceux dont nous avons besoin pour le moment:
HL est un peu comme l'accumulateur mais pour les valeurs 16 bits. Aussi toute opération sur 16 bits passera par lui et il contiendra le resultat.
B est un registre utilisé par une instruction bien pratique de bouclage. Voyons cela de plus pret:
L'instruction DJNZ adr permet de faire un saut lorsque B est différent de 0.
A chaque fois que l'instruction DJNZ adr est executée, le registre B est décrémenté. Cela signifie que l'on fait -1 sur la valeur de B. B=B-1.
Ainsi pour faire une boucle sur 8 fois, on mettra 8 dans B avec un LD B,8, puis on fera à la suite notre boucle.
Bien entendu, l'initialisation de B à 8 devra être en dehors de la boucle pour le pas redonner la valeur à B ce qui créerait une boucle infinie.
Vous vous demandez certainement comment préciser l'adresse de saut ?
Dans l'ancien temps on aurait pu (et on le faisait) la calculer à la main.
Heureusement pour nous, l'assembleur permet de s'affranchir de cette difficulté. Vous pourrez simplement placer un label dans votre source, là où vous souhaiterez sauter.
Un label c'est simplement un mot. On pourra le précéder d'un point pour que l'assembleur comprenne qu'il s'agisse d'un label (mais ce n'est pas obligatoire selon l'assembleur que vous utilisez.).
Lors de l'assemblage, l'assembleur stockera l'adresse à laquelle apparait votre label et remplacera alors automatiquement tous les endroits ou ce label apparait par l'adresse correspondante.
Ainsi comme sur le schéma ci-dessus, vous pourrez mettre un label ".boucle" à l'endroit ou vous souhaiter sauter après le saut.
Suffira ensuite de faire un "DJNZ .boucle" pour sauter directement à ce label quand B sera différent de 0.
Maintenant que nous savons faire une boucle, réflechissons à ce que cela peut nous apporter dans notre cas.
Nous devons ajouter #01 au poids fort de l'adresse de l'écran à chaque fois que nous voulons descendre d'une ligne.
Il nous suffit donc de faire cela dans une boucle avec un compteur à 8 pour que cela fonctionne !!!
Pour faire +1 sur la valeur d'un registre, rien de plus simple: on utlisera l'instruction INC.
Cette instruction existe à la foi pour les registres 8 bits ou pour les registres 16 bits.
INC signifie Incrémenter. Incrémenter c'est faire +1...
Nous avons donc la possibilité de faire un INC r8 (r8 signifie Registre 8 bits) pour augmenter notre poids fort de l'adresse:
ORG &8000 ;Notre programme commencera en &8000 LD C,255 ;A=255 LD HL,&4000 ;HL=&4000 LD B,8 ;notre compteur utilisera B pour fonctionner avec un DJNZ .boucle ;voici notre label pour boucler ici LD (HL),C ;on envoie la valeur du registre C à l'adresse HL INC H ;on incrémente le poids fort de l'adresse DJNZ .boucle ;tant que B est différent de 0 on saute ou se trouve le label .boucle RET ;quand b=0 on passe à l'instruction suivante, c'est à dire ici.
Comme vous avez pu le voir tout ceci n'était pas bien compliqué. Vous avez sans soucis affiché un caractère plein.
Vous pouvez même le déplacer sur la ligne en modifiant l'adresse de départ.
Cependant les choses vont se compliquer un peu pour le prochain cour. Nous allons en effet non pas afficher sur un bloc ligne, mais à cheval sur plusieurs !!!
Révisez donc bien tout ce que vous venez de voir car il est indispensbale d'avoir bien assimilé le peu d'instructions évoquées jusqu'ici pour passer à la suite.
Retenez bien que lorsqu'un registre est placé entre parenthèse, on parle d'une case mémoire.
Retenez bien que DJNZ diminue B et saute à l'adresse donnée tant que celle-ci est différente de 0.
Retenez aussi qu'un RET renvoie à l'endroit d'où l'on vient.
Amusez vous surtout à modifier le contenu de votre caractère. Vous pouvez tout à fait envoyer des octets différents pour chaque ligne par exemple.
Le meilleur moyen d'apprendre c'est de tester et d'expérimenter. Plus vous testerez, mieux vous retiendrez et évoluerez.