C64 Interrupts FR Part06

Télécharger au format pdf ou txt
Télécharger au format pdf ou txt
Vous êtes sur la page 1sur 13

RetroProgramming Italia - RP Italia présente :

Tutoriel Interruptions C64 – Partie 6


(Raster Interrupts 3)
Par
Attilio Capuozzo - Fondateur du group facebook
"RetroProgramming Italia - RP Italia"
&
Antonio Savona - Programmeur de jeux video et de démos sur
commodore 64

avec la collaboration de
Olivier Bernhard - Collaborateur externe "RetroProgramming
Italia - RP Italia".

Nous allons maintenant proposer une version légèrement modifiée de la routine


d’interruption présentée dans la partie 5, qui est reproduite ci-dessous pour
faciliter la tâche des lecteurs :
Résumons brièvement ce que fait cette routine :

- Lorsque le faisceau de trame atteint la ligne de trame $08, une


interruption de trame se déclenche et le gestionnaire d’interruption
associé (nommé irq00) exécute les opérations suivantes :

• Modification de la couleur des bords d’écran et de la zone


d’affichage en blanc
• Programmation d’une nouvelle interruption de trame pour un
déclenchement à la ligne de trame $96 avec irq01 comme gestionnaire
d’interruption associé.

- Lorsque le faisceau de balayage électronique atteint la ligne de trame


$96, Une interruption de trame se déclenche et le gestionnaire
d’interruption associé (nommé irq01) exécute les opérations suivantes :

• Modification de la couleur des bords d’écran et de la zone


d’affichage en noir
• Nouvelle programmation d’une interruption de trame pour un
déclenchement à la ligne de trame $08 avec irq00 comme gestionnaire
d’interruption associé.

Le résultat que nous obtenons est la séparation de l'écran en deux zones de


couleur (bordure + fenêtre d'affichage), l'une supérieure blanche et l'autre
inférieure noire, séparation qui se produit de manière fixe à la ligne de
trame $96 (150).

Nous pourrions maintenant décider de changer la ligne de trame où


l'interruption de trame irq01 est déclenchée, à chaque nouveau balayage de
l’écran.

De cette façon, l'interruption de trame irq00 sera toujours déclenchée à la


ligne de trame $08 tandis que l'irq01 sera déclenchée à une ligne de trame
non plus fixe, mais variable, dans une certaine plage afin de produire un
effet d'animation.

C’est ce que nous allons faire en montrant comment avec peu de modifications
nous pouvons introduire ce comportement différent :
Dans le programme modifié, nous avons tout d'abord défini comme premier
gestionnaire d’interruption irq00 suivi de irq01 (contrairement à ce qui se
passait dans la routine originale).

Notez également que nous avons éliminé la constante IRQSPLIT car, comme nous
l'avons dit, l'irq01 ne se déclenche plus à une position fixe.

Examinons maintenant les modifications apportées à l'irq00 :

Dans le registre $d012, c'est-à-dire les 8 bits les moins significatifs du


registre de trame $d012-$d011, nous ne chargeons plus la constante IRQSPLIT
comme paramètre de comparaison de ligne de trame pour irq01, mais nous lisons
les valeurs de la table irq_table créée grâce à la directive .fill de Kick
assembler :

.fill 128,144.0 + 64.0 * sin(i * PI * 2 / 128.0)

Essayons de comprendre comment fonctionne cette directive :

.fill 128,144.0 + 64.0 * sin(i * PI * 2 / 128.0)

Ceci signifie que nous générerons 128 valeurs (bytes)

.fill 128,144.0 + 64.0 * sin(i * PI * 2 / 128.0)

Ceci détermine comment chacune des 128 valeurs est calculée. Chacune des 128
valeurs est indicée par i, i variant de 0 à 127 (128 valeurs).

i=0 valeur= 144.0 + 64.0 * sin(0 * PI * 2 / 128.0) = 144

i=1 valeur= 144.0 + 64.0 * sin(1 * PI * 2 / 128.0) = 147

i=2 valeur= 144.0 + 64.0 * sin(2 * PI * 2 / 128.0) = 150

i=3 valeur= 144.0 + 64.0 * sin(3 * PI * 2 / 128.0) = 153

...

...

i=127 valeur= 144.0 + 64.0 * sin(127 * PI * 2 / 128.0) = 141


nous obtenons le tableau ci-dessous :
irq_table :
.byte 144,147,150,153,156,160,163,166,168,171,174,177,180,182,185,187
.byte 189,191,193,195,197,199,200,202,203,204,205,206,207,207,208,208
.byte 208,208,208,207,207,206,205,204,203,202,200,199,197,195,193,191
.byte 189,187,185,182,180,177,174,171,168,166,163,160,156,153,150,147
.byte 144,141,138,135,132,128,125,122,120,117,114,111,108,106,103,101
.byte 99 , 97, 95, 93, 91, 89, 88, 86, 85 ,84, 83, 82, 81, 81, 80, 80
.byte 80 , 80, 80, 81, 81, 82, 83, 84, 85 ,86, 88, 89, 91, 93, 95, 97
.byte 99 ,101,103,106,108,111,114,117,120,122,125,128,132,135,138,141

En fin de compte, cette directive nous permet calculer simplement 128 valeurs
de lignes de trame qui oscillent entre 144+64 et 144-64 selon la fonction
sinusoïdale indiquée ci-dessus.

Voyons maintenant comment définir, dans le code, la ligne de trame pour le


l’interruption de trame irq01 en utilisant la technique du « SELF MODIFYING
CODE » ou « Code auto-modifiant ».

A la première exécution de l'irq00, à la ligne 45 du code, nous chargeons


avec l’adressage immédiat la valeur 127 dans le registre X du 6510.

À la ligne 46, nous chargeons une valeur de la table irq_table dans Y en


utilisant le contenu du registre X comme index (adressage indexé absolu) et
à la ligne suivante, nous stockons la valeur de la table dans $d012 pour
définir la prochaine interruption de trame servie par le gestionnaire
d’interruption irq01.

à la ligne 48, nous trouvons l'une des 2 instructions clés du « SELF MODIFYING
CODE » : dec idx+1.

Le label idx correspond à l'adresse où se trouve l’opcode de l'instruction


ldx de la ligne 45 ; l'octet suivant, idx+1, contient la valeur 127.

Ainsi, avec la décrémentation effectuée à la ligne 48, nous ne faisons que


décrémenter de 1 l'indice X du tableau à partir de la valeur 127.

Tant que le contenu de l'adresse idx+1 est >=0, nous continuons l'exécution
du programme à partir de la ligne 52 (instruction BPL de la ligne 49).
Lorsque l'indice - à la suite des décrémentations successives – atteint la
valeur $00, un « DEC » supplémentaire provoque un changement de la valeur
contenue à l'emplacement idx+1 qui devient alors égal à $ff ($00-$01=$ff).
dès lors, la condition du saut conditionnel bpl (ligne 49) ne sera plus
vérifiée puisque le MSB de l'emplacement sera fixé et par conséquent changera,
l'indicateur N du registre d'état en le fixant à 1.

N'oubliez pas que le « FLAG » N du registre de statut (également appelé bit


de signe) ne fait rien d'autre que copier le statut du bit de poids fort
(MSB) de l'emplacement concerné, que le contenu soit un complément à 2 (signé)
ou un numéro non signé.

Pour ramener l'index de la table à 127, à la ligne 50, nous chargeons 127
dans l'Accumulateur, dont nous stockons ensuite le contenu à la ligne 51 dans
l'adresse idx+1.

La ligne 51 utilise donc à nouveau du « SELF MODIFYING CODE ».

Nous aurions également pu ne pas utiliser la technique du « SELF MODIFYING


CODE », mais dans ce cas, nous aurions dû définir une variable supplémentaire
en mémoire pour stocker la valeur de l'index de la table irq_table, ce qui
aurait fait perdre 1 octet. Le code serait le suivant :

Bien que les économies, dans cet exemple précis, puissent sembler minimes et
peut-être négligeables - nous rappelons aux lecteurs que notre objectif ici
est essentiellement didactique - il existe de nombreux autres cas dans
lesquels la technique ci-dessus s'avère fondamentale pour obtenir des
économies en termes de mémoire et de cycles cpu (code compact et plus rapide).
Voici quelques captures d'écran de l’exécution de la routine modifié :
Le code complet du programme modifié se trouve ci-dessous :

.pc = $c000 "main"


sei
lda #$7f
sta $dc0d
sta $dd0d
bit $dc0d
bit $dd0d
lda #$01
sta $d01a
lda #$08
sta $d012
lda #$1b
sta $d011
lda #<irq00
sta $0314
lda #>irq00
sta $0315
cli
rts

irq01:
lda #0
ldx $d012
!: cpx $d012
beq !-
sta $d020
sta $d021
lda #<irq00
sta $0314
lda #>irq00
sta $0315
lda #$08
sta $d012
lsr $d019
jmp $ea31

irq00:
lda #$01
sta $d020
sta $d021

lda #<irq01
sta $0314
lda #>irq01
sta $0315

idx: ldx #127


ldy irq_table,x
sty $d012
dec idx + 1
bpl !ok+
lda #127
sta idx + 1
!ok:

lsr $d019

jmp $ea81

irq_table:
.fill 128,144.0 + 64.0 * sin(i * PI * 2 / 128.0)
Avant de montrer d'autres exemples d'application, nous pensons qu'il est
nécessaire de clarifier certains aspects théoriques rencontrés précédemment.

Lorsque nous avons traité les accès en mémoire effectués par le VIC notamment
pour l’affichage du texte ou des graphismes, nous avons évoqué les accès à
la mémoire vidéo pour la lecture des Codes Ecran (au moins en Mode Texte).

A cette occasion, nous avons utilisé comme synonymes de « mémoire vidéo » les
termes de « mémoire d'écran » et de « matrice vidéo ».

Néanmoins, si l’on se base sur la cartographie de la mémoire du commodore 64


(Fig. 1), il convient de différencier ces deux termes.

Figure 1 : Différence entre la mémoire d’'écran et la matrice vidéo (extrait de “Mappa di Memoria del C64”)

En particulier, la mémoire d’écran est une zone de RAM d'une taille de 1K


(1024 octets) dont la plage d’adresses s’étend de $0400 à $07ff et comprend
également les 8 pointeurs de données des SPRITES mappés aux adresses $07f8 à
$07ff.

La matrice vidéo représente les 1000 premiers octets de la mémoire d'écran


(par défaut de $0400 à $07e7) où, en mode texte, les codes d'écran de 1 octet
sont stockés. Il s'agit des indexes ou « pointeurs » vers un « Character
Set » qui contient la description binaire de la forme de tous les caractères
qui peuvent être affichés à l'écran (fenêtre d'affichage).

Le « Character Set » peut être en ROM si nous utilisons les caractères


standard du C64, et dans ce cas nous utiliserons le terme « Character
Generator ROM » (ou plus brièvement « Char ROM ») ou, en mémoire RAM dans
le cas de caractères définis par l'utilisateur ; dans ce dernier cas, nous
pouvons parler de « User-Defined Character RAM ».

La fiche technique originale de la puce graphique du VIC-II indique le jeu


de caractères mentionné ci-dessus avec le terme « Character Base ».

Quoi qu'il en soit, en pratique, les deux termes - mémoire d'écran et matrice
vidéo - peuvent en principe être considérés comme équivalents et donc
interchangeables.
Toujours à propos des accès mémoire supplémentaires effectués par le VIC lors
d’une « BAD LINE », nous avons dit qu'en plus des accès à la matrice vidéo,
le VIC lit également les informations de la couleur - code couleur à 4 bits,
Fig. 2 - de la « color RAM » entre les adresses $d800 et $dbff.

Figure 2 : palette de 16 couleurs du C64 (Color nibble)

La « color RAM » est une zone de mémoire fixe qui n'est pas déplaçable par
l'utilisateur contrairement à la mémoire d'écran.

Il convient de noter que les accès à la « color RAM » ne soustraient pas de


cycles d'horloge de l'unité centrale parce que le VIC-II a quatre broches de
données (c'est-à-dire les broches du bus de données de 12 bits du VIC)
connectées directement au chip de mémoire vive des couleurs (Fig. 3).

Figure 3 : Broches de données du VIC-II 35 à 38 (DB11 à DB8) connectées à la “color RAM”


De cette façon, le VIC-II peut toujours lire la mémoire vive des couleurs,
qu'elle se trouve ou non dans son espace d'adressage de 16K (bus d'adresse
du VIC de 14 bits).

Le code couleur sera stocké dans les quatre bits les plus significatifs du
bus de données du VIC (DB8 à DB11) à la fois en Mode text – Standard
(également appelé Hires ou Normal ou Monocolor), Multicolor et Extended
Background (ECM) - et en mode Bitmap Multicolor.

Les 8 bits restants du bus de données de la puce graphique (DB0 à DB7) peuvent
contenir soit la valeur du code écran - en mode texte - soit, en mode bitmap,
des informations supplémentaires sur les couleurs (Fig. 4, 5 et 6).

Figure 1: origine des couleurs en mode texte

Figure 5 : Origine des couleurs en mode texte Extended Background (ou ECM : Extended Color
Mode). Les bits D6 et D7 dans la figure se réfèrent aux 2 bits les plus significatifs du code caractère.

Figure 6 : Origine des couleurs en mode Bitmap


Montrons maintenant un premier exemple d'application où nous réalisons un
simple défilement horizontal du texte avec une interruption de trame.

Comme d'habitude, le code source est accompagné de commentaires explicatifs


détaillés qui facilitent grandement la compréhension. D'autres commentaires
sont rapportés après la source de la routine de l'Assemblée.

// Scroll Text
// Défilement horizontal du texte (HSCroll) Registre du $d016 du VIC (bit 0-2)
// Par Attilio Capuozzo pour "RetroProgramming Italia - RP Italia"
// KickAssembler Version

* = $c000
.label NMIRoutine = $fec1 // Adresse de la dernière instruction du NMI Handler par défaut ($fe47) correspondant à l'instruction RTI

Sei // désactivation temporaire des interruption masquables de type IRQ


lda #<IRQRoutine // Modification du vecteur IRQ de la RAM CINV ($0314-$0315) pointant par défaut vers le gestionnaire d'IRQ système ($ea31)
sta $0314
lda #>IRQRoutine
sta $0315
lda #$7f // 127 dec-->%01111111 = Masque binaire pour réinitialiser le MSB de l'ICR des 2 CIA et pour régler les 7 premiers bits ( Désactivation de toutes les sources
// d'interruption)
sta $dc0d // désactivation des interruptions IRQ du CIA 1 et en particulier l'interruption du système, c'est-à-dire l'interruption IRQ
// du Timer A (déclenchée toutes les 1/60 secondes), pour éviter le scintillement de l'écran

sta $dd0d // désactivation des toutes les interruption NMI du CIA2


lda $dc0d // Suppression de tout indicateur de demande d'interruption (IRQ) antérieur provenant du CIA 1 (La lecture réinitialise le registre)
lda $dd0d // Suppression de tout indicateur de demande d'interruption (IRQ) antérieur provenant du CIA 2 (la lecture réinitialise le registre)
lda #$00 // Première interruption de trame à la ligne $00
sta $d012 // stockage de la valeur dans le registre de trame
sta $fb // Variable de Compteur du défilement horizontal (stockée en Zero Page)
lda #$1b // Decimal (27)
sta $d011 // Réinitialisation du MSB de $d011, c'est-à-dire le bit 8 du registre de trame $d012-$d011
lda #$01
sta $d01a // Réactivation de la possibilité pour le VIC de signaler une interruption de trame
cli // Ré-initialisation de l'indicateur de désactivation d'interruption, c'est-à-dire réactivation de la possibilité pour le cpu de traiter les IRQs (interruptions masquables).
ldx #<NMIRoutine // Nous chargeons dans le vecteur NMINV ($0318-$0319), dans le format little-endian low byte (LSB)-high byte (MSB),
ldy #>NMIRoutine // l'adresse $fec1 qui correspond à la dernière instruction, le RTI, du NMI Handler par défaut ($fe47)
stx $0318 // Grâce à cette astuce, nous "désactivons" l'utilisation de la combinaison de touches RUN STOP + RESTORE
sty $0319 // avec laquelle l'utilisateur pourrait forcer la sortie du programme.
rts
IRQRoutine:
asl $d019 // Acquittement de l'interruption de trame
bit $d012 // Nous copions le MSB (bit 7 ou plus significatif) de $d012 dans le flag N du registre des statuts
bmi nextRaster // Si MSB est défini, alors la Rasterline est >127, alors je passe au label "nextRaster" pour gérer la ligne de trame 154 (9a)
ldx $fb // Chargez la valeur de la variable du Compteur du défilement horizontal dans X
inx // On augmente la valeur de X (SEULEMENT la 1ère fois qu'on saute la lecture de la 1ère valeur du tableau, soit 8)
cpx #$0d+1 // Si X=14 ($d0e=$d0d+1) alors nous avons déjà lu la dernière valeur du tableau scrollValue (14 valeurs)
bne scrolling // Si nous ne sommes pas à la fin du tableau, nous sautons au label "scrolling" SANS remettre le compteur à zéro.
ldx #$00 // Nous remettons le compteur à zéro si nous avons atteint la dernière valeur de la table.

scrolling: // Label de Gestion des Interruptions de trame à la ligne 0 (moitié supérieure de l'écran avec effet de défilement horizontal "smooth")
lda scrollValue,x // Lecture de la valeur de défilement horizontal du tableau
sta $d016 // Stocker la valeur du défilement horizontal en bits 0-2 (valeurs de 0 à 7) de $d016
stx $fb // Sauvez le compteur en mémoire
lda #$9a // Ligne de trame de la prochaine interruption de trame : ligne 154 (9a)
sta $d012
jmp testTimerAInterrupt
nextRaster: // Gestion de la moitié inférieure de l'écran à la ligne de trame 154 (9a)
lda #$08
sta $d016 // Nous chargeons la valeur par défaut (08) en $d016 qui correspond à l'absence de Défilement Horizontal (bit 0-2=0)
lda #$00 // Ligne de trame de la prochaine interruption de trame : ligne $00
sta $d012

testTimerAInterrupt:
lda $dc0d // Vérifions si une demande d'interruption système a été générée dans l'intervalle (IRQ du Timer A du CIA 1)
and #$01 // Vérifions donc si le bit 0 (LSB) de $dc0d est défini(=1)
beq exitPullStack // Si une interruption IRQ du Timer A n'est PAS signalée (c'est-à-dire le bit 0=0 de $dc0d), nous sortons normalement
jmp $ea31 // sinon (bit 0=1 de $dc0d) nous faisons un JMP vers le gestionnaire d'interruption du système ($ea31) pour le Keyboard Scan, etc.

exitPullStack:
pla // Les instructions suivantes occupent 6 octets et sont l'équivalent de Jmp $ea81 ou Jmp $febc
tay // les points d'entrée (pointant sur les 6 derniers octets) du gestionnaire d'interruption du système (IRQ-->$ea31 et NMI-->$fe47)
pla // Extraction de la pile des registres Y, X et A + retour depuis l'interruption
tax // La poussée vers la pile des 3 registres de données du 6510 est effectuée par le gestionnaire d'interruption IRQ principal du système
pla // mappé à l'adresse ROM $ff48 et vectorisée par les emplacements $fffe-$ffff de la ROM Kernal (derniers 8K de la carte mémoire C64).
rti // Le gestionnaire d'interruption IRQ principal du système ($ff48) est exécuté AVANT le gestionnaire d'IRQ système ($ea31) vectorisé en RAM
// par $0314-$0315 (CINV).
scrollValue: // Tableau des 14 valeurs de défilement horizontal du registre $d016 de (0 à 7) + 8 (valeur par défaut du registre $d016)
.byte 8,9,10,11,12,13,14,15
.byte 14,13,12,11,10,9
Nous notons tout d'abord l'astuce utilisée dans le Setup pour désactiver la
sortie forcée du programme en appuyant simultanément sur les touches RUN/STOP
et RESTORE :

ldx #<NMIRoutine
ldy #>NMIRoutine
stx $0318
sty $0319

N'oubliez pas qu'en appuyant sur la touche RESTORE, vous déclenchez une
interruption NMI.

En chargeant dans le Vecteur NMI ($0318-$0319) l'adresse $fec1 - correspondant


au label NMIRoutine déclaré en haut de la Routine (directive .label) - nous
faisons en sorte que notre NMI Handler n'exécute qu'une seule instruction RTI
ce qui équivaut à ignorer la touche RESTORE !

$fec1 correspond, en fait, à l'adresse de la dernière instruction (la RTI)


du System NMI Handler qui commence à la position $fe47 et est vectorisée par
$0318-$0319.

Au début du IRQ Handler IRQRoutine, juste après l'ACK de l'IRQ Raster


Interrupt, nous utilisons l'instruction BIT (Bit Test) pour tester ensuite
le bit haut (MSB) de $d012 via une instruction de saut conditionnel BMI
(Branch if MInus) :

bit $d012
bmi nextRaster

Nous savons, grâce à la documentation ISA (Instruction Set Architecture) du


6510, que l’instruction BIT effectue un ET volatil entre l'accumulateur et
un emplacement mémoire, c'est-à-dire qu'il ne stocke pas le résultat de
l'opération logique susmentionnée dans le registre A.

En outre, l’instruction copie également les bits 7 et 6 de l'opérande -


l'emplacement mémoire - dans les indicateurs N (négatif) et V (oVerflow),
respectivement, du registre d'état.

Et c'est cette fonctionnalité qui nous permet d'exécuter le branchement de


de l'instruction suivante (BMI), avec un test sur le drapeau N du registre
des statuts.

Dans testTimerAInterrupt, nous lisons le contenu de $dc0d, le registre de


contrôle des interruptions (ICR) du CIA 1, pour vérifier si une demande
d'interruption d'IRQ a été générée par le Timer A du CIA 1 - l'interruption
du système se déclenche généralement toutes les 1/60 secondes - puis
éventuellement la servir via un JuMP à $ea31, c'est-à-dire le gestionnaire
d'IRQ du système :

testTimerAInterrupt:
lda $dc0d
and #$01

Nous tenons à souligner que, bien que toutes les sources d’interruption de
la CIA 1 aient été précédemment désactivées dans la configuration :

lda #$7f
sta $dc0d
le flag d'interruption du Timer A - bit 0 de $dc0d - sera également activé
au cas où la condition d'interruption se produirait (Underflow du Timer A de
CIA 1 comme déjà vu) mais la demande ne sera PAS servie et l'interruption
IRQ ne sera PAS déclenchée.

Le dernier point que nous avons l'intention de soulever est le suivant :

exitPullStack:
pla
tay
pla
tax
pla
rti

Les instructions ci-dessus qui, dans l'ordre, récupèrent de la pile les 3


registres de données Y, X et A du 6510 (suivis d'un RTI) correspondent aux 6
dernières instructions de l'un des 2 gestionnaires d'interruption de système
- IRQ ou NMI - et sont souvent remplacées par un JMP $ea81 ou JuMP $febc
équivalents.

Rendez-vous pour la prochaine partie !

Attilio Capuozzo - Fondateur du groupe facebook


"RetroProgramming Italia - RP Italia"
&
Antonio Savona - Programmeur de jeux video et de
démos sur commodore 64
Olivier Bernhard - Collaborateur externe
"RetroProgramming Italia - RP Italia".

Vous aimerez peut-être aussi