		TITLE	TDD VERSION 5.2 BY JOHN W. SPALDING
		PAGE	62,132

;This program allows an IBM PC to be used as a Telecommunications Device for
;the Deaf (TDD).  The cassette port of the PC must be coupled to the telephone
;line by a modified telephone amplifier or other suitable means.  Note that
;the modem function is provided by this program, not the coupling device.
;
;The cassette port pinouts, as described in the PC Technical Reference
;Manual from IBM are as follows:
;
;	1 and 3: Relay contacts normally used for cassette motor control.
;		TDD5 closes them at startup, opens them when it ends.  Could
;		be used to control on/off hook in your coupler.  Possible
;		future use for pulse dialing.
;
;	2:	Ground.
;
;	4:	Data in (received data) rated at +/- 13v max.
;
;	5:	Data out (transmitted data) rated at .075 vdc, may
;		be jumpered to .68vdc
;
;NOTE: This version is coded to produce an .EXE file to simplify installation.
;The following commands should be issued to assemble and link the program:
;
;		MASM TDD5;
;		LINK TDD5;
;		ERASE TDD5.OBJ
;
;The the uppercase letter keys on the keyboard are mapped to their
;corresponing TTY FIGS characters, for example, you can send "#" by
;typing SHIFT-H or by typing "#".  Linefeeds may be sent by typing
;CTRL-J, however the program will insert them automatically as needed.
;
;Type "TDD5" to run the program, type CTRL-Break to end it.

;8253 COUNTER/TIMER EQUATES

TIMER_MODE	EQU	43H		;PORT TO WRITE MODE WORD
TIMER_0 	EQU	40H		;TIMER 0 FOR XMIT/RECV TIMING
TIMER_2 	EQU	42H		;TIMER 2 FOR CARRIER GENERATION
TIMER_0_MODE	EQU	00110110B	;MODE WORD FOR TIMER 0
TIMER_2_MODE	EQU	10110110B	;MODE WORD FOR TIMER 2

NEW_TIMER_TICK	EQU	305		;GENERATE TICKS AT 3912 HZ

BAUD_RATE	EQU	86		;22 MILLISECONDS
SEND_TIMEOUT	EQU	3912/2		;ABOUT A HALF A SECOND
RECEIVE_TIMEOUT EQU	3912*3/4	;ABOUT 3/4 SECOND

SPACE_TONE	EQU	663		;1800 HZ SPACE
MARK_TONE	EQU	852		;1400 HZ MARK

;CASSETTE PORT EQUATES

PPI_B		EQU	061H		;8255 PPI PORT B
T2_GATE 	EQU	01H		;SET ON TO GATE TIMER 2 OUTPUT
SPEAKER_DATA	EQU	02H		;SET ON TO TURN ON SPEAKER
MOTOR_OFF	EQU	08H		;BIT OFF TO ENABLE CASSETTE CIRCUIT

PPI_C		EQU	062H		;8255 PPI PORT C
CASS_DATA_IN	EQU	10H		;SAMPLE CASSETTE INPUT
CASS_SHIFTS	EQU	4		;BITS TO SHIFT LEFT TO GET CASS DATA

;8259 PROGRAMMABLE INTERRUPT CONTROLLER EQUATES

INT_CONTROL	EQU	20H		;PORT ADDRESS OF 8259
EOI_COMMAND	EQU	20H		;END OF INTERRUPT COMMAND

;DEFINE THE INTERRUPT VECTORS WE'LL USE:

ABSOLUTE_ZERO	SEGMENT AT 0H

		ORG	4*08H		;TIMER_INT IS INT 08H
TIMER_INT	DD	?

		ORG	1BH*4		;CTRL-BRK INTERRUPT VECTOR
BRK_INT 	DD	?

ABSOLUTE_ZERO	ENDS

		PAGE

CODE		SEGMENT
		ASSUME	CS:CODE,DS:CODE,ES:CODE,SS:NOTHING

BANNER		DB	'TDD Version 5.2 (IBM PC)'
		DB	   13,10,'by John W. Spalding'
		DB	13,10,10,'Ctrl-Break to exit',13,10,10,'$'

SHIFT_REG	DB	0	;INCOMMING SIGNAL SHIFTED IN HERE
OUR_CLOCK	DW	0	;INCREMENTED AT 1909 HZ
SYSTEM_CLOCK	DW	0	;TO GENERATE 18HZ TICKS FOR SYSTEM
SAVE_PPI_B	DB	?	;ORIGINAL CASSETTE PORT SETTINGS
TIMER_SAVE	DD	?	;ORIGINAL TIMER INTERRUPT VECTOR
BREAK_SAVE	DD	?	;ORIGINAL BREAK KEY INTERRUPT
SHIFT_STATUS	DB	40H	;FIGS, LTRS STATUS OF SCREEN/KEYBOARD
FIGS_SHIFT	EQU	20H	;CURRENTLY IN FIGS SHIFT, OFF=LTRS SHIFT
FORCE_SHIFT	EQU	40H	;SEND FIGS/LTRS FOR NEXT CHARACTER
LAST_CHAR	DB	0	;LAST CHAR SEND -- USED TO FORCE LF IF REQ'D
HOLD_CHAR	DB	000H	;HOLD BAUDOT CHARACTER FOR KEYBOARD ROUTINE
CANCEL		DB	0	;SET TO 0FFH BY BREAK INTERRUPT

TABLE_BAUD_ASC	DB	8,'E',10,'A SIU',13,'DRJNFCK'           ;LETTERS
		DB	'TZLWHYPQOBG',0A0H,'MXV',80H
		DB	8,'3',10,'- ',7,'87',13,'$4',27H,',!:(' ;FIGURES
		DB	'5")2#6019?&',0A0H,'./;',80H

CODE_FIGS	EQU	1BH	;BAUDOT CODE FOR FIGS SHIFT
CODE_LTRS	EQU	1FH	;BAUDOT CODE FOR LTRS SHIFT
CODE_LF 	EQU	02H	;BAUDOT CODE FOR LINE FEED
CODE_CR 	EQU	08	;BAUDOT CODE FOR CARRIAGE RETURN

;IN THE FOLLOWING TABLE, BIT7=1 IS NO BAUDOT EQUIV, BIT6=1 MEANS EITHER SHFT
;BIT5=1 MEANS MUST BE FIGS SHIFT.

;			 0   1	 2   3	 4   5	 6   7
;			 8   9	 A   B	 C   D	 E   F

TABLE_ASC_BAUD	DB	80H,80H,80H,80H,80H,80H,80H,25H 	;00H-07H
		DB	40H,80H,42H,80H,80H,48H,80H,80H 	;08H-0FH
		DB	80H,80H,80H,80H,80H,80H,80H,80H 	;10H-17H
		DB	80H,80H,80H,80H,80H,80H,80H,80H 	;18H-1FH
		DB	44H,2DH,31H,34H,29H,80H,3AH,2BH 	;20H-27H
		DB	2FH,32H,80H,80H,2CH,23H,3CH,3DH 	;28H-2FH
		DB	36H,37H,33H,21H,2AH,30H,35H,27H 	;30H-37H
		DB	26H,38H,2EH,3EH,80H,80H,80H,39H 	;38H-3FH
		DB	80H,23H,39H,2EH,29H,21H,2DH,3AH 	;40H-47H
		DB	34H,26H,2BH,2FH,32H,3CH,2CH,38H 	;48H-4FH
		DB	36H,37H,2AH,25H,30H,27H,3EH,33H 	;50H-57H
		DB	3DH,35H,31H,80H,80H,80H,80H,80H 	;58H-5FH
		DB	80H,03H,19H,0EH,09H,01H,0DH,1AH 	;60H-67H
		DB	14H,06H,0BH,0FH,12H,1CH,0CH,18H 	;68H-6FH
		DB	16H,17H,0AH,05H,10H,07H,1EH,13H 	;70H-77H
		DB	1DH,15H,11H,80H,80H,80H,80H,40H 	;78H-7FH

END_MSG 	DB	13,10,10,'+++ TDD5 ENDED +++',13,10,10,'$'

BEL_MSG 	DB	'<BEL>$'        ;SUBSTITUTED FOR ^G  (07H)

SIG_SW		DW	OFFSET $	;INDICATES <SIGNAL> OR <      > WRITTEN
SIG_REG 	DB	0		;SLOW SHIFT REG FOR RING/BUSY
SIG_CLK 	DB	0		;LOCAL CLOCK FOR RING/BUSY TESTS
SIG_ON		DB	'<SIGNAL>$'     ;LEGEND TO INDICATE A SIGNAL
SIG_OFF 	DB	'<      >$'     ;LEGEND TO INDICATE NO SIGNAL

		PAGE

MAIN_ENTRY:	XOR	AX,AX		;PUSH A RETURN ADDRESS
		PUSH	DS		;AND SET UP SEGMENTS
		PUSH	AX
		MOV	AX,CS
		MOV	DS,AX
		MOV	ES,AX

		CALL	CLR_SCRN	;CLEAR THE SCREEN AND ...
		MOV	DX,OFFSET BANNER	;PRINT HELLO MESSAGE
		CALL	PRINT_STRING

		CALL	TIMER_INIT		;SET UP INTERRUPT VECTORS

;MAIN LOOP -- ONCE XMIT/RECV BEGINS STAY IN THAT MODE FOR GIVEN TIMEOUT.
;THE CALLED ROUTINES RETURN OR EXPECT A BAUDOT CHARACTER CODE IN AL

RECEIVE1:	CALL	RECEIVE_TTY		;TRY TO RECEIVE A CHARACTER
		JZ	TRANSMIT1		;NONE

RECEIVE2:	CALL	PRINT_BAUDOT		;PRINT THE CHARACTER
		MOV	OUR_CLOCK,0		;SET TIMEOUT

RECEIVE3:	CALL	RECEIVE_TTY		;TRY TO RECEIVE ANOTHER CHAR
		JNZ	RECEIVE2
		CMP	OUR_CLOCK,RECEIVE_TIMEOUT ;CHECK TIMEOUT
		JC	RECEIVE3		;TRY UNTIL EXPIRED

TRANSMIT1:	OR	SHIFT_STATUS,FORCE_SHIFT ;SEND SHIFT ON 1ST CHR
		CALL	GET_BAUDOT		;TRY TO READ THE KEYBOARD
		JZ	SIGNAL1 		;NO, LOOK FOR RING/BUSY

TRANSMIT2:	CALL	TRANSMIT_TTY		;SEND THE CHARACTER
		MOV	OUR_CLOCK,0		;SET TIMEOUT

TRANSMIT3:	CALL	GET_BAUDOT		;LOOK FOR ANOTHER CHARACTER
		JNZ	TRANSMIT2		;SEND IT IF ANY
		CMP	OUR_CLOCK,SEND_TIMEOUT ;CHECK TIMEOUT
		JC	TRANSMIT3		;TRY UNTIL EXPIRED
		CALL	CARRIER_OFF		;TURN OFF CARRIER
		JMP	RECEIVE1		;AND ENTER RECEIVE MODE

SIGNAL1:	CALL	SIG_TEST		;GO TEST FOR RING/BUSY
		CMP	CANCEL,0		;BRK KEY PRESSED
		JZ	SHORT RECEIVE1		;NO
		MOV	CANCEL,0		;RESET SW FOR DEBUG

;CTRL_BRK KEY WAS PRESSED

FINISH_UP:	CALL	TIMER_QUIT		;CLEAN UP VECTORS AND TIMERS

		MOV	DX,OFFSET END_MSG	;PRINT ENDING MESSAGE
		CALL	PRINT_STRING
DUMMY1		PROC	FAR
		RET				;SHOULD BE A FAR RETURN HERE
DUMMY1		ENDP
		PAGE

;BREAK KEY INTERRUPT ROUTINE

BREAK_KEY:	MOV	CS:CANCEL,0FFH		;SET THE FLAG
		IRET				;AND RETURN

;RECEIVE A TTY CHARACTER.

RECEIVE_TTY:	MOV	AL,SHIFT_REG		;LOOK FOR CARRIER TONE
		CMP	AL,01010101B
		JZ	HAVE_CARRIER		;FOUND START BIT (SPACE)
		SHL	AL,1			;START WITH 01 OR 10?
		JO	RECEIVE_TTY		;YES, STILL POSSIBLE
		XOR	AL,AL			;SET ZERO FLAG AND AL=0
		RET

HAVE_CARRIER:	MOV	OUR_CLOCK,0		;RESET THE CLOCK

START_WAIT:	CMP	OUR_CLOCK,BAUD_RATE+BAUD_RATE/8
		JC	START_WAIT		;DELAY TO 1ST DATA BIT
		XOR	AL,AL			;SET UP TO RECEIVE DATA BITS
		MOV	CX,5

DATA_BITS:	MOV	OUR_CLOCK,0

FIND_TONE:	CMP	SHIFT_REG,01010101B	;LOOK FOR SPACE
		JZ	HAVE_BIT		;HAVE ONE, NOTE CF=0
		CMP	OUR_CLOCK,BAUD_RATE/3	;ONLY TRY SO LONG
		JC	FIND_TONE
		STC				;DIDN'T FIND IT, SET 1 BIT

HAVE_BIT:	RCR	AL,1			;MOVE THE CARRY IN

BIT_DELAY:	CMP	OUR_CLOCK,BAUD_RATE	;SIT HERE FOR
		JC	BIT_DELAY		;REST OF BIT TIME

		LOOP	DATA_BITS		;SHIFT REST OF BITS
		SHR	AL,1			;ONLY SHIFTED 5 BITS
		SHR	AL,1			;SO SHIFT OTHER 3
		SHR	AL,1

		CMP	AL,0FFH 		;SET NONZERO FLAG
		RET				;RETURN WITH CHARACTER

		PAGE

;SEND TTY CHARACTER OVER THE CASSETTE INTERFACE USING TIMER 2

TRANSMIT_TTY:	MOV	BL,AL			;MOVE CHARACTER
		CLC				;AND CLEAR THE CARRY BIT
		MOV	CX,6			;NUMBER OF BITS

XMIT_LOOP:	CALL	XMIT_BIT		;SEND ONE BIT
		RCR	BL,1			;GET NEXT BIT
		LOOP	XMIT_LOOP		;AND DO IT AGAIN

		MOV	DX,BAUD_RATE+BAUD_RATE/2 ;SEND 1 1/2 STOP BITS
		CALL	XMIT_BIT1

		RET				;RETURN LEAVING CARRIER ON

XMIT_BIT:	MOV	DX,BAUD_RATE		;TIME FOR ONE BIT
		MOV	AX,SPACE_TONE		;ASSUME SPACE
		JNC	XMIT_BIT0		;YES, BIT IS ZERO

XMIT_BIT1:	MOV	AX,MARK_TONE		;ELSE, SET MARK

XMIT_BIT0:	CALL	WRITE_TIMER2		;SET THE TIMER
		CALL	CARRIER_ON		;TURN ON (USUALLY ALREADY ON)
		MOV	OUR_CLOCK,0		;SET THE TIMER

XMIT_BIT2:	CMP	OUR_CLOCK,DX		;WAIT TIL IT
		JC	XMIT_BIT2		;GETS UP THERE
		RET				;THEN RETURN

		PAGE

;TEST FOR NON-CARRIER SIGNALS SUCH AS RING/BUSY

SIG_TEST:	MOV	AL,BYTE PTR OUR_CLOCK+1 ;DO IT EVERY 256 TICKS
		CMP	AL,SIG_CLK
		JZ	SIG_RET

		MOV	SIG_CLK,AL
		MOV	AL,SHIFT_REG
		SHL	AL,1
		RCR	SIG_REG,1	;SHIFT INTO SLOW REG
		MOV	DX,OFFSET SIG_OFF
		JZ	SIG_PRT
		MOV	DX,OFFSET SIG_ON

SIG_PRT:	CMP	DL,BYTE PTR SIG_SW ;DON'T PRINT IF ALREADY THERE
		JZ	SIG_RET
		MOV	SIG_SW,DX

		MOV	AH,3		;GET CURSOR POSITION
		CALL	SIG_VID
		PUSH	DX

		MOV	DX,72		;SET NEW CURSOR POSITION
		CALL	SIG_SET

		MOV	DX,SIG_SW	;NOW PRINT THE FLAG
		CALL	PRINT_STRING

		POP	DX		;RESTORE ORIGINAL CURSOR POSITION
		JMP	SHORT SIG_SET

CLR_SCRN:	MOV	AX,0600H	;SCROLL/CLEAR FUNCTION
		XOR	CX,CX		;UPPER LEFT CORNER
		MOV	DX,24*256+79	;LOWER RIGHT CORNER
		MOV	BH,07H		;DEFAULT ATTRIBUTE
		INT	10H		;ROM BIOS CALL
		XOR	DX,DX		;NEED TO HOME THE CURSOR

SIG_SET:	MOV	AH,2		;CODE TO SET CURSOR

SIG_VID:	MOV	BH,0		;ASSUME VIDEO PAGE 0
		INT	10H		;VIDEO FUNCTION INTERRUPT
SIG_RET:	RET


;CONSOLE I/O AND ASCII/BAUDOT TRANSLATION ROUTINES

GET_BAUDOT:	MOV	AL,HOLD_CHAR		;GET PREVIOUS CHARACTER IF ANY
		OR	AL,AL			;IF ANY
		JNZ	HAVE_CHAR		;THEN SKIP READ

TRY_STATUS:	CALL	CONSOLE_GET		;TRY TO GET CHAR FROM KEYBOARD
		JZ	GET_NULL		;NO EXIT WITH ZERO FLAG SET

HAVE_CHAR:	MOV	HOLD_CHAR,AL		;ASSUME WANT TO HOLD
		CMP	LAST_CHAR,13		;WAS PREVIOUS CHAR CR?
		JNZ	GET_XLAT		;NO TRANSLATE THIS ONE
		CMP	AL,10			;CR FOLLOWED BY LF?
		JZ	GET_XLAT		;YES, WE'RE OK

		MOV	AL,CODE_LF		;INSERT A LINE FEED
		JMP	SHORT SET_FORCE 	;AND GO FORCE SHIFT NEXT

GET_XLAT:	PUSH	BX			;TRANSLATE CHARACTER TO BAUDOT
		MOV	BX,OFFSET TABLE_ASC_BAUD ;XLATE TABLE
		XLAT
		POP	BX

		CMP	AL,80H			;UNKNOWN CHARACTER
		JNZ	GOOD_XLAT		;SKIP OF KNOWN

GET_NULL:	AND	HOLD_CHAR,0		;CLEAR HOLD AND SET ZERO FLAG
		RET

GOOD_XLAT:	MOV	AH,AL			;GET BAUDOT CODE IN AL
		AND	AL,1FH			;SHIFT CODE IN AH
		AND	AH,60H			;40=ANY,20=FIGS,00=LTRS

		TEST	AH,40H			;ANY SHIFT OK?
		JZ	TEST_FORCE		;NO
		MOV	AH,SHIFT_STATUS 	;YES MAKE REQUESTED = CURRENT

TEST_FORCE:	TEST	SHIFT_STATUS,FORCE_SHIFT ;FORCE FIGS/LTRS?
		JNZ	GET_FORCE

		TEST	AH,40H			;ANY SHIFT NEEDED?
		JNZ	CLEAR_HOLD		;NO, SKIP

		CMP	AH,SHIFT_STATUS 	;NEED TO SEND A SHIFT?
		JNZ	GET_FORCE		;YES

CLEAR_HOLD:	MOV	HOLD_CHAR,0		;WON'T NEED THIS
		JMP	SHORT TEST_LF

GET_FORCE:	TEST	AH,FIGS_SHIFT		;ASSUME NEED FIGS
		MOV	AL,CODE_FIGS		;TRUE?
		JNZ	TEST_LF 		;YES, SEND THE FIGS
		MOV	AL,CODE_LTRS		;SET TO INSERT LTRS SHIFT

TEST_LF:	CMP	AL,CODE_LF		;SENDING A LINE FEED THIS TIME?
		JNZ	GET_ECHO		;NO

SET_FORCE:	OR	SHIFT_STATUS,FORCE_SHIFT ;YES SEND FIGS/LTRS AFTER LF

GET_ECHO:	PUSH	AX
		CALL	PRINT_BAUDOT		;PRINT THE CHARACTER
		POP	AX
		CMP	AL,0FFH 		;MAKE SURE NO ZERO FLAG

		RET

CONSOLE_GET:	MOV	AH,1			;ROM BIOS CONSOLE STATUS
		INT	16H
		JZ	CON_RET

		MOV	AH,0			;ROM BIOS CONSOLE GET
		INT	16H
		AND	AL,7FH

CON_RET:	RET

PRINT_BAUDOT:	MOV	SIG_SW,OFFSET SIG_SW	;FORCE <SIGNAL> NEXT TIME
		PUSH	BX			;PRINT BAUDOT CHAR IN AL
		MOV	BX,OFFSET TABLE_BAUD_ASC
		OR	AL,SHIFT_STATUS 	;INCLUDE FIGS/LTRS SHIFT
		AND	AL,3FH			;CLEAR EXTRA BITS
		XLAT				;HAVE ASCII CHAR IN AL
		POP	BX

		TEST	AL,80H			;SEE IF THIS WAS
		JZ	CONSOLE_PUT		;A FIGS OR LETTERS SHIFT

		AND	AL,FIGS_SHIFT		;IF THIS WAS A SHIFT,
		MOV	SHIFT_STATUS,AL 	;THEN UPDATE THE SHIFT
		RET				;RETURN

CONSOLE_PUT:	MOV	LAST_CHAR,AL		;THEN RESTORE CHAR WE WANT
		CMP	AL,07H			;IS THIS BEL?
		JZ	PRINT_BEL		;YES PRINT [BEL]

TTYFUNC:	MOV	AH,14			;TTY VIDEO FUNCTION
		INT	10H
		RET

PRINT_BEL:	PUSH	DX			;PRINT <BEL>
		MOV	DX,OFFSET BEL_MSG
		CALL	PRINT_STRING
		POP	DX
		RET

PRINT_STRING:	PUSH	SI			;POINTER TO STRING
		MOV	SI,DX
		CLD

PS1:		LODSB				;LOAD BYTE TO PRINT
		CMP	AL,'$'                  ;TERMINATED BY $
		JZ	PSR
		CALL	TTYFUNC
		JMP	SHORT PS1

PSR:		POP	SI
		RET

		PAGE

;TIMER AND CASSETTE INTERFACE SUPPORT ROUTINES

;TIMER INTERRUPT ROUTINE PERFORMS THE FOLLOWING FUNCTIONS:

;	- SHIFTS ONE BIT OF CASSETTE INPUT DATA INTO A 1 BYTE SHIFT REGISTER
;	- ADDS 1 TO A WORD USED BY MAINLINE ROUTINES AS A CLOCK
;	- JUMPS TO IBM TIMER INTERRUPT ROUTINE AT CORRECT INTERVAL (18HZ)

NEW_TIMER_INT:	STI				;INTERRUPTS BACK ON
		PUSH	DS
		PUSH	AX
		MOV	AX,CS
		MOV	DS,AX
		ASSUME	ES:NOTHING

		IN	AL,PPI_C		;READ THE CASSETTE PORT
		REPT	CASS_SHIFTS		;SHIFT CORRECT NUMBER
		RCL	AL,1			;TO GET INTO CARRY BIT
		ENDM				;(FASTER THAN RCL AL,CL)
		RCR	SHIFT_REG,1		;CASSETTE DATA SHIFTED IN

		INC	OUR_CLOCK		;ADD 1 TO OUR CLOCK

		ADD	SYSTEM_CLOCK,NEW_TIMER_TICK  ;NEED TO UPDATE IBM CLOCK?
		JC	IBM_TICK		;YES

		MOV	AL,EOI_COMMAND		;END OF INTERRUPT TO 8259
		OUT	INT_CONTROL,AL

		POP	AX			;RESTORE REGS AND RETURN
		POP	DS
		IRET

IBM_TICK:	POP	AX			;RESTORE REGS AND GO TO
		POP	DS			;IBM TIMER INT (THEY'LL
		JMP	CS:TIMER_SAVE		;DO THE EOI INSTR.

TIMER_INIT:	CLI				;NO INTERRUPTS WHILE WE FIDDLE
		PUSH	DS
		PUSH	ES

		XOR	AX,AX			;SET ES
		MOV	DS,AX			;TO ZERO
		ASSUME	DS:ABSOLUTE_ZERO

		CLD
		MOV	DI,OFFSET TIMER_SAVE
		MOV	SI,OFFSET TIMER_INT

		MOVSW
		MOVSW

		MOV	SI,OFFSET BRK_INT

		MOVSW
		MOVSW

		MOV	WORD PTR TIMER_INT[0],OFFSET NEW_TIMER_INT
		MOV	WORD PTR TIMER_INT[2],CS

		MOV	WORD PTR BRK_INT,OFFSET BREAK_KEY
		MOV	WORD PTR BRK_INT+2,CS

		MOV	AX,NEW_TIMER_TICK	;INITIALIZE TIMER 0
		CALL	WRITE_TIMER0

		IN	AL,PPI_B		;INITIALIZE CASSETTE PORT
		MOV	SAVE_PPI_B,AL		;(SAVE FOR QUIT ROUTINE)
		AND	AL,NOT (MOTOR_OFF OR T2_GATE OR SPEAKER_DATA)
		OUT	PPI_B,AL		;MOTOR ON, GATE, SPKR OFF

		JMP	SHORT TIMER_INITEND	;GO RESTORE ES AND RETURN

TIMER_QUIT:	CLI				;INTERRUPTS OFF FOR THIS
		PUSH	DS
		PUSH	ES

		XOR	AX,AX
		MOV	ES,AX
		ASSUME	ES:ABSOLUTE_ZERO,DS:CODE

		MOV	SI,OFFSET TIMER_SAVE
		MOV	DI,OFFSET TIMER_INT
		MOVSW
		MOVSW
		MOV	DI,OFFSET BRK_INT
		MOVSW
		MOVSW

		XOR	AX,AX			;RESTORE TIMER 0 TO IBM VALUE
		CALL	WRITE_TIMER0

		MOV	AL,SAVE_PPI_B		;RESTORE CASSETTE PORT
		OUT	PPI_B,AL

TIMER_INITEND:	POP	ES			;SET BACK ORIGINAL ES REG
		POP	DS			;AND DS
		STI
		RET

		ASSUME	DS:CODE,ES:CODE

;WRITE TIMER ROUTINES -- SET TIMER0 OR TIMER2 TO VALUE IN AX

WRITE_TIMER0:	PUSH	DX			;ENTER HERE FOR TIMER 0
		PUSH	AX

		MOV	DX,TIMER_0		;PORT ADDRESS
		MOV	AL,TIMER_0_MODE 	;MODE WORD FOR TIMER 0

		JMP	SHORT WRITE_TIMERX

WRITE_TIMER2:	PUSH	DX			;ENTER HERE FOR TIMER 2
		PUSH	AX

		MOV	DX,TIMER_2		;PORT ADDRESS
		MOV	AL,TIMER_2_MODE 	;MODE WORD FOR TIMER 2

WRITE_TIMERX:	OUT	TIMER_MODE,AL		;SET MODE OF REQUESTED TIMER

		POP	AX			;WRITE LSB
		OUT	DX,AL
		MOV	AL,AH			;THEN MSB
		OUT	DX,AL

		POP	DX			;ALREADY POPPED AX, POP DX
		RET

;ROUTINES TO TURN CARRIER TONE ON OR OFF AT CASSETTE INTERFACE.

CARRIER_ON:	PUSH	AX			;START CARRIER TONE
		IN	AL,PPI_B		;TO MONITOR, ADD:
		OR	AL,T2_GATE		;OR SPEAKER_DATA
		JMP	SHORT CARRIER_EXIT

CARRIER_OFF:	PUSH	AX			;TURN OFF CARRIER TONE
		IN	AL,PPI_B
		AND	AL,NOT (T2_GATE OR SPEAKER_DATA)

CARRIER_EXIT:	OUT	PPI_B,AL
		POP	AX
		RET

CODE		ENDS

STACK		SEGMENT STACK
		DB	256 DUP(?)
STACK		ENDS
		END	MAIN_ENTRY
