Witajcie,
Przeglądając kod mojego starego demka (z przełomu 1990/1991) znalazłem ciekawą (niestety bardzo słabo przenośną na klony, inne modele i rozszerzone gumiaczki) metodę uruchamiania kodu na przerwaniach.
Metoda polega na wykorzystaniu trybu przerwań IM 2 procesora Z80 (tryb ma chyba nazwę vectored interrupt), a polega na tym, że do rejestru I wstawia się starszy bajt adresu tablicy zawierającej adresy procedur obsługujących przerwania, której adres w teorii (chyba również w praktyce, choć podobno Z80 nie obsługuje poprawnie nieparzystych indeksów, czyli potrafi procedurę z spod nieparzystego indeksu również wywołać) powinna być w momencie wygenerowania przerwania wczytana z szyny danych z urządzenia zewnętrznego.
Trik, który stosowaliśmy polegał na dwóch założeniach:
- W niezmodyfikowanym ROM-ie ZX Spectrum 48k od adresu $3900 znajduje się ciągły blok ponad 257 bajtów wypełnionych wartościami $FF, więc ustawienie rejestru I na wartość $39 (przy trybie przerwań IM 2) powodowało przy każdym przerwaniu VBLANK skok do adresu $FFFF, bo niezależnie jaka wartość była aktualnie na szynie danych ($00 - $FF), to w bloku pamięci ROM wskazanym jako tablica wektorów przerwań ($3900 - $3A00, bo I = $39) zawsze otrzymywaliśmy adres procedury obsługi przerwania = $FFFF.
Uwaga: tablica wektorów przerwań musi mieć rozmiar 257 bajtów, a nie 256, bo potencjalnie na szynie danych może pojawić się wartość $FF i wtedy adres procedury obsługi przerwania jest pobierany z lokalizacji $39FF i $3A00 (257 bajt, licząc od $3900).
- W pamięci ROM pod adresem $0000 mieści się wartość $F3, więc jeśli do komórki pamięci $FFFF wpiszemy opcode dla rozkazu JR ($18), to rozkaz ten przy wykonaniu w trakcie obsługi przerwania pobierze swój operand z kolejnego adresu, czyli $0000, co w efekcie spowoduje skok pod adres $FFF4, gdzie umieszczaliśmy rozkaz JP irq_routine
Przykładowy kod, który korzysta z ten sztuczki i działa chyba tylko na oryginalnym ZX Spectrum 48k (w formacie pasmo) może wyglądać tak:
org $8000
start:
call irq_init
; basically do nothing, as the main job of changing backgroud colors
; is done in the irq routine
loop:
halt
jr loop
irq:
di
push af
push hl
ld hl, color
ld a, (hl)
and 7
out (254), a
inc (hl)
pop hl
pop af
ei
ret
; VARIABLES
color: db 0
;; ========================================================
;; IRQ Init Routine
;; ========================================================
irq_init:
halt
di
ld hl, $FFFF
ld (hl), $18 ; JR opcode
; set the IM2 mode to use jump table at $3900 (block of $FF in ROM)
ld a, $39
ld i, a
im 2
; insert JP irq at address $FEFE
ld hl, $FFF4
ld (hl), $C3 ; JP opcode
inc hl
ld (hl), LOW irq
inc hl
ld (hl), HIGH irq
ei
retn
end $8000
Tak to robiliśmy wtedy ... oczywiście ten kod można trochę poprawić (pracuje wtedy np. na 128k), używając spreparowanej tablicy wektorów w pamięci RAM, co realizuje poniższa procedura irq_init, którą można podmienić we wcześniejszym kodzie.
;; ========================================================
;; IRQ Init Routine - more generic
;; ========================================================
irq_init:
halt
di
; fill 257 bytes starting from $FD00 with $FE,
; so IRQ in IM 2 mode will always jump to $FEFE
ld hl, $FD00
ld de, $FD01
ld bc, 257
ld (hl), $FE
ldir
; set the IM2 mode to use jump table at $FD00
ld a, $FD
ld i, a
im 2
; insert JP irq at address $FEFE
ld hl, $FEFE
ld (hl), $C3 ; JP opcode
inc hl
ld (hl), LOW irq
inc hl
ld (hl), HIGH irq
ei
ret
Pozdrawiam i ciekaw jestem waszych uwag!