REM > Z80/src REM Source for Single Port Tube MOS for Z80 REM ======================================= REM J.G.Harston, 09-Apr-1995 REM This code may be freely reused, with acknowledgements : REM This implements a Tube client communicating with a host REM via a single port, such as a serial port, a parallel port, REM or even a single Tube register, all in less than 1K. : REM The link is a text channel that escapes out to send REM commands and receive data transfers. : A%=0:X%=1:os%=(USR&FFF4 AND &FF00)DIV256:IFos%=6 AND PAGE>&8000:PRINT"Running Z80...":SYS "OS_GetEnv" TO A$:OSCLI"TextToBas "+MID$(A$,INSTR(A$," ",1+INSTR(A$," ")))+" -crunch *Z80":END : REM I/O values, these are suitable for a 6850 REM ========================================= TxStatus=0:TxReady =1:TxData =1:TxInit=&13:REM &13=Reset ACIA RxStatus=0:RxReady =0:RxData =1:RxInit=&16:REM &16=8b1s clock/64 : REM Values REM ====== esc=&7F : REM Declare code start and space REM ============================ load%=&FC00:DIM mcode% &500 : : FOR P=0 TO 1:P%=load%:O%=mcode% [OPT P*3+4 ; STARTUP ; ======= .STARTUP DI:LD SP,STARTUP ; Set up initial stack below code LD A,TxInit:OUT (TxStatus),A ; Initialise I/O hardware LD A,RxInit:OUT (RxStatus),A ; Initialise I/O hardware CALL INITERR:CALL PRTEXT ; Print startup banner .StartupMessage DEFB 10:DEFM "Serial TUBE Z80 64K 0.10" DEFB 10:DEFB 10 .StartupCR DEFB 13:DEFB 0 CALL WaitEnterCode ; Wait for ack and enter code if needed JP CLICOM ; Enter command prompt if nothing entered ; Error handlers ; ============== .InitErr LD A,&C3:LD (&38),A ; Put 'JP' opcode at &38 LD HL,(ERRJMP+1) ; Get destination of ERRJMP LD (&39),HL:RET ; RST &38 generates an error .RestartHandler POP HL:LD (FAULT),HL ; Get address after RST &38 LD HL,(BRKV):JP (HL) ; Vector via BRKV .ErrorHandler CALL Report:JP CLICOM ; Report error and enter command prompt ; Printout routines ; ================= .Report CALL OSNEWL:LD HL,(FAULT) ; Get error pointer INC HL:CALL PrString ; Print text after error number JP OSNEWL ; Print final NL .PrString LD A,(HL):CALL OSASCI ; Print current character INC HL:AND A:JR NZ,PrString; Loop until &00 printed RET .PrText EX (SP),HL:CALL PrString ; Print from address on stack EX (SP),HL ; Return updated address to stack .EventHandler .InterruptHandler .osFSC RET ; OSRDCH - Wait for character from input stream ; ============================================= ; On exit, A =char, Cy=Escape flag ; .osRDCH PUSH BC:CALL WaitByte ; Wait for character LD B,A ; Save character LD A,(ESCFLG):ADD A,A ; Get Escape flag to Carry LD A,B:POP BC:RET ; Get character to A ; OSRDCH_IO - Request character via Tube ; ====================================== ; On exit, A =char, Cy=carry ; ; Tube data &00 -- Carry Char ; .osRDCH_IO XOR A:CALL SendCommand ; Send command &00 - OSRDCH .WaitCarryChar CALL WaitByte:ADD A,A ; Wait for Carry JP WaitByte ; Wait for Char ; OSCLI - Send command line to host ; ================================= ; On entry, HL=>command string ; On exit, A, HL corrupted ; ; Tube data &02 string &0D -- &7F or &80 ; ; Should use a temporary stack in case command causes a ; data transfer that overwrites current stack. Needs to ; be able to cope with OSCLI calling code that then calls ; another OSCLI and be able to return recusively. ; .osCLI LD A,&02:CALL SendCommand ; Send command &02 - OSCLI CALL SendString ; Send command string .CLIAck PUSH BC:PUSH DE ; Save registers CALL WaitEnterCode ; Wait for ack and enter code if needed POP DE:POP BC:RET ; Restore registers and return ; OSBYTE - Byte MOS functions ; =========================== ; On entry, A, HL=OSBYTE parameters ; On exit, A preserved ; If A<&80, L=returned value ; If A>&7F, HL, Carry=returned values ; .osBYTE CP &80:JR NC,ByteHigh ; Jump for long OSBYTEs ; ; Tube data &04 X A -- X ; PUSH AF LD A,&04:CALL SendCommand ; Send command &04 - OSBYTELO LD A,L:CALL SendByte ; Send single parameter POP AF:PUSH AF:CALL SendByte ; Send function CALL WaitByte:LD L,A ; Get return value POP AF:RET ; .ByteHigh CP &82:JR Z,Byte82 ; Read memory high word CP &83:JR Z,Byte83 ; Read bottom of memory CP &84:JR Z,Byte84 ; Read top of memory ; ; Tube data &06 X Y A -- Cy Y X ; PUSH AF LD A,&06:CALL SendCommand ; Send command &06 - OSBYTEHI LD A,L:CALL SendByte ; Send parameter 1 LD A,H:CALL SendByte ; Send parameter 2 POP AF:CALL SendByte ; Send function CP &9D:RET Z ; Fast return with 'Fast BPUT' CP &8E:JR Z,CLIAck ; Check acknowledge with 'Enter language' PUSH BC:LD B,A ; Save function CALL WaitByte:ADD A,A ; Wait for Carry CALL WaitByte:LD H,A ; Wait for return high byte CALL WaitByte:LD L,A ; Wait for return low byte LD A,B:POP BC:RET ; Restore function, BC .Byte82:LD HL,&0000:RET ; Read memory high word .Byte83:LD HL,(MEMBOT):RET ; Read bottom of memory .Byte84:LD HL,(MEMTOP):RET ; Read top of memory ; OSWORD - Various functions ; ========================== ; On entry, A =function ; HL=>control block ; .osWORD AND A:JR Z,RDLINE ; OSWORD 0, jump to read line of text ; ; Tube data &08 A in_length block out_length -- block ; PUSH AF ; Save function LD A,&08:CALL SendCommand ; Send command &08 - OSWORD POP AF:PUSH AF:CALL SendByte ; Send function PUSH BC:PUSH HL ; Save BC, save control block address ADD A,A:JR C,WordIndex ; If &80+, use control block entries LD BC,&1010 CP 42:JR NC,WordSend ; If 21..127, use 16 both ways LD HL,WordLengths LD B,0:LD C,A:ADD HL,BC ; If 1..20, index into length table .WordIndex LD B,(HL):INC HL:LD C,(HL) ; B=send length, C=receive length .WordSend POP HL:PUSH HL:PUSH BC ; Get control block address back, save lengths LD A,B:CALL SendByte ; Send send length LD C,B:CALL SendBlockC ; Send control block POP BC:POP HL:PUSH HL ; Get lengths and control block address back LD A,C:CALL SendByte ; Send receive length CALL WaitBlockC ; Wait for returned control block POP HL:POP BC:POP AF:RET ; Restore registers and return ; OSWORD control block lengths ; ---------------------------- .WordLengths DEFB &00:DEFB &05 ; &01 =TIME DEFB &05:DEFB &00 ; &02 TIME= DEFB &00:DEFB &05 ; &03 =Timer DEFB &05:DEFB &00 ; &04 Timer= DEFB &04:DEFB &05 ; &05 =IO DEFB &05:DEFB &00 ; &06 IO= DEFB &08:DEFB &00 ; &07 SOUND DEFB &0E:DEFB &00 ; &08 ENVELOPE DEFB &04:DEFB &05 ; &09 =POINT DEFB &01:DEFB &09 ; &0A =CHR$ DEFB &01:DEFB &05 ; &0B =Palette DEFB &05:DEFB &00 ; &0C Palette= DEFB &00:DEFB &08 ; &0D =Coord DEFB &10:DEFB &19 ; &0E =TIME$ DEFB &19:DEFB &10 ; &0F TIME$= DEFB &10:DEFB &01 ; &10 Net_Tx DEFB &0D:DEFB &0D ; &11 Net_Args DEFB &00:DEFB &80 ; &12 Net_Rx DEFB &08:DEFB &08 ; &13 NetFS_Info DEFB &80:DEFB &80 ; &14 NetFS_Op ; RDLINE - Read a line of text ; ============================ ; On entry, A =0 ; HL=>control block ; On exit, A =undefined ; H =length of returned string ; Cy=0 ok, Cy=1 Escape ; ; Tube data &0A block -- &FF or &7F string &0D ; .RDLINE LD A,&0A:CALL SendCommand ; Send command &0A - RDLINE INC HL:INC HL LD C,3:CALL SendBlock ; Send control block LD A,&07:CALL SendByte XOR A:CALL SendByte ; Send &0700 CALL WaitByte:ADD A,A ; Wait for response JR C,RdLineEscape DEC HL:LD A,(HL) DEC HL:LD L,(HL):LD H,A ; Get address of text buffer PUSH BC:LD BC,&FF00 ; Save BC, initialise counter .RdLineLp CALL WaitByte:LD (HL),A ; Wait for a byte INC HL:INC B ; Inc. pointer and count CP 13:JR NZ,RdLineLp ; Loop until PUSH BC:POP HL:POP BC ; Copy counter to H, restore BC XOR A:RET ; Return A=0, Cy=0 .RdLineEscape LD HL,&00FF ; Return count=0 XOR A:SCF:RET ; Return A=0, Cy=1 ; OSARGS - Read info on open file ; =============================== ; On entry, A =function ; HL=>control block ; E =handle ; On exit, A =returned value ; HL preserved ; E preserved ; ; Tube data &0C handle block function -- result block ; .osARGS PUSH BC:PUSH AF ; Save BC, function LD A,&0C:CALL SendCommand ; Send command &0C - OSARGS LD A,E:CALL SendByte ; Send handle LD C,4:CALL SendBlock ; Send data POP AF:CALL SendByte ; Send function CALL WaitByte:PUSH AF ; Wait for result LD C,4:CALL WaitBlock ; Wait for data POP AF:POP BC:RET ; Get result, restore BC ; OSBGET - Get a byte from open file ; ================================== ; On entry, H =handle ; On exit, A =byte Read ; H =preserved ; Cy set if EOF ; ; Tube data &0E handle -- Carry byte ; .osBGET LD A,&0E:CALL SendCommand ; Send command &0E - OSBGET LD A,H:CALL SendByte ; Send handle JP WaitCarryChar ; Wait for Carry and byte ; OSBPUT - Put a byte to an open file ; =================================== ; On entry, A =byte to write ; H =handle ; On exit, A =preserved ; H =preserved ; ; Tube data &10 handle byte -- &7F ; .osBPUT PUSH AF ; Save byte LD A,&10:CALL SendCommand ; Send command &10 - OSBPUT LD A,H:CALL SendByte ; Send handle POP AF:CALL SendByte ; Send byte PUSH AF:CALL WaitByte ; Wait for acknowledge POP AF:RET ; Restore A, return ; OSFIND - Open or Close a file ; ============================= ; On entry, A =function ; H =handle or HL=>filename ; On exit, A =zero or handle ; ; Tube data &12 function string &0D -- handle ; &12 &00 handle -- &7F ; .osFIND PUSH AF ; Save function LD A,&12:CALL SendCommand ; Send command &12 - OSFIND POP AF:CALL SendByte ; Send function AND A:JR NZ,OPEN ; If A<>0, jump to do OPEN ; CLOSE LD A,H:CALL SendByte ; Send handle CALL WaitByte ; Wait for acknowledgement XOR A:RET ; Restore function .OPEN CALL SendString ; Send pathname JP WaitByte ; Wait for and return handle ; OSFILE - Operate on whole files ; =============================== ; On entry, A =function ; HL=>control block ; On exit, A =result ; control block updated ; ; Tube data &14 block string function -- result block ; .osFILE PUSH BC:PUSH AF ; Save BC and function LD A,&14:CALL SendCommand ; Send command &14 - OSFILE INC HL:INC HL:PUSH HL ; Point to control block contents LD C,16:CALL SendBlock ; Send 16-byte control block DEC HL:LD A,(HL) DEC HL:LD L,(HL):LD H,A ; Get pointer to pathname CALL SendString ; Send pathname POP HL ; Get control block address back POP AF:CALL SendByte ; Send function CALL WaitByte:PUSH AF ; Wait for result LD C,16:CALL WaitBlock ; Wait for 16-byte control block DEC HL:DEC HL ; Restore HL POP AF:POP BC ; Get result back, restore BC RET ; OSGBPB - Multiple byte Read and write ; ===================================== ; On entry, A =function ; HL=>control block ; On exit, A =returned value ; control block updated ; ; Tube data &16 block function -- block Carry result ; .osGBPB PUSH BC:PUSH AF ; Save BC and function LD A,&16:CALL SendCommand ; Send command &16 - OSGBPB LD C,13:CALL SendBlock ; Send 13-byte control block POP AF:CALL SendByte ; Send function LD C,13:CALL WaitBlock ; Wait for 13-byte control block POP BC:JP WaitCarryChar ; Jump to get Carry and A ; OSWRCH - Send character to output stream ; ======================================== ; On entry, A =character ; On exit, A =preserved ; ; Tube data character -- ; .osWRCH ; WRCH is simply SendByte ; Tube I/O Routines ; ================= ; Characters and commands are sent over the same single port ; Outward commands are escaped, and inward responses are escaped ; ; Outward ; x VDU x ; esc,esc VDU esc ; esc,n MOS function, control block follows ; ; Inward ; x char/byte x ; esc,esc char/byte esc ; esc,&00 BRK, error number+text+null follows ; esc,<&80 read returned control block set length ; esc,&8n Escape change, b0=new state ; esc,&9x,Y,X,A Event ; esc,&Ax reserved for networking ; esc,&Bx end transfer ; esc,&Cx,addr set address ; esc,&Dx,addr execute address ; esc,&Ex,addr start save from address ; esc,&Fx,addr start load from address ; Send a byte, escaping it if needed ; ---------------------------------- .SendByte CP esc:CALL Z,SendData ; If esc, prefix it with another esc .SendData PUSH AF .SendWait IN A,(TxStatus) BIT TxReady,A:JR Z,SendWait; Wait until data can be sent POP AF:OUT (TxData),A ; Sent data RET ; Send an escaped command ; ----------------------- .SendCommand PUSH AF ; Save command byte LD A,esc:CALL SendData ; Send esc prefix POP AF:JR SendData ; Send command byte ; Send a block of data ; -------------------- .SendBlockC DEC C:RET M:INC C ; Don't send if &81..&FF or &00 .SendBlock LD B,0:ADD HL,BC ; Point to end of data block .SendBlockLp DEC HL ; Move downwards through data LD A,(HL):CALL SendByte ; Send a byte DEC C:JR NZ,SendBlockLp ; Loop for whole block RET ; Send a string ; ------------- .SendString LD A,(HL):CALL SendByte ; Send a character INC HL ; Move to next character CP 13:JR NZ,SendString ; Loop until sent RET ; Wait for a block of data ; ------------------------ .WaitBlockC DEC C:RET M:INC C ; Don't wait if &81..&FF or &00 .WaitBlock LD B,0:ADD HL,BC ; Point to end of data block .WaitBlockLp DEC HL ; Move downwards through data CALL WaitByte:LD (HL),A ; Wait for a byte DEC C:JR NZ,WaitBlockLp ; Loop for whole block RET ; Check if a byte is waiting, and read it if there ; ------------------------------------------------ .ReadByte IN A,(RxStatus) BIT RxReady,A:RET Z ; Nothing waiting ; Continue into WaitByte ; Wait for a byte, decoding escaped data ; -------------------------------------- ; On exit, A =byte ; F =preserved ; .WaitByte PUSH BC:PUSH AF ; Save BC and flags .WaitByteLp CALL WaitData:JR NZ,WaitByteOk ; Not esc, return it CALL WaitData:JR Z,WaitByteOk ; esc,esc, return as esc PUSH HL:PUSH DE:CALL WaitCommand ; Decode escaped command POP DE:POP HL:JR WaitByteLp ; Loop back to wait for a byte .WaitByteOk LD B,A:POP AF ; Save byte, get flags back LD A,B:POP BC ; Put byte in A, restore BC RET ; Wait for data ; ------------- ; On exit, A =byte ; F =Z byte=esc, NZ byte<>esc ; .WaitData IN A,(RxStatus) BIT RxReady,A:JR Z,WaitData; Wait until data present IN A,(RxData) CP esc:RET ; Fetch data ; Decode escape command ; --------------------- ; AF, BC, DE, HL can be trashed ; .WaitCommand AND A:JR Z,WaitError ; esc,&00 - error JP M,WaitTransfer ; esc,>&7F - data transfer ; esc,1..127 - read a control block ; --------------------------------- LD B,A ; Move count to B LD HL,(CTRL) ; Get data pointer .WaitLength CALL WaitByte:LD (HL),A ; Wait for a byte and store it INC HL:DJNZ WaitLength ; ** Should HL be returned to (ADDR)? RET ; Return to WaitByte ; esc,&00 - error ; --------------- .WaitError LD HL,ERRBUF:PUSH HL ; Stack error pointer LD (HL),&FF:INC HL ; Store error RST CALL WaitByte:LD (HL),A:INC HL ; Store error number .WaitErrorLp CALL WaitByte:LD (HL),A:INC HL ; Store error character AND A:JR NZ,WaitErrorLp ; Loop until final &00 JP ERRJMP ; Enter error handler, no return ; esc,&8n - Escape change ; ----------------------- .WaitTransfer CP &C0:JR NC,WaitStart CP &A0:JR NC,WaitEnd CP &90:JR NC,WaitEvent RRCA:LD (ESCFLG),A:RET ; Set error flag from b0 ; esc,&9x - Event ; --------------- .WaitEvent CALL WaitByte:LD H,A ; Fetch event Y parameter CALL WaitByte:LD L,A ; Fetch event X parameter CALL WaitByte ; Fetch event A parameter PUSH HL:LD HL,(EVENTV) ; Get event vector EX (SP),HL:RET ; Dispatch and return ; esc,&Ax - Reserved ; ------------------ .WaitEnd CP &B0:RET C ; Return to WaitByte ; esc,&Bx - End transfer ; ---------------------- POP HL:POP HL:POP HL ; Drop data from stack to fall POP HL:POP HL:XOR A ; out of WaitSave/WaitLoad loop RET ; Return to WaitByte ; esc,&C0+ - Start transfer ; ------------------------- .WaitStart PUSH AF:LD HL,ADDR ; Save command, point to ADDR LD C,4:CALL WaitBlock ; Wait for 4-byte data address LD HL,(ADDR):POP AF ; Get address to HL CP &D0:RET C ; esc,&Cx - set address CP &E0:JR C,EnterCode ; esc,&Dx - enter code CP &F0:JR C,WaitSave ; esc,&Ex - save data .WaitLoad CALL WaitByte:LD (HL),A ; esc,&Fx - load data INC HL:JR WaitLoad ; Loop until terminated by esc,&Bx .WaitSave LD A,(HL):CALL SendByte ; esc,&Ex - save data INC HL:CALL ReadByte ; Poll input for termination JR WaitSave ; Loop until terminated by esc,&Bx ; Wait for acknowledge and enter code if >&7F ; ------------------------------------------- .WaitEnterCode CALL WaitByte:ADD A,A:RET NC ; Wait for ack, exit if <&80 LD HL,(ADDR) ; Fetch transfer address ; EnterCode - Enter code at HL ; ---------------------------- .EnterCode JP (HL) ; Should check for ROM header DEFM STRING$(&FF20-P%,CHR$&FF) .ERRBUF ; 96 bytes for error strings/system stack DEFM STRING$(&FF80-P%,CHR$&00) .SystemStack ; Stack grows down from here ; &FF80 :.ESCFLG :DEFB &00 ; Escape flag ; &FF81 :.FF81 :DEFB &00 ; TempA ; &FF82 :.FAULT :DEFW StartupMessage ; Fault pointer ; &FF84 :.ERRDEF :DEFW ErrorHandler ; Default error handler ; &FF86 :.LPTR :DEFW StartupCR ; Command line tail pointer ; &FF88 :.MEMBOT :DEFW &0100 ; Bottom of available memory ; &FF8A :.MEMTOP :DEFW STARTUP ; Top of available memory ; &FF8C :.ADDR :DEFW &0000:DEFW &0000 ; Transfer address ; &FF90 :.CTRL :DEFW ERRBUF ; Control block ; &FF92 :.LFF92 :RET:RET:RET ; &FF95 :.LFF95 :RET:RET:RET ; &FF98 :.LFF98 :RET:RET:RET ; &FF9B :.LFF9B :RET:RET:RET ; &FF9E :.LFF9E :RET:RET:RET ; &FFA1 :.RDDEC :RET:RET:RET ; &FFA4 :.RDHEX :RET:RET:RET ; &FFA7 :.OSQUIT :RET:RET:RET ; &FFAA :.PRHEX :RET:RET:RET ; &FFAD :.PR2HEX :RET:RET:RET ; &FFB0 :.USERINT:RET:RET:RET ; &FFB3 :.PRTEXT :JP PrText ; &FFB6 :.SPARE1 :RET:RET:RET ; &FFB9 :.CLICOM :JP CLICOM ; &FFBC :.ERRJMP :JP RestartHandler ; &FFBF :.INITERR:JP InitErr ; &FFC2 :.SPARE2 :RET:RET:RET ; &FFC5 :.SPARE3 :RET:RET:RET ; &FFC8 :.SPARE4 :RET:RET:RET ; FILE I/O ENTRIES ; ================ ; &FFCB :.OSFSC :JP osFSC ; &FFCE :.OSFIND :JP osFIND ; &FFD1 :.OSGBPB :JP osGBPB ; &FFD4 :.OSBPUT :JP osBPUT ; &FFD7 :.OSBGET :JP osBGET ; &FFDA :.OSARGS :JP osARGS ; &FFDD :.OSFILE :JP osFILE ; CHARACTER I/O ENTRIES ; ===================== ; &FFE0 :.OSRDCH :JP osRDCH ; &FFE3 :.OSASCI :CP 13:JR NZ,OSWRCH ; &FFE7 :.OSNEWL :LD A,10:CALL OSWRCH ; &FFEC :.OSWRCR :LD A,13 ; &FFEE :.OSWRCH :JP osWRCH ; MOS ENTRIES ; =========== ; &FFF1 :.OSWORD :JP osWORD ; &FFF4 :.OSBYTE :JP osBYTE ; &FFF7 :.OS_CLI :JP osCLI ; SYSTEM VECTORS ; ============== ; &FFFA :.BRKV :DEFW ErrorHandler ; &FFFC :.EVENTV :DEFW EventHandler ; &FFFE :.IRQV :DEFW InterruptHandler ]NEXT OS."SAVE Z80Client "+STR$~mcode%+" "+STR$~O%+" "+STR$~load%+" "+STR$~load% *Quit