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, |
Taille de Sprites |
8x8, 16x16, 32x32, 64x64 |
Son |
Sony SPC700 |
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 |
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 |
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 |
ORA |
OU logique sur la mémoire ou l'accumulateur. |
A = A | n |
ASL |
Décalage vers la gauche la mémoire ou l'accumulateur. |
A = A << 1 |
LSR |
Décalage vers la droite la mémoire ou l'accumulateur. |
A = A >> 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 |
0xB6 |
7 octets |
Fixed Value |
0x01 0x01 0x00 0x01 0x00 0x01 |
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 |
|
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 |
0x60 0x40 |
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 |
8x8 ou 16x16 |
8x8 ou 16x16 |
8x8 ou 16x16 |
1 |
8x8 ou 16x16 |
8x8 ou 16x16 |
8x8 ou 16x16 |
|
2 |
8x8 ou 16x16 |
8x8 ou 16x16 |
||
3 |
8x8 ou 16x16 |
8x8 ou 16x16 |
||
4 |
8x8 ou 16x16 |
8x8 ou 16x16 |
||
5 |
8x8 ou 16x16 |
8x8 ou 16x16 |
||
6 |
16x8 |
|||
7 |
8x8 |
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 |
BGSC1/BGSC2 |
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 |
BG1H0FS/BG1V0FS |
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 |
Type Adresse (pour le HDMA). |
D3 : indique si l'adresse est fixe ou si on l'incrémente |
Type écriture sur 1 octet |
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 |
STD CNTRL 1L/1H |
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▲
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.