(* MODEM SOFTWARE PACKAGE FOR CPM *) (* PAUL L. GREENE *) (* MICRO ENGINEERING *) (* P.O. BOX 8094 *) (* LA CRESCENTA, CA. 91214 *) (* THIS PROGRAM IS BEING SUBMITTED TO THE PASCAL/MT USER'S *) (* GROUP FOR DISTRIBUTION AND USE BY INDIVIDUALS ONLY. *) (* MICRO ENGINEERING RESERVES ALL OTHER RIGHTS TO THIS *) (* PROGRAM. *) (* WRITTEN FOR THE PASCAL/MT COMPILER VERSION 3.1 *) (* PASCAL/MT COPYRIGHT MT MICROSYSTEMS *) (* CP/M COPYRIGHT DIGITAL RESEARCH *) (*************************************************************) (* OPERATING INSTRUCTIONS *) (* THIS PROGRAM REQUIRES THAT THE "IOBYTE" BE IMPLEMENTED, *) (* AND THAT THE MODEM IS CONNECTED TO ONE OF THE CONSOLE I/O *) (* INTERFACES. THE PROGRAM PERFORMS CONSOLE SWAPPING TO *) (* COMMUNICATE BETWEEN THE USER AND THE MODEM. THE USER'S *) (* CONSOLE MUST BE AS FAST OR FASTER THAN THE MODEM SPEED. *) (* THIS PROVIDES A HARDWARE INDEPENDENT MEANS OF CONTROLLING *) (* THE MODEM. *) (* THE PROGRAM FIRST ASKS WHICH CONSOLE NUMBER THE MODEM IS *) (* IS CONNECTED TO. ANSWER WITH A SINGLE DIGIT, 0-3. YOU *) (* ARE THEN ASKED TO SELECT WHETHER CONSOLE INPUT IS TO BE *) (* ECHOED ON THE CONSOLE OUTPUT. ANSWER 'Y' OR 'N'. *) (* YOU ARE THEN ASKED TO DEFINE SEVERAL SPECIAL CONTROL *) (* CHARACTERS. ONLY THE CHARACTERS FOR "QUIT" AND "MODE" *) (* ARE NOT SENT TO THE MODEM. THE OTHERS ARE USED TO *) (* CONTROL THE REMOTE COMPUTER FROM THIS PROGRAM. A COMMAND *) (* PROMPT LINE IS THEN DISPLAYED, AND THE DESIRED COMMAND *) (* ENTERED BY TYPING THE FIRST LETTER. *) (* THE "INITIAL" COMMAND REPEATS THE ABOVE INITIALIZATION. *) (* THE "MODEM" COMMAND CONNECTS THE USER'S CONSOLE TO THE *) (* MODEM FOR COMMUNICATION WITH THE REMOTE COMPUTER. THE *) (* "COMMAND MODE" CONTROL CHARACTER CAN THEN BE USED ANYTIME *) (* TO RETURN TO COMMAND LEVEL. *) (* THE "QUIT" COMMAND RETURNS CONTROL TO CP/M. *) (* ALL COMMUNICATION FROM THE MODEM TO THE USER MAY BE *) (* CAPTURED ON A DISK FILE USING THE "RECEIVE" COMMAND. THE *) (* PROGRAM WILL ASK FOR THE FILE NAME, WHICH MAY INCLUDE A *) (* DISK DESIGNATOR. A RECEIVE FILE MAY BE OPENED AT ANY *) (* TIME BY RETURNING TO COMMAND MODE. WHEN ALL DESIRED *) (* TEXT HAS BEEN CAPTURED, THE FILE MUST BE CLOSED AND *) (* LOGGED ON THE DISK USING THE "CLOSE" COMMAND AFTER *) (* RETURNING TO COMMAND MODE. THE AMOUNT OF TEXT THAT CAN *) (* BE CAPTURED IS ONLY LIMITED BY AVAILABLE DISK SPACE. *) (* THE TEXT IS INITIALLY BUFFERED IN MAIN MEMORY UNTIL THE *) (* BUFFER IS FULL. A "SUSPEND" CHARACTER IS THEN SENT TO *) (* THE MODEM, AND THE BUFFER IS WRITTEN TO DISK. THE *) (* PROGRAM THEN SENDS A "CONTINUE" CHARACTER TO THE MODEM *) (* AND RESUMES CAPTURING THE TEXT. THEREFORE, THE REMOTE *) (* COMPUTER THAT YOU ARE COMMUNICATING WITH MUST HAVE BOTH *) (* "SUSPEND" AND "CONTINUE" CAPABILITY. *) (* DISK FILES MAY BE SENT TO THE MODEM USING THE "SEND" *) (* COMMAND. THE PROGRAM ASKS FOR THE NAME OF THE FILE TO *) (* BE SENT, WHICH MAY INCLUDE A DISK DESIGNATOR. THE FILE *) (* IS THEN OPENED AND PREPARED FOR TRANSMISSION. ACTUAL *) (* SENDING OF THE FILE DOES NOT BEGIN UNTIL RETURNING TO *) (* "MODEM" MODE. THE SEND FILE IS ECHOED ON THE USER'S *) (* CONSOLE UNTIL COMPLETE OR UNTIL SUSPENDED BY RETURNING *) (* TO COMMAND LEVEL. THE SEND FILE IS ALSO FULLY BUFFERED *) (* AND CAN BE ANY LENGTH UP TO AVAILABLE DISK CAPACITY. *) (* A FILE CAN BE SENT AT ANY TIME BY USING THE ABOVE *) (* PROCEDURE AFTER RETURNING TO COMMAND LEVEL. *) (* MODIFICATIONS *) (* A DEDICATED MODEM CAN BE USED BY MODIFYING THE FOLLOWING *) (* PROCEDURES TO DIRECTLY ACCESS THE DEVICE: *) (* PROCEDURE WRITEMODEM *) (* PROCEDURE READMODEM *) (* PROCEDURE TESTMODEM *) (*************************************************************) (*-----------------------------------------------------------*) (*************************************************************) PROGRAM MODEM; CONST ENDFILE=$1A; (* CP/M EOF (CONTROL-Z) *) BUFSIZE=4095; (* DISK FILE BUFFERS *) RECSIZE=79; CR=$0D; LF=$0A; SPACE=$20; NULL=00; (* BDOS FUNCTIONS - SEE CP/M MANUAL *) IOCHK=7;IOSET=8; (* BIOS FUNCTIONS - SEE CP/M MANUAL *) WBOOT=0;CONSTS=3;CONIN=6;CONOUT=9; TYPE BUFFER=ARRAY [0..BUFSIZE] OF CHAR; REC=ARRAY [0..RECSIZE] OF CHAR; VAR INTITLE,OUTTITLE :ARRAY [0..13] OF CHAR; INFILE,OUTFILE :TEXT; INBUF,OUTBUF :BUFFER; TRANSDATA,RECDATA, SUSPEND,CONTINUE, TERMINATE,MODE,QUIT, CIOBYTE,MIOBYTE, ININDEX,OUTINDEX :INTEGER; DISKFULL,EOF, FISTAT,FOSTAT, CISTAT,MISTAT, COREADY,MOREADY, DONE,MWAIT,CRLF, CONECHO,PARITY :BOOLEAN; (*************************************************************) (*************************************************************) (*$L-*) (*$I FILEIO*) { CP/M FILE I/O LIBRARY } (*$L+*) (*************************************************************) { CP/M BDOS SUBROUTINE CALL } PROCEDURE EXTERNAL [5] MON2(FUNCT:INTEGER;INFO:INTEGER); (*************************************************************) { CP/M BDOS FUNCTION CALL } FUNCTION MON1(FUNCT:INTEGER;INFO:INTEGER):INTEGER; VAR TEMP:INTEGER; BEGIN (* CALL MONITOR(BDOS), VALUE RETURNED IN REG A(LOW),B(HIGH) *) MON2(FUNCT,INFO); (* MOVE BA TO HL, SAVE AS TEMP *) INLINE ("MOV L,A/ "MOV H,B/ "SHLD / TEMP ); MON1:=TEMP; END; (*************************************************************) { THIS PROCEDURE PERFORMS DIRECT ACCESS OF BIOS SUBROUTINES. } { THE ADDRESS OF THE BIOS SUBROUTINE JUMP TABLE IS ASSUMED } { TO BE AVAILABLE AT LOCATION 0001. THE PARAMETER "FUNCT" } { IS AN INDEX, MODULO-3, TO THE JUMP TABLE TO SELECT THE } { DESIRED FUNCTION. THE PARAMETER "INFO" IS PASSED TO THE } { BIOS SUBROUTINE IN REGISTER C. WITH SINCERE APOLOGIES, } { THIS ROUTINE IS SELF-MODIFYING, BUT IT IS ALSO SELF- } { RESTORING. THIS WAS NECESSARY SINCE FORWARD LABEL } { REFERENCES ARE NOT ALLOWED IN INLINE CODE, AND NO OTHER } { MEANS OF PERFORMING A COMPUTED SUBROUTINE CALL COULD BE } { DEVISED. } PROCEDURE BIOS2(FUNCT,INFO:INTEGER); VAR ENTRY,ADDRESS,TEMP:INTEGER; BEGIN ENTRY:=$0001; (* LOCATION OF BIOS WARM START ENTRY ADDRESS *) ADDRESS:=ENTRY^ + FUNCT; (* INDEX JUMP TABLE ENTRY *) TEMP:=INFO; (* PUT INFO AT KNOWN ADDRESS *) INLINE( [START]/"NOP / (* MODIFIED OPCODE *) "LDA /TEMP/ (* GET INFO *) "MOV C,A/ (* PUT IN C *) "LHLD /ADDRESS/ (* LOAD BIOS ENTRY *) "MVI A,/$E9/ (* 'PCHL' OPCODE *) "STA /START/ (* MODIFY LOCATION *) "CALL /START/ (* BRANCH TO BIOS *) "MVI A,/$0/ (* 'NOP' OPCODE *) "STA /START ); (* RESTORE OPCODE *) END; (*************************************************************) { THIS FUNCTION IS SIMILAR TO PROCEDURE BIOS2 EXCEPT THAT THE } { CONTENT OF REGISTER A (AFTER RETURN FROM BIOS SUBROUTINE) } { IS RETURNED AS THE FUNCTION VALUE. } FUNCTION BIOS1(FUNCT,INFO:INTEGER):INTEGER; VAR ENTRY,ADDRESS,TEMP:INTEGER; BEGIN ENTRY:=$0001; ADDRESS:=ENTRY^ + FUNCT; TEMP:=INFO; INLINE( [START]/"NOP/ "LDA /TEMP/ "MOV C,A/ "LHLD /ADDRESS/ "MVI A,/$E9/ "STA /START/ "CALL /START/ "MOV L,A/ (* SAVE RETURNED DATA*) "MVI H,/0/ (* CLEAR HIGH BYTE *) "SHLD /TEMP/ "MVI A,/$0/ "STA /START ); BIOS1:=TEMP; END; (*************************************************************) PROCEDURE WRITECONSOLE(DATA:INTEGER); BEGIN IF DATA=CR THEN CRLF:=TRUE; IF (DATA <> CR) AND (CRLF=TRUE) THEN BEGIN IF DATA <> LF THEN BIOS2(CONOUT,LF); CRLF:=FALSE; END; BIOS2(CONOUT,DATA); END; (*************************************************************) PROCEDURE TESTCONSOLE(VAR STAT:BOOLEAN); VAR DATA:INTEGER; BEGIN DATA:=BIOS1(CONSTS,0); IF DATA=$00FF THEN STAT:=TRUE ELSE STAT:=FALSE; END; (*************************************************************) PROCEDURE SETPARITY(VAR DATA:INTEGER); VAR RESULT:BOOLEAN; (*****************************) FUNCTION TESTPARITY(DATA:INTEGER):BOOLEAN; VAR RESULT:BOOLEAN; X:INTEGER; BEGIN X:=DATA; INLINE( "LDA /X/ (* GET CHAR *) "ORA A/ (* SET PARITY FLAG *) "PUSH PSW/ (* GET FLAGS TO A *) "POP H/ "MOV A,L/ "RRC/ (* SHIFT PARITY TO LSB *) "RRC/ "ANI /$01/ (* MASK OTHER BITS *) "MOV L,A/ (* MAKE BOOLEAN *) "MVI H,/0/ "SHLD /RESULT ); (* TRUE IF EVEN PARITY *) TESTPARITY:=RESULT; END; (******************************) PROCEDURE CHANGEPARITY(VAR DATA:INTEGER); VAR X:INTEGER; BEGIN X:=DATA; INLINE( "LDA /X/ "XRI /$80/ (* COMPLEMENT MSB *) "STA /X ); DATA:=X; END; (******************************) BEGIN (*SETPARITY*) RESULT:=TESTPARITY(DATA); IF ((RESULT=TRUE) AND (PARITY=FALSE)) OR ((RESULT=FALSE) AND (PARITY=TRUE)) THEN CHANGEPARITY(DATA); END; (*SETPARITY*) (*************************************************************) PROCEDURE WRITEMODEM(VAR DATA:INTEGER); BEGIN SETPARITY(DATA); MON2(IOSET,MIOBYTE); BIOS2(CONOUT,DATA); MON2(IOSET,CIOBYTE); END; (*************************************************************) PROCEDURE TESTMODEM(VAR STAT:BOOLEAN); VAR DATA:INTEGER; BEGIN MON2(IOSET,MIOBYTE); DATA:=BIOS1(CONSTS,0); IF DATA=$00FF THEN STAT:=TRUE ELSE STAT:=FALSE; MON2(IOSET,CIOBYTE); END; (*************************************************************) PROCEDURE READMODEM(VAR DATA:INTEGER); BEGIN REPEAT TESTMODEM(MISTAT) UNTIL MISTAT=TRUE; MON2(IOSET,MIOBYTE); DATA:=BIOS1(CONIN,0); DATA:=DATA & $007F; MON2(IOSET,CIOBYTE); END; (*************************************************************) PROCEDURE READFILE(VAR DATA:INTEGER); BEGIN IF OUTINDEX > BUFSIZE THEN BEGIN WRITEMODEM(SUSPEND); DATA:=ORD(GNB(OUTFILE,OUTBUF,OUTINDEX,EOF)); WRITEMODEM(CONTINUE); END ELSE DATA:=ORD(GNB(OUTFILE,OUTBUF,OUTINDEX,EOF)); IF EOF THEN BEGIN DATA:=0; WRITELN;WRITELN('* * * END OF TRANSMIT FILE * * *'); FOSTAT:=FALSE; MOREADY:=FALSE; END; END; (*************************************************************) PROCEDURE WRITEFILE(DATA:INTEGER); VAR CH:CHAR; TBUF:ARRAY[0..80] OF INTEGER; I,TDATA,TIMER,TINDEX:INTEGER; BEGIN IF ININDEX >= BUFSIZE THEN { WRITE BUFFER TO DISK } BEGIN WRITEMODEM(SUSPEND); { CONTINUE READING MODEM UNTIL NO CHARACTERS ARE RECEIVED } { FOR A RESPECTABLE PERIOD OF TIME. THIS ALLOWS THE REMOTE } { TIME TO REACT TO THE "SUSPEND". THE CHARACTERS ARE PLACED } { IN A TEMPORARY BUFFER UNTIL MODEM TRANSFERS STOP. CURRENT } { TIMEOUT PERIOD IS APPROXIMATELY 2 SEC. } TINDEX:=0; TBUF[TINDEX]:=DATA; TIMER:=0; WHILE TIMER < 1000 DO BEGIN TESTMODEM(MISTAT); IF MISTAT THEN BEGIN READMODEM(TDATA); TINDEX:=TINDEX+1; TBUF[TINDEX]:=TDATA; TIMER:=0; END ELSE TIMER:=TIMER+1; END; { NOW THE CHARACTERS ARE RETRIEVED FROM THE TEMP BUFFER } { AND WRITTEN TO DISK. IF MORE THAN 1 CHARACTER IS IN } { THE BUFFER, THEN WRITE TO THE CONSOLE ALSO. } FOR I:=0 TO TINDEX DO BEGIN TDATA:=TBUF[I]; CH:=CHR(TDATA); IF (NOT DISKFULL) THEN WNB(INFILE,INBUF,ININDEX,DISKFULL,CH); IF I <> 0 THEN WRITECONSOLE(TDATA); END; { THE REMOTE CAN NOW BE ALLOWED TO CONTINUE. } WRITEMODEM(CONTINUE); END ELSE BEGIN CH:=CHR(DATA); WNB(INFILE,INBUF,ININDEX,DISKFULL,CH); END; IF DISKFULL THEN BEGIN WRITEMODEM(TERMINATE); WRITELN; WRITELN('* * * DISK FULL - TRANSFER TERMINATED * * *'); FISTAT:=FALSE END; END; (*************************************************************) PROCEDURE INITIAL; VAR CONSOLE:INTEGER; CH:CHAR; FUNCTION CONTROL:INTEGER; VAR CH:CHAR; BEGIN READ(CH);WRITELN; CONTROL:=ORD(CH) & $001F END; BEGIN DONE:=FALSE;MWAIT:=FALSE;CRLF:=FALSE; FOSTAT:=FALSE;FISTAT:=FALSE; COREADY:=FALSE;MOREADY:=FALSE; CISTAT:=FALSE;MISTAT:=FALSE; CIOBYTE:=MON1(IOCHK,0); MIOBYTE:=CIOBYTE & $FFFC; (* LOGICAL AND TO CLEAR 2 LSB *) CONSOLE:=CIOBYTE & $0003; (* LOGICAL AND TO MASK 2 LSB *) WRITELN;WRITELN('* * * MODEM COMMUNICATION PROGRAM * * *'); WRITELN;WRITELN('CURRENT CONSOLE NUMBER: ',CONSOLE); REPEAT WRITE('ENTER MODEM CONSOLE NUMBER (0-3): '); READ(CONSOLE);WRITELN UNTIL (CONSOLE >= 0) AND (CONSOLE <= 3); MIOBYTE:=MIOBYTE + CONSOLE; WRITE('WANT CONSOLE ECHO (Y OR N)?'); READ(CH);WRITELN; IF CH='Y' THEN CONECHO:=TRUE ELSE CONECHO:=FALSE; WRITE('ENTER SUSPEND CHARACTER: CONTROL-'); SUSPEND:=CONTROL; WRITE('ENTER CONTINUE CHARACTER: CONTROL-'); CONTINUE:=CONTROL; WRITE('ENTER TERMINATE CHARACTER: CONTROL-'); TERMINATE:=CONTROL; WRITE('ENTER COMMAND MODE CHARACTER:CONTROL-'); MODE:=CONTROL; WRITE('ENTER SYSTEM RETURN CHARACTER: CONTROL-'); QUIT:=CONTROL; WRITE('WANT EVEN OR ODD PARITY (E OR O)?'); READ(CH);WRITELN; IF CH='E' THEN PARITY:=TRUE ELSE PARITY:=FALSE; WRITELN('* * * INITIALIZATION COMPLETE * * *'); WRITELN; END; (*************************************************************) PROCEDURE COMMAND; TYPE NAME=ARRAY[0..11] OF CHAR; STRING=RECORD LEN:INTEGER; VAL:ARRAY[1..79] OF CHAR END; VAR CH:CHAR; RESULT:INTEGER; FILENAME:NAME; (*$L-*) (*$ISTRIO*) (*$IFILESTUF*) (*$L+*) PROCEDURE READTITLE(VAR T:NAME); VAR TITLE,FILENAME:STRING; BEGIN WRITE(' FILE:'); READSTR(FILENAME);WRITELN; NAMEPARSER(FILENAME,TITLE); MOVE(TITLE.VAL[1],T[0],12); END; BEGIN (* COMMAND *) WRITEMODEM(SUSPEND); CH:=' '; REPEAT WRITELN;WRITELN; WRITE('COMMAND:I(NITIAL),S(END),R(ECEIVE),C(LOSE),M(ODEM),Q(UIT) ?'); READ(CH);WRITELN; IF CH='I' THEN INITIAL; IF CH='S' THEN BEGIN WRITE('SEND'); READTITLE(FILENAME); OPEN(OUTFILE,FILENAME,RESULT); IF RESULT=255 THEN WRITELN('* * * UNABLE TO OPEN FILE * * *') ELSE BEGIN OUTINDEX:=BUFSIZE+1; FOSTAT:=TRUE; END; END; IF CH='R' THEN BEGIN WRITE('RECEIVE'); READTITLE(FILENAME); OPEN(INFILE,FILENAME,RESULT); IF RESULT <> 255 THEN DELETE(INFILE); CREATE(INFILE,FILENAME,RESULT); IF RESULT=255 THEN WRITELN('* * * DIRECTORY FULL * * *') ELSE BEGIN ININDEX:=0; FISTAT:=TRUE; END; END; IF CH='C' THEN BEGIN WRITEFILE(ENDFILE); IF ININDEX <> 0 THEN BEGIN ININDEX:=BUFSIZE; WRITEFILE(ENDFILE); END; CLOSE(INFILE,RESULT); FISTAT:=FALSE; END; IF CH='Q' THEN BIOS2(WBOOT,0); UNTIL CH='M'; WRITELN('*** RETURNING TO MODEM ***'); WRITELN; WRITEMODEM(CONTINUE); END; (*************************************************************) (*************************************************************) BEGIN INITIAL; COMMAND; WHILE NOT DONE DO BEGIN IF NOT MOREADY THEN (* GET NEW OUTPUT *) IF FOSTAT THEN (* GET FROM FILE *) BEGIN READFILE(TRANSDATA); MOREADY:=TRUE; END ELSE (* GET FROM CONSOLE *) BEGIN TESTCONSOLE(CISTAT); IF CISTAT THEN BEGIN TRANSDATA:=BIOS1(CONIN,0); MOREADY:=TRUE; END; END; IF MOREADY THEN BEGIN IF TRANSDATA = QUIT THEN BIOS2(WBOOT,0); IF TRANSDATA = MODE THEN BEGIN MOREADY:=FALSE; COMMAND; END; END; IF NOT COREADY THEN (* GET NEW INPUT *) BEGIN TESTMODEM(MISTAT); IF MISTAT THEN (* GET FROM MODEM *) BEGIN READMODEM(RECDATA); COREADY:=TRUE; IF RECDATA=SUSPEND THEN BEGIN REPEAT READMODEM(RECDATA) UNTIL RECDATA=CONTINUE; COREADY:=FALSE; END; END; END; IF COREADY THEN (* SEND TO CONSOLE *) BEGIN WRITECONSOLE(RECDATA); IF FISTAT THEN WRITEFILE(RECDATA); COREADY:=FALSE; END; IF MOREADY THEN (* SEND TO MODEM *) BEGIN WRITEMODEM(TRANSDATA); IF CONECHO THEN BEGIN BIOS2(CONOUT,TRANSDATA); IF TRANSDATA=CR THEN BIOS2(CONOUT,LF); END; MOREADY:=FALSE; END; END; END. (* MODEM *)