Programmer sur SNES

Kannagi

La Super Nintendo est une console qui a su nous égayer de nombreuses heures durant notre jeunesse. Voyons comment, à notre tour, réaliser des programmes pour cette console mythique.

Commentez

- Titre : Programmer sur SNES

- Auteur : Kannagi

- Parution : 24 juin 2015

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2015 Kannagi. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. Description technique

- II. Assembleur 65C816

- II-A. Instructions principales

- II-A-1. Les instructions de chargement

- II-A-2. Les instructions de comparaison

- II-A-3. Les instructions de stockage

- II-A-4. Les instructions de branchement

- II-A-5. Les instructions mathématiques

- II-A-6. Les instructions de manipulation de bits

- II-A-7. Les autres instructions

- II-B. Syntaxe

- III. Fonctionnement de la SNES

- III-A. En-tête d'une ROM SNES

- III-A-1. La disposition de la mémoire

- III-B. La mémoire

- III-C. Palette et couleurs

- III-D. Arrière-plans

- III-E. Sprites

- III-F. DMA

- III-G. VRAM

- III-H. La représentation des images

- III-I. Informations supplémentaires

- III-J. Organigramme

- IV. Programmation et outils

- IV-A. Compilation avec WLA-DX

- IV-B. Premier programme

- IV-C. Background

- IV-D. DMA

- IV-E. Sprite

- V. Ressources

- V-A. Code source

- V-B. Liens

- VI. Remerciements

I - Introduction

Avant de commencer la programmation sur la Super Nintendo, il faut avoir quelques bases en développement de jeux vidéo et en assembleur. Je ne ferai pas de cours d'assembleurs ici, mais je ferai un rappel de l'assembleur 65C816 dont nous aurons besoin pour programmer la console.
La programmation sur cette console peut s'avérer difficile, donc ce tutoriel s'adresse aux personnes ayant un minimum d'expérience.
Aussi, je ne mettrai ici aucune liste complète des instructions du processeur ou des registres SNES, mais seulement ce qu'on utilisera dans ce tutoriel.

I-A - Description technique

Tout d'abord, revoyons ce dont est capable la console :

Processeur

16 bits 65C816 cadencé à 3,58, 2,68, ou 1,79 MHz

RAM

128 ko

VRAM

Mémoire vidéo 64 ko

Résolution

Par défaut 256x224, possibilité de mettre en 256x239, 512x224 512x239 ou 512x448

Couleurs

32 768 couleurs différentes et 256 simultanément

Sprites

128 sprites maximum, 32 max par ligne,
4 plans de fonds max

Taille de Sprites

8x8, 16x16, 32x32, 64x64

Son

Sony SPC700
8 Canaux

De plus, les cartouches ont la possibilité d'embarquer des processeurs additionnels telle la Super FX qui permet de faire de la 3D.

II - Assembleur 65C816

L'assembleur 65C816 est très simple. Il fait partie de la famille des 6502, mais c'est un processeur 16 bits. Il partage donc les opcodes de la plupart des instructions. Il n'y a pas de registre 16 bits à proprement parler, mais le processeur permet de passer de 8 à 16 bits à l'aide d'instructions spéciales.
En conclusion, les cours et tutoriels sur le 6502 sont valides pour le 65C816.

II-A - Instructions principales

Voici une liste non exhaustive des instructions de ce processeur.

II-A-1 - Les instructions de chargement

Instruction

Explication

Algorithme

LDA

Met la valeur dans l'accumulateur.

A = n

LDX

Met la valeur dans le registre X.

X = n

LDY

Met la valeur dans le registre Y.

Y = n

II-A-2 - Les instructions de comparaison

Instruction

Explication

Algorithme

CMP

Compare l'accumulateur avec la valeur.

if(A ? n)

CMX

Compare le registre X avec la valeur.

if(X ? n)

CMY

Compare le registre Y avec la valeur.

if(Y ? n)

II-A-3 - Les instructions de stockage

Instruction

Explication

Algorithme

STA

Envoie la valeur de l'accumulateur en mémoire.

A -> mémoire

STX

Envoie la valeur du registre X en mémoire.

X -> mémoire

STY

Envoie la valeur du registre Y en mémoire.

Y -> mémoire

STZ

Met la valeur à zéro en mémoire.

0 -> mémoire

II-A-4 - Les instructions de branchement

Instruction

Explication

Algorithme

BEQ

Saute si la comparaison est « égal ».

==

BNE

Saute si la comparaison est « différent ».

!=

BMI

Saute si la comparaison est « plus petit ».

<

BPL

Saute si la comparaison est « plus grand ».

>

II-A-5 - Les instructions mathématiques

Instruction

Explication

Algorithme

ADC

Additionne l'accumulateur avec le carry.

A = A + n + C

SBC

Soustrait de l'accumulateur le carry.

A = A - n - C

INA/INC

Incrémente l'accumulateur ou la mémoire.

A= A + 1
ou
mémoire = mémoire + 1

INX

Incrémente le registre X.

X = X + 1

INY

Incrémente le registre Y.

Y = Y + 1

DEC

Décrémente l'accumulateur ou la mémoire.

A= A - 1
ou
mémoire = mémoire - 1

DEX

Décrémente le registre X.

X = X - 1

DEY

Décrémente le registre Y.

Y = Y - 1

II-A-6 - Les instructions de manipulation de bits

Instruction

Explication

Algorithme

AND

ET logique sur la mémoire ou l'accumulateur.

A = A & n
ou
A = mémoire & n

ORA

OU logique sur la mémoire ou l'accumulateur.

A = A | n
ou
A = mémoire | n

ASL

Décalage vers la gauche la mémoire ou l'accumulateur.

A = A << 1
ou
A = mémoire << 1

LSR

Décalage vers la droite la mémoire ou l'accumulateur.

A = A >> 1
ou
A = mémoire >> 1

II-A-7 - Les autres instructions

Instruction

Explication

Algorithme

JMP/JML

Saute toujours.

goto Label

JSR/JSL

Saute sur l'adresse d'une fonction.

function()

RTS/RTL

Retour de la fonction.

return

WAI

Attend une interruption.

XCE

Rend le processeur compatible avec le 6502.

REP

Réinitialise les drapeaux.

SEP

Enlève les drapeaux.

II-B - Syntaxe

La syntaxe utilise les caractères suivants : :

- # permet de mettre une valeur immédiate ;

- $ signifie que les valeurs sont hexadécimales ;

- % pour les valeurs binaires.

Voici quelques exemples :

Code

Explication

LDA $0050

La valeur de l'accumulateur sera remplie avec ce qui se trouve dans l'adresse 0x0050 (80 en décimal).

LDA 50

La valeur de l'accumulateur sera remplie avec ce qui se trouve à l'adresse 50.

LDA #50

La valeur de l'accumulateur sera 50.

STA %00000011

Envoie en mémoire la valeur de l'accumulateur à l'adresse écrite en binaire 0000 0011(3 en décimal).

Certains assembleurs permettent de mettre un .b/.w/.l (pour byte, word ou long) sur les étiquettes (syntaxe prise du m68k).

Exemple :

Code

Explication

LDA etiquette.l,X

La valeur de l'accumulateur sera remplie avec ce qui se trouve dans l'adresse de l'étiquette (valeur absolue en long) plus la valeur du registre X dans la ROM.

III - Fonctionnement de la SNES

Nous entrons dans un important chapitre. Comme nous l'avons vu précédemment, ce processeur ne devrait pas vous posez trop de soucis pour programmer avec, mais cela ne répond pas aux questions liées à la gestion de la manette, des images ou même des ressources ou encore à l'organisation de la ROM.
Ce chapitre apportera donc toutes les précisions nécessaires pour la création d'un jeu pour la Super Nintendo.
Nous verrons donc l'en-tête d'une ROM SNES, l'affichage du background, d'un sprite et le contrôle du joypad.

III-A - En-tête d'une ROM SNES

La SNES possède un en-tête que l'on nomme LoRom ou HiRom. Il se trouve dans deux adresses différentes qui commencent aux adresses 0x7FB0 ou 0xFFB0 (sur certaines ROM, c'est 0x81B0 ou 0x101B0).

Adresse

Taille

Nom

Description

0xB0

2 octets

Maker Code

Code assigné par Nintendo.

0xB2

4 octets

Game Code

Code assigné par Nintendo en ASCII.

0xB6

7 octets

Fixed Value

Valeur fixe inutilisée.

0xBD

1 octet

RAM Size

Taille de l'expansion de la RAM.

0xBE

1 octet

Special Version

Si le jeu est utilisé dans un événement particulier.

0xBF

1 octet

Cartridge Type Sub-Number

Seulement utilisé si on utilise un Custom Chip.

0xC0

21 octets

Game Title

Le nom du jeu en ASCII.

0xD5

1 octet

Mode map

Détermine la vitesse du CPU et la carte de la mémoire.

0xD6

1 octet

Cartridge Type

Indique le type de cartouche.

0xD7

1 octet

ROM size

Taille de la ROM.

0xD8

1 octet

RAM size

Taille de la RAM.

0xD9

1 octet

Destination Code

Indique la région géographique de vente de la cartouche (zonage).

0xDA

1 octet

Fixed Value

Utilisé pour ID ou pour activer l'extension de l'en-tête.

0xDB

1 octet

Mask ROM Version

Numéro de la version du jeu.

0xDC

2 octets

Complement Check

Voir plus bas.

0xDE

2 octets

Check Sum

Voir plus bas.

0xE0

4 octets

Inutilisé/inconnu.

0xE4

2 octets

COP

0xE6

2 octets

BRK

0xE8

2 octets

ABORT

0xEA

2 octets

NMI

Adresse pour le Vblank.

0xEC

2 octets

RESET

Adresse pour la réinitialisation de la console.

0xEE

2 octets

IRQ

Adresse pour interruption externe ou H/V-Timer.

0xF0

4 octets

Inutilisé / inconnu.

0xF4

2 octets

COP

0xF6

2 octets

/

0xF8

2 octets

ABORT

0xFA

2 octets

NMI

Adresse pour le Vblank.

0xFC

2 octets

RESET

Adresse pour la réinitialisation de la console.

0xFE

2 octets

IRQBRK

Il faut savoir que l'extension de l'en-tête (0xB0 à 0xBF) n'est lue que si 0xDA est à 0x33, sinon l'en-tête est lu à partir de 0xC0.

Pour 0xDC et 0xDE, le « Check Sum » se calcule en effectuant la somme de chaque octet de la ROM. Le « Complement Sum » se base lui aussi sur tous les octets de la ROM, mais à la place d'une somme, ils sont à chaque fois soustraits.

La différence entre 0xE0-0xEF et 0xF0-0xFF est que le premier (0xE0-0xEF) est destiné au mode natif (65C816) alors que le second (0xF0-0xFF) est pour le mode émulé (6502).

Pour COP, BRK, ABORT, NMI, RESET et IRQ, ce sont pour les interruptions du processeur, mais normalement vous n'avez pas besoin d'utiliser les interruptions COP/BRK/ABORT.

Voici quelques détails supplémentaires sur les valeurs de ces adresses.

0xBD

Taille de l'expansion de la RAM

0x00

Aucune

0x01

16 kbit

0x03

64 kbit

0x05

256 kbit

0x06

512 kbit

0x07

1 Mbit

0xD5

Map Mode

Super NES CPU Clock

0x20

Mode 20

2,68 MHz

0x21

Mode 21

2,68 MHz

0x22

Réservé

0x23

Mode 23 (SA-1)

2,68 MHz

0x25

Mode 25

2,68 MHz

0x30

Mode 20

3,58 MHz

0x31

Mode 21

3,58 MHz

0x35

Mode 25

3,58 MHz

0xD6

Configuration de la cartouche

0x00

ROM seulement

0x01

ROM + RAM

0x02

ROM + RAM + Sauvegarde

0x0*

Coprocesseur = DSP

0x1*

Coprocesseur = Super-FX

0x2*

Coprocesseur = OBC1

0x3*

Coprocesseur = SA-1

0xE*

Coprocesseur = Autre

0xF*

Coprocesseur = Custom chip

0x*3

ROM + Coprocesseur

0x*4

ROM + Coprocesseur + RAM

0x*5

ROM + Coprocesseur + RAM + Sauvegarde

0x*6

ROM + Coprocesseur + Sauvegarde

0xD7

Taille de la ROM

0x09

3 - 4 Mbit

0x0A

5 - 8 Mbit

0x0B

9 - 16 Mbit

0x0C

17 - 32 Mbit

0x0D

33 - 64 Mbit

0xD8

Taille de la RAM

0x00

Pas de RAM

0x01

16 kbit

0x03

64 kbit

0x05

256 kbit

0x06

512 kbit

0x07

1 Mbit

0xD9

Région

0x00

Japon

0x01

USA et Canada

0x02

Europe

0x06

France

0x08

Espagne

0x09

Allemagne

Je vous propose de regarder quelques en-têtes de ROM de vos jeux préférés. Cela vous aidera à comprendre l'en-tête et aussi voir que, finalement, beaucoup d'informations ne sont pas utilisées.

Adresse

Taille

Nom

Secret of Mana

Super Double Dragon

Star Fox

/

/

En-tête

HIROM (0X101B0)

LOROM (0x7FB0)

LOROM (0x7FB0)

0xB0

2 octets

Maker Code

0x01 0x00

0xFF 0xFF

0xFF 0xFF

0xB2

4 octets

Game Code

0x01 0x00 0x01 0x00

0xFF 0xFF
0xFF 0xFF

0xFF 0xFF
0xFF 0xFF

0xB6

7 octets

Fixed Value

0x01 0x01 0x00 0x01 0x00 0x01
0x00

0xFF 0xFF
0xFF 0xFF
0xFF 0xFF
0xFF

0xFF 0xFF
0xFF 0xFF
0xFF 0xFF
0xFF

0xBD

1octet

RAM Size

0x01

0xFF

0xFF

0xBE

1 octet

Special Version

0x01

0xFF

0xFF

0xBF

1 octet

Cartridge Type Sub-Number

0x00

0xFF

0xFF

0xC0

21 octets

Game Title

SECRET OF MANA

RETURN OF DOUBLE DRAG

STAR FOX

0xD5

1 octet

Mode map

0X21

0x20

0x20

0xD6

1 octet

Cartridge Type

0X02

0X00

0x13

0xD7

1octet

ROM size

0X0B

0x0A

0x0A

0xD8

1 octet

RAM size

0X03

0X00

0X00

0xD9

1 octet

Destination Code

0X06

0X00

0X01

0xDA

1 octet

Fixed Value

0X01

0XA9

0X01

0xDB

1 octet

Mask ROM Version

0X00

0X00

0X00

0xDC

2 octets

Complement Check

0XB0 0X8A

0XAA 0XFF

0X46 0X09

0xDE

2 octets

Check Sum

0X4F 0X75

0X55 0X00

0XB9 0XF6

0xE0

4 octets

0xFF 0xFF
0xFF 0xFF

0xFF 0xFF
0xFF 0xFF

0xFF 0xFF
0xFF 0xFF

0xE4

2 octets

COP

0xFF 0xFF

0xF2 0xFF

0x9A 0xFF

0xE6

2 octets

BRK

0xFF 0xFF

0xF2 0xFF

0x9A 0xFF

0xE8

2 octets

ABORT

0xFF 0xFF

0xF1 0xFF

0x9A 0xFF

0xEA

2 octets

NMI

0X00 0X01

0X3B 0xBF

0x08 0x01

0xEC

2 octets

RESET

0X04 0X80

0XF2 0XFF

0x00 0x00

0xEE

2 octets

IRQ

0x04 0x01

0XD8 0XC0

0x0C 0x01

0xF0

4 octets

0x00 0x00
0x00 0x00

0x60 0x40
0x80 0xFE

0xFF 0xFF
0xFF 0xFF

0xF4

2 octets

COP

0x00 0x00

0xF2 0xFF

0x9A 0xFF

0xF6

2 octets

/

0x00 0x00

0xF1 0xFF

0x00 0x00

0xF8

2 octets

ABORT

0x00 0x00

0xF1 0xFF

0x9A 0xFF

0xFA

2 octets

NMI

0x00 0x00

0xF1 0xFF

0x9A 0xFF

0xFC

2 octets

RESET

0X04 0X80

0X00 0X80

0x96 0xFF

0xFE

2 octets

IRQBRK

0xFF 0xFF

0xF2 0xFF

0xFF 0xFF

En général les 0xFF sont des valeurs mises par défaut par certains assembleurs.

Il faut savoir que pour Secret of Mana, l'extension de l'en-tête n'est pas lue, les 0x00 et 0x01 n'ont pas de sens ici (et il n'y a pas de 0x33 dans 0xDA).
Il utilise la map 21,une ROM + RAM + une sauvegarde, 2 Mo de ROM, la RAM fait 64KBit et il s'agit de la version française.

Pour Super Double Dragon, l'extension de l'en-tête n'est pas lue, il utilise une map 20, il utilise simplement la ROM sans extension, il fait 1 Mo de ROM et c'est la version japonaise.

Pour Star Fox, l'extension de l'en-tête n'est pas lue, il utilise une map 20, le coprocesseur Super-FX + ROM, il fait 1 Mo de ROM et c'est la version américaine.

Vous pouvez ignorer l'extension de l'en-tête. Ces informations sont beaucoup trop anecdotiques pour être utilisées. Pour la valeur à l'adresse 0xDA, de nombreux jeux utilisent 0x01 et Mask ROM Version 0x00 et les autres valeurs non utilisées sont mises à 0x00.

III-A-1 - La disposition de la mémoire

Il faut savoir que la SNES est divisée par des banques de données dans la ROM. Si l'en-tête est le LOROM, la banque de données sera de 0x8000 octets et le HIROM 0x10000 octets.

Ce qui se trouve en mémoire dépend du Map Mode choisi par l'en-tête (0xD5) et de la banque de données utilisée, mais la mémoire comprise entre 0x0000 a 0x8000 est toujours la même, peu importe les banques de données de 0x00 à 0x3F, ce qui représente 2 Mo en LOROM ou 4 Mo en HIROM.

III-B - La mémoire

À propos de la RAM, il faut savoir que celle-ci n'est pas statique et dépend du mode de mapping sélectionné, comme dit précédemment. En règle général, elle n'est que très peu modifiée, donc le programmeur a accès de l'adresse 0x0000 à 0x2000 pour ses propres données (soit l'équivalent de 8 ko). En effet, vous n'aurez pas 64 ko ou 128 ko pour votre jeu, mais 64 ko sont toujours disponibles pour les sprites qui sont eux dans la VRAM.
Les registres de la SNES sont en mémoire, les registres PPU commencent à l'adresse 0x2100 et CPU 0x4200.
Le PPU (Picture Processing Unit) sert pour toute accélération matérielle et l'affichage de la 2D.
La SNES possède une extension des registres CPU, qui permet d'avoir une accélération matérielle.

Nous reviendrons sur les détails et leur utilisation un peu plus tard.

III-C - Palette et couleurs

Les couleurs sur SNES sont représentées en deux octets RGB avec cinq bits pour chaque couleur primaire, donc cinq bits pour le rouge, cinq bits pour le vert et cinq bits pour le bleu. Le dernier bit est inutilisé.

Les images ne sont pas des images matricielles en RGB, mais elles utilisent une palette. Elles peuvent avoir quatre ou seize couleurs (voire plus), nous ne verrons ici que les palettes quatre et seize couleurs.
Vous pouvez afficher au maximum 256 couleurs différentes à l'écran.
Le registre CGADD (0X2121) permet de choisir l'adresse de la CG-RAM qui, elle, est une simple RAM contenant nos 256 couleurs.

L'adresse de 0x00 à 0x80 sert pour l'arrière-plan et le nombre de palettes dépendra du mode choisi (que nous verrons en détail plus tard).
L'adresse à partir de 0x80 sert pour les sprites qui possèdent toujours huit palettes de seize couleurs (quinze couleurs + une alpha).

Si un sprite ou un arrière-plan utilise une palette, la première couleur sera considérée comme l'alpha.

Son utilisation est donc simple. CGADD permet de choisir l'adresse et le registre CGDATA (0x2122) est utilisé pour l'écriture (sur deux octets). De cette façon, sur le premier octet de CGDATA, on écrit VVVR RRRR et sur le suivant BBBB B0VV, puis l'adresse sera incrémentée.

Tableau récapitulatif :

ADDRESS

NAME

DESCRIPTION

0x2121

CGADD

Adresse pour la palette

D7

D6

D5

D4

D3

D2

D1

D0

Adresse de la couleur

ADDRESS

NAME

DESCRIPTION

0x2122

CGDATA

Données pour la palette

D7

D6

D5

D4

D3

D2

D1

D0

CG DATA (Low/High)

Un code exemple :

;admettons qu'on veuille écrire la première couleur rouge sur le background

;on choisit l'adresse ici CGADD = 0
lda #$00
sta $2121

;on écrit la couleur rouge donc CGDATA = 0X1F00
lda #%00011111 ;VVVR RRRR
sta $2122

lda #%00000000 ;BBBB B0VV
sta $2122

;ici on se trouvera donc à CGADD = 1

Une image qui représente sur seize bits la couleur :

Voici donc une image qui représente la mémoire de la palette :

III-D - Arrière-plans

Le registre BGMODE (0x2105) permet d'initialiser les arrière-plans, leur taille (8x8 ou 16x16), la priorité d'affichage du BG3 et de choisir le mode de 0 à 7.

Voici un tableau récapitulatif des différents modes :

Mode

BG1

BG2

BG3

BG4

0

8x8 ou 16x16
4 couleurs.

8x8 ou 16x16
4 couleurs.

8x8 ou 16x16
4 couleurs.

8x8 ou 16x16
4 couleurs.

1

8x8 ou 16x16
16couleurs.

8x8 ou 16x16
16 couleurs.

8x8 ou 16x16
4 couleurs.

2

8x8 ou 16x16
16 couleurs.

8x8 ou 16x16
16 couleurs

3

8x8 ou 16x16
256 couleurs.

8x8 ou 16x16
16 couleurs.

4

8x8 ou 16x16
16 couleurs.

8x8 ou 16x16
4 couleurs.

5

8x8 ou 16x16
16 couleurs.

8x8 ou 16x16
4 couleurs.

6

16x8
16 couleurs.

7

8x8
256 couleurs.

Les cases vides signifient que cet arrière-plan n'est pas activé dans ce mode.
Le Mode 7 est le seul mode où il est possible de faire des rotations sur l'arrière-plan.

Chaque tileset de l'arrière-plan possède un bit pour la priorité d'affichage, alors que les sprites en ont deux. Voici la priorité entre chaque plan :

Les registres BGSC1-4 (0x2107 - 0x210A) ne sont pas les adresses des données d'images, mais des tilesets. Elles sont sur deux octets et doivent être utilisées comme ceci :

D7

D6

D5

D4

D3

D2

D1

D0

Tileset

D7

D6

D5

D4

D3

D2

D1

D0

Flip H/V

Priorité affichage

Palette (0 a 7)

Tileset

Les registres BG12NBA/BG34NBA (0x210B/0x210C) sont bien les adresses des données dans la VRAM. De même, les données des tilesets sont dans la VRAM.

Les registres BG1H0FS/BG1V0FS - BG4H0FS/BG4V0FS (0x210D/0x210E - 0x2113/0x2114) servent pour le scrolling sur X ou Y des différents arrière-plans.

Le résumé est que nous pouvons initialiser l'arrière-plan avec BGMODE, choisir l'adresse des tilesets et sa taille de stockage avec BGSC1-4, l'adresse des images avec BG12NBA/BG34NBA et le scrolling avec BG1H0FS/BG1V0FS - BG4H0FS/BG4V0FS.

Tableau récapitulatif :

ADDRESS

NAME

DESCRIPTION

0x2105

BGMODE

Configuration de l'arrière-plan.

D7

D6

D5

D4

D3

D2

D1

D0

Taille des tilesets pour BG 4,3,2,1 (0 : 8x8 ou 1 :16x16 )

BG 3 Priorité

Mode de l'arrière-plan ( 0 a 7 )

ADDRESS

NAME

DESCRIPTION

0x2107/0x2108
0x2109/0x210A

BGSC1/BGSC2
BGSC3/BGSC4

Configuration de l'arrière-plan pour les données (Tileset)

D7

D6

D5

D4

D3

D2

D1

D0

Adresse des tilesets (2 ko segment)

Taille du Stockage des tilesets

ADDRESS

NAME

DESCRIPTION

0x210B/0x210C

BG12NBA/BG34NBA

Adresse de l'arrière-plan

D7

D6

D5

D4

D3

D2

D1

D0

Arrière-plan 2 adresses (8 ko segment)

Arrière-plan 1 adresse (8 ko segment)

D7

D6

D5

D4

D3

D2

D1

D0

Arrière-plan 4 adresses (8 ko segment)

Arrière-plan 3 adresses (8 ko segment)

ADDRESS

NAME

DESCRIPTION

0x210D/0x210E
0x210F/0x2110
0x2111/0x2112
0x2113/0x2114

BG1H0FS/BG1V0FS
BG2H0FS/BG2V0FS
BG3H0FS/BG3V0FS
BG4H0FS/BG4V0FS

Scrolling pour les arrière-plans

D7

D6

D5

D4

D3

D2

D1

D0

Défilement arrière plan Scrolling (Low/High)

Un code exemple :

;On initialise avec BGMODE par exemple 1 BG en 16x16 en mode 2
lda #%00010010;on pourrait l'écrire $12
sta $2105

;ici je parle de l'adresse dans la VRAM que nous verrons en détails plus tard.
;Admettons que nos données de tiles de BG1 se trouvent à l'adresse 0x4000 et BG2 à l'adresse 0x5000
lda #$40
sta $2107 ;BG1SC
lda #$50
sta $2108 ;BG2SC

;Admettons que nos données contiennent les images de BG1 se trouvent à l'adresse 0x0000 et BG2 à l'adresse 0x2000
lda #$20
sta $210B

;si on veut faire un scrolling de BG1
lda #25
sta $210D ; Horizontal Low
stz $210D ; Horizontal High

lda #30
sta $210E ; Vertical Low
stz $210E ; Vertical High

III-E - Sprites

La SNES donne souvent le mot « Object » pour les sprites. Il est possible d'afficher au maximum 128 sprites.

Le registre OBJSEL (0x2101) permet de configurer les sprites, l'adresse des sprites dans la VRAM et leur taille (Petite SM et Grande LG) qu'on peut changer à tout moment sur OAMADD.

L'OAM signifie Object Address Memory. Le registre OAMADDL (0x2102) permet d'indiquer l'adresse de OAM, il s'incrémente toutes les deux écritures de OAMDATA (0x2104).

L'adresse de OAM MSB, qu'on active sur le registre OAMADDH, permet de mettre les sprites en grand,et de faire un scrolling du sprite de +256 en x avec OAMDATA.

Donc, si le MSB est activé, on a accès à l'adresse de OAM qui permet de changer certains paramètres. Pour cela, il faudra mettre dans OAMADDL une valeur entre 0 et 15.

La priorité d'affichage par défaut se fait par ordre croissant, le premier sprite dans OAM s'affichera par-dessus les suivants et ainsi de suite, sauf si on active la rotation OAM dans les registres OAMADDH, qui inverse cette priorité d'affichage.

Le résumé OBJSEL permet d'initialiser les sprites , OAMADDL de sélectionner un sprite, OAMADDH de paramétrer les sprites, OAMDATA d'y écrire.

Tableau récapitulatif :

ADDRESS

NAME

DESCRIPTION

0x2101

OBJSEL

Permet de configurer les sprites (Object)

D7

D6

D5

D4

D3

D2

D1

D0

Taille des sprites

Adresses des sprites (16 ko segment)

D7

D6

D5

SM

LG

0

0

0

8x8

16x16

0

0

1

8x8

32x32

0

1

0

8x8

64x64

0

1

1

16x16

32x32

1

0

0

16x16

64x64

1

0

1

32x32

64x64

ADDRESS

NAME

DESCRIPTION

0x2102/0x2103

OAMADDL/OAMADDH

Adresse pour OAM

D7

D6

D5

D4

D3

D2

D1

D0

Adresse OAM

D7

D6

D5

D4

D3

D2

D1

D0

Priorité de OAM rotation

OAM Adresse MSB

ADDRESS

NAME

DESCRIPTION

0x2104

OAMDATA

Donnée pour OAM

D7

D6

D5

D4

D3

D2

D1

D0

OAM DATA

OAM DATA

D7

D6

D5

D4

D3

D2

D1

D0

Position x

D7

D6

D5

D4

D3

D2

D1

D0

Position y

D7

D6

D5

D4

D3

D2

D1

D0

Sélection tileset

D7

D6

D5

D4

D3

D2

D1

D0

Flip V/H

Priorité affichage

Sélection Palette (de 0 a 7)

tileset

OAM DATA MSB activé

D7

D6

D5

D4

D3

D2

D1

D0

Taille sprite

Position x +256

Taille sprite

Position x +256

Taille sprite

Position x +256

Taille Sprite

Position x +256

Un code exemple :

;On initialise avec OBJSEL, par exemple les sprites en 32x32, à l'adresse $6000
lda #%10100011
sta $2101

;On met notre premier sprite en x : 100, y : 50, premier tile, première palette et un flip en horizontal

;OAMADDL = 0 , donc premier sprite
lda #$00
sta $2102 ;OAMADDL
;position x
lda #100
sta $2104 ;OAMDATA
;position y
lda #50
sta $2104 ;OAMDATA

;ici, ça a incrémenté OAMADDL, donc on est à OAMADDL = 1
;tile
lda #0
sta $2104 ;OAMDATA
;flip/prio/palette/tile
lda #%01000000
sta $2104 ;OAMDATA

;ici, ça a incrémenté OAMADDL, donc on est à OAMADDL = 2
;et donc ici, on est sur le deuxième sprite

Voici donc une image qui représente la mémoire de OAM :

III-F - DMA

DMA signifie « Direct Memory Access ». Il permet d'envoyer rapidement des données dans la VRAM. Il n'est pas obligatoire de l'utiliser, mais dans la pratique, il est indispensable vu que le transfert par le CPU est trop long pour permettre d'avoir des performances correctes.

Le DMA peut sembler un peu complexe, mais il permet juste de choisir comment on lit/envoie les données en VRAM en fonction de nos utilisations.

On a donc le registre 0x43X0 (X de 0 à 7) permettant l'envoi de données sur huit canaux à la fois.
On peut choisir sur ce registre l'écriture de un, deux ou quatre octets, et on peut choisir l'ordre de cette écriture. On peut choisir si on incrémente ou décrémente l'adresse et le type de transfert.

Il existe aussi le HDMA. Le HDMA permet d'appliquer un effet à chaque scanline (les lignes de l'écran). Le HDMA utilise les mêmes registres que le DMA.

Le registre 0x43X1 permet de choisir quel registre PPU on va écrire, donc 0x21XX, et donc on peut écrire sur OAMDATA (0x2104), CGDATA (0x2122) ou VMDATA (0x2118/0x2119).

Seules des informations basiques sont stockées de 0x43X2 à 0x43X6 pour trouver l'adresse dans la ROM de vos données et le nombre d'octets.

Pour envoyer des données, il faut utiliser le registre MDMAEN (0x420B) où chaque bit représente un canal.

Pour résumer, 0x43X0 permet d'initialiser le DMA , 0x43X1 de choisir le registre PPU qu'on va écrire, 0x43X2 à 0x43X6 d'indiquer où se trouve l'adresse de nos données et leur taille.

Tableau récapitulatif :

Dans l'adresse, le X représente le canal choisi (de 0 à 7) .

ADDRESS

NAME

DESCRIPTION

0x43X0

Paramètre pour l'envoi par DMA

D7

D6

D5

D4

D3

D2

D1

D0

Transferts
CPU ->PPU /
PPU ->CPU

Type Adresse (pour le HDMA).
Absolue/Indirect

D3 : indique si l'adresse est fixe ou si on l'incrémente
ou décrémente.
D4 :indique l'incrémentation
ou la décrémentation.

Type écriture sur 1 octet
2 octets L/H, 2 octets
4 octets en L/L/H/H
4 octets en L/H/L/H

ADDRESS

NAME

DESCRIPTION

0x43X1

Adresse pour le B-BUS

D7

D6

D5

D4

D3

D2

D1

D0

B-Adresse est l'adresse pour les registres SNES (21XX)

ADDRESS

NAME

DESCRIPTION

0x43X2 / 0x43X3 / 0x43X4

Adresse des données

D7

D6

D5

D4

D3

D2

D1

D0

Adresse (Low)

D7

D6

D5

D4

D3

D2

D1

D0

Adresse (High)

D7

D6

D5

D4

D3

D2

D1

D0

Adresse de la banque

ADDRESS

NAME

DESCRIPTION

0x43X5 / 0x43X6

Nombre d'octets à transférer

D7

D6

D5

D4

D3

D2

D1

D0

Nombre d'octets (Low)

D7

D6

D5

D4

D3

D2

D1

D0

Nombre d'octets (High)

ADDRESS

NAME

DESCRIPTION

0x420B

MDMAEN

Sélection du canal qu'on envoie pour le DMA

D7

D6

D5

D4

D3

D2

D1

D0

Canal de 0 a 7

Un code exemple :

;On initialise le DMA canal 0, pour une écriture sur 2 octets.
lda #$02
sta $4300

;admettons qu'on veuille envoyer plusieurs données sur la CGRAM
lda #$22;pour $2122 CGDATA
sta $4301

;ensuite, il faut initialiser CGADD vu qu'on l'utilise, ici on va écrire sur les sprites
lda #$80
sta $2121 ;CGADD

; alors la banque sera de 0 ici
lda #$00
sta $4304

;l'adresse de notre palette, il faut que le registre X soit sur 2 octets
ldx #pallette
stx $4302 ; le registre x étant sur 2 octets, il écrit sur 4302 et 4303

;la taille de transferts en octets, ici on va devoir envoyer 0X20 octets soit 32 octets. Vu qu 'une couleur fait 2 octets, ici on a donc bien 16 couleurs à transférer
ldx #$20
stx $4305 ; le registre x étant sur 2 octets, il écrit sur 4305 et 4306

;quand tout cela est fait, il faut l'envoyer maintenant. Pour cela il faut utiliser MDMAEN
lda #$01 ; chaque bit représente un canal,ici on envoie le canal 0
sta $420B

;une palette pour les sprites, je rappelle qu'il faut 16 couleurs
par palette:
.db $00, $42, $aa, $2c, $4d, $41, $36, $66, $3e, $7f, $df, $7f, $ff, $36, $bf, $4b, $d5, $21, $b4, $31, $db, $3e, $9f, $3b, $ff, $43, $15, $09, $9e, $0e, $5f, $0f

III-G - VRAM

Il est temps de parler de la VRAM : la mémoire vidéo. Il y a juste à retenir VMADDL/VMADDH (0x2116/0x2117) qui permet d'initialiser notre adresse et VMDATAL/VMDATAH (0x2118/0x2119) qui permet d'y écrire.

Il faut utiliser VMAINC (0x2115) pour configurer l'incrémentation automatique de l'adresse après chaque écriture avec VMDATAL/VMDATAH.

Il faut savoir que l'adresse sera toujours par deux octets (vu que c'est la valeur minimale qu'on peut mettre pour tout type de données). Par exemple, si on met VMADD à 1, cela ne représentera pas en mémoire l'adresse d'un octet, mais l'adresse de deux octets.

Tableau récapitulatif :

ADDRESS

NAME

DESCRIPTION

0x2115

VMAINC

Configuration pour l'incrémentation de la VRAM

D7

D6

D5

D4

D3

D2

D1

D0

Incrémente l'adresse après l'écriture sur 0 : 0x2118 ou 1 : 0x2119

Incrémente de 32/64/128 en VRAM)

Adresse incrémentée de 1/32/128

ADDRESS

NAME

DESCRIPTION

0x2116/0x2117

VMADDL/VMADDH

Adresse pour la VRAM

D7

D6

D5

D4

D3

D2

D1

D0

Adresse VRAM (Low)

D7

D6

D5

D4

D3

D2

D1

D0

Adresse VRAM (High)

ADDRESS

NAME

DESCRIPTION

0x2118/0x2119

VMDATAL/VMDATAH

Données pour la VRAM

D7

D6

D5

D4

D3

D2

D1

D0

VRAM DATA (Low)

D7

D6

D5

D4

D3

D2

D1

D0

VRAM DATA (High)

Un code exemple :

;À chaque fois qu'on écrit sur la VRAM avec le registre $2118/$2119 (VMDATA),il incrémentera l'adresse quand on écrit à $2119
lda #$80
sta $2115 ;VMAINC
;Adresse $0000 en VRAM
ldx #$0000
stx $2116;VMADD

;On écrit #$00FF en VRAM
ldx #$00FF
stx $2118;VMDATA

III-H - La représentation des images

Une image sur SNES est représentée sur deux octets au minimum.

Le principe est qu'en passant par une palette de couleurs, avec deux octets, on représente huit pixels de quatre couleurs différentes.

Il faut lire bit par bit et non par octet. Un bit représente une couleur du pixel, donc sur un octet, on a huit pixels et deux couleurs possibles (0 et 1). Sur deux octets, on a toujours huit pixels, mais en combinant, on peut avoir pour chaque pixel quatre couleurs (0 et 1 du premier et 0 et 1 du deuxième octet).

Sachant que la valeur 0 sera la transparence, on a donc trois couleurs + 1 alpha.

Par exemple :
0x00 0x01, 0x01 0x00 et 0x01 0x01 affichent tous les trois un pixel, mais de couleur différente.

Les seize couleurs se basent sur le même principe, mais sur quatre octets. Attention ! La SNES se base uniquement sur le format deux couleurs, donc les deux autres octets ne sont pas contigus, mais se trouveront après seize octets.

Donc, on a besoin pour une image de 8x8 pixels sur quatre couleurs de seize octets pour le représenter, on a besoin de deux octets pour représenter huit pixels.

Voilà une image qui devrait éclaircir cela :

III-I - Informations supplémentaires

Voici quelques autres détails sur la manette. Il peut y avoir jusqu'à quatre manettes accessibles par les registres STD CNTRL 1-4 L/H (0x4218 - 0x421F). Une manette est représentée par un octet pour lequel un bit est mis à 1 lorsqu'un des boutons est appuyé.

Avant cela, il faut activer la manette avec NMITIMEN (0x4200).
Ce registre permet aussi d'activer un chronomètre (que nous ne verrons pas) et le NMI (Non Maskable Interrupt).
Il faut savoir que, suivant la fréquence du téléviseur (50 ou 60 Hz), le nombre d'images par seconde change et donc à chaque image (si le NMI est activé) il appellera ce qu'on appelle le V-Blank. Cette partie est importante parce que certains registres ne marchent que pendant le V-Blank (ou perdent en performances ailleurs) principalement les registres PPU.

Le registre INIDISP (0x2100) permet de choisir la luminosité de l'écran, mais aussi de faire un Forced Blank. En gros, pendant le Blanking, le V-Blank n'est pas appelé (plus exactement il finira les calculs avant de repasser la main).

Tableau récapitulatif :

ADDRESS

NAME

DESCRIPTION

0x4200

NMITIMEN

Active/Désactive certains paramètres

D7

D6

D5

D4

D3

D2

D1

D0

NMI Activé/Désactivé

Chronomètre activé/désactivé

Manette activée/désactivée

ADDRESS

NAME

DESCRIPTION

0x4218/0x4219
0x421A/0x421B
0x421C/0x421D
0x421E/0x421F

STD CNTRL 1L/1H
STD CNTRL 2L/2H
STD CNTRL 3L/3H
STD CNTRL 4L/4H

Contrôle de la manette

D7

D6

D5

D4

D3

D2

D1

D0

A

X

L

R

D7

D6

D5

D4

D3

D2

D1

D0

B

Y

Select

Start

Haut

Bas

Gauche

Droite

ADDRESS

NAME

DESCRIPTION

0x2100

INIDISP

Initialisation de l'écran

D7

D6

D5

D4

D3

D2

D1

D0

Blanking activé/désactivé

Luminosité de l'écran

III-J - Organigramme

Pour résumer ce qui a été dit, voici un organigramme. Il peut changer par rapport à nos utilisations, mais nous allons utiliser pour le moment celui-ci :

IV - Programmation et outils

Nous allons enfin pouvoir commencer à coder, mais avant cela, il nous faut un assembleur 65C816. Sur le Net, il en existe un multiplateforme et très efficace wla-dx : http://www.villehelin.com/wla.html.
Comme EDI, je conseille Geany, facilement configurable et facile d'utilisation, il est disponible sous Windows et Linux.
Finalement, comme émulateur, il en existe plusieurs. Pour tester rapidement, je conseille snes9x qui est le plus rapide et le plus léger, ensuite il existe NO$SNS qui est un bon débogueur, ainsi que bsnes qui est assez fidèle à la console.
Quand vous aurez tout ceci, on peut commencer.

IV-A - Compilation avec WLA-DX

Pour compiler :

./wla-65816 -o main.asm main.obj

Ensuite, il suffit de créer un fichier temporaire avec ceci à l'intérieur :

[objects]
main.obj

Et ensuite on utilise le wlalink comme ceci :

./wlalink -vr temp game.smc

Sur Linux vous pouvez définir un script bash comme ceci :

#!/bin/sh

echo '[objects]' > temp
echo $1.obj >> temp

/chemin_de_votre_repertoire/.../wla-65816 -o $1.asm $1.obj
/chemin_de_votre_repertoire/.../wlalink -vr temp $1.smc

rm $1.obj
rm temp

Et avec Geany définir vos paramètres de compilation.

Nous pouvons commencer par notre premier programme sur SNES.

IV-B - Premier programme

Nous n'allons pas faire l'en-tête à la main, wla-dx permet d'en créer un basique :

.MEMORYMAP
SLOTSIZE $8000
DEFAULTSLOT 0
SLOT 0 $8000
.ENDME

.ROMBANKSIZE $8000
.ROMBANKS 8

.SNESHEADER
ID    "SNES"
NAME  "TEST1                "
;     "123456789012345678901"
LOROM
SLOWROM
CARTRIDGETYPE $00
ROMSIZE $08 ;size rom 08-0c
SRAMSIZE $00
COUNTRY $02 ;0 = japan , 1 = US , 2 = Europe
LICENSEECODE $00
VERSION 00
.ENDSNES

.SNESNATIVEVECTOR
COP    $0000
BRK    $0000
ABORT  $0000
NMI    VBlank
UNUSED $0000
IRQ    $0000
.ENDNATIVEVECTOR

.SNESEMUVECTOR
COP    $0000
UNUSED $0000
ABORT  $0000
NMI    VBlank
RESET  Main
IRQBRK $0000
.ENDEMUVECTOR

On le nommera header.asm.
Si vous souhaitez en faire un plus complet, rien ne vous empêche de le faire à la main.

Voilà un autre fichier snes.asm, ce n'est pas un fichier officiel. Ce n'est pas une norme de l'utiliser, mais je pense qu'il sera plus agréable d'employer des mots que des chiffres pour les registres SNES.

Exemple pour afficher un fond de couleur orange.

Ce code permet un peu d'expliquer les couleurs.

.include "header.asm"
.include "snes.asm"

Main:
xce ; cela permet de remettre le processeur en mode native (65816)

;cette instruction permet de réinitialiser les flags avec une valeur
rep #$10    ;met les registres xy en 16 bits

;Je laisse la macro, ça met à zéro les registres SNES, au cas où...
SNES_INIT

;INITIAL SETTINGS

; c'est le registre $2100, il permet de faire un Forced Blank et de régler la luminosité
lda #$8F
sta INIDISP

;general init

;Registre $212C, active l'utilisation des plans obj + bg
lda #$01 ; on active le BG1
sta TM

;Registre $2115, c'est pour dire que, à chaque fois qu'on écrit sur la VRAM avec le registre $2118/$2119, il incrémentera l'adresse quand on écrit à $2119
lda #$80
sta VMAINC

;La première couleur sera la couleur de fond de la SNES,
;si un BG ou un sprite l'utilisent,la première couleur sera la transparence.
;adresse de CG-RAM = 0
lda #$00
sta CGADD

;Registre $2122 écrit sur la palette

;orange
lda #$FF ; VVVR RRRR
sta CGDATA

lda #$01 ; BBBB B0VV
sta CGDATA

;Là, il a incrémenté, donc on est à l'adresse de CG-RAM = 1

;bleu
lda #$00 ; VVVR RRRR
sta CGDATA

lda #$F7 ; BBBB B0VV
sta CGDATA

;ainsi de suite

;On désactive le Forced Blank
lda #$0F
sta INIDISP

;Registre $4200, on active le NMI(VBlank) et le joypad
lda #$81
sta NMITIMEN

Game:
wai ; interruption (qui permet d'attendre le NMI)

jmp Game

VBlank:

rti

Je vous conseille de l'étudier, de le tester, de le modifier avant de passer au suivant.

IV-C - Background

Le code pour l'arrière-plan n'est pas bien compliqué lui non plus, sur ce code on verra juste en plus l'utilisation de l'arrière-plan au niveau données (image et tileset).

.include "header.asm"
.include "snes.asm"

Main:
xce ; cela permet de remettre le processeur en mode native (65816)

;cette instruction permet de réinitialiser les drapeaux avec une valeur
rep #$10    ;met les registres xy en 16 bits

;Je laisse la macro, ça met à zéro les registres SNES, au cas où
SNES_INIT

;INITIAL SETTINGS

; c'est le registre $2100, il permet de faire un Forced Blank et de régler la luminosité
lda #$8F
sta INIDISP

;Registre $2105, configure la taille des sprites / la priorité pour le BG 3/ le mode (de 0 à 7)
; BG 1,2,3,4 8x8,mode 0
lda #$00
sta BGMODE

;Registre $2107, adresse pour les données du premier arrière-plan. Bon ! Le $1000 n'est pas le plus indiqué, les données d'arrière-plan sont les données des tuiles (ou tileset)

lda #$10
sta BG1SC

;Registre $210B/$210C, adresse des arrière-plans par segment, eux ce sont bien les données 'pixel par pixel'
;adresse des données du BG 1 et 2 sera de 0, sur cet exemple je n'utiliserai pas le BG 2,3 et 4
lda #$00
sta BG12NBA

; initialisation générale

;Registre $212C, active l'utilisation des plans obj + bg
; bg1 enable
lda #$01
sta TM

;Registre $2115 ,c'est pour dire que, à chaque fois qu'on écrit sur la VRAM avec le registre $2118/$2119, il incrémentera l'adresse quand on écrit à $2119
lda #$80
sta VMAINC

;Donc adresse de la palette, la première couleur sera la couleur de fond de la SNES,
;si un BG ou un sprite l'utilisent, la première couleur sera la transparence
;CG = 0
lda #$00
sta CGADD

;Registre $2122 écrit sur la palette

;noir
lda #$00 ; VVVR RRRR
sta CGDATA

lda #$00 ; BBBB B0VV
sta CGDATA

;Là, il a incrémenté donc on est à CG = 1

;bleu
lda #$00 ; VVVR RRRR
sta CGDATA

lda #$F7 ; BBBB B0VV
sta CGDATA

;rouge
lda #$1F ; VVVR RRRR
sta CGDATA

lda #$00 ; BBBB B0VV
sta CGDATA

;jaune
lda #$FF ; VVVR RRRR
sta CGDATA

lda #$03 ; BBBB B0VV
sta CGDATA

;palette suivante
;noir
lda #$00 ; VVVR RRRR
sta CGDATA

lda #$00 ; BBBB B0VV
sta CGDATA

;rouge
lda #$1F ; VVVR RRRR
sta CGDATA

lda #$00 ; BBBB B0VV
sta CGDATA
;On va écrire sur la VRAM ici

;Adresse $0000
ldy #$0000
sty VMADDL

; 2 x 8 pixels couleur 1 (bleu)
ldy #$00FF
sty VMDATAL
sty VMDATAL

; 2 x 8 pixels couleur 2 (rouge)
ldy #$FF00
sty VMDATAL
sty VMDATAL

; 2 x 8 pixels couleur 3 (jaune)
ldy #$FFFF
sty VMDATAL
sty VMDATAL

; 2 x 8 pixels couleur 0 (alpha)
ldy #$0000
sty VMDATAL
sty VMDATAL

;tileset suivant

; 8 x 8 pixels couleur 1 (bleu)
ldy #$00FF
sty VMDATAL
sty VMDATAL
sty VMDATAL
sty VMDATAL

sty VMDATAL
sty VMDATAL
sty VMDATAL
sty VMDATAL

;Tileset
ldy #$1000
sty VMADDL

;tileset suivant donc notre carré bleu
ldy #$0001
sty VMDATAL

;tileset suivant mais avec la deuxième palette (donc ça donne un carré rouge)
ldy #$0401
sty VMDATAL

;On désactive le Forced Blank
lda #$0F
sta INIDISP

;Registre $4200, on active le NMI et la manette
lda #$81
sta NMITIMEN

Game:
wai ; interruption

jmp Game

VBlank:

rti

IV-D - DMA

Alors, sur ce code, je ferai des 'grands changements', donc je vous conseille de bien comprendre ce qui a été vu auparavant. J'utiliserai aussi quelques macros pour l'initialisation de la SNES vu que lda, sta sont redondants, j'enlèverai/réduirai aussi les commentaires.

Ce code rajoutera l'utilisation du DMA, qui permet d'envoyer plus rapidement des données que la version manuelle avec *DATA.

.include "header.asm"
.include "snes.asm"

; la plupart des macros peuvent être remplacées par lda #argument sta $registre (ou ldy/sty pour ceux qui envoient 2 octets)

Main:
xce

rep #$10    ;met les registres xy en 16 bits

SNES_INIT

;INITIAL SETTINGS

SNES_INIDISP $8F ; FORCED BLANK , luminosité 15

SNES_BGMODE $00 ; BG 1 8x8,mode 0
SNES_BG1SC $10 ;address data BG1 $1000
SNES_BGNBA $00 $00; address segment BG1,2,3,4 (2,1 / 4,3)

SNES_TM $01 ; bg1 enable

SNES_VMAINC $80

;À partir ici on va utiliser le DMA

;Registre $4300 , Pour dire que le DMA écrira par un octet à la fois  et qu'il incrémentera (adresse de la ROM)
SNES_DMA0 $00
SNES_DMA1 $00 ; un peu inutile on ne pourrait utiliser qu'un canal, mais juste pour montrer comment on envoie sur plusieurs canaux

;Registre $4301 , le bus adresse pour écrire sur 21XX, on choisit quel registre il va écrire. Là, c'est le registre $2122 qui permet d'écrire sur la palette
SNES_DMA0_BADD $22
SNES_DMA1_BADD $22 ; pareil

SNES_CGADD $00 ; met l'adresse de la palette a $00

;Pour la palette, on va lire ce qui se trouve dans Palette1 et Palette2 dans la ROM (j'ai mis les labels en bas)
;Il existe une macro, mais on va l'écrire manuellement pour l'exemple

lda #$01 ; la banque de données, ici il se trouve dans la banque 1
ldy #Palette1
ldx #$0008  ; taille des données

sta DMA_BANK
sty DMA_ADDL
stx DMA_DATAL

;canal 2 pour la deuxième palette
ldy #Palette2

sta DMA_BANK + $10
sty DMA_ADDL + $10
stx DMA_DATAL + $10

SNES_MDMAEN $03 ;envoie dans le canal 1 et 2

;Les données pour l'arrière-plan maintenant

SNES_DMA0 $01 ;Pour dire que le DMA écrira par 2 octets L,H
SNES_DMA0_BADD $18 ;c'est le registre $2118 (VMDATA)qui permet d'écrire sur la VRAM

SNES_VMADD $0000

SNES_DMA0_ADD Data $0020

SNES_MDMAEN $01

;Tileset
SNES_VMADD $1000

SNES_DMA0_ADD Tileset $0020

SNES_MDMAEN $01

SNES_INIDISP $0F ;On désactive le Forced Blank

SNES_NMITIMEN $81 ; Active le NMI et le joypad

Game:
wai

jmp Game

VBlank:

rti

.bank 1 slot 0
.org 0

Palette1:
.dw $0000 , $F700 , $001F , $03FF

Palette2:
.dw $0000 , $001F , $1F00 , $FF03

Data:
.dw $00FF , $00FF , $FF00 , $FF00 , $FFFF , $FFFF  , $0000 , $0000 ; Tile 1
.dw $00FF , $00FF , $00FF , $00FF , $00FF , $00FF  , $00FF , $00FF ; Tile 2

Tileset:
.dw $0000 , $0001 , $0001 , $0001 , $0001 , $0001  , $0001 , $0001
.dw $0401 , $0401 , $0401 , $0401 , $0401 , $0401  , $0401 , $0401

IV-E - Sprite

Voilà la dernière partie qui montrera l'utilisation des sprites et de la manette.
Cet exemple se veut simple, mais sur une SNES réelle, si vous voulez que ce code fonctionne, il faudra mettre les instructions PPU (ici l'OAM) , dans le Vblank.

Pour les données de sprites, il faut télécharger perso.asm.

.include "header.asm"
.include "snes.asm"

; la plupart des macros peuvent être remplacées par lda #argument sta $registre (ou ldy/sty pour ceux qui envoient deux octets)

Main:
xce

rep #$10    ;met les registres xy en 16 bits

SNES_INIT

;INITIAL SETTINGS

SNES_INIDISP $8F ; FORCED BLANK , luminosité 15

SNES_BGMODE $00 ; BG 1 8x8,mode 0
SNES_BG1SC $10 ;address data BG1 $1000
SNES_BGNBA $00 $00; address segment BG1,2,3,4 (2,1 / 4,3)

;Registre $2101 , permet de choisir la taille des sprites et l'adresse où ils se trouvent
SNES_OBJSEL $A3 ;32x32 , $6000 address

SNES_TM $11 ; obj & bg1 enable

SNES_VMAINC $80

;Chargement de la palette
SNES_DMA0 $00
SNES_DMA1 $00

SNES_DMA0_BADD $22
SNES_DMA1_BADD $22

SNES_CGADD $00

SNES_DMA0_ADD Palette1 $0008
SNES_DMA1_ADD Palette2 $0008

SNES_MDMAEN $03

;Palette Sprite
SNES_CGADD $80 ; la palette des sprites commence à cette adresse
SNES_DMA0_ADD pal $0020
SNES_MDMAEN $01

;Chargement BG 1
SNES_DMA0 $01
SNES_DMA0_BADD $18

SNES_VMADD $0000
SNES_DMA0_ADD Data $0020
SNES_MDMAEN $01

;Chargement Sprite
SNES_VMADD $6000
SNES_DMA0_ADD Sprite $0800
SNES_MDMAEN $01

;chargement des Tilesetq
SNES_VMADD $1000
SNES_DMA0_ADD Tileset $0020
SNES_MDMAEN $01

SNES_INIDISP $0F ;On désactive le Forced Blank

SNES_NMITIMEN $81 ; Active le NMI et la manette

;adresse où on stockera la position X du perso
lda #0
sta 0

;adresse où on stockera la position Y du perso
lda #100
sta 1

Game:
wai

;On va lire le Joypad 1:

lda STDCONTROL1H    ; joypad 1 High
and #$01
cmp #$01
bne +
inc 0
+:

lda STDCONTROL1H    ; joypad 1 High
and #$02
cmp #$02
bne +
dec 0
+:

lda STDCONTROL1H    ; joypad 1 High
and #$04
cmp #$04
bne +
inc 1
+:

lda STDCONTROL1H    ; joypad 1 High
and #$08
cmp #$08
bne +
dec 1
+:

;OAM
;adresse de OAM
lda #00
sta OAMADDL

;Position x
lda 0
sta OAMDATA

;Position y
lda 1
sta OAMDATA
;Note il incrémente ici donc on est à OAM 1

;tile
lda #0
sta OAMDATA

;flip,prio,palette,tile
lda #$20
sta OAMDATA

;ici a OAM 2

jmp Game

VBlank:

rti

.bank 1 slot 0
.org 0

Palette1:
.dw $FFFF , $F700 , $001F , $03FF

Palette2:
.dw $0000 , $001F , $1F00 , $FF03

Data:
.dw $00FF , $00FF , $FF00 , $FF00 , $FFFF , $FFFF  , $0000 , $0000 ; Tile 1
.dw $00FF , $00FF , $00FF , $00FF , $00FF , $00FF  , $00FF , $00FF ; Tile 2
.dw $0000 , $0000 , $0000 , $0000 , $0000 , $0000  , $0000 , $0000 ; Tile 3

Tileset:
.dw $0002 , $0002 , $0002 , $0002 , $0001 , $0001  , $0001 , $0001
.dw $0401 , $0401 , $0401 , $0401 , $0401 , $0401  , $0401 , $0401

Sprite:
.include "perso.asm"

Vous pourrez remarquer deux choses. Un : que votre personnage est affiché 'deux fois'. Une fois en haut à gauche et celui que vous contrôlez. Si vous vous déplacez votre personnage en haut, il s'efface et c'est normal. On n'a écrit que sur un seul OAM, les autres sont tous initialisés à zéro donc position X et Y à zéro aussi, donc on a 127 sprites affichés en haut à gauche. Et comme la SNES ne supporte pas un certain nombre de sprites sur la même ligne, elle les enlève. Du coup, votre personnage s'efface si vous allez en haut de l'écran. Pour résoudre ce problème, il suffit d'initialiser OAM à -32 en Y par exemple.

L'autre : vous remarquerez que votre personnage réapparaît directement à droite si vous allez à gauche. Si vous voulez que votre personnage soit coupé, il faut voir du côté de OAM, MSB et faire un +256 en X : il réapparaîtra coupé à gauche de l'écran.

Normalement, si vous avez bien tout compris, vous pouvez changer cet affreux arrière-plan.

V - Ressources

V-A - Code source

Vous pouvez télécharger le code source des exemples ici : https://jeux.developpez.com/tutoriels/SNES/debuter-programmation-super-nintendo/fichiers/snes-base.zip.
Celui-ci contient aussi l'outil de conversion d'image PNG vers le format pour la SNES.

V-B - Liens

- http://problemkaputt.de/fullsnes.htm

- http://www.smwiki.net/wiki/65c816_instructions

- http://wiki.superfamicom.org/snes/show/HomePage

- http://en.wikibooks.org/wiki/Super_NES_Programming

VI - Remerciements

Merci à LittleWhite , archMqx. et chrtophe pour leur relecture et leurs conseils dans l'amélioration de ce tutoriel.

Merci à ced pour sa relecture orthographique.