NES ASM Tutorial

publicité
NES ASM Tutorial
Auteur : Bunnyboy
Traduction : Mathieu Soula (Rid)
Sommaire
NES ASM Tutorial ................................................................................................................. 1
Outils..................................................................................................................................... 1
Outils utilisés dans ce tutoriel............................................................................................. 1
D'autres outils .................................................................................................................... 1
Architecture de la NES .......................................................................................................... 2
Présentation générale du système..................................................................................... 2
Présentation générale du CPU .......................................................................................... 3
Présentation du PPU ......................................................................................................... 3
Présentation des systèmes graphiques.............................................................................. 4
Tiles ............................................................................................................................... 4
Sprites............................................................................................................................ 4
Background .................................................................................................................... 5
Pattern Tables................................................................................................................ 5
Attribute Tables .............................................................................................................. 5
Palettes .......................................................................................................................... 5
Assembleur 6502 .................................................................................................................. 6
Présentation ...................................................................................................................... 6
Binaire/Hexadécimal .......................................................................................................... 6
Calcul binaire ................................................................................................................. 6
Calcul hexadécimal ........................................................................................................ 6
Les registres du 6502 ........................................................................................................ 7
L'Accumulateur............................................................................................................... 7
Le registre d'index X....................................................................................................... 7
Le registre d'index Y....................................................................................................... 7
Le registre de statut........................................................................................................ 7
Disposition du code............................................................................................................ 8
Les directives ................................................................................................................. 8
Les labels ....................................................................................................................... 8
Les opcodes................................................................................................................... 8
Les opérandes ............................................................................................................... 8
Les commentaires .......................................................................................................... 8
La structure du code NES ..................................................................................................... 9
Introduction........................................................................................................................ 9
L'entête iNES..................................................................................................................... 9
Banking.............................................................................................................................. 9
Les vecteurs ...................................................................................................................... 9
Ajouter des fichiers binaires ..............................................................................................10
Le code RESET ................................................................................................................10
Compléter le programme ..................................................................................................11
Assembler le tout ..............................................................................................................12
Les palettes..........................................................................................................................13
Les sprites............................................................................................................................15
Le DMA.............................................................................................................................15
Les données sprite ...........................................................................................................15
Activer le NMI sprite..........................................................................................................15
Assembler le tout ..............................................................................................................16
Les sprites composites .........................................................................................................17
Une autre copie de bloc ....................................................................................................17
Assembler le tout ..............................................................................................................17
Lecture des contrôleurs ........................................................................................................19
Les ports de manettes ......................................................................................................19
L'instruction AND ..............................................................................................................19
L'instruction BEQ ..............................................................................................................20
Les instructions CLC/ADC ................................................................................................20
Les instructions SEC/SBC ................................................................................................20
Assembler le tout ..............................................................................................................21
Les variables.....................................................................................................................21
Outils
Outils utilisés dans ce tutoriel
Ci-dessous sont présentés les outils que vous utiliserez pour réaliser ce tutoriel.
NESASM 3 – C'est l'assembleur qui vous permettra de convertir votre code en un fichier
NES.
Tile Layer Pro – C'est un éditeur graphique de tiles de format NES. Il permet aussi
d'importer des bitmaps que vous aurez créés.
FCEUXD SP – Un émulateur NES très fiable, proposant plusieurs fonctionnalités de
déboguage. Si votre jeu se lance correctement sur cet émulateur, il le fera certainement sur
une vraie NES. Il est fortement déconseillé d'utiliser de vieux émulateurs dépassés comme
Nesticle.
Powerpak – Un matériel permettant de faire tourner vos jeux sur une vraie console. Avec
ceci, il n'est pas nécessaire de modifier votre NES ou votre jeu.
Powerpak Lite – Un moyen rapide et efficace pour tester votre jeu sur une vraie NES. Il
nécessite un système CopyNes.
NESDevWiki – Un wiki qui contient un grand nombre d'informations techniques. La plupart
des articles ont été corrigés…
NESDev – Une collection de liens et de forums contenant tout en tas d'informations
techniques. Posez des questions à cet endroit une fois que vous aurez appris les bases.
D'autres outils
Lorsque vous aurez plus d'expérience en programmation NES, vous serez peut-être amené
à utiliser ces outils :
Les assembleurs - Il existe un grand nombre d'autres assembleurs populaires tels que
CA65, P65 et WLA-DX. Ils ont de bonnes performances au sein de grands projet, mais sont
plus compliqués à paramétrer correctement. La syntaxe du code assembleur peut changer
en fonction de l'assembleur utilisé, donc votre code devra normalement être adapté à chacun
d'eux.
Les éditeurs graphiques - Les principales alternatives à Tile Layer Pro sont Tile Molester
et YY-CHR. De plus, il n'est pas rare que certaines personnes développent leur propre
éditeur graphique.
Les émulateurs - Il existe d'autres émulateurs fiables tels que Nintendulator, Nestopia et
FCEU. Les émulateurs peu fiables comme Nesticle sont une des raisons qui expliquent que
certains vieux jeux et hacks ne tournent pas sur une vraie NES.
Les cartes flash - Si vous possédez un programmateur EPROM (Erasable Programmable
Read-Only Memory), vous pourrez relier votre jeu à des cartouches NES. Cette méthode est
largement plus économique que celle du PowerPak ou du PowerPak Lite mais demande
plus de manipulations.
1
Architecture de la NES
ROM – Read Only Memory, mémoire qui contient des données non modifiables.
RAM – Random Access Memory, contient des données qui peuvent être lues et écrites.
Lorsqu'on coupe le courant, la RAM est effacée.
PRG – Program Memory, le code du jeu.
CHR – CHaracter Memory, les données pour les graphismes.
CPU – Central Processing Unit, le processeur principal.
PPU – Picture Processing Unit, le processeur graphique.
APU – Audio Processing Unit, le composant utilisé pour les sons. Il est intégré dans le CPU.
Présentation générale du système
La NES dispose d'un CPU auquel sont intégrés un APU et un gestionnaire de pads, et d'un
PPU qui gère l'affichage. Votre code est exécuté par le CPU et envoie des commandes à
l'APU et au PPU. Les clones NOAC (Nes On A Chip) réunissent tous ces composants sur
une seule puce.
Il y a seulement 2Ko de RAM connecté au CPU pour stocker les variables, et 2Ko de RAM
connecté au PPU pour contenir deux backgrounds (arrière-plans). Il existe des cartouches
qui fournissent de la RAM supplémentaire au CPU, et d'autres qui en fournissent au PPU.
Au moins deux puces sont présentes sur chaque cartouche de jeu. Une pour code
programme (PRG) et l'autre pour les éléments graphiques (CHR). La puce CHR peut être
RAM au lieu de ROM, ce qui signifie que le code pourra copier les éléments à afficher de la
puce PRG vers la RAM CHR.
Architecture
2
Présentation générale du CPU
Le CPU de la NES est une version modifiée du 6502 de MOS, un processeur de données
8bits similaire à celui présent sur l'Apple 2, l'Atari 2600, C64 ainsi que d'autres systèmes. A
l'époque de la Famicom, ce processeur était considéré faible pour équiper un ordinateur
mais puissant pour une console de jeux.
Le CPU utilise un bus d'adresse 16bits permettant d'accéder ainsi jusqu'à 64Ko de mémoire.
Celle-ci dispose, entres autres, de 2Ko de RAM, de ports entrée-sortie PPU, APU, et
manettes, ainsi que de 32Ko pour la PRG ROM.
Comme 32Ko de PRG ROM est devenu rapidement insuffisant pour contenir un jeu, on a
commencé à utiliser des mappers. Ces derniers sont utilisés d'échanger différentes banks
(blocs) de code PRG ou d'éléments graphiques. Aucun mapper ne sera utilisé dans cette
doc.
Schéma de la mémoire du CPU
Présentation du PPU
Le PPU de la NES est une puce customisée qui assure l'affichage des éléments graphiques.
Il dispose de RAM pour les sprites et les palettes couleur. Une partie de la RAM est utilisée
pour contenir les backgrounds, et tous les éléments graphiques à afficher proviennent de la
mémoire CHR de la cartouche.
3
Votre programme ne s'exécute pas sur le PPU car ce n'est pas son rôle. Les seules
opérations que vous pouvez effectuer consistent à paramétrer certaines options comme les
couleurs ou bien le scrolling (défilement du background).
Les systèmes NTSC et PAL possèdent tous deux une résolution de 256x240 pixels. Il faut
savoir que 8 lignes en haut et en bas sont retirées sur les télés NTSC, rabaissant ainsi la
résolution de ces systèmes à 256x224 pixels.
Les systèmes NTSC ont une fréquence de 60Hz alors que celle des systèmes PAL est à
50Hz. Un jeu NTSC lancé sur un système PAL s'exécutera donc plus lentement.
Les systèmes PAL ont une période de VBlank (temps mis par le faisceau pour aller du bas
droit de l'écran vers le haut gauche) plus longue, ce qui donne plus de temps pour mettre à
jour l'affichage. Ceci explique pourquoi certains jeux et démos PAL ne fonctionnent pas sur
des systèmes NTSC.
Schéma de la mémoire du PPU
Présentation des systèmes graphiques
Tiles
Sur la NES, l'affichage est le résultat d'un assemblage de tiles (carreaux) de 8x8 pixels. Les
gros éléments graphiques comme Mario sont construits à partir d'une composition de tiles de
8x8 pixels. Les backgrounds sont eux aussi composés de ces tiles.
Sprites
Le PPU dispose de suffisamment de mémoire pour afficher 64 sprites, ou objets qui bougent
sur l'écran comme Mario. Seulement 8 sprites par scanline (ligne horizontale) sont autorisés,
4
les sprites supplémentaires seront ignorés. Ceci est la cause du flickering (clignotement de
l'image) dans certains jeux lorsqu'il y a trop d'objets à afficher à l'écran.
Background
Il s'agit du paysage qui se déroule en fond. Les sprites peuvent soit être affichés devant ou
derrière le background. L'écran est suffisamment grand pour afficher les 32x30 tiles
constituant le background.
Pattern Tables
Les pattern tables (table modèle) sont les zones mémoires où les données de tiles affichées
sont stockées. Ces tables sont présentes sur la cartouche sous forme de ROM ou de RAM.
Chaque pattern table contient 256 tiles. Une pattern table est utilisée pour les backgrounds,
et l'autre pour les sprites.
Attribute Tables
Ces tables définissent l'information couleur de zones de l'écran. Ces zones d'écran sont
constituées de 2x2 tiles. Cela signifie qu'une zone de 16x16 pixels ne peut seulement avoir
que 4 couleurs différentes sélectionnées dans la palette.
Palettes
Il s'agit de deux zones mémoire contenant les informations couleur. Il existe une palette pour
le background et une palette pour les sprites. Chaque palette contient 16 couleurs.
Pour afficher une tile à l'écran, l'index de couleur pour un pixel est calculé à partir d'une
pattern table et de l'attribute table. Cet index est alors utilisé avec la palette pour récupérer la
couleur à afficher.
Calcul des couleurs des pixels d'une tile
5
Assembleur 6502
Bit – La petite unité de mesure en informatique. C'est soit un 1 (on) soit un 0 (off), comme un
interrupteur.
Octet – Un ensemble de 8 bits constitue un octet, un nombre compris entre 0 et 255. Deux
octets assemblés sur 16 bits forment un nombre compris entre 0 et 65535. Les bits d'un
octet sont lus de la droite (bit 0) vers la gauche (bit 7).
Instruction – Une commande exécutée par un processeur. Les instructions sont exécutées
de manière séquentielle.
Présentation
Cette section donne un bref aperçu du 6502, pas une explication détaillée. Des informations
plus
précises
peuvent
être
trouvées
sur
des
sites
comme
http://www.obelisk.demon.co.uk/6502/.
Binaire/Hexadécimal
Avant de commencer la partie programmation, il est indispensable de comprendre les calculs
binaires et hexadécimaux. La calculatrice Windows, en mode scientifique, permet de passer
rapidement entre ces modes de calcul.
Calcul binaire
Base 2, les chiffres utilisés sont uniquement le 1 et le 0. Avec NESASM, on les écrit avec un
% placé devant (ex: %00101110). Comme la NES est un système de données 8 bits, on
écrira les nombres binaires sur 8 chiffres. Les bits sont numérotés à partir de 0 depuis la
droite.
0
1
2
3
128
255
=
=
=
=
=
=
76543210
||||||||
%00000000
%00000001
%00000010
%00000011
%10000000
%11111111
Calcul hexadécimal
Base 16, les chiffres utilisés vont de 0 à F. Avec NESASM, on écrit les chiffres
hexadécimaux avec un $ placé devant (ex: $8B). Chaque chiffre hexadécimal est constitué
de 4 bits, donc une donnée 8 bits est écrite avec 2 hexadécimaux.
6
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
128
255
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
$00
$01
$02
$03
$04
$05
$06
$07
$08
$09
$0A
$0B
$0C
$0D
$0E
$0F
$10
$11
$80
$FF
Les adresses sont codées sur 16 bits, ou 4 chiffres hexadécimaux.
$0000 = début de l'espace mémoire
$8000 = début de la PRG ROM
$FFFF = dernier octet de l'espace mémoire
Les registres du 6502
Le processeur 6502 utilise 3 registres de 8bits ainsi qu'un registre de statut. Tous vos
traitements de données utilisent ces registres. Il existe des registres supplémentaires mais
ils ne sont pas traités dans ce tutorial.
L'Accumulateur
L'accumulateur (A) est le registre 8bits principal pour charger, stocker, comparer, et faire des
calculs sur vos données. Voici un exemple d'utilisation fréquente :
LDA #$FF
STA $0000
;
;
;
;
Charge la valeur hexadécimale $FF (256 en décimal)
dans A
Stocke la valeur de l'accumulateur à l'adresse mémoire
$0000 (RAM interne)
Le registre d'index X
Le registre d'index X (X), un autre registre 8bits, est souvent utilisé pour compter et faire des
accès mémoire. Dans les boucles, vous utiliserez ce registre pour conserver le nombre de
boucles réalisées, tandis que A vous servira à traiter les données. Voici un exemple
d'utilisation fréquente:
LDX $0000
INX
; Charge la valeur de l'adresse $0000 dans X
; Incrémente X
X = X + 1
Le registre d'index Y
Le registre d'index Y (Y) s'utilise de la même manière que X. Voici un exemple d'utilisation
fréquente :
STY $00BA
TYA
; Stocke la valeur de Y à l'adresse mémoire $00BA
; Transfère la valeur de Y dans l'Accumulateur
Le registre de statut
Le registre de statut conserve des informations sur chaque instruction venant d'être exécutée
au moyen de flags (drapeaux). Par exemple, après avoir effectué une soustraction, le
registre de statut vous permet de vérifier si le résultat de cette soustraction était 0.
7
Disposition du code
Dans les langages assembleur, on distingue 5 types d'éléments. Chacun de ces éléments
doit être correctement indenté pour pouvoir être utilisé par l'assembleur.
Les directives
Les directives sont les commandes que vous envoyez à l'assembleur pour effectuer des
opérations comme positionner du code dans la mémoire. Elles sont précédées par un . et
sont indentées. Certaines personnes utilisent des tabulations, d'autres 4 espaces, ou 2
espaces. Cet exemple de directive indique à l'assembleur de placer le code en mémoire à
l'adresse $8000 :
.org $8000
Les labels
Les labels sont placés tout à gauche et sont terminés par un :. Les labels sont simplement
un moyen d'organiser votre code. L'assembleur traduit les labels en adresses. Un exemple
de label :
.org $8000
MaFonction:
Les opcodes
Les opcodes sont des instructions que le processeur exécute. Ils sont indentés tout comme
les directives. Dans cet extrait, JMP est l'opcode qui dit au processeur de "sauter" jusqu'au
label MaFonction :
.org $8000
MaFonction:
JMP MaFonction
Les opérandes
Les opérandes sont des arguments pour les opcodes. Les opcodes peuvent nécessiter de
une à trois opérandes. Dans cet exemple, la valeur #$FF est l'opérande de LDA :
.org $8000
MaFonction:
LDA #$FF
JMP MaFonction
Les commentaires
Les commentaires sont là pour vous aider à comprendre en français ce que le code fait. Les
commentaires sont un bon moyen de reprendre facilement un code délaissé depuis un
certain temps. Il n'est pas nécessaire de commenter chaque ligne, mais il doit y en avoir
suffisamment pour comprendre ce qu'il se passe. Les commentaires commencent par un ; et
sont entièrement ignorés par l'assembleur. Ils peuvent être placés n'importe où sur la ligne,
mais sont généralement espacés du code.
.org $8000
MaFonction:
; charge #$FF dans l'accumulateur
LDA #$FF
JMP MaFonction
Ce code bouclera infiniment, chargeant la valeur hexadécimale $FF dans l'accumulateur à
chaque fois.
8
La structure du code NES
Introduction
Cette section contient un grand nombre d'informations car elle présente tout ce qu'il faut
préparer pour créer votre premier programme NES. La plupart du code peut être copié/collé
puis oublié pour l'instant. L'objectif principal est de produire à NESASM quelque chose de
fonctionnel.
L'entête iNES
L'entête iNES de 16bits donne à l'émulateur toutes les informations sur le jeu, ce qui inclue
le mapper, le mirroring graphique, et les tailles de la PRG et de la CHR. Vous devez
mentionner tout cela au début de votre fichier asm.
.inesprg
.ineschr
.inesmap
.inesmir
1
1
0
1
;
;
;
;
1x 16Ko bank de code PRG
1x 8Ko bank de données CHR
mapper 0 : NROM, pas de swap de bank
mirroring du background
Banking
NESASM arrange tout dans des banks (blocs) de code de 8Ko et des banks d'éléments
graphiques de 8Ko. Pour remplir les 16Ko d'espace PRG, 2 banks sont nécessaires. Comme
la plupart du temps en informatique, les nombres démarrent à 0. Pour chaque bank, vous
devez indiquer à l'assembleur où la mémoire débutera.
.bank 0
.org $C000
; un peu de code là
.bank 1
.org $E000
; un peu de code ici
.bank 2
.org $0000
; les éléments graphiques là
Les vecteurs
Il existe 3 cas où le processeur de la NES pourra interrompre l'exécution normale du code
pour "sauter" vers une autre adresse dans la mémoire. Ces vecteurs, placé dans la PRG
ROM indiquent au processeur où aller lors d'interruption. Seuls deux seront utilisés dans ce
tutorial.
Le vecteur NMI - Ce vecteur est déclenché une fois par frame (période lors de laquelle le
faisceau part du haut gauche de l'écran pour le bas droit) vidéo, lorsqu'il est activé. Le PPU
dit au processeur qu'il est en train de démarrer une période VBlank et qu'il est disponible
pour une mise à jour des éléments graphiques.
Le vecteur RESET - Ce vecteur est déclenché à chaque fois que la NES démarre, ou
lorsque le bouton reset est appuyé.
Le vecteur IRQ - Ce vecteur est déclenché par certaines puces mappers ou interruption
audio. Il n'est pas traité dans ce tutorial.
Ces trois vecteurs doivent toujours apparaître dans votre fichier asm dans un ordre précis :
9
.bank 1
.org $FFFA
.dw NMI
.dw RESET
.dw 0
;
;
;
;
;
;
;
Premier des trois vecteurs démarre ici
Quand une NMI survient (une fois par frame si
activé), le processeur sautera au label NMI:
Quand le processeur se lance ou est redémarré,
il sautera au label RESET:
Interruption IRQ externe qui n'est pas
utilisée dans ce tutorial
Ajouter des fichiers binaires
Des fichiers de données supplémentaires sont souvent utilisés pour les éléments graphiques
ou les données des niveaux. La directive incbin peut être utilisée pour inclure ces données
dans votre fichier NES.
.bank 2
.org $0000
.incbin "mario.chr"
; Inclue un fichier de 8Ko d'éléments
; graphiques extraits de SMB1
Le code RESET
Certaines opérations sont à effectuer obligatoirement au démarrage de la NES (modes à
positionner, RAM à vider, attendre que le PPU soit démarré, …). Elles sont indispensables,
mais il n'est pas nécessaire de savoir pourquoi pour l'instant :
10
.bank 0
.org $C0000
RESET:
SEI
CLD
LDX #$40
STX $4017
LDX #$FF
TXS
INX
STX $2000
STX $2001
STX $4010
; Désactiver l'IRQ
; Désactiver le mode décimal
; Désactiver l'IRQ pour les frames APU (audio)
;
;
;
;
;
Vblankwait1:
Mettre en place la pile
X = 0
Désactiver le NMI
Désactiver le rendering
Désactiver les IRQ pour la DMC
; Attendre d'abord le VBlank pour s'assurer que
; le PPU est prêt
BIT $2002
BPL vblankwait1
clrmem:
LDA
STA
STA
STA
STA
STA
STA
STA
STA
LDA
STA
INX
BNE
; Boucle qui met à $00 un bloc de 2Ko de RAM
; (par effet miroir, c'est l'ensemble de la
; RAM qui sera mise à $00)
#$00
$0000,
$0100,
$0200,
$0300,
$0400,
$0500,
$0600,
$0700,
#$FE
$0300,
x
x
x
x
x
x
x
x
x
clrmem
; X++ - si X == $FF, INX X = $00
; tant que X != $00
vblankwait2:
BIT $2002
BPL vblankwait2
Compléter le programme
Votre premier programme sera très excitant, car il affichera sur tout un écran une seule et
même couleur! Pour cela, les paramètres initiaux du PPU doivent être réglés. On utilise
l'adresse $2001 pour communiquer avec le PPU. Le 76543210 correspond à la position des
bits de 7 à 0. Ces 8 bits forment l'octet que vous écrirez à l'adresse $2001.
11
PPUMASK ($2001)
76543210
||||||||
|||||||+---Niveau de gris (0: couleur normale: 1: pour chaque
|||||||
couleur de la palette, effectue un AND avec $30 enfin de
|||||||
produire un affiche monochrome; notez que les différences
|||||||
entre couleurs sont conservées
||||||+----Désactive la coupure de l'arrière-place au niveau des
||||||
8 pixels se situant à la gauche de l'écran
|||||+-----Désactive la coupure des sprites au niveau des 8 pixels
|||||
se situant à la gauche de l'écran
||||+------Active l'affichage de l'arrière-plan
|||+-------Active l'affichage des sprites
||+--------Intensifie les rouges (et assombrit les autres couleurs)
|+---------Intensifie les verts (et assombrit les autres couleurs)
+----------Intensifie les bleus (et assombrit les autres couleurs)
Donc si vous souhaitez afficher les sprites, vous positionnez le bit 4 à 1. Pour cette
application, les bits 7, 6 et 5 seront utilisés pour rêgler la couleur de l'écran:
LDA %10000000
STA $2001
Forever:
JMP Forever
; Intensifier les bleus
; Boucle infinie
Assembler le tout
Téléchargez et dézippez le fichier background.zip. L'ensemble du code présenté se trouve
dans le fichier background.asm. Assurez-vous que les fichiers mario.chr et background.bat
soient dans le même répertoire que NESASM, puis double-cliquez sur background.bat. Cela
lancera NESASM et créera la rom background.nes. Lancez cette rom avec FCEUXD SP
pour voir la couleur du background! Editez le fichier background.asm pour changer les bits
d'intensité 7-5 et donner à l'écran une couleur rouge ou verte.
Vous pouvez lancer le déboguage depuis le menu Outils de FCEUXD SP pour voir votre
code s'exécuter. Cliquez sur le bouton Step Into , puis cliquez sur le menu Reset, et continuez
à cliquer sur Step Into pour exécuter une à une les instructions. A gauche se trouve la plage
mémoire, suivie de l'opcode en hexadécimal que le 6502 est en train d'exécuter. Les
instructions sont sur un à trois octets. A la suite se trouve le code que vous avez écrit sans
les commentaires ni les labels qui ont été changés en adresses. La ligne du haut contient la
prochaine instruction à exécuter. Essayez de deviner ce que l'instruction fera avant de
cliquer sur Step Into .
12
Les palettes
Avant d'afficher quoique ce soit à l'écran, vous devez d'abord mettre en place la palette
couleur. Il y a 2 palettes distinctes, chacune d'entre elles fait 16 octets. L'une est utilisée pour
le background, et l'autre pour les sprites. Chaque octet de la palette correspond à l'une des
52 couleurs de base pouvant être affichées par la NES. $0D est une couleur mauvaise et ne
devra jamais être utilisée.
Figure 1 - La palette des couleurs NES
Les palettes sont placées aux adresses $3F00 et $3F10 de la mémoire du PPU. Pour
travailler sur ces adresses, on utilise le port $2006. Ce port doit être utilisé deux fois, une fois
pour transmettre l'octet de poids forts, et un fois pour transmettre l'octet de poids faible:
LDA $2002
LDA #3F
STA $2006
LDA #$00
STA $2006
; Lit le statut du PPU pour remettre en place le
. verrou haut/bas
; haut/bas
; Ecrit l'octet de poids fort de l'adresse
; $3F00
; Ecrit l'octet de poids faible de l'adresse
; $3F00
Maintenant le port de données du PPU à l'adresse $2007 est prêt à recevoir les données. La
première écriture ira à l'adresse que vous avez envoyée ($3F00), puis le PPU incrémentera
cette adresse ($3F01, $3F02, …). Tant que vous continuerez d'écrire sur le port $2007,
l'adresse incrémentera. Cet extrait de code stocke les 4 premières couleurs de la palette :
LDA
STA
LDA
STA
LDA
STA
LDA
STA
#$31
$2007
#$14
$2007
#$2A
$2007
#$16
$2007
; blue clair
; fushia
; un vert
; rouge
Vous pourriez continuer ainsi de remplir le reste de votre palette. Heureusement, il existe
une méthode plus directe. Tout d'abord, vous commencez par utiliser la directive .db pour
enregistrer les octets de données couleur.
PaletteData:
.db $0F,$31,$32,$33,$34,$35,$36,$37,$38,$39,$3A,$3B,$3C,$3D,$3E,$3F
.db $0F,$1C,$15,$14,$31,$02,$38,$3C,$0F,$1C,$15,$14,$31,$02,$38,$2C
Puis, vous utilisez une boucle pour copier ces octets dans les palettes du PPU. Le registre X
est utilisé comme index pour la palette, ainsi que pour compter combien de fois la boucle
s'est exécutée. Vous souhaitez copier 32 octets, donc la boucle commence à 0 et se
s'exécute 32 fois.
13
LDX #$00
LoadPalettesLoop:
LDA PaletteData, x
STA $2007
INX
CPX #$20
BNE LoadPalettesLoop
; Initialiser l'index à 0
;
;
;
;
;
;
;
;
;
;
;
;
;
Charger la donnée de l'adresse (PaletteData
+ valeur dans X)
1ère fois dans la boucle : PaletteData + 0
2ème fois dans la boucle : PaletteData + 1
3ème fois dans la boucle : PaletteData + 2
etc…
écriture dans le PPU
X = X + 1
Comparer X et la valeur hexadécimal $20
(32 en décimal)
Boucler sur LoadPalettesLoop si la
comparaison n'est pas égale à 0, sinon
exécuter l'instruction qui suit
14
Les sprites
Tout ce qui déplace indépendamment du background sur l'écran est constitué de sprites.
Une sprite est un carré de 8x8 pixels que le PPU peut afficher n'import où. Le PPU dispose
d'assez de mémoire interne pour gérer 64 sprites.
Le DMA
La méthode la plus rapide et facile pour transférer vos sprites au PPU est d'utiliser le DMA
(Direct Memory Access). Cela consiste en un bloc de RAM qui est copié directement de la
mémoire du CPU vers celle du PPU. On utilise généralement l'espace RAM $0200-02FF
pour ce type de transfert. Pour commencer, 2 octets doivent être écrits sur les ports de la
PPU :
LDA #$00
STA $2003
LDA #$02
STA $4014
; Positionne l'octet de poids faible ($00) de
; l'adresse de la RAM
; Positionne l'octet de poids fort ($02) de
; l'adresse de la RAM
Comme tous les rafraichissements de l'affichage, cette opération doit être effectuée lors
d'une période VBlank, on l'exécute donc dans une section NMI.
Les données sprite
Chaque sprite nécessite 4 octets de données pour indiquer sa position et les informations
tile. Ces données sont écrites dans cet ordre en mémoire :
La coordonnée Y - Position verticale du sprite sur l'écran. $00 correspond au haut de
l'écran. Toute valeur supérieure à $EF est située en dehors de l'écran.
Le numéro tile - C'est un nombre compris entre 0 et 255. Il s'agit de l'index de la tile dans la
pattern table des sprites
Les attributs - Cet octet fournit les informations sur la couleur et l'affichage :
76543210
|||
||
|||
++---Palette (4 à 7) du sprite
|||
||+--------Priorité (0: devant le background; 1: derrière le background)
|+---------Flip (retournement) horizontal du sprite
+----------Flip vertical du sprite
La coordonnée X - Position horizontale du sprite sur l'écran. $00 correspond à la gauche de
l'écran. Toute valeur supérieure à $F9 est située en dehors de l'écran.
Pour modifier le sprite 0, utiliser les octets $0300-$0303; le sprite 1, les octets $0304-$0307;
le sprite 2, les octets $0308-$030B, etc…
Activer le NMI sprite
Cette fois encore, c'est le port $2001 du PPU qui est utilisé pour activer les sprites. Pour
afficher les sprites à l'écran, il faut positionner le bit 4 à 1.
Le NMI a aussi besoin d'être mis en route, afin de faire marcher le sprite DMA et de
permettre la copie des sprites. Pour cela, on utilise le port $2000 du PPU. La Pattern Table 1
est aussi sélectionnée pour choisir les sprites.
15
PPUCTRL ($2000)
76543210
| ||||||
| ||||++---Adresse de la nametable de base
| ||||
(0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00)
| |||+-----Adresse de la VRAM incrémentée par lecture/écriture du PPUDATA
| |||
(0 : incrémentation de 1, aller en avant; 1: incrémentation de
| |||
32, aller en bas)
| ||+
Adresse de la pattern table pour les sprites pour un carré de
| ||
8x8 sprites (0 : $0000; 1 = $1000)
| |+
Adresse de la pattern table du background (0 : $0000; 1 : $1000)
| +
Taille du sprite (0 : 8x8 pixels, 1 : 8x16 pixels)
|
+----------Générer une NMI au début d'un intervalle de VBlank
(0 : off; 1 : on)
Et le code :
LDA
STA
STA
LDA
STA
STA
#$80
$0200
$0203
#$00
$0201
$0202
LDA #%10000000
STA
; Positionner le sprite 0 au centre de l'écran
; Positionner le sprite 0 au centre de l'écran
; Numéro de tile dans la pattern table = 0
; Couleur = 0, pas de flipping
; Activer la NMI, et les sprites pour la pattern
; table 0
$2000
LDA #%00010000
; Ne pas intensifier les couleur, activer les
; sprites
STA $2001
Assembler le tout
Téléchargez et dézippez le fichier sprites.zip. L'ensemble du code présenté se trouve dans
le fichier sprites.asm. Assurez-vous que les fichiers mario.chr et sprites.bat soient placés
dans le même dossier que NESASM, et double-cliquez sur sprites.bat. Cela devrait lancer
NESASM et produire le fichier sprite.nes. Lancez ce fichier NES avec FCEUXD SP pour voir
votre sprite! Le tile numéro 0 correspond à l'arrière de la tête de Mario, vous arrivez à le voir?
Modifiez le fichier sprites.asm pour changer la position du sprite, ou pour changer la palette
couleur.
Vous pouvez sélectionner la vue de la PPU dans FCEUXD SP pour voir les deux pattern
tables, et les deux palettes. Vous pouvez aussi voir que la couleur dans la palette à l'adresse
$3F00 est copiée à plusieurs endroits et utilisée pour la couleur du background. En général,
cette couleur sera noire ou blanche dans les jeux.
16
Les sprites composites
Une autre copie de bloc
Au lieu d'effectuer 4 LDA/STA pour chaque sprite, vous pouvez utiliser la directive .db de la
même manière que pour les données des palettes et utiliser une boucle pour copier tout en
une fois. Tout d'abord, préparer les données :
sprites:
;
.db
.db
.db
.db
vert
$80,
$80,
$88,
$88,
tile
$32,
$33,
$34,
$35,
attr
$00,
$00,
$00,
$00,
horiz
$80
$88
$80
$88
;
;
;
;
sprite
sprite
sprite
sprite
0
1
2
3
Il y a 4 octets par sprite, un sprite par ligne. Les octets sont dans écrits dans l'ordre et
facilement paramétrables. Ce ne sont que les données de départ ; quand le programme
s'exécute, le contenu de la RAM peut être modifié pour déplacer les sprites.
Ensuite vous exécutez une boucle pour copier les données dans la RAM. Cette boucle
fonctionne de la même manière que lors du chargement de la palette, avec le registre X
comme compteur de boucle.
LoadSprites:
LDX #$00
LoadSpritesLoop:
LDA sprites, x
STA $0200,x
INX
CPX #$10
BNE LoadSpritesLoop
; Initialiser le compteur
;
;
;
;
;
;
;
;
;
Charger les données de l'adresse
(sprites + x)
Stocker dans la RAM à l'adresse ($0200 + x)
X = X + 1
Comparer X avec la valeur hexadécimale $10
(16 en décimal)
Boucler sur LoadSpritesLoop si le résultat
de la comparaison n'est pas égal à 0, sinon
exécuter l'instruction suivante
Si vous voulez ajouter plus de sprites, vous devrez ajouter des lignes dans la section .db
sprites puis augmenter la valeur de comparaison de CPX. Cela augmentera le nombre de
boucles de copie à effectuer.
Assembler le tout
Téléchargez et dézippez le fichier sprites2.zip. L'ensemble du code présenté se trouve dans
le fichier sprites2.asm. Assurez-vous que les fichiers mario.chr et sprites2.bat soient placés
dans le même dossier que NESASM, et double-cliquez sur sprites2.bat. Cela devrait lancer
NESASM et produire le fichier sprite2.nes. Lancez ce fichier NES avec FCEUXD SP pour
voir un petit Mario! Essayez d'éditer la palette couleur pour afficher les vraies couleurs de
Mario. Vous pouvez aussi changer les coordonnées horizontales/verticales dans les
données sprites pour le changer de place.
Lancez Tile Layer Pro, et ouvrez le fichier sprites2.nes. Les parasites que vous voyez en
début du fichier correspondent à votre code programme. Faites défiler vers le bas jusqu'à ce
que vous puissiez visualiser les tiles graphiques. Vous voyez d'abord les 256 (16x16) tiles
des sprites, puis les 256 tiles de background. Utilisez la flèche de défilement pour aller à la
fin du fichier, et revenez aux sprites. Comptez le nombre de tiles sprite pour voir le Mario
affichée ($32-$35). Continuer à compter, et éditez les données sprites pour afficher une
tortue ou un scarabée à l'écran.
17
18
Lecture des contrôleurs
Cette section présentera comment lire les deux manettes, et utiliser ces données pour
déplacer des sprites.
Les ports de manettes
Les contrôleurs sont accessibles aux adresses mémoire $4016 et $4017. D'abord, vous
devez écrire la valeur $01, puis la valeur $00 sur le port $4016. Ceci indique au contrôleur de
verrouiller l'état courant des boutons. Puis, vous pouvez lire le port $4016 pour le premier
joueur et $4017 pour le second joueur. Les boutons sont envoyés un à un dans le bit 0. Si le
bit 0 est à 0, le bouton n'est pas pressé. Si le bit 0 est 1, le bouton est pressé.
Le statut bouton pour chaque manette est retourné dans l'ordre suivant : A, B, Select, Start,
Haut, Bas, Gauche, Droite.
LDA
STA
LDA
STA
#$01
$4016
#$00
$4016
LDA
LDA
LDA
LDA
LDA
LDA
LDA
LDA
$4016
$4016
$4016
$4016
$4016
$4016
$4016
$4016
;
;
;
;
;
;
;
;
Joueur
Joueur
Joueur
Joueur
Joueur
Joueur
Joueur
Joueur
1
1
1
1
1
1
1
1
–
–
–
–
–
–
–
–
A
B
Select
Start
Haut
Bas
Gauche
Droite
LDA
LDA
LDA
LDA
LDA
LDA
LDA
LDA
$4017
$4017
$4017
$4017
$4017
$4017
$4017
$4017
;
;
;
;
;
;
;
;
Joueur
Joueur
Joueur
Joueur
Joueur
Joueur
Joueur
Joueur
2
2
2
2
2
2
2
2
–
–
–
–
–
–
–
–
A
B
Select
Start
Haut
Bas
Gauche
Droite
; Indiquer aux manettes de verrouiller l'état
; des boutons
L'instruction AND
Les informations bouton sont toujours envoyées dans le bit 0, donc nous avons intérêt à
ignorer les autres bits. Pour cela, on utilise l'instruction AND. Chacun des 8 bits est ANDé
avec les bits d'une autre valeur. Si le bit de la première ET de la seconde valeur sont à 1,
alors le résultat est 1. Sinon, le résultat est 0.
0
0
1
1
AND
AND
AND
AND
0
1
0
1
=
=
=
=
0
0
0
1
Voici un exemple de AND avec 2 valeurs prises au hasard :
01011011
AND
10101101
-------------00001001
Comme nous ne nous intéressons qu'au bit 0, alors la valeur utilisée pour le AND n'aura que
ce bit à 1, les autres seront à 0 :
19
01011011
AND
00000001
-------------00000001
données du contrôleur
valeur pour le AND
Donc pour ignorer tous les autres bits lors d'une lecture des manettes, le AND devra être
utilisé à chaque lecture sur $4016 et $4017:
LDA $4016
AND #%00000001
; Joueur 1 – A
LDA $4016
AND #%00000001
; Joueur 1 – B
LDA $4016
AND #$00000001
; Joueur 1 – Select
L'instruction BEQ
L'instruction BNE a été présentée plus haut dans les boucles pour Branch when Not Equal
(brancher si inégal) à une valeur de comparaison. Ici, BEQ sera utilisé sans l'instruction de
comparaison pour Branch when Equal (brancher si égal) à zéro. Quand un bouton n'est pas
pressé, la valeur est à zéro, donc le branchement est effectué. Cela permet de sauter toutes
les instructions à faire quand le bouton est pressé :
ReadA:
LDA $4016
AND #%00000001
BEQ ReadADone
ReadADone:
;
;
;
;
;
;
;
Joueur 1 – A
Vérifier uniquement le bit 0
Brancher à ReadADone si le bouton n'est PAS
pressé (0)
Mettre ici les instructions à exécuter si le
bouton A est pressé (1)
La gestion du bouton est terminée
Les instructions CLC/ADC
Pour cette démo nous utiliserons la manette du joueur 1 pour déplacer le sprite de Mario.
Pour cela, nous avons besoin d'être capable d'ajouter des valeurs. L'instruction ADC
correspond à ADd with Carry (addition avec retenue). Avant d'ajouter, vous devez vous
assurer que la retenue a été effacée en utilisant CLC. Cet extrait de code charge la
coordonnée X du sprite dans A, efface la retenue, ajoute 1 à la valeur de la coordonnée, et
replace cette valeur dans la RAM :
LDA $0203
CLC
ADC #$01
STA $0203
;
;
;
;
Charger la coordonnée X du sprite 0
Abaisser le flag pour la retenue
A = A + 1
Placer la valeur de A dans la coordonnée X du sprite 0
Les instructions SEC/SBC
Pour déplacer le sprite dans l'autre direction, une soustraction est nécessaire. SBC signifie
Substract with Carry (soustraction avec retenue). Cette fois, la retenue doit être positionnée
avec d'effectuer la soustraction :
20
LDA $0203
SEC
SBC #$01
STA $0203
;
;
;
;
;
Charger la coordonnée X du sprite
Positionner le flag de retenue
A = A – 1
Placer la valeur résultat dans la coordonnée X du
sprite
Assembler le tout
Téléchargez et dézippez le fichier controller.zip. L'ensemble du code présenté se trouve
dans le fichier controller.asm. Assurez-vous que les fichiers mario.chr et controller.bat soient
placés dans le même dossier que NESASM, et double-cliquez sur controller.bat. Cela devrait
lancer NESASM et produire le fichier controller.nes. Lancez ce fichier NES avec FCEUXD
SP pour voir un petit Mario! Appuyez sur les boutons A et B avec la manette du joueur 1
pour déplacer un des sprites Mario. Le mouvement sera de 1 pixel par frame, ou 60 pixels
par seconde sur les machines NTSC (50 pixels pour les PAL). Si Mario ne bouge pas,
assurez vous que vos manettes soient correctement configurées dans le menu Config >
Input… Si vous appuyez sur les deux boutons en même temps, la valeur sera ajoutée puis
soustraite, et donc aucun mouvement ne surviendra.
Essayez d'éditer les valeurs ADC et SBC pour faire bouger le sprite plus rapidement. L'écran
ne contient que 256 pixels en longueur, donc si c'est la valeur de déplacement est trop
grande, le sprite sautera de manière aléatoire. Essayez aussi d'éditer le code pour déplacer
en même temps les 4 sprites.
Les variables
Pour enregistrer les informations bouton pour une utilisation postérieure, une variable est
nécessaire. D'abord, réservez l'espace pour la variable dans la RAM :
.rsset $0000
Buttons1
.rs 1
; Démarrer le compteur de réservation à l'adresse
; mémoire $0000
; Réserver un octet d'espace
Maintenant dans votre code, vous pouvez faire un STA Button1, ce qui stockera la valeur de
l'accumulateur dans l'espace mémoire. Vous pouvez aussi faire un LDA Button1 pour
charger cette valeur en mémoire dans l'accumulateur.
21
Téléchargement