Skip to content

Instantly share code, notes, and snippets.

@ped7g
Last active October 12, 2020 22:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ped7g/59e7b3941177444ffb48ac974f3e8ba6 to your computer and use it in GitHub Desktop.
Save ped7g/59e7b3941177444ffb48ac974f3e8ba6 to your computer and use it in GitHub Desktop.
ZX Spectrum Next - ULA double buffering example v2 (advanced techniques: DMA + custom IM1 interrupt)
DEFINE USE_DMA_TO_CLEAR_SCREEN ; comment out to get LDIR clear version
DEFINE USE_DOUBLE_BUFFERING ; comment out to see single-buffer redraw issues
DEVICE ZXSPECTRUMNEXT
BORDER MACRO color?
ld a,color?
out (254),a
ENDM
ORG $8000 ; this example does map Bank5/Bank7 ULA to $4000 (to use "pixelad" easily)
start:
nextreg $07,3 ; ensure 28MHz
; init zxnDMA to known state and with some values partially pre-loaded
ld hl,dmaInitData
ld bc,(dmaInitSize<<8) + $6B
otir
; init IM1 interrupt mode with RAM mapped into ROM area to provide our custom handler
nextreg $50,rst38page ; map 8ki page with IM1 handler to $0000 ROM area
im 1 ; make sure IM1 interrupt mode is used
nextreg $22,%00000'110 ; disable ULA interrupt, enable video-line interrupt, line.MSB=0
nextreg $23,0 ; line.LSB=0 ("moves" IM1 interrupt about ~100T ahead of pixel[0,0])
ei
mainLoop:
halt ; wait for custom interrupt (in IM1 mode mapped in ROM area)
call ClearUlaBuffer ; clear screen (make it blink without double-buffering)
call drawDots
jr mainLoop
ClearUlaBuffer:
BORDER 1 ; blue border to "time" DMA ULA clearing
IFDEF USE_DMA_TO_CLEAR_SCREEN
xor a
ld (dmaSourceByte),a
ld hl,dmaClearPixels
ld bc,(dmaClearPixelsSize<<8) + $6B
otir
ld a,$05 ; black paper, cyan ink
ld (dmaSourceByte),a
ld hl,dmaClearAttr
ld bc,(dmaClearAttrSize<<8) + $6B
otir
ELSE
ld hl,$4000
ld de,$4001
ld bc,$1800
ld (hl),l
ldir
ld (hl),$05 ; black paper, cyan ink
ld bc,$02FF
ldir
ENDIF
BORDER 0
ret
drawDots:
; adjust position for next frame
ld de,(DotPos)
inc e ; ++X
ld (DotPos),de
BORDER 2 ; red border to "time" dot drawings
; draw dot on every second line
ld b,192/2
dotLoop:
pixelad
setae
ld (hl),a
inc d
inc d
djnz dotLoop
BORDER 0
ret
dmaSourceByte:
db 0
ULABank:
db 10 ; holds current ULA screen in use
DotPos:
db 0, 0 ; X=0, Y=0
dmaInitData:
hex C3 C3 C3 C3 C3 C3 ; reset DMA from any possible state (6x reset command)
db %10'0'0'0010 ; WR5 = stop on end of block, /CE only
db %1'0'000000 ; WR3 = all disabled (important only for real DMA chips)
db %0'01'11'1'01 ; WR0 = A->B transfer
dw dmaSourceByte ; + port address A (byte to fill memory with)
db 0 ; + size.LO = always zero
db %0'1'10'0'100 ; WR1 = A address fixed, memory
db 2 ; + custom 2T timing
db %0'1'01'0'000 ; WR2 = B address ++, memory
db 2 ; + custom 2T timing
db %1'01'0'01'01 ; WR4 = continuous mode
db 0 ; + port address B.LO = always zero
dmaInitSize: EQU $ - dmaInitData
dmaClearPixels:
db %1'01'0'10'01, $40 ; WR4: addresB.HI = $40 (no other change)
db %0'10'00'1'01, $18 ; WR0: size.HI = $18 (no other change)
hex CF 87 ; LOAD + ENABLE (transfer is executed)
dmaClearPixelsSize: EQU $ - dmaClearPixels
dmaClearAttr:
db %1'01'0'10'01, $58 ; WR4: addresB.HI = $58 (no other change)
db %0'10'00'1'01, $03 ; WR0: size.HI = $03 (no other change)
hex CF 87 ; LOAD + ENABLE (transfer is executed)
dmaClearAttrSize: EQU $ - dmaClearAttr
; create new custom interrupt handler routine at $0038
ORG $A038 ; put it into next page after main code at $8000 at +$38 offset
DISP $0038 ; but assemble it as if destined for address $0038
rst38:
rst38page: EQU $$$$ ; remember "physical" 8ki page where this code is landing (it's 5)
IFDEF USE_DOUBLE_BUFFERING ; if double-buffering is OFF, then nothing to do here
push af
BORDER 5 ; cyan border to "time" IM1 handler
; switch currently displayed ULA (ASAP to make it switch before first line will start)
ld a,(ULABank) ; Get screen to display this frame
; if A==14, then do NR_69=64 (display Bank7), if A==10: NR_69=0 (display Bank5)
; its %0000'1110 vs %0000'1010 in binary, so extract bit2 and move it to bit6
and %0000'0100 ; $04 from A=14, $00 from A=10
swapnib ; bit6 set from bit2
nextreg $69,a ; Select Timex/ULA screen to show
; switch the active drawing buffer at $4000 to make next drawing into new buffer
ld a,(ULABank)
xor 10^14 ; alternate between 10 and 14
ld (ULABank),a
nextreg $52,a ; map the new "backbuffer" to $4000 (for next drawing)
; exit interrupt handler
BORDER 0
pop af
ENDIF
ei
reti
ENDT ; end the DISP block
SAVENEX OPEN "flipULA2.nex", start, $BF00 : SAVENEX AUTO : SAVENEX CLOSE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment