        .TITLE ELMER.TXT        ;8031 MONITOR PROGRAM
                                ;Copyright (C) 1996 by Anthony Marchese
                                ;
        ;PHYSICAL LOCATIONS
P0        .EQU  $80             ;PORT 0
SP        .EQU  $81             ;STACK PTR
DPL       .EQU  $82             ;DATA PTR - LO BYTE
DPH       .EQU  $83             ;DATA PTR - HI BYTE
PCON      .EQU  $87             ;POWER CONTROL REG
TCON      .EQU  $88             ;TIMER CONTROL REG
TMOD      .EQU  $89             ;TIMER MODE REG
TL0       .EQU  $8A             ;TIMER REG 0 - LOW
TH0       .EQU  $8C             ;TIMER REG 0 - HIGH
TL1       .EQU  $8B             ;TIMER REG 1 - LOW
TH1       .EQU  $8D             ;TIMER REG 1 - HIGH
P1        .EQU  $90             ;PORT 1
SCON      .EQU  $98             ;SERIAL CONTROL REG
RX_FLG    .EQU  SCON+0          ;SERIAL CHAR RECVD WHEN SET
TX_RDY    .EQU  SCON+1          ;TX BUFFER CLR WHEN SET
SBUF      .EQU  $99             ;SERIAL DATA BUFFR
P2        .EQU  $A0             ;PORT 2
IE        .EQU  $A8             ;IRQ ENABLE
P3        .EQU  $B0             ;PORT 3
P3.2      .EQU  P3+$2           ;IRQ0 INPUT
IP        .EQU  $B8             ;IRQ PRIORITY
PSW       .EQU  $D0             ;STATUS WORD
ACC       .EQU  $E0             ;ACCUMULATOR
ASC_LT    .EQU  $E6             ;ASCII LTR FLAG
B_REG     .EQU  $F0             ;B REG
R_START   .EQU  $2000           ;START EXT RAM
R_END     .EQU  $3FFF           ;END EXT RAM
I_TEMP    .EQU  $3FF0           ;TEMP INSTR SCRATCH PAD

        ;BUFFERS
TEMP      .EQU  $60             ;TEMP SCRATCH PAD - INT RAM
INSTR     .EQU  TEMP            ;INSTR CHAR
ADR_A     .EQU  TEMP+1          ;START ASCII ADDR
ADR_HH    .EQU  TEMP+6          ;HIGH HEX ADDR
ADR_HL    .EQU  TEMP+7          ;LOW HEX ADDR
DATA_H    .EQU  TEMP+8          ;HEX DATA
DATA_A    .EQU  TEMP+9          ;ASCII DATA
BYTE_C    .EQU  TEMP+$0E        ;BYTE COUNTR
BIT       .EQU  TEMP+$0F        ;PORT BIT POSITN
PTR       .EQU  TEMP+$10        ;DATA BUFF INDEX PTR
CK_SUM    .EQU  TEMP+$11        ;DOWNLOAD CHECK SUM
BP_HH     .EQU  TEMP+$12        ;HI ADDR HEX BRKPT
BP_HL     .EQU  TEMP+$13        ;LO ADDR HEX BRKPT
BP_OP     .EQU  TEMP+$14        ;ORIG OP CODE @ BRKPT

        ;CONSTANTS
CR        .EQU $0D
LF        .EQU $0A
SPACE     .EQU $20
EOT       .EQU $04
EOF       .EQU $1A

          .ORG     $0000
C_STRT    LJMP  INIT            ;JUMP OVER VECTOR TABLE
          .ORG     $0003
IE0       LJMP  ISR0            ;EXT INT0 VECTOR
          .ORG     $000B
TF0       LJMP  TMR_OI0         ;TIMER0 OVRFLW VECTOR
          .ORG     $0013
IE1       LJMP  ISR1            ;EXT INT1 VECTOR
          .ORG     $001B
TF1       LJMP  TMR_OI1         ;TIMER1 OVRFLW VECTOR
          .ORG     $0023
RI_TI     LJMP  S_ISR           ;SERIAL RX/TX INT VECTOR

          .ORG     $0030
INIT      MOV   SP,#$30         ;INIT STACK AFTER REG & BIT ADR SEG
          LCALL BEGIN           ;INIT SYSTEM
          LCALL P_BY            ;SEND HEADER TO SERIAL PORT
          LCALL CLR_BP          ;CLEAR BREAKPT POINTER
MAIN      LCALL G_CMD           ;GET CMD
          LCALL WAT2DO          ;WHICH COMMAND
          MOV   A,INSTR         ;GET INSTR
          CJNE  A,#'B',NEW_P    ;SHOW REG @BRKPT EXIT
          LCALL R_SUB
NEW_P     LCALL P_PMT           ;PROMPT
          LJMP  MAIN

BEGIN     MOV   IE,#$00         ;DISABLE IRQ
          ORL   PCON,#$80       ;SERIAL PORT MODE 1
          MOV   TH1,#$F3        ;BAUD RATE = 2400@6MHz OSC
          ORL   TMOD,#$20       ;AUTO RELOAD TIMER (CLOCK)
          ORL   TCON,#$40       ;START BAUD RATE CLOCK
          MOV   SCON,#$50       ;ENABLE SERIAL COMM
          RET

P_BY      MOV   DPTR,#BOOT_M    ;BOOT MESSAGE
          LCALL P_BLK
P_PMT     MOV   DPTR,#PROMPT    ;PROMPT
          LCALL P_BLK
          RET

WAT2DO    MOV   R0,INSTR        ;GET INSTR
CK_B      CJNE  R0,#'B',CK_C    ;SET/CLEAR BRKPT
          LJMP   B_SUB
CK_C      CJNE  R0,#'C',CK_D
          LJMP   C_SUB          ;CLEAR RAM ($2000-$3FFF)
CK_D      CJNE  R0,#'D',CK_G
          LJMP   D_SUB          ;DISPLAY MEMORY BLOCK
CK_G      CJNE  R0,#'G',CK_H
          LJMP   G_SUB          ;GO
CK_H      CJNE  R0,#'?',CK_I    ;HELP MENU
          LJMP   H_SUB
CK_I      CJNE  R0,#'I',CK_L
          LJMP   I_SUB          ;INT RAM MODIFY
CK_L      CJNE  R0,#'L',CK_M    ;DOWNLOAD FILE FROM PC
          LJMP   L_SUB
CK_M      CJNE  R0,#'M',CK_P
          LJMP   M_SUB          ;EXT RAM MODIFY
CK_P      CJNE  R0,#'P',CK_R    ;READ/MOD PORT
          LJMP   P_SUB
CK_R      CJNE  R0,#'R',OOPS    ;DISPLAY REG INFO
          LJMP  R_SUB
OOPS      MOV   DPTR,#ERROR     ;UNKNOWN COMMAND - TELL USER
          LCALL P_BLK
          RET

        ;SERIAL ENTRY OF COMMAND
G_CMD     MOV   R1,#TEMP        ;INIT BUFFER POINTER
WAIT      JNB   RX_FLG,WAIT     ;TEST FOR RECVD CHAR
          CLR   RX_FLG          ;HAVE CHAR, CLEAR FLAG
          MOV   A,SBUF
          CJNE  A,#SPACE,CKCR   ;IF NOT A SPACE CHECK IF <CR>
          LJMP  TX_CHR          ;ECHO SPACE, DO NOT STORE SPACES
ST_C      MOV   @R1,A           ;STORE CHAR IN TEMP BUFF
          INC   R1              ;INC PTR
TX_CHR    MOV   SBUF,A          ;ECHO CHAR
          LCALL TXBUSY
          LJMP  WAIT            ;GET NEXT CHAR
CKCR      CJNE  A,#CR,ST_C      ;IF NOT <CR> OR SPACE ECHO & STORE
          MOV   SBUF,#CR
          LCALL TXBUSY
          MOV   SBUF,#LF
          LCALL TXBUSY
          MOV   A,#EOT
          MOV   @R1,A           ;STORE DONE FLAG @END OF COMMAND
          RET                   ;END OF INFO, WHAT COMMAND ISSUED?

        ;UTILITIES
OUTA      MOV   SBUF,A          ;SEND TO SERIAL PORT
          LCALL TXBUSY
          INC   DPTR            ;INCREMENT TEXT POINTER
P_BLK     CLR   A
          MOVC  A,@A+DPTR       ;GET CHARACTER
          CJNE  A,#EOT,OUTA     ;IF NOT DONE SEND TO SER.PORT
          RET                   ;ELSE RETURN

          ;CLEARS TX IRQ AFTER CHAR SENT
TXBUSY    JNB   TX_RDY,TXBUSY   ;WAIT FOR CHAR TO CLEAR SER PORT
          CLR   TX_RDY          ;THEN RESET TX IRQ
          RET

        ;SEND HEX DATA IN REG (A) TO SERIAL PORT AS ASCII
HEX2CH    PUSH  ACC             ;RETAIN REGISTER DATA
          PUSH  B_REG
          MOV   R6,#$02         ;SET PASS COUNTER
CNVT      MOV   R5,A            ;COPY FOR NEXT PASS
          MOV   B_REG,#$A0
          DIV   AB              ;CHECK IF HI BYTE> $9
          MOV   B_REG, #$37
          CJNE  A,#$00,NMBIAS   ;IF NUMBER >$9 ADD LETTER OFFSET
          MOV   B_REG,#$30      ;ELSE ASCII # OFFSET
NMBIAS    MOV   A,R5
          ANL   A,#$F0          ;MASK OUT LOW NIBBLE
          SWAP  A
          ADD   A,B_REG         ;ADD PROPER OFFSET
          MOV   SBUF,A          ;SEND TO SERIAL PORT
          LCALL TXBUSY
          MOV   A,R5            ;NEXT PASS
          SWAP  A               ;SWAP LOW DIGIT INTO RT LOCA
          DJNZ  R6,CNVT         ;RETURN IF BOTH BYTES COMPLETED
          POP   B_REG           ;RESTORE ORIG REG DATA
          POP   ACC
          RET                   ;DONE


        ;CK 4 VALID HEX ENTRY THEN CONVERT ASCII@R1,(A) HOLDS HEX RESULT
AS2HEX    MOV   R4,#00          ;CLEAR TEMP STORAGE
          MOV   R6,#$02         ;INIT PASS COUNTER
LOW_B     MOV   A,@R1           ;GET CHAR TO CONVERT
          LCALL CK_HEX          ;CHECK FOR HEX CHAR
          JC    NOT_HEX         ;IF CARRY SET, NOT HEX CHAR
          MOV   A,@R1           ;RESTORE ACC, THEN CONVERT
          JNB   ASC_LT,NOBIAS   ;IS ASCII 0-9?
          ADD   A,#$09          ;NO,ASCII A-F,ADD CONVERT FACTOR
NOBIAS    ANL   A,#$0F          ;REMOVE ASCII BIAS
          DJNZ  R6,HIGH_B       ;JUMP IF HIGH BYTE
          ADD   A,R4            ;CONVERT DONE, COMBINE RESULTS
          INC   R1              ;SET POINTER FOR NEXT CHAR
          CLR   C               ;CONVERTD OK,MAKE SURE FLG CLR
NOT_HEX   RET                   ;DONE, ACC HOLDS RESULT

HIGH_B    SWAP  A               ;THIS IS THE HIGH BYTE
          MOV   R4,A            ;STORE TEMPORARILY WHILE FINISHING
          INC   R1              ;POINT TO NEXT CHARACTER
          LJMP  LOW_B           ;CONVERT 2ND HALF OF NUMBER

          ;CONVERT 2 ASCII CHAR TO HEX WORD..(A) HOLDS RESULT
DA_CNV    MOV   R1,#DATA_A      ;CHAR IN ASCII DATA BUFFER
IAD_CNV   MOV   R5,#$01         ;ONLY (1) HEX WORD TO CONVERT
          LJMP  DOWORD

          ;CONVERT 4 CHAR ASCII ADDRESS TO HEX ADDRESS (A)=LOBYTE, R0=HIBYTE
AD_CNV    MOV   R5,#$02         ;2 HEX WORDS TO CONVERT
          MOV   R1,#ADR_A       ;SET POINTER TO 1ST ASCII CHAR

DOWORD    MOV   R0,A            ;SAVE HI_ADR IN R0
          MOV   A,@R1           ;FETCH 1ST OF 2 CHAR TO CONVERT
          LCALL AS2HEX          ;CK4 HEX THEN CONVERT 2CHAR 2HEX WORD
          JC    CNV_ERR         ;IF CARRY SET, NOT HEX CHAR
          DJNZ  R5,DOWORD       ;SEE IF BOTH PARTS OF ADDRESS CONVERTED
CNV_ERR   RET                   ;NOT HEX CHAR, POP BACK THRU SUBS

          ;CLR CARRY IF (A)=VALID HEX,ENTRY NOT MODIFIED
CK_HEX    MOV   R2,#$0A         ;0-9 NUMBER LOOP COUNTER
          CLR   C               ;INIT CARRY FLG
          SUBB  A,#$30          ;REMOVE ASCII '0-9'BIAS
          JZ    CHR_OK          ;NUMBER=0, OK
          MOV   R3,A            ;COPY
LOOP_1    DJNZ  R2,IS_129       ;CHECK FOR #1 TO 9
          CLR   C               ;INIT CARRY FLG
          SUBB  A,#$11          ;NOT #1-9, REMOVE A-F BIAS,RECHECK
          JZ    CHR_OK          ;IF 0, CHAR='A'
          MOV   R3,A            ;INIT CHAR VALUE
          MOV   R2,#$06         ;INIT LETTER COUNTER
          LJMP  IS_B2F          ;CHECK IF B-F

IS_129    DJNZ  R3,LOOP_1       ;DECREMENT VALUE 9X TO SEE IF NUMBER
          LJMP  CHR_OK

IS_B2F    DJNZ  R2,CK_B2F
          LCALL OOPS            ;NOT VALID HEX CHARACTER,TELL USER
          SETB  C               ;SET 'ERROR' FLAG
          RET

CK_B2F    DJNZ  R3,IS_B2F       ;CHECK FOR B TO F
CHR_OK    CLR   C               ;VALID LETTER,CLEAR 'ERROR' FLAG
          RET

        ;COMMAND SUBROUTINES
          ;HELP SCREEN
H_SUB     MOV   DPTR,#BOOT_M
          LCALL P_BLK
          MOV   DPTR,#HELP
          LCALL P_BLK
          RET

          ;CLEAR RAM
C_SUB     MOV   DPTR,#R_START   ;LOCATE FIRST RAM LOCATION
C_NEXT    MOV   A,#00           ;CLEAR RAM LOCATION
          MOVX  @DPTR,A
          INC   DPTR            ;POINT TO NEXT RAM LOCATION
          MOV   A,#$00
          CJNE  A,DPL,C_NEXT   ;CHECK FOR LAST RAM LOCA
          MOV   A,#$40
          CJNE  A,DPH,C_NEXT
          MOV   DPTR,#MEM_CLR   ;NOTIFY USER MEMORY CLEAR
          LCALL P_BLK
          RET                   ;DONE - RETURN TO MAIN

          ;GO COMMAND
G_SUB     LCALL AD_CNV          ;SEND ASCII START ADDRESS TO SER.PORT
          JC    DO_OVR
          MOV   DPH,R0          ;SAVE HEX ADDRESS DATA
          MOV   DPL,A
          MOV   A, #$00         ;CLEAR A FOR NO OFFSET
          JMP   @A+DPTR         ;SWITCH PROGRAM CONTROL TO APPLICATION
DO_OVR    RET

          ;DISPLAY MEMORY LOCATION
ADDRS     LCALL AD_CNV          ;CONVT/SEND ASCII ADDRESS = (2) HEX BYTES
          JC    DO_OVR          ;MISTAKE, ENTER AGAIN
          MOV   ADR_HH,R0       ;SAVE HEX ADDRESS DATA
          MOV   ADR_HL,A
          MOV   DPH,R0          ;GET HIGH ADDRESS BYTE
          MOV   DPL,A           ;GET LOW ADDRESS BYTE
          RET

P_DATA    MOV   SBUF,#SPACE     ;PRINTS A SPACE FOLLOWED BY THE DATA
          LCALL TXBUSY
          MOVX  A,@DPTR         ;BYTE FROM THE EXTERNAL LOCATION
          LCALL HEX2CH          ;POINTED TO BY DPTR, THEN ANOTHER
          MOV   SBUF,#SPACE     ;SPACE
          LCALL TXBUSY
          RET

          ;MODIFY EXTERNAL MEMORY COMMAND
M_SUB     LCALL ADDRS           ;CONVERT/SEND ADDRESS TO SER.PORT
          LCALL P_ADDR          ;DISPLAY DATA
IFDONE    MOV   R1,#DATA_A      ;RE-INIT POINTER
          LCALL WAIT            ;GET NEW DATA
          MOV   A,DATA_A        ;SEE IF ANOTHER LOCATION TO BE MODIFIED
          CJNE  A,#EOT,MORE     ;IF NOT DONE, MODIFY NEXT LOCATION
          RET                   ;DONE

          ;DATA = MODIFY THIS LOCATION TOO
MORE      LCALL DA_CNV          ;CONVERT DATA TO HEX
          JC    DO_OVR          ;MISTAKE, ENTER AGAIN
          MOV   DPH,ADR_HH      ;GET HIGH ADDRESS BYTE
          MOV   DPL,ADR_HL      ;GET LOW ADDRESS BYTE
          MOVX  @DPTR,A         ;STORE DATA
          INC   DPTR            ;NEXT LOCATION
          MOV   ADR_HH,DPH      ;CHANGE ADDRESS POINTER
          MOV   ADR_HL,DPL
          LCALL P_ADDR
          LJMP  IFDONE

P_ADDR    MOV   A,ADR_HH        ;CONVERT/SEND ADDRESS & DATA FOLLOWED
          LCALL HEX2CH          ;& SEPARATED BY SPACES TO THE SERIAL
          MOV   A,ADR_HL        ;PORT
          LCALL HEX2CH
          LCALL P_DATA
          RET

          ;DISPLAY (16) MEMORY LOCATIONS
D_SUB     LCALL ADDRS           ;CONVERT/PRINT START ADDRESS
          MOV   BYTE_C,#$0F     ;INIT BYTE COUNTER = 16 BYTE DISPLAY
          LCALL P_ADDR
P_LINE    INC   DPTR            ;NEXT BYTE
          LCALL P_DATA          ;SEND DATA TO SER.PORT
          DJNZ  BYTE_C,P_LINE   ;DO (16) LOCATIONS
          RET                   ;GET NEXT COMMAND

          ;DOWNLOAD INTO RAM
L_SUB     MOV   DPTR,#OK_DL     ;NOTIFY USER,OK TO DOWNLOAD
          LCALL P_BLK
          LCALL WAIT_L          ;GET 1ST CHAR
          CJNE  A,#':',X_ERR    ;NOT COLON/WRONG FORMAT
NEWLINE   MOV   CK_SUM,#$00     ;CLEAR CHECKSUM BUFFER
          LCALL GET_D           ;GET BYTE COUNT/CONVERT TO HEX
          JC    X_ERR
          MOV   BYTE_C,A        ;STORE BYTE COUNT FOR LATER
          MOV   CK_SUM,A        ;ALSO 1ST CHECKSUM DATA
          LCALL GET_D           ;GET HI ADDR CHARS
          JC    X_ERR
          MOV   ADR_HH,A        ;INIT HI ADDR PTR
          ADD   A,CK_SUM        ;UPDATE CKSUM
          XCH   A,CK_SUM        ;STORE CKSUM
          LCALL GET_D           ;GET LO ADDR CHARS
          JC    X_ERR
          MOV   ADR_HL,A        ;INIT LO ADDR PTR
          ADD   A,CK_SUM        ;UPDATE CKSUM
          XCH   A,CK_SUM        ;STORE CKSUM
          LCALL GET_D           ;GET REC TYPE
          JC    X_ERR
          MOV   DPH,ADR_HH      ;INIT ADRS PTR
          MOV   DPL,ADR_HL
          CJNE  A,#$01,D_READ   ;IF NOT LAST REC,READ DATA
          LJMP  CLRCOM          ;LAST RECORD - DONE
D_READ    LCALL GET_D           ;GET 2 ASCII CHAR(HEX WORD)
          JC    X_ERR
          MOVX  @DPTR,A         ;STORE IN RAM
          ADD   A,CK_SUM        ;UPDATE CHECKSUM
          XCH   A,CK_SUM        ;STORE CURRENT CHECKSUM
          INC   DPTR            ;POINT NEXT ADDRESS
          DJNZ  BYTE_C,D_READ   ;REDUCE BYTE COUNT
          MOV   A,CK_SUM        ;LAST DATABYTE FROM LINE, GET CHECKSUM
          CPL   A               ;CONVERT CK_SUM TO 1'S COMP
          INC   A               ;CONVERT CK_SUM TO 2'S COMP
          MOV   CK_SUM,A        ;UPDATE CK_SUM
          LCALL GET_D           ;GET CHECKSUM FOR LINE
          CJNE  A,CK_SUM,X_CHK  ;VERIFY CHECKSUM
PURGE     LCALL WAIT_L          ;OK, PURGE CR &/0R LF
          MOV   DATA_A,A
          CJNE  A,#':',PURGE
          LJMP  NEWLINE         ;DO NEXT LINE

WAIT_L    JNB   RX_FLG,WAIT_L   ;CHECK RECV FOR NEW CHAR
          CLR   RX_FLG
          MOV   A,SBUF
          RET

X_CHK     MOV   DPTR,#CS_FAIL
          LCALL P_BLK
X_ERR     MOV   DPTR,#X_MSG     ;DOWNLOAD ERROR MSG
          LCALL P_BLK

          ;SER PORT@2400 BAUD W/10BITS/CHAR=4.1mS/CHAR
          ;WATCHDOG ALLOWS 4 SEC IDLE TIME FOR NEW CHAR BEFORE
          ;EXIT FROM LOAD SUB, NEW CHAR RESETS WATCHDOG
CLRCOM    MOV   R2,#$0C         ;INIT SERIAL WATCHDOG TMR
RELOAD1   MOV   R1,#$FF
RELOAD0   MOV   R0,#$FF
TICK      DJNZ  R0,TICK         ;24 OSC*1/6MHz*255=1mS DELAY
          DJNZ  R1,CK_MORE      ;CHK WATCHDOG,TIME2CHK SER PORT
          DJNZ  R2,RELOAD1      ;16(R2)*255(R1)*1mS(R0)=4.2 SEC DELAY
          MOV   DPTR,#DC        ;NO CHAR IN 4 SEC,LOAD DONE
          LCALL P_BLK           ;NOTIFY USER
          RET                   ;BACK TO COMMAND LINE

CK_MORE   JNB   RX_FLG,RELOAD0  ;CHK FOR SER CHAR
          CLR   RX_FLG          ;NEW CHAR,RESET RX BUFF
          MOV   A,SBUF          ;PURGE BUFFER
          SJMP  CLRCOM          ;RESET WATCHDOG

GET_D     LCALL WAIT_L          ;FETCH 2 ASCII CHAR FROM SER BUF
          MOV   DATA_A,A
          LCALL WAIT_L          ;GET 1 CHAR
          MOV   DATA_A+1,A
          LCALL DA_CNV          ;CONVERT TO HEX
          RET

          ;READ / MODIFY PORT
P_SUB     MOV   A,ADR_A+1       ;SEE IF WHOLE PORT OR 1 BIT
          CJNE  A,#EOT,WHICH_B

          ;NO BIT NUMBER,READ WHOLE PORT
CK_WHL    MOV   A,ADR_A         ;CONVERT ASCII PORT# TO HEX
          ANL   A,#$0F          ;MASK ASCII BIAS
          SWAP  A
          ADD   A,#P0           ;ADD PORT ADDRESS OFFSET
          CJNE  A,#P0,TRY1      ;CHECK FOR VALID PORT ADDRESS
          LJMP  P_OK            ;VALID PORT, REST OF REQUEST
TRY1      CJNE  A,#P1,TRY2
          LJMP  P_OK
TRY2      CJNE  A,#P2,TRY3
          LJMP  P_OK
TRY3      CJNE  A,#P3,NOT_P     ;IF NOT PORT ADDRESS,NOTIFY USER
P_OK      MOV   ADR_HL,A        ;INIT PTR
          LCALL PIDATA          ;READ & PRINT PORT DATA
          LCALL G_CMD           ;SEE IF USER WILL MOD
          MOV   A,TEMP
          CJNE  A,#EOT,GOUPDAT  ;IF NEW INFO,UPDATE PORT
          RET
GOUPDAT   MOV   DATA_A,INSTR    ;UPDAT CONVERSN BUFFRS
          MOV   DATA_A+1,ADR_A  ;WITH NEW ASCII DATA
          LJMP  UPDAT           ;2FAR 2BRANCH,SO JMP THERE

WHICH_B   MOV   R1,#ADR_A
          LCALL IAD_CNV         ;CONVERT ADR TO HEX
          ADD   A,#P0           ;ADD PORT OFFSET
          MOV   BIT,A           ;STORE BIT ADR FOR LATER
          ANL   A,#$0F          ;MASK 4 BIT #
          CJNE  A,#$00,CK_BIT1  ;CHECK FOR VALID BIT NUMBER
          LJMP  RD_PIN          ;VALID BIT, READ
CK_BIT1   CJNE  A,#$01,CK_BIT2
          LJMP  RD_PIN
CK_BIT2   CJNE  A,#$02,CK_BIT3
          LJMP  RD_PIN
CK_BIT3   CJNE  A,#$03,CK_BIT4
          LJMP  RD_PIN
CK_BIT4   CJNE  A,#$04,CK_BIT5
          LJMP  RD_PIN
CK_BIT5   CJNE  A,#$05,CK_BIT6
          LJMP  RD_PIN
CK_BIT6   CJNE  A,#$06,CK_BIT7
          LJMP  RD_PIN
CK_BIT7   CJNE  A,#$07,NOT_P
RD_PIN    LCALL GET_B           ;GET DATA
          LCALL P_MOD           ;CHK 4 NEW DATA
          RET                   ;BACK TO PROMPT

NOT_P     MOV   DPTR,#ERROR     ;INVALID PORT ADDRESS
          LCALL P_BLK           ;TELL USER
          RET                   ;BACK TO MAIN MENU

          ;STORE BIT READ ROUTINE TO RAM BUFFER
          ;CAN ONLY DIRECT ADDRESS $80-$FF
GET_B     MOV   DPTR,#I_TEMP    ;GET SCRATCH PAD LOCA
          MOV   A,#$A2          ;MOV BIT TO C INSTR
          MOVX  @DPTR,A         ;STORE TO RAM
          INC   DPTR            ;SKIP SPACE FOR INT ADDR
          MOV   A,BIT           ;GET BIT ADR
          MOVX  @DPTR,A         ;STORE DIRECT INT ADDRESS
          INC   DPTR            ;POINT TO NEXT INSTR ADR
          MOV   A,#$22          ;RETURN COMMAND
          MOVX  @DPTR,A         ;STORE RETURN COMMAND
          LCALL I_TEMP          ;EXECUTE CODE (READ INT MEM)
          JC    ONE             ;IF C=1,ECHO 1
ZERO      MOV   SBUF,#'0'       ;OTHERWISE ECHO 0
          LCALL TXBUSY
          MOV   SBUF,#SPACE     ;INSERT SPACE
          LCALL TXBUSY
          RET
ONE       MOV   SBUF,#'1'       ;CARRY (BIT) SET, DISPLAY 1
          LCALL TXBUSY
          MOV   SBUF,#SPACE     ;INSERT SPACE
          LCALL TXBUSY
          RET

P_MOD     LCALL G_CMD           ;GET DATA / ECHO TO SCREEN
          MOV   A,INSTR
          CJNE  A,#EOT,NEW_D    ;CR, NOTHING TO MODIFY
NO_DATA   RET                   ;NOTHING NEW,PROMPT

NEW_D     MOV   A,INSTR         ;GET NEW DATA
          CJNE  A,#'0',MAYB1    ;CHK 4 VALID DATA
          CLR   C               ;RESET CARRY TO CLR BIT
          LJMP  STORE_B
MAYB1     CJNE  A,#'1',NOT_P    ;CHK IF 1,OTHERWISE BAD DATA
          SETB  C               ;INPUT=1,SET CARRY
STORE_B   MOV   DPTR,#I_TEMP    ;GET SCRATCH PAD LOCA
          MOV   A,#$92          ;MOV C,BIT INSTR
          MOVX  @DPTR,A         ;STORE TO RAM
          MOV   A,BIT           ;BIT LOCA
          LCALL I_TEMP          ;EXECUTE CODE (WRITE TO BIT)
          RET

          ;REGISTER DISPLAY
R_SUB     PUSH  DPL
          PUSH  DPH
          PUSH  ACC
          PUSH  B_REG
          PUSH  PSW
          PUSH  ACC
          PUSH  B_REG
          PUSH  PSW
          PUSH  DPL
          PUSH  DPH
          MOV   DPTR,#REG_D     ;SEND REGISTER HEADER TO SER.PORT
          LCALL P_BLK
          POP   ACC             ;GET DPH & SEND TO SERIAL PORT
          LCALL HEX2CH
          POP   ACC             ;GET DPL & SEND TO SERIAL PORT
          LCALL HEX2CH
          POP   ACC             ;GET PSW & SEND TO SERIAL PORT
          LCALL P_NEXT
          POP   ACC             ;GET B_REG & SEND TO SERIAL PORT
          LCALL P_NEXT
          MOV   A,SP
          SUBB  A,#$08          ;REFLECT SP AFTER THIS ROUTINE
          LCALL P_NEXT
          POP   ACC             ;RESTORE A & SEND TO SERIAL PORT
          LCALL P_NEXT
          MOV   DPTR,#REG_D2
          LCALL P_BLK
          POP   PSW             ;RESTORE ORIGINAL DATA
          POP   B_REG
          POP   ACC
          POP   DPH
          POP   DPL
          RET

P_NEXT    MOV   SBUF,#SPACE
          LCALL TXBUSY
          MOV   SBUF,#SPACE
          LCALL TXBUSY
          LCALL HEX2CH          ;CONVERT & SEND DATA TO SER.PORT
          RET

          ;MODIFY INTERNAL MEMORY COMMAND
I_SUB     MOV   R1,#ADR_A       ;INIT ASCII ADDRESS POINTER
          LCALL IAD_CNV         ;CONVERT INT ASCII ADRS TO 1 HEX BYTE
          JC    HEX_ER          ;IF CARRY SET, NOT HEX ADDRESS
          MOV   ADR_HL,A        ;SAVE ADDRESS
          LCALL PIDATA          ;SEND DATA TO SER.PORT
          MOV   R1,#DATA_A      ;RE-INIT POINTER FOR NEW DATA
          LCALL WAIT            ;GET NEW DATA
          MOV   A,DATA_A        ;SEE IF ANOTHER LOCA TO BE MOD
          CJNE  A,#EOT,UPDAT    ;IF DATA ENTERED, UPDATE LOCATION
HEX_ER    RET                   ;OTHERWISE RETURN TO PROMPT

          ;DATA = MODIFY LOCATION
UPDAT     LCALL DA_CNV          ;CNVRT ASCII DATA IN DATA_A(&+1)2HEX
          JC    DO_ERR          ;MISTAKE, ENTER AGAIN
          MOV   DATA_H,A        ;DATA_H HOLDS NEW DATA
          LCALL STORE_I         ;STORE DATA
          RET

          ;PRINT DATA
PIDATA    LCALL GET_I           ;GET INT DATA
          LCALL HEX2CH          ;CONVERT / PRINT INT MEM DATA
          MOV   SBUF,#SPACE     ;THEN A SPACE
          LCALL TXBUSY
DO_ERR    RET

          ;STORE INT RAM READ ROUTINE TO RAM BUFFER
          ;CAN ONLY DIRECT ADDRESS $80-$FF
GET_I     MOV   DPTR,#I_TEMP    ;GET SCRATCH PAD LOCA
          MOV   A,#$E5          ;LOAD A INSTRUCTION
          MOVX  @DPTR,A         ;STORE TO RAM
          INC   DPTR            ;SKIP SPACE FOR INT ADDR
          MOV   A,ADR_HL        ;GET INT HEX ADR
          MOVX  @DPTR,A         ;STORE DIRECT INT ADDRESS
          INC   DPTR            ;POINT TO NEXT INSTR ADR
          MOV   A,#$22          ;RETURN COMMAND
          MOVX  @DPTR,A         ;STORE RETURN COMMAND
          LCALL I_TEMP          ;EXECUTE CODE (READ INT MEM)
          MOV   DATA_H,A
          RET

STORE_I   MOV   DPTR,#I_TEMP    ;GET SCRATCH PAD LOCA
          MOV   A,#$F5          ;STORE A INSTRUCTION
          MOVX  @DPTR,A         ;STORE TO RAM
          MOV   A,DATA_H        ;LOAD A WITH NEW DATA
          LCALL I_TEMP          ;EXECUTE CODE (WRITE TO INT MEM)
          RET

          ;BREAKPOINT SUBROUTINE - DISPLAYS REG @ BP
B_SUB     MOV   A,ADR_A         ;CHK IF ADD OR REMOVE BREAKPT
          CJNE  A,#'-',SHO_BP   ;IF NOT REMOVING BP,CHK OPTIONS
CLR_BP    MOV   DPH,BP_HH       ;GET LAST BREAKPT ADDRESS
          MOV   DPL,BP_HL
          MOV   R0,#$07         ;SPACE FOR BP ROUTINE
          MOV   R1,#BP_OP       ;INIT TEMP BUFF PTR
NXT_CD    MOV   A,@R1           ;FETCH ORIG CODE
          MOVX  @DPTR,A         ;RESTORE IN ORIG LOCA
          INC   R1              ;POINT TO NEXT BUFF LOCA
          INC   DPTR            ;POINT TO NEXT MEM LOCA
          DJNZ  R0,NXT_CD       ;SEE IF LAST CODE BYTE
          MOV   BP_HH,#$00      ;CLEAR BREAKPT POINTER
          MOV   BP_HL,#$00
          RET

SHO_BP    CJNE  A,#EOT,ADD_BP
P_BP      MOV   A,BP_HH         ;PRINT BREAKPOINT LOCATION
          LCALL HEX2CH
          MOV   A,BP_HL
          LCALL HEX2CH
DO_AGN    RET                   ;BACK TO MAIN

ADD_BP    LCALL AD_CNV          ;CONVERT ASCII ADDRESS TO HEX
          JC    DO_AGN
          MOV   BP_HH,R0        ;SAVE HEX ADDRESS
          MOV   DPH,R0
          MOV   BP_HL,A
          MOV   DPL,A
          MOV   R1,#BP_OP       ;INIT PTR
          MOV   R0,#$07         ;7BYTES REQ FOR BP SUB
OP_SV     MOVX  A,@DPTR         ;FETCH CODE
          MOV   @R1,A           ;SAVE IN BUFFER
          INC   R1              ;INCREMENT PTRS
          INC   DPTR
          DJNZ  R0,OP_SV        ;SAVE 7BYTES
          MOV   R0,#$07         ;INIT COUNTR
          MOV   B_REG,#$22
          PUSH  B_REG           ;SAVE CODE ON STACK
          MOV   DPTR,#R_SUB     ;FIND REG DISPLAY SUB
          PUSH  DPL
          PUSH  DPH
          MOV   A,#$12          ;LCALL OP CODE
          PUSH  ACC
          MOV   A,#$00          ;CODE 2 DISABLE IRQs
          PUSH  ACC
          MOV   A,#IE
          PUSH  ACC
          MOV   A,#$85
          PUSH  ACC
          MOV   DPH,BP_HH       ;INIT PTR TO BP
          MOV   DPL,BP_HL
BR_CODE   POP   ACC             ;NEXT CODE BYTE
          MOVX  @DPTR,A         ;PUSH LCALL R_SUB,RET
          INC   DPTR            ;INTO BP LOCA
          DJNZ  R0,BR_CODE      ;DO UNTIL ALL CODE SAVED
          RET

          ;MESSAGES

BOOT_M    .BYTE CR \ .BYTE LF \ .BYTE LF ;BOOT UP MESSAGE
   .TEXT "ELMER <Embedded Low-level Monitor for Experiments & Realtime-Debug>"
          .BYTE CR \ .BYTE LF
          .TEXT "  VER 2.1 - Copyright (C) 1996 by A.Marchese"
          .BYTE CR \ .BYTE LF \ .BYTE EOT

PROMPT    .BYTE CR \ .BYTE LF   ;COMMAND PROMPT
          .TEXT "=> " \ .BYTE EOT

ERROR     .TEXT "    ! invalid entry" ;ERROR MESSAGE
          .BYTE CR \ .BYTE LF \ .BYTE EOT

OK_DL     .TEXT "   BEGIN TRANSFER"   ;OK TO DOWNLOAD
          .BYTE EOT

CS_FAIL   .BYTE CR \ .BYTE LF
          .TEXT "    ! checksum error" \ .BYTE CR \ .BYTE LF \ .BYTE EOT

X_MSG     .BYTE CR \ .BYTE LF
          .TEXT "    ! file format error" \ .BYTE CR \ .BYTE LF
          .TEXT "    ! transfer aborted" \ .BYTE CR \ .BYTE LF \ .BYTE EOT

DC        .BYTE CR \ .BYTE LF   ;TRANSFER COMPLETE
          .TEXT "   READY" \ .BYTE CR \ .BYTE LF \ .BYTE EOT

REG_D     .BYTE CR \ .BYTE LF \ .BYTE LF   ;REGISTER DISPLAY HEADER
          .TEXT "DPTR  PSW B   SP  A" \ .BYTE CR \ .BYTE LF
          .TEXT "83+82 D0  F0  81  E0  - INT MEM ADDR"
          .BYTE CR \ .BYTE LF \ .BYTE EOT

REG_D2    .TEXT "  - DATA" \ .BYTE CR \ .BYTE LF \ .BYTE LF \ .BYTE EOT

HELP      .BYTE CR \ .BYTE LF \ .BYTE LF
          .TEXT "COMMAND  DESCRIPTION" \ .BYTE CR \ .BYTE LF \ .BYTE LF
          .TEXT "B        SHOW BREAKPOINT" \ .BYTE CR \ .BYTE LF
          .TEXT "B<aaaa>  SET BREAKPOINT" \ .BYTE CR \ .BYTE LF
          .TEXT "B-       CLEAR BREAKPOINT" \ .BYTE CR \ .BYTE LF
          .TEXT "C        CLEAR RAM $2000-$3FFF" \ .BYTE CR \ .BYTE LF
          .TEXT "D<aaaa>  DISPLAY (16) EXT MEM LOCA" \ .BYTE CR \ .BYTE LF
          .TEXT "G<aaaa>  RUN PROGRAM @ LOCA" \ .BYTE CR \ .BYTE LF
          .TEXT "I<aa>    DISPLAY/MOD INT RAM" \ .BYTE CR \ .BYTE LF
          .TEXT "L        DOWNLOAD INTEL HEX FILE" \ .BYTE CR \ .BYTE LF
          .TEXT "M<aaaa>  MOD EXT RAM LOCA" \ .BYTE CR \ .BYTE LF
          .TEXT "P<nb>    DISPLAY/MOD PORT (BIT)" \ .BYTE CR \ .BYTE LF
          .TEXT "R        DISPLAY REG" \ .BYTE CR \ .BYTE LF
          .TEXT "?        HELP" \ .BYTE CR \ .BYTE LF \ .BYTE LF
          .TEXT "a=address d=data n=port# b=bit#"
          .BYTE CR \ .BYTE LF \ .BYTE LF \ .BYTE EOT

MEM_CLR   .BYTE CR \ .BYTE LF
          .TEXT "   $2000-$3FFF = 00" \ .BYTE CR \ .BYTE LF \ .BYTE EOT

ISR0      .ORG $3A00            ;EXT INT 0 RESET VECTOR POINTS HERE
TMR_OI0   .ORG $3B00            ;TIMER0 OVERFLOW INT POINTS HERE
ISR1      .ORG $3C00            ;EXT INT 1 RESET VECTOR POINTS HERE
TMR_OI1   .ORG $3D00            ;TIMER1 OVERFLOW INT POINTS HERE
S_ISR     .ORG $3E00            ;SERIAL RX/TX INT VECTOR POINTS HERE

FINISH    .END
          NOP
          .END