My Tandy CoCo stuff.
29/03/2018.
3D Deathchase - Tandy Color Computer.
CoCoCode.
Here I will place a series of snippets from the routines I've written in the process of making my games. They will not be ideal for the absolute beginner but, hopefully, they will come in useful to others and help spread cococoding!
I personally use the A09 assembler a lot and the code shown here will certainly work on that. Other assemblers may require some modification to the code for it to assemble it.
One thing that is certainly worth explaining is the "Tandy disk prefix" and "suffix". These are two sections of five bytes which surround any code file that you save onto a CoCo. In the examples shown below, I include these in the code so that they are ready-made for insertion into a CoCo disk, however there are various reasons why you may not want them to be there. You may be using a different DOS, for example Dragon DOS, which uses a completely different header. In such a case, you should just comment out the two sections.
Detecting Dragon or Tandy CoCo...
You've got your universal loader (below), but how can you detect if you've loaded it on a Dragon? One simple way is just to search for the string "DRAGON" in the ROM! Take it a bit further and search for "BASIC 2" to determine if you're on a CoCo 3. Just call DETECT_MACHINE and get the result in A.
MT_COCO EQU 0 MT_DRAGON EQU 1 MT_COCO3 EQU 2 ROM_START EQU $8000 ROM_LEN EQU $4000 STRING_ASCII_DRAGON FCB "DRAGON",0 STRING_BASIC_2 FCB "BASIC 2",0 MEMSTR ; Y = String to search for (zero terminated). ; X = Start address of search space. ; U = Length of search space. PSHS U CLRB 1 LDA B,Y BNE 21F ; Found! LEAS 2,S ; Returns Z set. RTS 21 CMPA B,X BNE 22F ; Match. TSTB BNE 211F ; Keep U count. STU 0,S 211 INCB BRA 23F 22 TSTB BEQ 221F ; Get U back. LDU 0,S 221 CLRB LEAX 1,X 23 LEAU -1,U CMPU #0 BNE 1B ; NOT FOUND! LEAS 2,S CLRA INCA ; A = 0 (Z) - Found. ; A = 1 (NZ) - Not Found. ; X = Address of string in search space. RTS DETECT_MACHINE LDY #STRING_ASCII_DRAGON LDX #ROM_START LDU #ROM_LEN BSR MEMSTR BNE 1F ; Dragon LDA #MT_DRAGON RTS 1 ; Try and find the difference between a Coco1/2 and 3 here. LDY #STRING_BASIC_2 LDX #ROM_START LDU #ROM_LEN BSR MEMSTR BNE 1F LDA #MT_COCO3 RTS ; A = 0 - Tandy CoCo 1/2. ; A = 1 - Dragon. ; A = 2 - Tandy CoCo 3. 1 LDA #MT_COCO RTS
Detecting PAL/NTSC models.
You don't need to, but you like the idea of changing your game speed so that it's the same on PAL (50hz) or NTSC (60hz) models. Alternatively, you might be thinking of using an "artifact" mode, which only looks OK on an NTSC television. If you know that you're on a 60hz machine then it's a pretty good guess (although, the CoCo 3 can run with an RGB monitor, which shows no "artifacts" at all). For this routine to work, you must detect whether it's running on a Dragon first, so use the above routine to do that, then put the result in MachineFlag before calling DETECT_TV.
MachineFlag FCB 0 ; Put result of "DETECT_MACHINE" in here. HSYNCCOUNTER PSHS CC ORCC #$10|$40 LDX #0 TST $FF02 1 TST $FF03 BPL 1B TST $FF02 2 TST $FF00 3 TST $FF01 BPL 3B LEAX 1,X TST $FF03 BPL 2B PULS CC,PC VSYNCTIMER PSHS CC ORCC #$10|$40 LDX #0 TST $FF02 1 TST $FF03 BPL 1B TST $FF02 2 LEAX 1,X TST $FF03 BPL 2B PULS CC,PC DETECT_TV LDA MachineFlag RORA ; Test for Dragon. BCS 1F ; Tandy. LDX #((312-262)/2)+262 ; 287 PSHS X BSR HSYNCCOUNTER BRA 11F 1 LDX #((1193-993)/2)+993 ; 1093 PSHS X BSR VSYNCTIMER BSR VSYNCTIMER ; Called twice for the benefit of the T3 emulator! 11 ; A = 0 - PAL. ; A = 1 - NTSC. ; X = Number of lines/Arbritary number (Dragon). CLRA CMPX 0,S BHS 1F INCA 1 PULS X,PC ; X = junk. ; RTS STRLENY ; Y = Address CLRB PSHS Y 1 LDA ,Y+ BEQ 2F INCB BRA 1B 2 PULS Y,PC ; RTS
An autorunning cassette loader (for machine code programs) that works on all models of CoCo and Dragon, regardless of any disk DOS running.
The code hides in the section between $015E and $03FF, allowing you to load code just about anywhere from $0400 and upwards. It replaces just about every vector used by BASIC to jump to our EXEC point. The example here goes beyond that and loads in two block after the EXEC point. The first one is assumed to be a screen and the second a code block, which have both been saved in the usual BASIC way. The second code block should have its EXEC address set, which we jump to with JMP [$9D].
One thing to note here is the use of bCounterOn. When this is non-zero, it will decrement an on-screen counter (located at COUNTADDR) each time it loads a block from cassette (the standard is that each cassette file is broken up into 255 byte chunks). It does not know how many blocks are in the file, so you just have to figure out the value yourself and set it (in ASCII) into COUNTADDR, as the code below does.
; "Loader" by Jane McKay, 2006 VRAM EQU $0400 START EQU $015E COUNTADDR EQU VRAM + $20 ; The second line of the screen. ORG START-5 JMPEXEC MACRO FCB $7E ; JMP FCW EXEC ENDM ; Tandy disk prefix. FCB 0 FCW ENDPC-START,START ; $015E JMPEXEC JMPEXEC JMPEXEC JMPEXEC JMPEXEC JMPEXEC JMPEXEC JMPEXEC JMPEXEC JMPEXEC JMPEXEC JMPEXEC ; $0182 - read a line of input (called after c/loadm). JMPEXEC JMPEXEC JMPEXEC JMPEXEC ; $018E - user error trap (called after cloadm (syntax error)). JMPEXEC ; $0191 - system error trap. JMPEXEC ; $0194 - other stuff... (called by some machines). JMPEXEC JMPEXEC JMPEXEC JMPEXEC JMPEXEC JMPEXEC JMPEXEC ; $01A9 (beyond the RAM hooks). RMB $0200-* ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; RAM RAMStart bCounterOn FCB 0 RAMEnd ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; CODE EXEC ; This is the point of entry, we could clear everything else out, but instead the code will load in two blocks and EXEC the last one. ; Make sure ROM is paged in (especially for Dragon 64 in 64K RAM mode). ; AND set the stack pointer. ORCC #$10|$40 LDS #$0400 CLR $FFDE ; Select 32K ROM (Dragon 64) and set orange palette. LDA $FF22 ORA #$04|$08 STA $FF22 ; Make sure RESET does a cold boot - as a very light form of protection. LDA #$00 STA $0071 CLR bCounterOn BSR CLOAD INC bCounterOn LDX #COUNTADDR LDA #$30 ; 0 STA ,X+ LDA #$39 ; 9 STA ,X+ LDA #$36 ; 6 STA ,X+ BSR CLOAD JMP [$9D] CAS_FNAME EQU 0 ; 8 ASCII bytes. CAS_FTYPE EQU 8 CAS_ASCII EQU 9 CAS_GAP EQU 10 CAS_EXEC EQU 11 ; WORD CAS_ADDR EQU 13 ; WORD CAS_LENGTH EQU 15 CASHEAD FCB "TESTCAS",0 ; Must be 8 bytes. FCB $02 ; Binary. FCB $00 ; Binary Data. FCB $00 ; Continuous Data. FCW $0000 ; EXEC. FCW $0500 ; Load Address. MOTOR_OFF LDB $FF21 ANDB #~$08 STB $FF21 ; ANDCC #~($10|$40) ; Only if we're returning to BASIC... ORCC #$10|$40 ; Try to prevent any interrupts from kicking in... RTS DECBLOCK LDA bCounterOn BNE 1F RTS 1 LDX #COUNTADDR + $02 LDB #$03 1 DEC ,X LDA ,X CMPA #$30 BHS 11F LDA #$39 STA ,X LEAX -1,X DECB BNE 1B 11 RTS CLOAD JSR [$A004] ; Cass Leader Read. LDX #CASHEAD STX $7E 1 BSR DECBLOCK JSR [$A006] ; Read Block. STX $7E LDA $81 ; Check Error. BNE 3F LDA $7C ; Block Type ( 0 = Header, $FF = EOF ). BEQ 2F ; $00 = Header. INCA BEQ 3F ; $FF = EOF. (A = 0). ; Loop to next block. BRA 1B 2 ; Header. LDX CASHEAD+CAS_ADDR STX $7E ; Start address. LDX CASHEAD+CAS_EXEC STX $9D ; EXEC address. JSR [$A004] ; Cass Leader Read. BRA 1B 3 ; Error (if A is non-zero). BSR MOTOR_OFF ; A = 0 - OK. ; A != 0 - ERROR. RTS ;;;;; MEMSET ; X = Address. ; Y = Length. ; A = Byte. 1 STA ,X+ LEAY -1,Y BNE 1B RTS MEMCPY ; X = Dest Address. ; Y = Src Address. ; D = Length. PSHS U TFR X,U TFR D,X 1 LDA ,Y+ STA ,U+ LEAX -1,X BNE 1B TFR U,X PULS U RTS ENDPC ; Tandy disk suffix. FCB 255,0,0 FCW EXEC .end
(C) Jane McKay, 2018