; This example program does one of two things:
; - If a file named DATA.TXT exists, it is read into memory at LOAD$ADR
; - If not, it reads data from the serial port instead, until a specific
; character is encountered, and saves it to the file
; Can be changed to read octal values too
LOAD$ADR EQU 6000Q ;where to load data
OMEGA EQU '}' ;end marker
SIO$STATUS EQU 020Q ;status port
SIO$DATA EQU 021Q ;data port
BDOS EQU 5 ;jump address to BDOS entry point
ORG 400Q
JMP START
FCB:
DB 0 ;drive number (0=default)
DB 'DATA ' ;8 char name
DB 'TXT' ;3 char extension
DB 0,0,0,0 ;zero init EX,S1,S2,RC
DS 16 ;reserve space for block numbers
DB 0 ;current record for sequential I/O
MSG$OLD DB 'DATA.TXT found, loading to memory',13,10,'$'
MSG$NEW DB 'DATA.TXT not found, enter new data',13,10,'$'
MSG$ERR DB 'Error creating file',13,10,'$'
MSG$DSK DB 'Disk full!',13,10,'$'
DMA$ADR DS 2 ;variable for storing pointer
END$ADR DS 2 ;end address of data read from serial port
;;;;;;;;
; Direct serial I/O routines (maybe use BIOS instead?)
;;;;;;;;
SIO$READ:
;input from serial port into register A
IN SIO$STATUS ;read the status
RAR ;shift "receive data" bit into carry
JNC SIO$READ ;loop until bit is set
IN SIO$DATA ;read the data
RET
SIO$READ$ECHO:
;read with echo
CALL SIO$READ
MOV C,A
SIO$WRITE:
;output from register C to serial port
IN SIO$STATUS ;read the status
ANI 2 ;test "transmit empty" bit
JZ SIO$WRITE ;loop until bit is set
MOV A,C ;character to write into A
OUT SIO$DATA ;write it
RET
;;;;;;;;
; BDOS interface
;;;;;;;;
OPEN$FILE:
;DE => FCB
;reserved fields in FCB (EX,S1,S2) should be clear
;returns high bit of A set on error (not found)
MVI C,15
JMP BDOS
DEL$FILE:
;DE => FCB
MVI C,19
JMP BDOS
MAKE$FILE:
;DE => FCB
;CP/M doesn't check if file already exists, and will
;create another one with same name. Shouldn't happen
;in this program, but in the general case we need to
;explicitly delete an existing file to avoid this.
PUSH D
CALL DEL$FILE
POP D
MVI C,22
JMP BDOS
CLOSE$FILE:
;DE => FCB
MVI C,16
JMP BDOS
READ$SEQ:
;DE => FCB
;returns A nonzero if error (end of file)
MVI C,20
JMP BDOS
WRITE$SEQ:
;DE => FCB
;returns A nonzero if error
MVI C,21
JMP BDOS
NEXT$DMA:
;set address to DMA$ADR and increment it by 128
LHLD DMA$ADR ;current address into HL
MOV D,H ;copy to DE
MOV E,L
LXI B,128 ;128 bytes per record
DAD B ;add to current address
SHLD DMA$ADR ;and save it for next time
SET$DMA:
;DE => new buffer for read/write
MVI C,26
JMP BDOS
PRINT$STR:
;DE => string terminated with '$'
MVI C,9 ;function number
JMP BDOS
;;;;;;;;
; Input routines for raw-with-end-marker and octal
; both preserve DE, return character in A and zero flag set on end
;;;;;;;;
GET$CHAR EQU SIO$READ$ECHO
TERM$READ:
CALL GET$CHAR
CPI OMEGA ;set zero flag if end marker
RET
OCT$READ:
;skips over space and control characters
;reads an octal number, returned in HL (16 bit) and A (8 bit)
CALL GET$CHAR ;get first char
CPI 041Q ;control/whitespace?
JC OCT$READ ;then loop until not
LXI H,0 ;init result
OCTL1: SUI '0' ;convert ASCII to digit
JC OCTXX ;below '0'? -> exit
CPI 8
JNC OCTXX ;above '7'? -> exit
DAD H ;multiply HL by 8
DAD H
DAD H
ORA L ;OR in the new digit
MOV L,A
CALL GET$CHAR ;get next
CPI 041Q ;control/whitespace?
JNC OCTL1 ;if not then loop
;got valid number, ZF clear
MOV A,L ;return byte result in both A and L
RET
OCTXX: ;invalid character - return with ZF set
CMP A ;compare A with itself to set ZF
RET
;;;;;;;;
; Main program
;;;;;;;;
START:
LXI H,LOAD$ADR ;init address variable
SHLD DMA$ADR
LXI D,FCB
CALL OPEN$FILE
ANA A ;check return status
JM NO$FILE ;high bit set if file not found
; File opened, read it into memory
LXI D,MSG$OLD
CALL PRINT$STR
READ$LOOP:
CALL NEXT$DMA
LXI D,FCB
CALL READ$SEQ
ANA A ;check return status
JZ READ$LOOP ;loop until nonzero
;CP/M actually doesn't require closing a file unless the size
;was changed (so never when only reading), but it's cleaner
;to do it anyway. This also avoids problems when running on an
;emulator on some other OS that does keep track of open files.
CLOSE$EXIT:
LXI D,FCB
JMP CLOSE$FILE ;will return to CP/M
; File not found, read from serial port and save to file
NO$FILE:
LXI D,MSG$NEW
CALL PRINT$STR
LXI D,LOAD$ADR ;init pointer to memory
READ$INP:
CALL TERM$READ ;get next character (raw)
JZ SAVE$FILE ;end?
STAX D ;store it
INX D ;point to next byte
JMP READ$INP ;and loop
SAVE$FILE:
XCHG ;end pointer into HL
SHLD END$ADR ;and save it for later
LXI D,FCB
CALL MAKE$FILE
ANA A ;check return status
LXI D,MSG$ERR
JM PRINT$STR ;if failed, print message and exit
SAVE$LOOP:
LHLD END$ADR
XCHG ;DE=end address
LHLD DMA$ADR ;HL=DMA address
MOV A,L ;compare DMA$ADR - END$ADR
SUB E
MOV A,H
SBB D
JNC CLOSE$EXIT ;if not below, we are done
CALL NEXT$DMA
LXI D,FCB
CALL WRITE$SEQ
ANA A ;check return status
JZ SAVE$LOOP ;loop until nonzero
;error writing to file
LXI D,MSG$DSK
CALL PRINT$STR
JMP CLOSE$EXIT
END