; Microterm: ASCII terminal for parallel modem TURBO56K version 1 (38400 bps)
;============================================================================
; https://pastbytes.com/microterm/index.html#msx
; https://pastbytes.com/wifimodem/#msx

; To compile use: pasmo --bin mterm38K_11.asm mterm38k.com

; Bit duration at 38400 bps: 26.0416666667 us
; MSX clock: 3579545 Hz, each cycle lasts 0.27936511484 us
; There are 93.2173177083 cycles per bit
; RTS duration: 64 us = 229.09088 cycles
; Receive timeout duration: 2 ms = 7159.09 cycles

; Version History
; ===============
; 12-Mar-2025	First public version (Version 1.0)
; 14-Mar-2025	A bug in SendByte was fixed: the last bit of data was 31 cycles shorter
;		(it was calculated for 57,600 bps), causing the stop bit to be sent prematurely.
;		The ReadByte routines and the MainPrg code were optimized to read the keyboard
;		every 60 byte reception attempts, to speed up reception.

	ORG	$0100

Start:
	JP	MainPrg			; Comienza en MainPrg

; System variables
;VDP_DR		EQU	&h0006		; VDP data read port
VDP_DW		EQU	&h0007		; VDP data write port
EXPTBL		EQU	&hFCC1		; Extended slot flags table (4 bytes)

; Constants
RTSTIME		EQU	5		; RTS pulse time: 64 microseconds (5 x 46 cycles = 230 cycles)
;CANCELTIME	EQU	155		; Time to wait before canceling reception: 2 milliseconds (155 x 46 cycles = 7130 cycles)
CANCELTIME	EQU	78		; Time to wait before canceling reception: 1 millisecond (78 x 46 cycles = 3588 cycles)

; BIOS
BOOT		EQU	&h0000		; Tests RAM and sets RAM slot for the system
BDOS		EQU	&h0005		; Entry point to BDOS
RDSLT		EQU	&h000C		; Read slot routine
CALSLT		EQU     &h001C		; Inter slot call
;CHGET		EQU	&h009C		; Get a character from the console
CHPUT		EQU	&h00A2		; Displays one character
BREAKX		EQU	&h00B7		; Tests status of CTRL-STOP

; BDOS
RAWIO		EQU	6		; Direct console I/O
WRITESTR	EQU	9		; Output string

; Variables
VPORTW:		DB	00h		; VDP write port
RDKBTIMER:	DB	00h		; Timer for reading the keyboard approximately every 3 frames

;///////////////////////////////////////////////////////////////////////////////////
; MAIN PROGRAM
;///////////////////////////////////////////////////////////////////////////////////

MainPrg:
	LD	HL,VDP_DW		; Gets the VDP write port and writes it to VPORTW
	LD	A,(EXPTBL)
	CALL	RDSLT
	LD	(VPORTW),A
	LD	E,7			; VDP REG 7 = 11110001 (White ink, black background)
	LD	D,%11110001
	CALL	WriteVReg
	LD	C,WRITESTR		; Prints the startup message
	LD	DE,StartMsg
	CALL	BDOS
	LD	A,60			; RDKBTIMER = 60 (We will read the keyboard every 60 reception attempts)
	LD	(RDKBTIMER),A
ChkKeyb:
	LD	HL,RDKBTIMER		; Decrement RDKBTIMER
	DEC	(HL)
	JR	NZ,RcvChar		; If it doesn't reach 0, we continue in RcvChar
	LD	A,60			; If it reaches 0, we restart the count with RDKBTIMER = 60
	LD	(RDKBTIMER),A
ChkBreak:
	LD      IX,BREAKX		; BREAKX function
        LD      IY,(EXPTBL-1)		; get expanded slot status to IYH
        CALL    CALSLT			; perform an inter-slot call
        EI				; because CALSLT do DI
	JR	NC,ReadKB		; If CTRL+STOP are not pressed, it continues in ReadKB
	JP	BOOT			; If they are pressed, it returns to DOS
ReadKB:
	LD	C,RAWIO			; Read the keyboard
	LD	DE,&h0FF		; FFh: Returns a character without printing it
	CALL	BDOS
	CP	0			; If no key was pressed (A=0), continue in RcvChar
	JR	Z,RcvChar
	DI				; Disables interrupts
	CALL	SendByte		; If a key was pressed, send the character
RcvChar:
	DI				; Disables interrupts
	CALL	ReadByte		; Attempts to receive a byte
	OR	A			; If one was not received, continue in EndLoop
	JR	Z,EndLoop
	LD	A,L			; Prints the received byte
	LD      IX,CHPUT		; CHPUT function
        LD      IY,(EXPTBL-1)		; get expanded slot status to IYH
        CALL    CALSLT			; perform an inter-slot call
EndLoop:
	EI				; Enable interrupts
        JR	ChkKeyb			; Return to ChkKeyb

;///////////////////////////////////////////////////////////////////////////////////
; Subroutine SendByte, receives in A the byte to be sent at 38400 bps

SendByte:
	LD	B,A			; Save the contents of A to B
	LD	C,8			; We're going to send 8 bits
	XOR	A			; Put a 0 in A
SendStart:
	OUT	($90),A			; Write the value to TX (start)
					; *** Start of start bit ***
	XOR 0				; 7+1 Lose 18 cycles
	NOP				; 4+1
	NOP				; 4+1
SBLoop:
	LD	A,0			; 7+1 It loses 43 cycles to complete the 93 cycles of the bit (start or data)
	LD	A,0			; 7+1
	INC	HL			; 6+1
	NOP				; 4+1
	NOP				; 4+1
	NOP				; 4+1
	NOP				; 4+1

	RR	B			; 8+2 Moves bit 0 of the byte to be sent to CARRY
	RL	A			; 8+2 Copies CARRY to bit 0 of A
	OUT	($90),A			; 11+1 Writes the value to TX (bit 0)
					; *** Start of bit ***
	DEC	C			; 4+1 Decrements C
	JR	NZ,SBLoop		; 12+1 If C has not reached 0, it returns to SBLoop
					; 7+1 If C has reached 0, JR consumes 8 cycles instead of 13

	INC	IY			; 10+2 Loses 60 cycles to complete the 93 cycles of the bit
	DEC	IY			; 10+2
	LD	A,0			; 7+1
	LD	A,0			; 7+1
	NOP				; 4+1
	NOP				; 4+1
	NOP				; 4+1
	NOP				; 4+1


	LD	A,%00000001		; 7+1 Set TX to 1
SendStop:
	OUT	($90),A			; 11+1 Write the value to TX (bit 0)
					; *** Start of stop bit ***
	LD	A,0			; 7+1 We lose 82 cycles
	LD	A,0			; 7+1
	NOP				; 4+1
	NOP				; 4+1
	NOP				; 4+1
	SCF				; 4+1
	RET	NC			; 5+1
	NOP				; 4+1
	NOP				; 4+1
	NOP				; 4+1
	NOP				; 4+1
	NOP				; 4+1
	NOP				; 4+1
	NOP				; 4+1
	NOP				; 4+1
	RET				; 10+1 We return (we complete the 93 cycles of the stop bit)

;///////////////////////////////////////////////////////////////////////////////////
; ReadByte subroutine, receives a byte and stores it in L
; A=0 if no byte was received, A=1 if one byte was received

ReadByte:
	LD	L,RTSTIME		; 7+1 We will activate RTS during RTSTIME
	LD	BC,$90			; 10+1 BC = port $90
	LD	H,8			; 7+1 We will receive 8 bits
	XOR	A			; 4+1 We activate RTS by setting it to zero
	OUT	($91),A			; 11+1
WaitStrt1:				; **** We wait for a byte with RTS activated ***
	IN	A,($90)			; 11+1 We read the RX input
	AND	%00000010		; 7+1 (bit 1)
	JR	Z,StartBit1		; 12+1 If RX = 0 we continue at StartBit1
					; 7+1 otherwise
	DEC	L			; 4+1 we decrement L
	JR	NZ,WaitStrt1		; 12+1 If L hasn't reached 0, we return to WaitStrt1 (46-cycle loop)
					; 7+1 if it reached 0
	LD	A,1			; 7+1 We disable RTS by setting it to one
	OUT	($91),A			; 11+1
	LD	L,CANCELTIME		; 7+1 We'll wait for CANCELTIME (1 millisecond) before canceling reception
WaitStrt2:				; *** We wait for a byte with RTS disabled ***
	IN	A,($90)			; 11+1 We read the RX input (here we are 15 cycles behind the normal loop)
	AND	%00000010		; 7+1 (bit 1)
	JR	Z,StartBit2		; 12+1 If RX = 0 we continue in StartBit2
					; 7+1 otherwise
	DEC	L			; 4+1 We decrement L
	JR	NZ,WaitStrt2		; 12+1 If L has not reached 0, we return to WaitStrt2 (46-cycle loop)
	XOR	A			; 4+1 If it reached 0, we indicate that no bytes were received
	LD	L,0			; 7+1
	RET				; 10+1 We cancel reception and return

StartBit1:				; *** StartBit with RTS enabled ***
	LD	A,1			; 7+1 Deactivate RTS by setting it to one
	OUT	($91),A			; 11+1
StartBit2:				; *** StartBit with RTS disabled ***
	LD	H,8			; 7+1 Delay to fall into bit 0
	LD	H,8			; 7+1
	LD	H,8			; 7+1
	LD	H,8			; 7+1
	LD	H,8			; 7+1
	LD	H,8			; 7+1
	LD	H,8			; 7+1
	NOP				; 4+1
ReadBit:
	IN	A,(C)			; 12+2 Read the RX input
	RR	A			; 8+2 Move the received bit to CARRY
	RR	A			; 8+2
	RR	L			; 8+2 Add the received bit to the L register

	LD	A,0			; 7+1 We lose 31 cycles to complete the 93 cycles of the bit
	LD	A,0			; 7+1
	NOP				; 4+1
	NOP				; 4+1
	NOP				; 4+1

	DEC	H			; 4+1 Decrement H
	JR	NZ,ReadBit		; 12+1 If H has not reached 0, it returns to ReadBit
					; 7+1 If H has reached 0, JR consumes 8 cycles instead of 13
	NOP				; 4+1 We lose 19 cycles to complete the 93 cycles to the stop bit
	BIT	0,(HL)			; 12+2

	;/////////////////////////////////////////////////////////////////////////////////////////
	; This part of the code waits for 1 bit to pass beyond the stop bit.
	; It is unnecessary because upon returning, we do not immediately receive another byte
	; (more than 93 cycles elapse before attempting a new reception).

;	LD	A,0			; 7+1 We lose 74 cycles
;	LD	A,0			; 7+1
;	NOP				; 4+1
;	NOP				; 4+1
;	NOP				; 4+1
;	INC	IY			; 10+2
;	DEC	IY			; 10+2
;	BIT	0,(HL)			; 12+2
;	NOP				; 4+1

	LD	A,1			; 7+1 Indicates that 1 byte was received
	RET				; 10+1 and returns

;///////////////////////////////////////////////////////////////////////////////////
; Routine to write a value to a VDP register
; The value in D and the register number in E are required

WriteVReg:
	LD	A,(VPORTW)		; C = VPORTW+1 (VDP register port)
	INC	A
	LD	C,A
	DI
	LD	A,E			; B = Register number with bit 7 = 1
	OR	%10000000
	LD	B,A
	LD	A,D			; A = Data to be written to the register
	OUT	(C),A			; Write the data
	OUT	(C),B			; Write the register number (with the bit 7 always set)
;	EI

	RET

;///////////////////////////////////////////////////////////////////////////////////
; Messages
;///////////////////////////////////////////////////////////////////////////////////

StartMsg:
	DB	$0C,$1B,$79,$35				; Clears the screen, and makes the cursor visible
	DB	"Microterm v1.1 - 38400,8N1"
	DB	&h0D,&h0A
	DB	"(c)2025 by Retrocomputacion.com"
	DB	&h0D,&h0A
	DB	"$"


	END
