;===================================================== ; tinyterm - a tiny terminal program with XMODEM ; file download facility. ; tinyterm v1.0 (Z-80 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. ;===================================================== .z80 ; use Zilog 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: ld a,(def_dma) ; get length of command line tail or a ; has user just typed "tinyterm" ? jr nz,main1 ; skip if not call print defm 'Usage: tinyterm file',cr,lf,0 jp 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: ld c,0bh ; cp/m "get console status" request call bdos ; has a character been typed ? or a ; test return value (00 or ff) jr z,main4 ; skip if no character ld c,06h ; cp/m "direct console I/O" request ld e,0ffh ; console input indicator call bdos ; get the character cp exit_char ; exit to the operating system ? jp z,warm_boot ; do a warm boot if so cp rx_char ; receive a file ? jr nz,main3 ; skip if not call rx_file ; receive the file, XMODEM style jr 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 ? jr nz,main2 ; keep looping if not in a,(mod_data) ; get the character (***) call put_char ; print the character jr 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: ld c,02h ; cp/m "console output" request ld e,a ; char to print in e jp bdos ; call bdos & return ;--------------------------------------------- ; print - print a null-terminated sequence ; of characters "in line" i.e. ; call print ; defm 'string of chars' ; defb 0 ; Clobbers : A, D, E, H, L, flags ;--------------------------------------------- print: pop hl ; get return address (start of string) push bc ; save prin1: ld a,(hl) ; get char inc hl or a ; end of string ? (0 byte terminator) jr z,prin2 ; skip if so push hl ; save ^ call put_char ; print the character pop hl ; retrieve ^ jr prin1 ; keep looping ... prin2: pop bc ; retrieve jp (hl) ; 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 a,(mod_status) ; get the modem status and rx_mask ; mask out all but the bits we want cp 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 a,(mod_status) ; get the modem status and tx_mask ; mask out all but the bits we want cp 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 af ; save byte tx_b1: call test_tx ; modem ready to transmit ? jr nz,tx_b1 ; loop until ready pop af ; get byte back out (mod_data),a ; 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 hl ; save ld hl,rx_delay ; delay counter for 1 second timeout rx_t1a: call test_rx ; is there a char ready for us ? jr z,rx_t1c ; jump if so xor a ; A := 0 rx_t1b: dec a jr nz,rx_t1b ; wait around a while ... dec hl ld a,h or l ; timed out ? jr nz,rx_t1a ; loop if not inc a ; set flags to NZ pop hl ret ; return (NZ) - timed out rx_t1c: in a,(mod_data) ; get the byte from the data port pop hl 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 hl ; save ld hl,rx_delay*10 ; counter for 10 second timeout jr 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 af ; save tx_a1: call rx_t1 ; get a char, with 1 second timeout jr z,tx_a1 ; if no timeout, keep gobbling chars ld a,ack ; char call tx_byte ; send it pop af ret ;--------------------------------------------------------- ; tx_nak - wait until the line is clear (i.e. we time out ; while receiving chars), and then send a ;--------------------------------------------------------- tx_nak: push af ; save tx_n1: call rx_t1 ; get a char, with 1 second timeout jr z,tx_n1 ; if no timeout, keep gobbling chars ld a,nak ; char call tx_byte ; send it pop af 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 bc push de push hl ; save call rx_t1 ; get the block no. jr nz,get_2 ; return (NZ) if we timed out ld c,a ; save the block no. call rx_t1 ; get ~ block no. jr nz,get_2 ; return (NZ) if we timed out cpl ; ~ ~ block no. (we hope) cp c ; is the block no. right ? jr nz,get_2 ; return (NZ) if block no. wrong ld hl,def_dma ; put data in the default DMA buffer ld b,128 ; 128 bytes / block ld d,0 ; initial checksum get_1: call rx_t1 ; get a data byte jr nz,get_2 ; return (NZ) if we timed out ld (hl),a ; save the byte inc hl add a,d ; generate a new checksum ld d,a ; save it djnz get_1 ; do the whole block call rx_t1 ; get checksum jr nz,get_2 ; return (NZ) if we timed out cp d ; checksums the same ? ld a,c ; return the block no. in A get_2: pop hl pop de pop bc 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: ld c,13h ; cp/m "delete file" request ld de,fcb ; & (file control block) call bdos ; delete the file (if it exists !) ld c,16h ; cp/m "make file" request ld de,fcb ; & (file control block) xor a ; a := 0 ld (fcb+12),a ; set things up ... ld (fcb+14),a ; ... in the file control block ld (fcb+32),a ; (don't know what it does !) call bdos ; create the file, and open it inc a ; error if a = 0ffh after the bdos call jr nz,rx_f1 ; skip if all's well call print ; print the error message defm 'can''t create file',cr,lf,0 ret rx_f1: ld c,0 ; initial "previous" block no. call tx_nak ; send an initital rx_f2: ld b,10 ; retry block errors 10 times rx_f3: call rx_t10 ; look for a or jr z,rx_f5 ; skip if we got something call print defm 'timeout',cr,lf,0 rx_f4: call tx_nak ; send a to signal error djnz rx_f3 ; retry 10 times call print defm 'aborted',cr,lf,0 ld c,13h ; cp/m "delete file" request ld de,fcb ; & (file control block) call bdos ; delete the file ret rx_f5: cp eot ; (end of transmission) ? jr nz,rx_f6 ; skip if not call print defm 'file transfer completed',cr,lf,0 call tx_ack ; acknowledge it ld c,10h ; cp/m "close file" request ld de,fcb ; & (file control block) call bdos ; close the file inc a ; error if a = 0ffh after bdos call ret nz ; return if o.k. call print defm 'can''t close file',cr,lf,0 ret rx_f6: cp soh ; (start of header) ? jr z,rx_f7 ; skip if so call print defm 'no or ',cr,lf,0 jp rx_f4 rx_f7: call get_block ; get the block jr z,rx_f8 ; skip if no errors call print defm 'error in block',cr,lf,0 jp rx_f4 rx_f8: cp c ; resent the old block ? jr nz,rx_f9 ; skip if not call tx_ack ; acknowledge it call print defm '(repeated block)',cr,lf,0 jp rx_f2 rx_f9: inc c ; next block no. cp c ; block no.s match ? jr z,rx_f10 ; skip if so call print defm 'error in block number',cr,lf,0 dec c ; go back a block jp rx_f4 rx_f10: call print defm 'block received',cr,lf,0 push bc ; save the block number ld c,15h ; cp/m "write sequential" request ld de,fcb ; & (file control block) call bdos ; write sector to disc pop bc ; retrieve the block number inc a ; error if a = 0ffh after bdos call jr nz,rx_f11 ; skip if write went o.k. call print defm 'disc limit reached',cr,lf,0 ld c,13h ; cp/m "delete file" request ld de,fcb ; & (file control block) call bdos ; delete the file (if it exists !) ret rx_f11: call tx_ack ; acknowledge the block jp rx_f2 ; loop for next block end main