	TITLE	'MODEM - PL/1 DC HAYES MODEM SUBROUTINES'
;	. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
;
;	MODEM I/O AND CONTROL SUBROUTINES FOR DC HAYES MODEM
;	(ADAPTED FROM MICROMODEM 100 PROGRAM)
;	(ADAPTED AGAIN TO RUN WITH PL/1 ON AUGUST 30, 1980)
;	. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

;	  ASSEMBLER STUFF
	NAME	'MODEM'
MODEM:	CSEG
FALSE:	EQU	0		;TRUE/FALSE SUBSTITUTIONS
TRUE:	EQU	NOT FALSE

;         PORT ASSIGNMENTS

DATA	EQU	80H		;DATA I/O PORT
STAT	EQU	DATA+1		;STATUS PORT
MODE	EQU	DATA+2		;MODE CONTROL PORT
CR1	EQU	DATA+1		;CONTROL REG 1
CR2	EQU	DATA+2		;CONTROL REG 2
CR3	EQU	DATA+3		;CONTROL REG 3


;	  BIT FUNCTIONS

;STATUS REGISTER
RRF	EQU	001H		;RECEIVE REGISTER FULL
TRE	EQU	002H		;TRANSMITTER HOLDING REGISTER EMPTY
PE	EQU	004H		;PARITY ERROR
FE	EQU	008H		;FRAMING ERROR
OE	EQU	010H		;DATA OVERRUN ERROR
TMR	EQU	020H		;TIMER STATUS
CD	EQU	040H		;CARRIER PRESENT
RI	EQU	080H		;NOT RING INDICATOR (LOW TRUE)

;CONTROL REGISTER 1 (CR1)
EPE	EQU	001H		;EVEN PARITY ENABLE
LS1	EQU	002H		;WORD LENGTH SELECT BIT 1
LS2	EQU	004H		;WORD LENGTH SELECT BIT 2
SBS	EQU	008H		;STOP BITS
PI	EQU	010H		;PARITY INHIBIT
TMIE	EQU	020H		;TIMER INTERRUPTS ENABLE

;CONTROL REGISTER 2 (CR2)
BRS	EQU	001H		;BAUD RATE CONTROL
TXE	EQU	002H		;TRANSMIT CARRIER ENABLE
MS	EQU	004H		;MODE (0=ANSWER 1=ORIGINATE)
BRK	EQU	008H		;SEND BREAK
ST	EQU	010H		;SELF TEST
TIE	EQU	020H		;TRANSMITTER INTERRUPT ENABLE
RIE	EQU	040H		;RECEIVER INTERRUPT ENABLE
OH	EQU	080H		;OFF-HOOK


;		* * *  RESET MODEM  * * *
MDMRST:
	PUBLIC	MDMRST
	XRA	A	;ZERO CONTROL REGS.
	OUT	CR1
	OUT	CR2
	STA	MICR1	;  AND MEMORY IMAGES OF REGISTERS.
	STA	MICR2
	RET

;		* * *  TRANSMIT CHAR  * * *
;	THIS ROUTINE SENDS A BYTE IN REGISTER C TO THE MODEM
;	TRANSMITTER.  ONLY REGISTER A IS DISTURBED.
MDMTXC:
	PUBLIC	MDMTXC
	CALL	PLICHR	;GET THE CHAR TO OUTPUT.
MDMTXC$DIR:
	IN	STAT	;GET MODEM STATUS.
	ANI	TXE	;XMIT BUFFER EMPTY?
	JZ	MDMTXC$DIR ;...YES, LOOP.
	MOV	A,C	;...NO, SEND THE BYTE.
	OUT	DATA
	RET

;		* * *  RECEIVE CHAR  * * *
;	THIS ROUTINE RECEIVES A BYTE FROM THE MODEM RECEIVER
;	IN REGISTER A.
MDMRXC:
	PUBLIC	MDMRXC
	IN	STAT	;GET MODEM STATUS.
	ANI	RRF	;RECV BUFFER EMPTY?
	JZ	MDMRXC	;...YES, LOOP.
	IN	DATA	;...NO, GET DATA.
	ANI	07FH	;STRIP PARITY.
	RET

;		* * *  GET RECV STATUS  * * *
;	THIS ROUTINE RETURNS A=TRUE IF A RECEIVED CHARACTER
;	IS WAITING OR A=FALSE IF NOT.
MDMRXS:
	PUBLIC	MDMRXS
	IN	STAT	;GET MODEM STATUS.
	ANI	RRF	;RECV BUFFER EMPTY?
	MVI	A,FALSE
	RZ		;...YES, RETURN A=FALSE.
	MVI	A,TRUE	;...NO, RETURN A=TRUE
	RET

;		* * *  CHECK CARRIER STATUS  * * *
;	THIS ROUTINE RETURNS TRUE IF CARRIER IS PRESENT.
MDMCDS:
	PUBLIC	MDMCDS
	IN	STAT	;GET MODEM STATUS.
	ANI	CD	;CARRIER DETECTED?
	MVI	A,FALSE
	RZ		;...NO.
	MVI	A,TRUE	;...YES.
	RET

;		* * *  CHECK RING STATUS  * * *
;	THIS ROUTINE CHECKS FOR PHONE RINGING RETURNING
;	TRUE IF SO.
MDMRIS:
	PUBLIC	MDMRIS
	IN	STAT	;GET MODEM STATUS.
	ANI	RI	;PHONE RINGING?
	MVI	A,TRUE
	RZ		;...YES.
	MVI	A,FALSE
	RET

;		* * *  CHECK FOR MODEM ERROR  * * *
;	THIS ROUTINE RETURNS THE MODEM ERROR FLAGS IN
;	REGISTER A.  A=0 IF NO ERRORS.  OTHERWISE,
;	BIT 0 = PARITY ERROR  BIT 1 = FRAMING ERROR
;	BIT 2 = OVERRUN ERROR
MDMERR:
	PUBLIC	MDMERR
	IN	STAT	;GET MODEM STATUS.
	RAR		;SHIFT BITS RIGHT 2 PLACES.
	RAR
	ANI	7	;KILL UNWANTED BITS.
	RET

;		* * *  CARRIER CONTROL  * * *
;	THIS ROUTINE TURNS THE CARRIER ON/OFF ACCORDING
;	TO REGISTER C.  C=FALSE FOR OFF; C=TRUE FOR ON.
MDMCRC:
	PUBLIC	MDMCRC
	CALL	PLICHR	;GET THE COMMAND.
MDMCRC$DIR:
	MOV	A,C
	ORA	A
	MVI	A,TXE	;CARRIER CONTROL BIT.
	JZ	MDMCC2	;CLEAR IT IF A=FALSE.
	JMP	MDMSC2	;SET IT IF A=TRUE.

;		* * *  BAUD RATE CONTROL  * * *
;	THIS ROUTINE SETS THE BAUD RATE GIVEN BY REGISTER
;	C.  C=FALSE FOR 110; C=TRUE FOR 300.
MDMBDC:
	PUBLIC	MDMBDC
	CALL	PLICHR	;GET THE COMMAND.
MDMBDC$DIR:
	MOV	A,C
	ORA	A
	MVI	A,BRS	;BAUD RATE SELECT BIT.
	JZ	MDMCC2	;CLEAR IT IF A=FALSE.
	JMP	MDMSC2	;SET IT IF A=TRUE.

;		* * *  HOOK CONTROL  * * *
;	THIS ROUTINE SETS THE HOOK ON OR OFF ACCORDING
;	TO REGISTER C.  C=FALSE FOR ON (IDLE); C=TRUE
;	FOR OFF (ACTIVE).
MDMSWH:
	PUBLIC	MDMSWH
	CALL	PLICHR	;GET THE COMMAND.
MDMSWH$DIR:
	MOV	A,C
	ORA	A
	MVI	A,OH	;HOOK CONTROL BIT.
	JZ	MDMCC2	;CLEAR IT IF A=FALSE.
	JMP	MDMSC2	;SET IT IF A=TRUE.

;		* * *  GET HOOK STATUS  * * *
;	THIS ROUTINE RETURNS THE HOOK STATUS IN REGISTER
;	A.  A=TRUE FOR OFF-HOOK; A=FALSE FOR ON-HOOK.
MDMSHS:
	PUBLIC	MDMSHS
	LDA	MICR2	;GET CR2.
	ANI	OH	;MASK FOR OFF-HOOK.
	MVI	A,TRUE
	RNZ		;...OFF-HOOK.
	MVI	A,FALSE ;...ON-HOOK.
	RET

;		* * *  MODE CONTROL  * * *
;	THIS ROUTINE SETS THE MODE (ANSWER OR ORIGINATE)
;	ACCORDING TO REGISTER C.  C=FALSE FOR ANSWER;
;	C=TRUE FOR ORIGINATE.
MDMMOD:
	PUBLIC	MDMMOD
	CALL	PLICHR	;GET THE COMMAND.
MDMMOD$DIR:
	MOV	A,C
	ORA	A
	MVI	A,MS	;MODE SELECT BIT.
	JZ	MDMCC2	;CLEAR IT IF A=FALSE.
	JMP	MDMSC2	;SET IT IF A=TRUE.

;		* * *  SELF-TEST CONTROL  * * *
;	THIS ROUTINE SETS THE SELF-TEST MODE
;	ACCORDING TO REGISTER C.  C=FALSE FOR NORMAL;
;	C=TRUE FOR SELF-TEST.
MDMSLT:
	PUBLIC	MDMSLT
	CALL	PLICHR	;GET THE COMMAND.
MDMSLT$DIR:
	MOV	A,C
	ORA	A
	MVI	A,ST	;SELF TEST BIT.
	JZ	MDMCC2	;CLEAR IT IF A=FALSE.
	JMP	MDMSC2	;SET IT IF A=TRUE.

;		* * *  SEND BREAK  * * *
;	THIS ROUTINE SENDS A BREAK CONSISTING OF A
;	SPACE (LOW TONE) FOR A NUMBER OF 50 MS IN-
;	TERVALS SPECIFIED BY REGISTER C.
MDMBRK:
	PUBLIC	MDMBRK
	CALL	PLICHR	;GET # OF INTERVALS.
MDMBRK$DIR:
	PUSH	B
	MVI	C,2	;WAIT 100 MS.
	CALL	MDMDLY$DIR
	POP	B
	PUSH	H	;SET THE BREAK BIT.
	LXI	H,MICR2
	MVI	A,BRK
	ORA	M
	OUT	CR2
	CALL	MDMDLY$DIR ;DELAY (C)*50 MS.
	MVI	A,BRK	;RESET BREAK BIT.
	CMA
	ANA	M
	OUT	CR2
	POP	H
	RET

;		* * *  SET PARITY  * * *
;	THIS ROUTINE SETS THE PARITY ACCORDING TO REG C.
;	C=0 FOR NO PARITY, C=1 FOR ODD PARITY AND C=2
;	FOR EVEN PARITY.
MDMPAR:
	PUBLIC	MDMPAR
	CALL	PLICHR	;GET THE COMMAND.
MDMPAR$DIR:
	MOV	A,C	;TEST FOR NO PARITY.
	ORA	A
	JZ	MDMPAF	;TURN OFF PARITY.
	MVI	A,PI	;CLEAR PARITY INHIBIT BIT.
	CALL	MDMCC1
	MOV	A,C	;SET LSB FOR EVEN/ODD PARITY.
	RAR
	ANI	1
	MVI	A,EPE	;CLEAR EPE IF ODD.
	JZ	MDMCC1
	JMP	MDMSC1	;SET EPE IF EVEN.
MDMPAF:
	MVI	A,PI	;INHIBIT PARITY.
	JMP	MDMSC1

;		* * *  SELECT WORD LENGTH  * * *
;	THIS ROUTINE SETS THE WORD LENGTH ACCORDING TO REG C.
;	C = NUMBER OF BITS (5,6,7,8)
MDMWLN:
	PUBLIC	MDMWLN
	CALL	PLICHR	;GET THE COMMAND.
MDMWLN$DIR:
	MOV	A,C	;REMOVE BIAS.
	SUI	5
	RC
	CPI	3+1
	CMC
	RC
	ADD	A	;SHIFT LEFT 1 PLACE.
	MOV	C,A	;SAVE RESULT.
	LDA	MICR1	;GET MEMORY IMAGE.
	ANI	NOT LS1+LS2 ;CLEAR WORD LENGTH BITS.
	ORA	C	;PUT IN NEW ONES.
	STA	MICR1	;UPDATE MEMORY IMAGE.
	OUT	CR1	;WRITE IT TO MODEM.
	RET

;		* * *  SET NUMBER OF STOP BITS  * * *
;	THIS ROUTINE SETS THE NUMBER OF STOP BITS ACCORDING
;	TO REGISTER C.  C = STOP BITS (1 OR 2)
MDMNST:
	PUBLIC	MDMNST
	CALL	PLICHR	;GET THE COMMAND.
MDMNST$DIR:
	MOV	A,C	;REMOVE BIAS.
	SUI	1
	RC
	CPI	1+1
	CMC
	RC
	ORA	A
	MVI	A,SBS	;STOP BIT SELECT BIT.
	JZ	MDMCC1	;CLEAR IT.
	JMP	MDMSC1

;		* * *  DELAY ROUTINE  * * *
;	THIS ROUTINE WAITS 50MS TIMES THE VALUE OF THE
;	REGISTER C.
MDMDLY:
	PUBLIC	MDMDLY
	CALL	PLICHR	;GET THE TIME AMOUNT.
MDMDLY$DIR:
	MOV	A,C
	ORA	A	;IF ZERO, DON'T WAIT.
	RZ
	PUSH	B
MDMDL1:
	OUT	CR3	;START TIMER.
MDMDL2:
	IN	STAT	;WAIT TILL TIMER BIT
	ANI	TMR	;TIMES OUT.
	JZ	MDMDL2
	DCR	C	;LOOP (C) TIMES.
	JNZ	MDMDL1
	POP	B
	RET

;		* * *  START 50MS TIMER  * * *
MDMSTM:
	PUBLIC	MDMSTM
	OUT	CR3
	RET

;		* * *  CHECK TIMER STATUS  * * *
MDMCTM:
	IN	STAT	;GET MODEM STATUS.
	ANI	TMR	;TIME OUT?
	MVI	A,TRUE
	RNZ		;...YES.
	MVI	A,FALSE
	RET

;		* * *  GO OFF HOOK  * * *
;	THIS ROUTINE GOES OFF HOOK AND WAITS FOR A DIAL TONE.
;	MICROMODEM HAS NO DIAL TONE DETECTOR.  WE JUST GO OFF
;	HOOK, WAIT 2 SEC, ASSUME A DIAL TONE IS PRESENT AND
;	RETURN.
MDMDLT:
	PUBLIC	MDMDLT
	PUSH	B
	MVI	C,TRUE	;GO OFF-HOOK.
	CALL	MDMSWH$DIR
	MVI	C,40	;WAIT 2 SECONDS.
	CALL	MDMDLY$DIR
	POP	B
	RET

;		* * *  DIAL PULSE GENERATION  * * *
;	THIS ROUTINE GENERATES A DIALING PULSE FOR THE DIGIT
;	IN REGISTER C.  IF C=0 THEN 10 PULSES WILL BE
;	PRODUCED.  THE DIGIT IN THE C REGISTER MAY BE
;	BINARY OR ASCII.  AFTER THE DIGIT IS OUT-PULSED,
;	THIS ROUTINE WILL PAUSE FOR 600 MS FOR INTER-DIGIT
;	SPACING.
MDMPLS:
	PUBLIC	MDMPLS
	CALL	PLICHR	;GET THE DIGIT.
MDMPLS$DIR:
	PUSH	B
	MOV	A,C	;GET DIGIT.
	MVI	B,10	;PRE-LOAD PULSE COUNTER
	ANI	00FH	;KILL POSSIBLE ASCII BIAS.
	CPI	0	;SEE IF WE'RE DOING 10 PULSES.
	JZ	MDMPL1
	MOV	B,A	;PULSE COUNT TO B
MDMPL1:
	MVI	C,FALSE	;GO ON-HOOK.
	CALL	MDMSWH$DIR
	MVI	C,1	;WAIT 50 MS.
	CALL	MDMDLY$DIR
	MVI	C,TRUE	;GO OFF-HOOK AGAIN.
	CALL	MDMSWH$DIR
	MVI	C,1	;DELAY 50 MS.
	CALL	MDMDLY$DIR
	DCR	B	;LOOP FOR ALL PULSES.
	JNZ	MDMPL1
	MVI	C,11	;WAIT 550 MS.
	CALL	MDMDLY$DIR
	POP	B
	RET

;		* * *  DIAL A NUMBER  * * *
;	THIS ROUTINE GENERATES ALL THE DIALING PULSES
;	FOR A NUMBER GIVEN AS A VARYING CHARACTER STRING.
;	THEREFORE, IT DIALS THE NUMBER.
MDMDLN:
	PUBLIC	MDMDLN
	CALL	MDMDLT	;GET DIAL TONE.
	CALL	PLICHR	;GET THE STRING LENGTH IN C.
	ORA	A	;LENGTH=ZERO?
	RZ		;...YES, RETURN.
	INX	H	;BUMP TO BEGINNING OF STRING.
MDMDLN$LP:
	MOV	A,M	;GET THE NEXT NUMBER.
	CPI	'*'	;DELAY?
	JNZ	MDMDLN$NUM ;...YES.
	PUSH	B
	MVI	C,40	;WAIT 2 SEC.
	CALL	MDMDLY$DIR
	POP	B
	JMP	MDMDLN$NXT ; GET NEXT CHAR.
MDMDLN$NUM:
	CPI	'9'+1	;ASCII 0-9?
	JNC	MDMDLN$NXT ;...NO, SKIP IT.
	CPI	'0'
	JC	MDMDLN$NXT ;...NO, SKIP IT.
	PUSH	B	;SAVE CURRENT COUNT.
	MOV	C,A	;PULSE OUT THE NUMBER.
	CALL	MDMPLS$DIR
	POP	B
MDMDLN$NXT:
	INX	H	;BUMP PTR.
	DCR	C	;DECREMENT COUNT.
	RZ		;RETURN IF NO MORE DIGITS.
	JMP	MDMDLN$LP ;GO PULSE ANOTHER DIGIT.

;		* * *  SET CR1  * * *
;	THIS ROUTINE SETS BITS IN CONTROL REGISTER
;	1.  REGISTER A HAS 1'S WHERE BITS ARE TO
;	BE SET.
MDMSC1:
	PUSH	H
	LXI	H,MICR1	;OR IN BITS TO SET.
	ORA	M
	MOV	M,A	;SAVE RESULT.
	OUT	CR1	;SET THE MODEM.
	POP	H
	RET

;		* * *  CLEAR CR1 * * *
;	THIS ROUTINE CLEARS THE BITS IN CONTROL REGISTER
;	1.  REGISTER A HAS 1'S FOR THE BITS TO BE CLEARED.
MDMCC1:
	PUSH	H
	LXI	H,MICR1	;MEMORY IMAGE OF CR1.
	CMA
	ANA	M	;TURN OFF THE BIT.
	MOV	M,A	;UPDATE MEMORY.
	OUT	CR1	;UPDATE MODEM.
	POP	H
	RET

;		* * *  SET CR2  * * *
;	THIS ROUTINE SETS BITS IN CONTROL REGISTER
;	2.  REGISTER A HAS 1'S WHERE BITS ARE TO
;	BE SET.
MDMSC2:
	PUSH	H
	LXI	H,MICR2	;OR IN BITS TO SET.
	ORA	M
	MOV	M,A	;SAVE RESULT.
	OUT	CR2	;SET THE MODEM.
	POP	H
	RET

;		* * *  CLEAR CR2 * * *
;	THIS ROUTINE CLEARS THE BITS IN CONTROL REGISTER
;	2.  REGISTER A HAS 1'S FOR THE BITS TO BE CLEARED.
MDMCC2:
	PUSH	H
	LXI	H,MICR2	;MEMORY IMAGE OF CR2.
	CMA
	ANA	M	;TURN OFF THE BIT.
	MOV	M,A	;UPDATE MEMORY.
	OUT	CR2	;UPDATE MODEM.
	POP	H
	RET

;		* * *  GET PL/I PARAMETER * * *
;	THIS ROUTINE GETS A PL/I PARAMETER ACCORDING TO 
;	ITS STANDARD CALLING CONVENTIONS.
PLICHR:
	PUSH	D	;SAVE DE.
	MOV	E,M	;GET PARM PTR FROM 0(HL).
	INX	H
	MOV	D,M
	LDAX	D	;GET THE ONE BYTE PARM.
	MOV	C,A	;PUT IT IN (C).
	XCHG		;PUT PARM PTR IN HL.
	POP	D
	RET

;		* * *  DATA AREA FOR MODEM I/O  * * *
	DSEG
MICR1:	DB	0	;MEMORY IMAGE OF CONTROL REGISTER 1
MICR2:	DB	0	;MEMORY IMAGE OF CONTROL REGISTER 2
	END
