;=================================================== ; tinyterm - a tiny terminal program with XMODEM ; file download facility. ; tinyterm v1.0 (8080 version) 3rd June, 1985 ; Written by Michael Brandon. ; The idea behind tinyterm is that it is a terminal ; program you can use to download a better terminal ; program (e.g. MODEM7A or YAM). Tinyterm is small ; enough to type in by hand (minus the comments of ; course) or to download the source code using PIP. ;=================================================== .8080 ; use Intel mnemonics org 100h ; normal cp/m origin ;========================================================== ; You will almost certainly have to change the following - ; ; mod$data - the SIO or UART data port through which the ; modem is accessed ; mod$status - the port from which the status of the SIO ; or UART can be read ;========================================================== mod$data equ 40h ; modem data port mod$status equ 41h ; modem status port ;============================================================== ; You may have to change the following - ; ; rx$mask - when ANDed with the SIO or UART status, extracts ; the bit (or bits) which tell whether or not the ; SIO or UART has a received a character ; rx$ready - the value of 'rx$mask AND status' when a ; character has been received ; tx$mask - when ANDed with the SIO or UART status, extracts ; the bit (or bits) which tell whether or not the ; SIO or UART is ready to transmit a character ; tx$ready - the value of 'tx$mask AND status' when the SIO ; or UART is ready to transmit a character ; * note * : the values for the above four constants as they ; stand are correct for a Zilog Z-80 SIO ; cpu$speed - the speed of the processor in kHz. Not very ; critical. ;============================================================== rx$mask equ 00000001b ; "Rx ready" modem status mask rx$ready equ 00000001b ; value when char is received tx$mask equ 00000100b ; "Tx ready" modem status mask tx$ready equ 00000100b ; value when ready to transmit cpu$speed equ 4000d ; cpu speed in kHz ;=================================================== ; You might like to change the following - ; ; exit$char - when this character is typed, the ; program exits to cp/m ; rx$char - when this character is typed, a file ; is downloaded, using XMODEM protocols ;=================================================== exit$char equ 03h ; C rx$char equ 12h ; R ;================================= ; Don't, what ever you do, change ; the following constants ... ;================================= warm$boot equ 0000h ; cp/m warm boot address bdos equ 0005h ; entry into cp/m's bdos fcb equ 005ch ; & (file control block 1) def$dma equ 0080h ; default DMA address rx$delay equ cpu$speed / 4 ; delay count for a one ; second receive timeout soh equ 01h ; ASCII char eot equ 04h ; ASCII char ack equ 06h ; ASCII char lf equ 0ah ; ASCII char cr equ 0dh ; ASCII char nak equ 15h ; ASCII char ;============================================================= ; You shouldn't have to modify any code beyond this point, ; other than adding code to initialise your SIO or UART ; between main1 and main2. ; The only things which will stand modification are - ; (i) test$rx - see if a character has been received ; (ii) test$tx - see if we can transmit a character ; (iii) tx$byte - transmit a character ; (iv) rx$t1 - receive a character with a 1 second timeout ; (v) the line marked (***) after main4 ; but modify them only if necessary. ;============================================================= main: lda def$dma ; get length of command line tail ora a ; has user just typed "tinyterm" ? jnz main1 ; skip if not call print db 'Usage: tinyterm file',cr,lf,0 jmp warm$boot main1: ;=============================================================== ; Put the code to initialise your SIO or UART chip in here. The ; communications port should be set for 300 baud, 8 data bits, ; 1 stop bit and no parity. You may not need to do any ; initialisation at all ... ;=============================================================== main2: mvi c,0bh ; cp/m "get console status" request call bdos ; has a character been typed ? ora a ; test return value (00 or ff) jz main4 ; skip if no character mvi c,06h ; cp/m "direct console I/O" request mvi e,0ffh ; console input indicator call bdos ; get the character cpi exit$char ; exit to the operating system ? jz warm$boot ; do a warm boot if so cpi rx$char ; receive a file ? jnz main3 ; skip if not call rx$file ; receive the file, XMODEM style jmp main4 ; (don't send the rx$char character) main3: call tx$byte ; send the byte out the modem port main4: call test$rx ; received a character from the modem ? jnz main2 ; keep looping if not in mod$data ; get the character (***) call put$char ; print the character jmp main2 ; and keep looping ... ;-------------------------------------------- ; put$char - print a character on the screen ; Input : char in a ; Clobbers : A, B, C, D, E, H, L, flags ;-------------------------------------------- put$char: mvi c,02h ; cp/m "console output" request mov e,a ; char to print in e jmp bdos ; call bdos & return ;--------------------------------------------- ; print - print a null-terminated sequence ; of characters "in line" i.e. ; call print ; db 'string of chars' ; defb 0 ; Clobbers : A, D, E, H, L, flags ;--------------------------------------------- print: pop h ; get return address (start of string) push b ; save prin1: mov a,m ; get char inx h ora a ; end of string ? (0 byte terminator) jz prin2 ; skip if so push h ; save ^ call put$char ; print the character pop h ; retrieve ^ jmp prin1 ; keep looping ... prin2: pop b ; retrieve pchl ; return to the code after the string ;----------------------------------------------- ; test$rx - test the modem status port to see ; if a character is ready to be read ; Output : Z if modem has a character ; NZ if not ; Clobbers : A ;----------------------------------------------- test$rx: in mod$status ; get the modem status ani rx$mask ; mask out all but the bits we want cpi rx$ready ; modem got a char for us to read ? ret ; return (Z) if ready, (NZ) if not ;----------------------------------------------- ; test$tx - test the modem status port to see ; if it is ready to send a character ; Output : Z if the modem is ready ; NZ if not ; Clobbers : A ;----------------------------------------------- test$tx: in mod$status ; get the modem status ani tx$mask ; mask out all but the bits we want cpi tx$ready ; modem ready to send a char ? ret ; return (Z) if ready, (NZ) if not ;---------------------------------------------------- ; tx$byte - transmit a byte when the modem is ready ; Input : A - byte to transmit ;---------------------------------------------------- tx$byte: push psw ; save byte tx$b1: call test$tx ; modem ready to transmit ? jnz tx$b1 ; loop until ready pop psw ; get byte back out mod$data ; send it out the data port ret ;------------------------------------------------ ; rx$t1 - wait for a character to be received; ; time out after one second ; Output : Z if a character was received ; NZ if we timed out ; A - character received if no time out ;------------------------------------------------ rx$t1: push h ; save lxi h,rx$delay ; delay counter for 1 second timeout rx$t1a: call test$rx ; is there a char ready for us ? jz rx$t1c ; jump if so xra a ; A := 0 rx$t1b: dcr a jnz rx$t1b ; wait around a while ... dcx h mov a,h ora l ; timed out ? jnz rx$t1a ; loop if not inr a ; set flags to NZ pop h ret ; return (NZ) - timed out rx$t1c: in mod$data ; get the byte from the data port pop h ret ; return (Z) - got a char ;------------------------------------------------ ; rx$t10 - wait for a character to be received; ; time out after ten seconds ; Output : Z if a character was received ; NZ if we timed out ; A - character received if no time out ;------------------------------------------------ rx$t10: push h ; save lxi h,rx$delay*10 ; counter for 10 second timeout jmp rx$t1a ; carry on as for 1 second timeout ;--------------------------------------------------------- ; tx$ack - wait until the line is clear (i.e. we time out ; while receiving chars), and then send an ;--------------------------------------------------------- tx$ack: push psw ; save tx$a1: call rx$t1 ; get a char, with 1 second timeout jz tx$a1 ; if no timeout, keep gobbling chars mvi a,ack ; char call tx$byte ; send it pop psw ret ;--------------------------------------------------------- ; tx$nak - wait until the line is clear (i.e. we time out ; while receiving chars), and then send a ;--------------------------------------------------------- tx$nak: push psw ; save tx$n1: call rx$t1 ; get a char, with 1 second timeout jz tx$n1 ; if no timeout, keep gobbling chars mvi a,nak ; char call tx$byte ; send it pop psw ret ;-------------------------------------------------- ; get$block - get a block of data from the modem, ; consisting of - ; (i) block number ; (ii) complement of the block number ; (iii) 128 data bytes (1 sector), and ; (iv) checksum ; Output : Z if all went o.k. ; A - block number of the block ; - 128 bytes at *(def$dma) ; NZ if an error occurred ; - timed out ; - block no. <> ~ (~ block no.) ; - checksum wrong ;-------------------------------------------------- get$block: push b push d push h ; save call rx$t1 ; get the block no. jnz get$2 ; return (NZ) if we timed out mov c,a ; save the block no. call rx$t1 ; get ~ block no. jnz get$2 ; return (NZ) if we timed out cma ; ~ ~ block no. (we hope) cmp c ; is the block no. right ? jnz get$2 ; return (NZ) if block no. wrong lxi h,def$dma ; put data in the default DMA buffer mvi b,128 ; 128 bytes / block mvi d,0 ; initial checksum get$1: call rx$t1 ; get a data byte jnz get$2 ; return (NZ) if we timed out mov m,a ; save the byte inx h add d ; generate a new checksum mov d,a ; save it dcr b jnz get$1 ; do the whole block call rx$t1 ; get checksum jnz get$2 ; return (NZ) if we timed out cmp d ; checksums the same ? mov a,c ; return the block no. in A get$2: pop h pop d pop b ret ;---------------------------------------------------------------- ; rx$file - download a file using the XMODEM file transfer ; protocol. Any existing file with the name specified ; on the command line will be overwritten (as will ; any file previously downloaded in this invocation ; of the program). ; Clobbers : A, B, C, D, E, H, L, flags ;---------------------------------------------------------------- rx$file: mvi c,13h ; cp/m "delete file" request lxi d,fcb ; & (file control block) call bdos ; delete the file (if it exists) mvi c,16h ; cp/m "make file" request lxi d,fcb ; & (file control block) xra a ; a := 0 sta fcb+12 ; set things up ... sta fcb+14 ; ... in the file control block sta fcb+32 ; (don't know what it does) call bdos ; create the file, and open it inr a ; error if a = 0ffh after the bdos call jnz rx$f1 ; skip if all's well call print ; print the error message db 'can''t create file',cr,lf,0 ret rx$f1: mvi c,0 ; initial "previous" block no. call tx$nak ; send an initital rx$f2: mvi b,10 ; retry block errors 10 times rx$f3: call rx$t10 ; look for a or jz rx$f5 ; skip if we got something call print db 'timeout',cr,lf,0 rx$f4: call tx$nak ; send a to signal error dcr b jnz rx$f3 ; retry 10 times call print db 'aborted',cr,lf,0 mvi c,13h ; cp/m "delete file" request lxi d,fcb ; & (file control block) call bdos ; delete the file ret rx$f5: cpi eot ; (end of transmission) ? jnz rx$f6 ; skip if not call print db 'file transfer completed',cr,lf,0 call tx$ack ; acknowledge it mvi c,10h ; cp/m "close file" request lxi d,fcb ; & (file control block) call bdos ; close the file inr a ; error if a = 0ffh after bdos call rnz ; return if o.k. call print db 'can''t close file',cr,lf,0 ret rx$f6: cpi soh ; (start of header) ? jz rx$f7 ; skip if so call print db 'no or ',cr,lf,0 jmp rx$f4 rx$f7: call get$block ; get the block jz rx$f8 ; skip if no errors call print db 'error in block',cr,lf,0 jmp rx$f4 rx$f8: cmp c ; resent the old block ? jnz rx$f9 ; skip if not call tx$ack ; acknowledge it call print db '(repeated block)',cr,lf,0 jmp rx$f2 rx$f9: inr c ; next block no. cmp c ; block no.s match ? jz rx$f10 ; skip if so call print db 'error in block number',cr,lf,0 dcr c ; go back a block jmp rx$f4 rx$f10: call print db 'block received',cr,lf,0 push b ; save the block number mvi c,15h ; cp/m "write sequential" request lxi d,fcb ; & (file control block) call bdos ; write sector to disc pop b ; retrieve the block number inr a ; error if a = 0ffh after bdos call jnz rx$f11 ; skip if write went o.k. call print db 'disc limit reached',cr,lf,0 mvi c,13h ; cp/m "delete file" request lxi d,fcb ; & (file control block) call bdos ; delete the file (if it exists) ret rx$f11: call tx$ack ; acknowledge the block jmp rx$f2 ; loop for next block end main