;*************************************************************
; CP/M Program to punch a null leader on the PUN: device
; or directly on the 88-4PIO, depending on assembly options
;
; Usage: PNULLS <count>
;  Count is the number of nulls to punch, no value defaults.
;*************************************************************
FALSE	equ	0
TRUE	equ	not FALSE

USEPUN	equ	TRUE		;TRUE uses PUN:
				;FALSE drives 4PIO directly
DFNULS	equ	60		;default number of nulls

;**************
; CP/M Equates
;**************
;-------------------------------------------
;BDOS Entry Points and low-memory locations
;-------------------------------------------
WBOOT	equ	0000H		;JUMP TO BIOS WARM BOOT
IOBYTE	equ	WBOOT+3
;CDISK	equ	WBOOT+4		;LOGIN DRIVE
BDOS	equ	WBOOT+5		;BDOS Entry Point


COMBUF	equ	WBOOT+80H	;disk & cmd line buffer
USAREA	equ	WBOOT+100H	;User program area

BLKBUF	equ	COMBUF	;We reuse 128-byte CP/M cmd/disk
			;..buffer to buffer XMODEM blocks
;-------------------------------------------
; BDOS Function Codes, passed in register C
;-------------------------------------------
BRESET	equ	0	;System Reset
BCONIN	equ	1	;Read Console Chr
BCONOT	equ	2	;Type Chr on Console
BRDRIN	equ	3	;Read Reader Chr
BPUNOT	equ	4	;Write Punch Chr
BPRINT	equ	9	;Print $-terminated String
BRDCON	equ	10	;Get Line from Console
BCONST	equ	11	;Console Status (<>0 IF CHR)
BDRST	equ	13	;Reset Disk
BSDISK	equ	14	;select disk
BOPEN	equ	15	;Disk File Open
BCLOSE	equ	16	;Close disk file, FCB at de
BSERCH	equ	17	;Search dir for file, FCB at de
BDELET	equ	19	;delete file, FCB at (de)
BREAD	equ	20	;Read from Disk, 0=OK, <>0=EOF
BWRITE	equ	21	;Write next record, 0=OK, <>0=ERR
BMAKE	equ	22	;Make new file, 0FFH=BAD
BCDISK	equ	25	;get current disk
BSTDMA	equ	26	;Set disk buffer to (de)

;--------------------------------------------------------------
;88-4PIO Device 0 Equates
; (The 88-4PIO has up to 4 such devices, in sequential
; I/O addresses, four addresses each.)
;--------------------------------------------------------------
; 88-4PIO Device 0 Registers (Altair-standard addresses)	

P4CA0	equ	20H		;port 0 section A ctrl/status
P4DA0	equ	21H		;port 0 section A data
P4CB0	equ	22H		;port 0 section B ctrl/status
P4DB0	equ	23H		;port 0 section B data

; Control Register Initialization Value

P4INIT	equ	2Ch	
	;bit 0 low disables interrupts on C1
	;bit 1 low makes C1 an active-low input
	;bit 2 high deselects the DDR register
	;bits 5:3 = 101b causes a low-going pulse on C2A when
	;                the CPU INputs from Port A
	;                and causes a low-going pulse on C2B
	;                when the CPU OUTputs to Port B
	;bits 6 & 7 don't care 

; Status Register value

P4RDY	equ	10000000B	;Data/port ready

;****************************
; Beginning of Code 
; Note: we use CP/M's stack.
;****************************
	ORG	USAREA		;normal place for CP/M programs

;*************************************************************
; Initialization
; Set up 4PIO device 0 as follows:
;   Port A:
;     input port
;     interrupts disabled
;     C1A signal is an inverting "data ready" input pin
;     C2A is an active-low read strobe signal
;   Port B:
;     output port
;     interrupts disabled
;     C1B signal is an inverting "port ready" input pin
;     C2B signal is an active-low write strobe signal
;*************************************************************

 if not USEPUN:
; Make 4PIO Port A an input port and Port B an output port

INIT4P:	xra	a
	out	P4CA0		;access Port A DDR
	out	P4DA0		;set Port A as input

	out	P4CB0		;access Port B DDR
	cma			;0FFh
	out	P4DB0		;set Port B as output

; Set up handshaking modes for the two ports

	mvi	a,P4INIT
	out	P4CA0
	out	P4CB0
 endif

;-----------------------------
; Point to command line text
;-----------------------------
	lxi	h,COMBUF	;CP/M put cmd line here
	mov	b,m		;1st byte is byte count
	inx	h

;-----------------------------------------------
; Get the requested number of nulls, or default
;-----------------------------------------------
	mvi	e,DFNULS	;default value

SSKIP:	call	CMDCHR
	jz	DEFALT		;Z set for nothing left


	cpi	' '		;white space?
	jz	SSKIP

	dcx	h		;back up 1
	inr	b

	call	GETDEC		;get number of nulls
DEFALT:

;----------------------------------------------------
; Send e nulls to the PUN: device or to the 88-4PIO
;----------------------------------------------------
NULLUP:

 if USEPUN
	push	d
	mvi	c,BPUNOT	;punch device
	mvi	e,0		;null
	call	BDOS
	pop	d
 endif

 if not USEPUN
; synchronize with the leading edge of ready

	in	P4DB0		;clear the handshake bit

	lxi	h,0		;timeout value with margin

PWAIT:	dcx	h		;(5)timeout?
	mov	a,h		;(4)
	ora	l		;(10)
	jz	TIMERR

 	in	P4CB0		;(10)wait for the handshake
	ani	P4RDY		;(7)..at start of punch phase
	jz	PWAIT		;(10)

; 46 cycles = 23 uS per pass.Looping for 64K will timeout
; in about 1.5 seconds. (One character time = 20 mS)

; immediately punch the data

	xra	a		;recover punch byte
	out	P4DB0		;write it to data port
 endif

; loop to do another

	dcr	e
	jnz	NULLUP

;--------------------------
; All done: Return to CP/M
;--------------------------
	ret

;*****Subroutine***************************************
; Get decimal parameter from LINBUF
; Max value = 255
;    b=remaining chrs in COMBUF <> 0
;   hl points to first chr of value in COMBUF
; On Exit:
;   e=value, 0 if no more chrs
;   hl points to first chr of next param
;   z set if no parameters found
;   a,d trashed
;******************************************************
GETDEC:	mvi	e,0		;init result

GDLOOP:	call	GETDD		;get new digit
	rz			;done?

	mov	d,a		;temp save new digit
	mov	a,c		;get result so far

	add	a		;x2
	add	a		;x4
	add	d		;x5
	add	a		;x10

	add	e		;add in new digit
	mov	e,a
	jmp	GDLOOP
	
;-------------------------------------------------
;Local subroutine to get & test a decimal digit
;On Exit:
;  Z set if end of cmd line or no digit (space)
; hl points to next cmd line chr unless it's a ' '
;  a=chr converted to binary, unless Z
;-------------------------------------------------
GETDD:	call	CMDCHR
	rz			;ret if no chr

	sui	'0'		;de-ASCII
	cpi	10		;carry means below 10
	rc

; fall into BADINP

;----------------
;Error: Bad digit 
;----------------
BADINP:	lxi	d,ERRMSG
	jmp	ERREXT

ERRMSG:	db	CR,LF,'Bad input$'

;---------------
; Timeout Error
;---------------
 if not USEPUN

TIMERR:	lxi	d,TERMSG

ERREXT:	mvi	c,BPRINT
	call	BDOS
	jmp	WBOOT

TERMSG:	db	CR,LF,'Timeout on 88-PIO$'

;*****Subroutine***************************************
;Get next chr from command line buffer
; On Entry:
;    b has remaining COMBUF byte count
;    hl points to the next chr in COMBUF
; On Exit:
;    a = chr from COMBUF, parity stripped
;    b has been decremented
;    hl has been advanced
;    Z=1 means end of buffer, and a=0
;    Carry = 0
;******************************************************
CMDCHR:	mov	a,b		;End of buffer already?
	ora	a		;also clears carry
	rz			;and clears a

	mov	a,m		;get buffer chr
	inx	h		;bump buffer pointers
	dcr	b	
	ani	7FH		;Strip parity, clear Z
	ret			;with Z cleared

