Code: Select all
;===================================================
;= =
;= ZX81 interrupt handler, By Andrew Rea. =
;= =
;= due to the complex way the ZX81 generates its =
;= display during 'SLOW' mode if you wish to =
;= carry out a regular task at every interrupt =
;= you must manipulate the IX register which is =
;= used as a jump vector within the ZX81 rom. =
;= =
;= But to keep a steady display you must also =
;= either copy the rom code to generate the display=
;= or write your own code. =
;= =
;= this solution uses mostly the original rom. =
;= =
;= it also saves and restores the IY register, =
;= which is use in the rom code, normally you =
;= should not run USER code that uses the IY =
;= but since it is saved things seem to work ok =
;= =
;===================================================
;writtne for tasm assembler, with undocumented instructions
;supported.
;
; Compile with "tasm -80 -b input_source_code.asm output_binary_file.bin
;
;
#define db .byte ; TASM cross-assembler definitions
#define DB .byte
#define dw .word
#define DW .word
#define ds .block
#define DS .block
#define org .org
#define ORG .org
#define end .end
#define END .end
;=========================
;= =
;= you can change the =
;= following to suit the =
;= address of the PT3 =
;= player module. =
;= =
;=========================
PLAYER_START .EQU $2000
PLAYER_PLAY .EQU $2002
PLAYER_MUTE .EQU $2005
;this will be compiled as a ZX81 .p file
org $4009
;= System variables ============================================
db 0 ;VERSN
dw 0 ;E_PPC
dw dfile ;D_FILE
dw dfile+1 ;DF_CC
dw var ;VARS
dw 0 ;DEST
dw var+1 ;E_LINE
dw last-1 ;CH_ADD
dw 0 ;X_PTR
dw last ;STKBOT
dw last ;STKEND
db 0 ;BERG
dw membot ;MEM
db 0 ;not used
db 2 ;DF_SZ
dw 1 ;S_TOP
db $FF,$FF,$FF ;LAST_K
db 55 ;MARGIN
dw dfile ;NXTLIN
dw 0 ;OLDPPC
db 0 ;FLAGX
dw 0 ;STRLEN
dw $0C8D ;T_ADDR
dw 0 ;SEED
dw $FFFF ;FRAMES
db 0,0 ;COORDS
db $BC ;PR_CC
db 33,24 ;S_POSN
db %01000000 ;CDFLAG
ds 33 ;Print buffer
membot:
ds 30 ;CalculatorĀ“s memory area
ds 2 ;not used
;= First BASIC line, asm code ==================================
line0:
db 0,0
dw dfile-$-2
db $ea ;zx81 token REM
;= Your code should follow this file ==========================
;=
;= This is the intterupt handler
CONTROL:
DB $00 ;used as a way of getting a signal into the interrupt handler
;
;on initialise ' call start '
;
; CONTROL = 0
;
; 0~ DON'T CALL PLAYER DURRING INT
; BUT SILENCE SOUND.
;
; 1 ~ CALL PLAYER DURRING INT
;
; 255 ~ SILENCE SOUND AND
; STOP INTTERUPT HANDLER
PT3FILE_ADDRESS:
DW $8000 ;DEFAULT ADDRESS FOR PT3 FILE, ALTER THIS
;TO THE ADDRESS OF YOUR PT3 FILE
;THEN START THE HANDLER
START:
XOR A ;ZERO
LD (CONTROL),A ;STORE IN CONTROL BYTE
;AFTER INITIALISING
;PLAYER WILL NOT PLAY
;UNTIL USER ALTERS THIS BYTE
;TO = 1
GO_HANDLER:
TEST_X:
;LD A,IXL ;margin is still in top
; DB $DD,%01111101
; CP $81
; JR NZ,TEST_X ;top of screen
;LD A,IXL
TEST_X_2:
DB $DD,%01111101 ;test for bottom
CP $8F
JR NZ,TEST_X_2 ;
;AND THEN LOAD IX REGISTER SO OUR
LD IX,END_OF_BOTTOM_MARGIN
;HANDLER IS USED INSTEAD
INIT_PLAYER:
LD HL,(PT3FILE_ADDRESS)
CALL PLAYER_START ;INITIALISE THE PLAYER
RET ;BACK TO USERS PROGRAM
;=============================
;= =
;= DISPLAY INTTERUPT HANDLER =
;= =
;==================================================
;= =
;= The display intturupt handler has 2 main parts =
;= =
;= during user program execution the NMI int are =
;= processed by incrementing the a' register (from=
;= the af' pair ) and when zero is reached the =
;= main registers AF, BC, DE, HL are pushed onto =
;= the stack and a jump is made to address held =
;= in IX. The nmi's are active during the 'blank' =
;= area's at the top and bottom of the diaply =
;= these are the top and bottom margins.
;= =
;= So by altering IX we can take control of the =
;= display intterupt cycle =
;= =
;= 1) create a vsync signal and read keyboard. =
;= 1a) start top margin, and resume user program. =
;= =
;= 2) generate the display. =
;= 2a) start bottom margin, and resume user prog- =
;= gram =
;= =
;==================================================
;NOW THE TRICKY BIT !
;
;==========
;= =
;= PART 1 =
;= =
;==========
END_OF_BOTTOM_MARGIN:
;NMI ROUTINE JUMPS HERE AT THE END OF THE BOTTOM MARGIN.
;THIS REPLACES THE $028F JUMP.
;when we get here the NMI routine has already put AF, BC, DE, HL on the stack
;HL on last
;
; sp ---> HL
; DE
; BC
; AF
;nmi is also turned off
;but i want it to return here so i can alter IX again.
;so beacuase the rom routine will pop these off before RET-ing
;i need to get my return address under 4 words on the stack
;
LD HL, AFTER_VSYNC ;the address i wish to return to
;after the call to rom.
;RIGHT GONNA TRY A DIFFERENT APPROACH TO THIS.
;
;four items on stack...
POP AF ;HL IN AF, leaves 3
POP DE ;DE IN DE, leaves 2
POP BC ;BC IN BC, leaves 1
;AF STILL ON STACK
EX (SP),IY ;swap IY and final value on stack
;IY it could be any value by now...
PUSH HL ;THE RETURN ADDRESS where execution will
;continue after ROM code has run
PUSH IY ;AF BACK ON STACK
PUSH BC ;BC BACK ON STACK
PUSH DE ;DE BACK ON STACK
PUSH AF ;HL BACK ON STACK
;so now we have IY on the stack then our return address and then the original
;4 registers pushed by the NMI routine.
LD IY,$4000 ;SET IY FOR ROM CODE TO RUN CORRECTLY
JP $0229
;THIS ROM ROUTINE NORMALLY POPS THE REGISTERS AND
;RETURNS TO USER CODE EXECUTION
;WITH IX LOADED $0281, READY FOR NEXT VIDEO INT.
;BUT NOW IT RETURNS HERE, BECAUSE I SHOVED EXTRA STUFF
;ON THE STACK
AFTER_VSYNC:
START_OF_TOP_MARGIN:
;so we should have returned here
;with just IY left on the stack
POP IY
;NOW ALL THIS PUSHING AND POPPING MEANS WE HAVE MISSED THE FIRST HSYNC
;REMEBER HSYNC ARE NOT RESET BY VSYNC, BUT A NORMAL INT ACK.
;SO NOW ALL THE CHARACTERS ARE MOVED UP 1 PIXEL LINE (LINE COUNTER IN ula)
;SO MUST DECREMENT THE NMI INTERUPT COUNTER (IT ACTUALLY COUNTS UP)
;
;Ha Ha, I've known for a while that eightyone does not correctly
;handle the hsync timming. so the character are displayed wrong on eightyone.
;
;wonder if this will work on any other emulator correctly ?
;vb81xur displays it ok, but my test for IXL going to $8f doesn't seem
;to work on VB81xur
;originally was turning off nmi whilst adjusting the a' register
;but tried it without and it works just fine on real ZX81
;could be another place for emulators to fall over ;-)
; out ($fd),a ;TURN OFF nmi JUST INCASE AN NMI HAPPENS WITH THE
ex af,af' ;THE WRONG AF PAIR ACTIVE
inc a ;DECREMENT THE LINE COUNTER (YES IT COUNTS UP)
ex af,af' ;SWAP BACK TO NORMAL AF
; out ($fe),a ;AND TURN ON THE nmi AGAIN
RESET_IX_VECT: ;FINALLY WE ARE READY TO RETURN TO USER CODE
LD IX,END_OF_TOP_MARGIN ;SET THE IX REG READY FOR NEXT END OF TOP MARGIN
RET ;RETURN TO USER PROGRAM
END_OF_TOP_MARGIN:
;==========
;= =
;= PART 2 =
;= =
;==========
;NMI ROUTINE HAS NOW JUMPED HERE AT THE END OF THE TOP MARGIN
;NMI'S ARE OFF
;STACK IS AS BEFORE
;MUST PRETTY MUCH COPY ROM ELSE TOP FEW LINES OF DISPLAY GET CORRUPTED
LD A,R ; SAME AS
LD BC,$1901 ; ROM
LD A,$F5 ; CODE
CALL $02B5 ; BUT....
;RETURNS HERE WHEN DISPLAY IS DONE
START_OF_BOTTOM_MARGIN
PUSH IY ;SAVE iy ONCE AGAIN
LD a,($4028) ; get the margin from sys-vars
NEG ; Negate
; normal rom increments (one less margin line)
;INC A ; MISSING THIS INC COMPENSATES FOR THE ONE
; ONE LESS ABOVE
EX AF,AF' ; place negative count of blank lines in A'
LD IX,END_OF_BOTTOM _MARGIN ;AND MAKE SURE IX IS LOADED
OUT ($FE),A ; enable the NMI generator.
; THATS PRETTY MUCH THE INTERRUPT HANDLER
; main registers are still on the stack with IY on top.
;LETS PLAY SOME MUSIC ;-)
; IF (CONTROL) = 255 THEN ix LOADED WITH NORMAL ROM VECTOR (STOPS HANDLER RUNNING )
; (NEXT TIME NMI ROUTINE )
; (DOES A JP (IX) after )
; (after the top margin. )
; AND ALSO MUTES PLAYER.
;
; IF (CONTROL) = 0 THEN MUTES PLAYER.
;
; IF (CONTROL) = 1 THEN CALLS PLAYER EVERY 1/50TH SECOND
;
; IF (CONTROL) = 'other value' THEN DO NOTHING; SO SOUND CHIP WILL CONTINUE TO SOUND...
; LAST DATA SENT TO IT...
LD A,(CONTROL)
INC A ;TEST FOR 255
JR NZ,DONT_EXIT_INTS ;ONLY 255 WILL RSULT IN ZERO
EXIT_INTS:
LD IX,$028F ;NORMAL ix VECTOR
INC A ;TRICK NEXT TEST
DONT_EXIT_INTS:
exx
push hl
exx
DEC A ;TEST FOR ZERO
CALL Z,PLAYER_MUTE ;IF IT WAS 0 RESULT WILL BE ZERO
DEC A ;OR 1
CALL Z,PLAYER_PLAY ;OTHER VALUES WILL DO NOTHING
exx
pop hl
exx ;HERE
; THATS IT FOR THE PLAYER BIT.
POPS:
pop IY ;restored from entry to int routine
POP HL ;AND FINNALY RESTORE USERS REGISTERS
POP DE ;
POP BC ;
POP AF ;
;AND ENDS HERE
RET ; BACK TO USER PROG.
;=====================================
;= your code should end here ===================================
;zx81end.asm
;used at end of file....
db $76 ;N/L end of line 0
;- Display file --------------------------------------------
dfile:
db $76
db $76,$76,$76,$76,$76,$76,$76,$76
db $76,$76,$76,$76,$76,$76,$76,$76
db $76,$76,$76,$76,$76,$76,$76,$76
;- BASIC-Variables ----------------------------------------
var:
db $80
;- End of program area ----------------------------
last:
end
hopefully you can tell what its doing from the comments....