MAKECART EQU 1 ; assemble with "-D MAKECART" to define this START EQU 0D40H ; start of code in built-in version A158B EQU 158BH ; ASCII bitmaps L1439 EQU 1A55H ; jump to built-in version (= 1439 in original ROM) INCLUDE coleco.h ;----------------------------------------------------------------------- ; RAM storage ;----------------------------------------------------------------------- ORG 7030H ;FrameCount DS 1 PrevDir1 DS 2 PrevKey1 DS 2 PrevDir2 DS 2 PrevKey2 DS 2 ; Index register offsets PrevDir EQU 0 PrevKey EQU 2 LSpin DS 1 RSpin DS 1 ;----------------------------------------------------------------------- ; Character codes for special font characters ;----------------------------------------------------------------------- NE EQU 01H NW EQU 02H SE EQU 03H SW EQU 04H F1 EQU 05H F2 EQU 06H F3 EQU 07H F4 EQU 08H LBar EQU 09H RBar EQU 0AH NoBar EQU 0BH Bar0 EQU 0CH Bar1 EQU 0DH Bar2 EQU 0EH Bar3 EQU 0FH Bar4 EQU 10H Bar5 EQU 11H Bar6 EQU 11H Bar7 EQU 12H IF MAKECART ;----------------------------------------------------------------------- ; Header for standalone cartridge ;----------------------------------------------------------------------- ORG 8000H DW 55AAH ; Cartridge signature bytes DW 0,0 ; No RAM sprite tables DW 0 ; No VDP_Temp storage DW 7000H ; Address of controller state table DW MAIN ; Main entry point JP 0000H ; RST 08 JP 0000H ; RST 10 JP 0000H ; RST 18 JP 0000H ; RST 20 JP 0000H ; RST 28 JP 0000H ; RST 30 JR RST38 ; RST 38 NOP JR RETN ; NMI NOP DB 'CONTROLLER TESTER/BRUCE''S/2004' RETN RETN ELSE ;----------------------------------------------------------------------- ; Put code in ROM space ;----------------------------------------------------------------------- ORG 0038H ; at 0038H, JP RST38 ; put a jump to our RST 38 interrupt handler ORG L1439 ; at the equivalent to 1439H in the original ROM, JP MAIN ; put a jump to the main code ORG START ENDIF ; MAKECART Main IM 1 ; initialize Z80 interrupt mode = always RST 38H DI ; disable interrupts for now ; Loop here until button pressed. ; This prevents the test from coming up due to screen blank timeout. ; Comment out this block if making a standalone cartridge. IF !MAKECART Main_a OUT (IO_Joy_Select),A ; select left fire/stick mode IN A,(IO_Joy1) ; read controller 1 status LD B,A IN A,(IO_Joy2) ; read controller 2 status AND B ; OR the two controllers together AND 40H ; mask out the left fire button bit JR NZ,Main_a ; loop until something pressed ENDIF ; !MAKECART CALL InitScrn ; re-initialize the VDP LD HL,1800H ; clear the screen LD DE,0300H LD A,' ' LD BC,01C0H ; Unblank the screen CALL WriteReg CALL ClearXtra ; Put static text on screen LD A,32 ; Initialize spinner counts LD (LSpin),A LD (RSpin),A EI ; Enable spinner interrupt Loop CALL VDP_Status ; wait for vblank AND 80H ; (note: reading clears the flag!) JR Z,Loop CALL ReadCtlRaw ; read controls CALL UpdFire ; draw directionals on screen CALL UpdDir ; draw fire buttons on screen CALL UpdKpd ; draw keypad on screen CALL UpdSpin ; draw spinner count on screen JR Loop ; JR $ ; that's all for today ;----------------------------------------------------------------------- ; ; This is the handler for the RST 38 interrupt generated by the ; spinner control. It first tests if a cartridge is installed, ; and jumps to the cartridge's RST 38 handler if so. This does ; add some latency, but apparently RST 38 is normally only used ; for the spinner control. ; ; Comment out everything between PUSH AF and PUSH HL if making ; a standalone cartridge. ; ;----------------------------------------------------------------------- RST38 PUSH AF IF !MAKECART LD A,(8000H) CP 55H JR NZ,RST38_a LD A,(8001H) CP 0AAH JR NZ,RST38_a POP AF JP 801EH ENDIF ; !MAKECART RST38_a PUSH HL CALL ReadSpinner POP HL POP AF EI RET ;----------------------------------------------------------------------- ; ;----------------------------------------------------------------------- ClearXtra LD HL,XTRA_TBL ClearXtra_a LD E,(HL) ; get next screen address INC HL LD D,(HL) INC HL LD A,D ; exit if address = FFFF AND E INC A RET Z LD B,(HL) ; get text length INC HL ClearXtra_b LD A,(HL) INC HL CALL WrScrnByte INC DE DJNZ ClearXtra_b JR ClearXtra_a ;----------------------------------------------------------------------- ; ; F1=018C F2=01CC F1=01AC F2=01EC ;----------------------------------------------------------------------- UpdFire LD H,0 ; left ctl LD DE,018CH ; screen position CALL UpdFire_a LD H,1 ; right ctl LD E,9CH ; screen position UpdFire_a LD L,0 ; left fire button LD B,F1 ; F1 character code CALL UpdFire_b LD L,1 ; right fire button LD A,E ; F2 screen position ADD A,40H LD E,A LD B,F2 ; F2 character code UpdFire_b PUSH BC CALL MyReadCtl ; read controller POP BC RLA ; convert 40H to 80H ADD A,B ; add in F1/F2 code JP WrScrnByte ; write to screen and return ;----------------------------------------------------------------------- ; ;----------------------------------------------------------------------- UpdDir LD H,0 ; left ctl LD E,0 ; screen position CALL UpdDir_a LD H,1 ; right ctl LD E,10H ; screen position UpdDir_a LD L,0 ; select joystick mode CALL MyReadCtl ; read controller LD A,B ; get controller bits LD HL,DIR_TBL UpdDir_b LD C,(HL) ; get character code LD A,C OR A RET Z ; exit if end of table PUSH DE INC HL LD A,(HL) ; get low byte of screen address INC HL ADD A,E LD E,A LD D,(HL) ; get high byte of screen address INC HL LD A,B ; get direction bits AND (HL) ; AND with direction bit mask INC HL CP (HL) ; test for desired bits INC HL LD A,C ; get character code JR NZ,UpdDir_c ; branch if direction bit not set XOR 80H ; inverse video if set LD C,A UpdDir_c CALL WrScrnByte ; write to screen and return POP DE ; restore screen base JR UpdDir_b ; loop for next direction bit ;----------------------------------------------------------------------- ; ;----------------------------------------------------------------------- UpdKpd LD H,0 ; left ctl LD E,0 ; screen position CALL UpdKpd_a LD H,1 ; right ctl LD E,10H ; screen position UpdKpd_a LD L,1 ; select keypad mode CALL MyReadCtl ; read controller LD A,B ; get keypad bits AND 0FH LD HL,KPD_TBL UpdKpd_b LD C,(HL) ; get character code INC HL LD A,C OR A RET Z ; exit if end of table LD A,(HL) ; get key code INC HL PUSH DE PUSH AF LD A,(HL) ; get low byte of screen address INC HL ADD A,E LD E,A LD D,(HL) ; get high byte of screen address INC HL POP AF CP B ; test key code LD A,C ; get character code JR NZ,UpdKpd_c ; branch if key doesn't match XOR 80H ; inverse video if set LD C,A UpdKpd_c CALL WrScrnByte ; write to screen and return POP DE ; restore screen base JR UpdKpd_b ; loop for next direction bit ;----------------------------------------------------------------------- ; ; ENTRY: H = 0 for left controller, 1 for right controller ; L = 0 for joystick/left fire/spinner, 1 for keypad/right fire ; EXIT: A = 40H if fire button set ; B = joystick directionals or raw key code ; C = spinner pulse counter if L = 0 ; ;----------------------------------------------------------------------- MyReadCtl PUSH DE PUSH HL CALL _ReadCtl LD A,H ; fire button bit AND 40H ; (exits with Z-flag set properly) LD B,L ; joystick directionals/raw key code LD C,E ; spinner pulse count (left only) POP HL POP DE RET ;*************************************** ; 1F79 ReadCtl ; ; Read a joystick or keypad controller and a fire button ; ; ENTRY H = 0 for left control, 1 for right ; L = 0 for joystick/left fire, 1 for keypad/right fire ; EXIT: H = fire button in 40H bit ; L = joystick directionals or key code ; E = old pulse counter (only if L=0) ;*************************************** _ReadCtl LD A,L ; Check if reading keypad CP 01H JR Z,_ReadCtl_b ; Branch if reading keypad LD BC,PulseCnt1 ; Point BC to pulse counter LD A,H OR A JR Z,_ReadCtl_a ; branch if left controller INC BC ; Point BC to PulseCnt2 _ReadCtl_a LD A,(BC) ; E = old pulse counter value LD E,A ; XOR A ; Clear pulse counter ; LD (BC),A LD A,H OR A LD A,(Joy1Shad) JR Z,L1148_a LD A,(Joy2Shad) L1148_a LD D,A ; Save port bits in D AND 0FH LD L,A ; L = directional bits JR _ReadCtl_c ; Read the keypad _ReadCtl_b LD A,H OR A LD A,(Key1Shad) JR Z,L1148_b LD A,(Key2Shad) L1148_b LD D,A ; Save fire button bit AND 0FH ; Mask off keypad bits LD HL,Keypad_Table ; Index into keypad table LD B,00H LD C,A ADD HL,BC LD L,(HL) ; L = key code (or 0FH if none) _ReadCtl_c LD A,D AND 40H LD H,A ; H = right fire button bit RET Keypad_Table DB 0FH,06H,01H,03H DB 09H,00H,0AH,0CH DB 02H,0BH,07H,0DH DB 05H,04H,08H,0FH ;----------------------------------------------------------------------- ; ;----------------------------------------------------------------------- UpdSpin LD HL,LSpin LD DE,PulseCnt1 LD A,(DE) ; add spin count to total ADD A,(HL) LD (HL),A LD A,0 ; clear out spinner count LD (DE),A INC HL INC DE LD A,(DE) ; add spin count to total ADD A,(HL) LD (HL),A LD A,0 ; clear out spinner count LD (DE),A DEC HL LD DE,02A4H ; screen position LD HL,LSpin CALL UpdSpin_a LD E,B4H ; screen position INC HL UpdSpin_a PUSH DE ; clear out existing bar LD B,8 LD A,NoBar UpdSpin_b CALL WrScrnByte INC E DJNZ UpdSpin_b POP DE LD A,(HL) ; get count RRCA ; div 8 RRCA RRCA AND 07H ; mod 64 ADD A,E ; find character offset LD E,A UpdSpin_c LD A,(HL) ; get count AND 07H ; get low bits ADD A,Bar0 ; convert to bar position ; JR WrScrnByte ; write to screen and return ;----------------------------------------------------------------------- ; ; DE = VRAM address ; A = byte to write ; ;----------------------------------------------------------------------- WrScrnByte PUSH AF PUSH BC PUSH DE PUSH HL PUSH AF ; add offset to name table base LD A,D ADD A,18H LD D,A POP AF EX DE,HL ; put screen address in HL LD DE,1 ; count = 1 CALL FillVRAM ; write byte, destroys AF, C, DE POP HL POP DE POP BC POP AF RET ;----------------------------------------------------------------------- ; ;----------------------------------------------------------------------- InitScrn XOR A ; Fill VRAM from address 0000H LD H,A ; with 00H LD L,A ; LD DE,4000H ; length 4000H CALL FillVRAM ; Do the fill CALL InitVDP ; Initialize the video chip LD HL,2000H ; Initialize color table to Wht/Blk LD A,0F0H LD DE,32 CALL FillVRam InitMyFont CALL InitFont ; Load normal ASCII bitmaps LD HL,A158B ; Point to main ASCII bitmaps LD DE,8*009DH ; Address of character code storage LD BC,8*0063H ; Length of data CALL NotWrtVRAM ; Copy inverse video data LD HL,CustomFont ; Write custom font characters LD DE,8*NE LD BC,CustomFontSize CALL XWrtVRAM LD HL,CustomFont ; Write inverse custom font characters LD DE,8*(NE+80H) LD BC,CustomFontSize JP NotWrtVRAM ; custom font characters CustomFont DB 090H,0D0H,0B0H,097H,004H,006H,004H,007H ; 01 = 'NE' DB 090H,0D0H,0B0H,090H,000H,011H,015H,00AH ; 02 = 'NW' DB 070H,040H,020H,017H,074H,006H,004H,007H ; 03 = 'SE' DB 070H,040H,020H,010H,070H,011H,015H,00AH ; 04 = 'SW' DB 078H,040H,070H,040H,040H,048H,008H,008H ; 05 = 'F1' DB 078H,040H,070H,040H,04CH,054H,008H,01CH ; 06 = 'F2' DB 078H,040H,070H,048H,054H,048H,004H,018H ; 07 = 'F3' DB 078H,040H,070H,044H,04CH,054H,03EH,004H ; 08 = 'F4' DB 001H,001H,001H,001H,001H,001H,001H,001H ; 09 = left edge DB 080H,080H,080H,080H,080H,080H,080H,080H ; 0A = right edge DB 0FFH,000H,000H,000H,000H,000H,000H,0FFH ; 0B = no bar DB 0FFH,080H,080H,080H,080H,080H,080H,0FFH ; 0C = bar 0 DB 0FFH,040H,040H,040H,040H,040H,040H,0FFH ; 0D = bar 1 DB 0FFH,020H,020H,020H,020H,020H,020H,0FFH ; 0E = bar 2 DB 0FFH,010H,010H,010H,010H,010H,010H,0FFH ; 0F = bar 3 DB 0FFH,008H,008H,008H,008H,008H,008H,0FFH ; 10 = bar 4 DB 0FFH,004H,004H,004H,004H,004H,004H,0FFH ; 11 = bar 5 DB 0FFH,002H,002H,002H,002H,002H,002H,0FFH ; 12 = bar 6 DB 0FFH,001H,001H,001H,001H,001H,001H,0FFH ; 13 = bar 7 CustomFontSize EQU $ - CustomFont ;======================================= ; NotWrtVRAM ; ; Write inverted data to VRAM (such as for an inverse video font) ; ; ENTRY HL points to data to be written ; DE = VRAM address ; BC = byte count ; EXIT: HL = first byte after data that was written ; AF, BC destroyed ;======================================= NotWrtVRAM LD A,E ; Send LSB of address OUT (IO_VDP_Addr),A LD A,D ADD A,40H ; Send MSB of address + 40H OUT (IO_VDP_Addr),A NotWrtVRAM_a LD A,(HL) ; Get next byte CPL ; Invert the bits OUT (IO_VDP_Data),A ; Send it to VRAM INC HL DEC BC LD A,B OR C JR NZ,NotWrtVRAM_a RET ;----------------------------------------------------------------------- ; This is a wrapper around WrtVRAM to get around a bug which ; causes it to write 256 bytes too few if (B != 00H) && (C != 00H) ; ; (ReadVRAM has the same bug) ; ; ENTRY HL = data address ; DE = VRAM address ; BC = byte count ;----------------------------------------------------------------------- XWrtVRAM LD A,C ; no bug if C=00H OR A JR Z,XWrtVRAM_a INC B ; else account for the missing page XWrtVRAM_a JP WrtVRAM ;----------------------------------------------------------------------- DIR_TBL DB '+' ; display character DW 00A8H ; screen address DB 0FH,00H ; mask and compare bits DB 'N' DW 0028H DB 0BH,01H DB 'E' DW 00ACH DB 07H,02H DB 'S' DW 0128H DB 0EH,04H DB 'W' DW 00A4H DB 0DH,08H DB NW DW 0045H DB 09H,09H DB NE DW 004BH DB 03H,03H DB SW DW 0105H DB 0CH,0CH DB SE DW 010BH DB 06H,06H DB 0 ;----------------------------------------------------------------------- KPD_TBL DB '0',00H ; display character and keypad code DW 0246H ; screen address DB '1',01H DW 0183H DB '2',02H DW 0186H DB '3',03H DW 0189H DB '4',04H DW 01C3H DB '5',05H DW 01C6H DB '6',06H DW 01C9H DB '7',07H DW 0203H DB '8',08H DW 0206H DB '9',09H DW 0209H DB '*',0AH DW 0243H DB '#',0BH DW 0249H DB F3,0CH DW 020CH DB F4,0DH DW 024CH DB 00H ;----------------------------------------------------------------------- XTRA_TBL DW 0003H ; screen address DB 2,'#1' ; length and display text DW 0013H DB 2,'#2' DW 02A3H DB 10,LBar,NoBar,NoBar,NoBar,NoBar,NoBar,NoBar,NoBar,NoBar,RBar DW 02B3H DB 10,LBar,NoBar,NoBar,NoBar,NoBar,NoBar,NoBar,NoBar,NoBar,RBar DW 02E5H DB 22,'Controller Tester V1.0' DW 0FFFFH IF !MAKECART ; this point should be less than 1105H for built-in version IF . >= 1105H ERROR code too large! ENDIF ENDIF ; !MAKECART END