CODINGApprendre pas à pas la programmation en assembleur Z80 par Roudoudou ★ Scrollings en folie! ★

Un split-screen en pure interruption (sans utiliser HALT)

suite de l'article [Rupture en assembleur]

Émancipation de l'instruction HALT

Programmer avec l'instruction HALT est intéressant, elle permet de rapidement de mettre au point les synchronisations dans un code compact. Néanmoins, on perd aussi le principal avantage des interruptions qui est d'interrompre!

Hé oui, l'instruction HALT est bloquante, et chaque temps d'attente est du temps de perdu.

Analyse

Dans notre code actuel, le code des six interruptions occupe moins de 128 octets. Je le sais car pour boucler pendant nos 6 interruptions, j'ai utilisé un saut relatif JR qui est incapable de dépasser 128 octets de déplacement.

Nous allons travailler sur ces bases et utiliser un changement de poids faible d'adresse seulement (car c'est plus rapide). Cela suppose que notre code est définitif. Je vous propose une optimisation adapté à ce cas. Si vous avez besoin de plus de choses sous interruption (et vous en aurez besoin), il faudra procéder légèrement différemment, en chageant le poids fort, avec une légère perte de place.

Interruption typique en usage séquentiel

Comme il n'est pas possible de forcer le Z80 d'appeler différentes routines en séquence, nous allons placer un opcode de saut en #38. Il est important de couper les interruptions car notre patch se fait en deux fois. Pour placer un EI:RET habituel avec HL, c'est inutile car l'écriture est atomique d'un point de vue des interruptions. Là non.

di
ld a,#C3 ; opcode du JUMP
ld hl,int01
ld (#38),a
ld (#39),hl
ei

int01
push af,bc ; on ne touchera que ces deux là

; nos OUT

ld a,hi(intSuivante) : ld (#39),a ; patcher le vecteur d'interruption
pop bc,af
ei : ret ; terminé, réactiver les INT

Adaptation complète de notre source de démonstration

J'ai ajouté un petit quelque chose pendant que les interruptions s'occupe de l'affichage. J'échange le contenu des deux pages vidéos! Vous pouvez bien évidemment placer n'importe quoi d'autre, sauuuuuf!

Sauf du code qui utilise de façon trop hardcore les interruptions, je vous conseille alors de lire la page sur [l'utilisation avancée de la pile].
Concrètement, nous prenons notre code actuel (le revoici)

halt
halt
ld bc,#BC07 : out (c),c : ld bc,#BD00+255 : out (c),c ; desactiver la VBL
ld bc,#BC06 : out (c),c : ld bc,#BD00+19 : out (c),c ; notre écran visible fait 19 blocs
ld bc,#BC04 : out (c),c : ld bc,#BD00+18 : out (c),c ; notre écran "complet" fait 19 blocs (R4=19-1)
halt
halt
; changer l'adresse de l'écran suivant AVANT qu'il arrive!
ld bc,#BC00+12 : out (c),c : ld bc,#BD20 : out (c),c ; écran suivant en #8000
; attendre 16 lignes soit 16x64 nops ou 4x256 nops, le DJNZ qui saute prend 4 nops
ld b,0 : djnz $
ld bc,#7F02 : out (c),c : ld a,#47 : out (c),a ; petit changement de couleur
ld bc,#BC07 : out (c),c : ld bc,#BD00+11 : out (c),c
ld bc,#BC06 : out (c),c : ld bc,#BD00+8 : out (c),c ; on raccourci le deuxième écran visible
ld bc,#BC04 : out (c),c : ld bc,#BD00+19 : out (c),c ; le deuxième écran "complet" fait 20 blocs pour avoir 39 au total
halt
halt
ld bc,#7F02 : out (c),c : ld a,#43 : out (c),a ; petit changement de couleur pendant la VBL :)
ld bc,#BC00+12 : out (c),c : ld bc,#BD30 : out (c),c ; écran du haut en #C000

Et on remplace chaque HALT par un label pour définir nos interruptions. On ajoute les PUSH, le changement de vecteur et les POP, EI, RET.
Notre code ressemble comme deux gouttes d'eau à notre POC. On remarque que je démarre avec ALIGN sur une adresse multiple de 256 pour n'avoir que l'octet de poids faible de l'adresse à changer.

Enfin, la dernière interruption reboucle sur la première, la boucle est infinie

;***********************************
; pool d'interruptions
;***********************************
align 256
int01 push af : ld a,lo(int02) : ld (#39),a : pop af : ei : ret
int02 push af,bc
ld bc,#BC07 : out (c),c : ld bc,#BD00+255 : out (c),c ; desactiver la VBL
ld bc,#BC06 : out (c),c : ld bc,#BD00+19 : out (c),c ; notre écran visible fait 19 blocs
ld bc,#BC04 : out (c),c : ld bc,#BD00+18 : out (c),c ; notre écran "complet" fait 19 blocs (R4=19-1)
ld a,lo(int03) : ld (#39),a
pop bc,af : ei : ret
int03 push af : ld a,lo(int04) : ld (#39),a : pop af : ei : ret
int04 push af,bc
; changer l'adresse de l'écran suivant AVANT qu'il arrive!
ld bc,#BC00+12 : out (c),c : ld bc,#BD20 : out (c),c ; écran suivant en #8000
; attendre 16 lignes soit 16x64 nops ou 4x256 nops, le DJNZ qui saute prend 4 nops
ld b,0 : djnz $
ld bc,#7F02 : out (c),c : ld a,#47 : out (c),a ; petit changement de couleur
ld bc,#BC07 : out (c),c : ld bc,#BD00+11 : out (c),c
ld bc,#BC06 : out (c),c : ld bc,#BD00+8 : out (c),c ; on raccourci le deuxième écran visible
ld bc,#BC04 : out (c),c : ld bc,#BD00+19 : out (c),c ; le deuxième écran "complet" fait 20 blocs pour avoir 39 au total
ld a,lo(int05) : ld (#39),a
pop bc,af : ei : ret
int05 push af : ld a,lo(int06) : ld (#39),a : pop af : ei : ret
int06 push af,bc
ld bc,#7F02 : out (c),c : ld a,#43 : out (c),a ; petit changement de couleur pendant la VBL :)
ld bc,#BC00+12 : out (c),c : ld bc,#BD30 : out (c),c ; écran du haut en #C000
ld a,lo(int01) : ld (#39),a ; et on revient à la première INT
pop bc,af : ei : ret

Voici notre programme complet modifié.

J'ai jouté une permutation du contenu des écrans dans la sous-section appelée "programmeInterruptible".

Il vous faudra les images [chien] et [chat] pour continuer ;)

buildsna : bankset 0
org #100 : run #100

ld sp,#100 : ld hl,#C9FB : ld (#38),hl : ei ; init pile+INT

ld bc,#BC06 : out (c),c : ld bc,#BD00+19 : out (c),c

ld bc,#7F00 : out (c),c : ld a,#54 : out (c),a
ld bc,#7F01 : out (c),c : ld a,#4E : out (c),a
ld bc,#7F02 : out (c),c : ld a,#43 : out (c),a
ld bc,#7F03 : out (c),c : ld a,#4B : out (c),a

call LaPause

novbl ld b,#F5
.vbl in a,(c) : rra : jr nc,.vbl
.novbl in a,(c) : rra : jr c,.novbl
ld b,0 : djnz $
; il nous reste presque 2000 nops avant l'interruption à venir
; largement le temps de préparer notre vecteur, mais attention!
; le point critique reste l'initialisation!
di ; facultatif ici car on SAIT
ld a,#C3 : ld hl,int01
ld (#38),a : ld (#39),hl
ei ; idem

;***********************************
programmeInterruptible
;***********************************
; copier la bank 3 en bank 1 puis effacer la bank 3
ld hl,#C000 : ld de,#4000 : ld bc,16384 : ldir
ld hl,#C000 : ld de,#C001 : ld bc,16383 : ld (hl),0 : ldir : call LaPause

; copier la bank 2 en bank 3 puis effacer la bank 2
ld hl,#8000 : ld de,#C000 : ld bc,16384 : ldir
ld hl,#8000 : ld de,#8001 : ld bc,16383 : ld (hl),0 : ldir : call LaPause

; copier la bank 1 en bank 2 puis effacer la bank 1
ld hl,#4000 : ld de,#8000 : ld bc,16384 : ldir
ld hl,#4000 : ld de,#4001 : ld bc,16383 : ld (hl),0 : ldir : call LaPause
jr programmeInterruptible

LaPause
ld bc,0 : ld a,5
.unepause djnz $ : dec c : jr nz,.unepause : dec a : jr nz,.unepause
ret

;***********************************
; pool d'interruptions
;***********************************
align 256
int01 push af : ld a,lo(int02) : ld (#39),a : pop af : ei : ret
int02 push af,bc
ld bc,#BC07 : out (c),c : ld bc,#BD00+255 : out (c),c ; desactiver la VBL
ld bc,#BC06 : out (c),c : ld bc,#BD00+19 : out (c),c ; notre écran visible fait 19 blocs
ld bc,#BC04 : out (c),c : ld bc,#BD00+18 : out (c),c ; notre écran "complet" fait 19 blocs (R4=19-1)
ld a,lo(int03) : ld (#39),a
pop bc,af : ei : ret
int03 push af : ld a,lo(int04) : ld (#39),a : pop af : ei : ret
int04 push af,bc
; changer l'adresse de l'écran suivant AVANT qu'il arrive!
ld bc,#BC00+12 : out (c),c : ld bc,#BD20 : out (c),c ; écran suivant en #8000
; attendre 16 lignes soit 16x64 nops ou 4x256 nops, le DJNZ qui saute prend 4 nops
ld b,0 : djnz $
ld bc,#7F02 : out (c),c : ld a,#47 : out (c),a ; petit changement de couleur
ld bc,#BC07 : out (c),c : ld bc,#BD00+11 : out (c),c
ld bc,#BC06 : out (c),c : ld bc,#BD00+8 : out (c),c ; on raccourci le deuxième écran visible
ld bc,#BC04 : out (c),c : ld bc,#BD00+19 : out (c),c ; le deuxième écran "complet" fait 20 blocs pour avoir 39 au total
ld a,lo(int05) : ld (#39),a
pop bc,af : ei : ret
int05 push af : ld a,lo(int06) : ld (#39),a : pop af : ei : ret
int06 push af,bc
ld bc,#7F02 : out (c),c : ld a,#43 : out (c),a ; petit changement de couleur pendant la VBL :)
ld bc,#BC00+12 : out (c),c : ld bc,#BD30 : out (c),c ; écran du haut en #C000
ld a,lo(int01) : ld (#39),a ; et on revient à la première INT
pop bc,af : ei : ret

teucha defb #54,#4E,#43,#4B

org #8000 : incbin 'iench.bin'
org #C000 : incbin 'teucha.bin' ; petite image

Le résultat en visuel avec un debug sur le contenu de la mémoire en bonus :)

Voilà, j'espère avoir démystifié un peu les ruptures, il y a finalement assez peu de choses à savoir, le plus difficile étant de se faire une bonne synchronisation. Nous parlerons dans un autre article (pas encore écrit) de la façon dont on peut organiser son écran pour optimiser les timings et ou améliorer le visuel, augmenter la possibilité d'optimisations ;)

Bientôt ^_^

Roudoudou

★ ANNÉE: 2026
★ AUTEUR: Roudoudou

Je participe au site:

» Vous avez remarqué une erreur dans ce texte ?
» Aidez-nous à améliorer cette page : en nous contactant via le forum ou par email.

CPCrulez[Content Management System] v8.732-desktop/c
Page créée en 340 millisecondes et consultée 18 fois

L'Amstrad CPC est une machine 8 bits à base d'un Z80 à 4MHz. Le premier de la gamme fut le CPC 464 en 1984, équipé d'un lecteur de cassettes intégré il se plaçait en concurrent  du Commodore C64 beaucoup plus compliqué à utiliser et plus cher. Ce fut un réel succès et sorti cette même années le CPC 664 équipé d'un lecteur de disquettes trois pouces intégré. Sa vie fut de courte durée puisqu'en 1985 il fut remplacé par le CPC 6128 qui était plus compact, plus soigné et surtout qui avait 128Ko de RAM au lieu de 64Ko.