!!! 6502 Forth like tiny OS


{{{
;
; LCD.AS
;
; Implements a "FORTH-like" operating system on a small 6502 SBC.
; The hardware includes a 6502 running at 1 Mhz, a 6522, 2 KB of RAM,
; 10 KB of EEPROM, a 4-line by 16-char LCD display, and a 22-key keyboard.
; The keyboard is scanned via the 6522 ports. Keys include hex chars 0 thru
; F, Space, <CR>, and <BS>. The keyboard is scanned via polling, whenever no
; other code is executing.
; The LCD display is mapped into memory.
; The SBC has other hardware features that are not currently used.
;
; All keystrokes are in hex. The OS identifies the purpose of a keyword
; based on it's length:
;   1-char symbols are simple functions (currently Examine and Deposit)
;   2-char symbols are byte values
;   3-char symbols are FORTH-like 'words'
;   4-char symbols are word values.
; Example: A3C2 5B F00
; The above line would do the following: push the word $A3C2 onto the stack,
; push the byte $5B onto the stack, then execute word 'F00'.
;
; 'Word' definitions are stored in an 8K EEPROM at $C000. The storage is
; divided into 32-byte blocks - all words start on a 32-byte boundary.
; The first byte of a block indicates the contents of the block:
;   0 - deleted or unused block
;   1 - block contains space-delimited tokens
;   2 - block contains executable code, ending with RTS
; The 2nd thru 4th bytes of a block contain the word's name, in ASCII.
; The remainder of the block contains either tokens or code.
;
; A number of words are defined so far:
;   E   examine memory (addr on top of stack)
;   D   deposit to memory (addr and data on stack)
;   CCC   compile a new word into EEPROM (finds an empty block)
;   001   POP - pop a byte from the stack
;   002   INCW - increment a word on the top of the stack
;   003   DUPW - duplicate a word
;   004   READ - read an addr - pop/read the addr, push the result byte
;   005   PRINTBNS - print a byte value
;   006   PRINTB - print a byte value plus a trailing space
;   007   PRINTW - print a word value plus a trailing space
;   008   DELAY - a short time delay - param is a byte
;   009   LDELAY - time delay, 1-second increments
;   00A   WRITE - write a location - addr word and data byte are on stack
;   00B   ADD - add 2 bytes on the stack, push the result
;   00C   SUB - subtract 2 bytes
;   00D   SHIFT-R - right-shift a byte
;   00E   SHIFT-L - left-shift a byte
;   00F   INC - increment a byte
;   010   DEC - decrement a byte
;   011   DO - the 'do' of a do-while loop
;   012   WHILE - the 'while' of a do-while loop
;
; The 'E' word examines/displays a memory location, but formats the
; output to be a subsequent 'D' command. For example, you enter:
;   0400 E
; then the next display line becomes:
;   0400 12 D
; where 12 is the current contents of address 0400. The cursor is
; sitting on the '1' in '12', so you can enter a new value easily
; if desired (just press Enter and the value is unchanged).
; After entering a new value, press Enter, and the display is now:
;   0401 AB D
; - note that the address incremented, and 'AB' is the current contents.
;
; The compile (CCC) word creates a new token-style word definition in
; the first available unused block (code-style words must be entered
; manually, using the 'D' command).
; Example:  CCC BAA 89 005
; This example compiles a new word, named 'BAA'. The function of BAA
; is to push $89 on the stack, then print that value.


;
; RAM is 0000-0800
; 6522 I/O is at "VIA" (2000-200F)
;   CA1 is for falling-edge priority interrupts
;   CA2
;   CB2 is SR out
;   CB1 is SR clock out
;   PA0-2 are inputs for keyboard scan
;     PA3
;     PA4-6 is interrupt priority
;     PA7
;   PB0-7 are outputs for keyboard scan

OUTBUF     EQU     $0040           ; transmit buffer
INBUFF     EQU     $0080           ; receiver buffer
ZPIND      EQU   $00FC      ; for indirect LDA/STA
DSTACK      EQU   $0700      ; bottom of Data Stack
VIA             EQU     $2000           ; address of 6522 VIA
ACIA            EQU     $4000           ; address of 6551 ACIA
LCD0      EQU   $6000      ; LCD display
LCD1      EQU   $6001
EE8K      EQU   $C000      ; FORTH 'word' storage, 8KB
EEPROM      EQU   $F800      ; boot and kernel code, 2KB

;
; 6522 VIA definitions
;
ORG     VIA
ORB     .DS     1               ;Output Register B
IRB     EQU     ORB             ;Input Register B
ORA     .DS     1               ;Output Register A
IRA     EQU     ORA             ;Input Register A
DDRB    .DS     1               ;Data Direction Register B
DDRA    .DS     1               ;Data Direction Register A
T1CL    .DS     1               ;read:  T1 counter, low-order
;write: T1 latches, low-order
T1CH    .DS     1               ;T1 counter, high-order
T1LL    .DS     1               ;T1 latches, low-order
T1LH    .DS     1               ;T1 latches, high-order
T2CL    .DS     1               ;read:  T2 counter, low-order
;write: T2 latches, low-order
T2CH    .DS     1               ;T2 counter, high-order
SR      .DS     1               ;Shift Register
ACR     .DS     1               ;Auxiliary Control Register
PCR     .DS     1               ;Peripheral Control Register
IFR     .DS     1               ;Interrupt Flag Register
IER     .DS     1               ;Interrupt Enable Register

;
; 6551 ACIA definitions
;
ORG     ACIA
ATXM    .DS     1               ;Transmitter Register (write only)
ARCX    EQU     ATXM            ;Receiver Register (read only)
ASTS    .DS     1               ;Status Register (read only)
ARES    EQU     ASTS            ;Soft Reset  (write only)
ACMD    .DS     1               ;Command Register
ACTL    .DS     1               ;Control Register

;
; page zero variable declarations
;
ORG     $0000
KBUFF   .DS   32   ; keyboard input buffer
ORACOP  .DS     1       ; copy of 6522 ORA
IFRCOP  .DS     1       ; copy of 6522 IFR
DLYCNT  .DS     1       ; delay counter
OUTNDX  .DS     1       ; output char index
UASTAT  .DS     1       ; UART status
FDA     .DS     1
CURSOR  .DS     1   ; LCD cursor location
TEMP    .DS     1
FRADLO  .DS     1
FRADHI  .DS     1
FCNT    .DS     1
FFDA    .DS     1
FRENLO  .DS     1
FRENHI  .DS     1
ECNTLO  .DS     1
ECNTHI  .DS     1
TOADLO  .DS     1
TOADHI  .DS     1
KEY   .DS   1
TOKEN1   .DS   1   ; ptr: beginning of token
TOKEN2   .DS   1   ; ptr: end of token
DSP   .DS   1   ; data stack pointer
ASCBYT   .DS   2
TKLEN   .DS   1   ; token length
EEPTR   .DS   2   ;
WLETT   .DS   1   ;
BTYPE   .DS   1   ; type: tokens, code, deleted
BUFFPTR   .DS   2   ; ptr to current token buffer
TKTMP   .DS   1
COMPTR   .DS   2   ; ptr used by Compile

ORG     INBUFF
INNDX   .DS     1       ; input buffer index



ORG   EEPROM   ; $F800

LCDBUSY:
; wait for the LCD to not be busy
PHA
L1:   LDA   LCD0
AND   #$80
BNE   L1
PLA
RTS

LCDINIT:
; init the LCD display
LDX   #$04      ; do it 4 times
L0:   LDA   #$38      ;
STA   LCD0
JSR   LCDBUSY
DEX
BNE   L0
LDA   #$06      ;
STA   LCD0
JSR   LCDBUSY
LDA   #$0E      ;
STA   LCD0
JSR   LCDBUSY
LDA   #$01      ; clear display
STA   LCD0
JSR   LCDBUSY
LDA   #$90      ; start on 3rd line
STA   CURSOR      ; init cursor location
STA   LCD0
JSR   LCDBUSY
RTS



ORG   $F850
SCROLL:
; Treat the 4 X 16 display as 2 lines of 32 char each.
; scroll everything up one line
; leave cursor at start of 2nd line (3rd physical line)
LDX   #$00      ; index, beginning of kybd buffer
LDA   #$90      ; 3rd line
S6:   STA   LCD0      ; set cursor pos
JSR   LCDBUSY
S1:   LDA   LCD1      ; get char from display
STA   KBUFF,X      ; copy char to buffer
JSR   LCDBUSY
INX         ; next char
CPX   #$20      ; all done?
BEQ   S2      ; yes
CPX   #$10      ; end of line?
BNE   S1      ; no
LDA   #$D0      ; yes, goto 4th display line
BNE   S6
S2:   ; copy buffer to 1st line
LDX   #$00      ; beginning of buffer
LDA   #$80      ; 1st line
S5:   STA   LCD0      ; set cursor pos
JSR   LCDBUSY
S3:   LDA   KBUFF,X      ; get char from buffer
STA   LCD1      ; write to display
JSR   LCDBUSY
INX         ; next char
CPX   #$20      ; all done?
BEQ   S4      ; yes
CPX   #$10      ; end of line?
BNE   S3      ; no
LDA   #$C0      ; yes, goto 2nd line
BNE   S5
S4:   ; fill 3rd and 4th lines with spaces
LDA   #$90      ; goto 3rd line
JSR   BLNKLN
LDA   #$D0      ; goto 4th line
JSR   BLNKLN
; move cursor to start of 3rd physical line
LDA   #$90
STA   CURSOR
STA   LCD0
JSR   LCDBUSY
RTS

BLNKLN:
; fill a 16-char line with spaces
STA   LCD0
JSR   LCDBUSY
LDX   #$10      ; line len is 16.
BL1:   LDA   #$20      ; write a space
STA   LCD1
JSR   LCDBUSY
DEX
BNE   BL1
RTS


ORG   $F8E0

PRINTCH:
; Display a char at the current cursor location.
;   80...8F 1st line
;   C0...CF 2nd line
;   90...9F 3rd line
;   D0...DF 4th line
CMP   #$0D      ; <CR> ?
BNE   P1      ; no
JMP   SCROLL      ; yes, just scroll and return
P1:   CMP   #$08      ; backspace ?
BNE   P2      ; no
JMP   BACKSPACE   ; yes, just BS and return
P2:   STA   LCD1      ; display it
JSR   LCDBUSY
; decide where the next char should go
LDX   CURSOR      ; get cursor location
INX         ; move cursor forward
CPX   #$E0      ; end of 4th line?
BNE   P4      ; no
DEX         ; yes, stay where we were
P4:   CPX   #$A0      ; end of 3rd line?
BNE   P3      ; no
LDX   #$D0      ; yes, begin 4th line
P3:   STX   CURSOR      ; remember new location
STX   LCD0      ; set the new location
JSR   LCDBUSY
RTS


ORG   $F90F

BACKSPACE:
; backup the cursor one space
LDX   CURSOR
CPX   #$D0      ; beginning of 4th line?
BNE   BS1      ; no
LDX   #$A0      ; yes, goto end of 3rd line
BS1:   CPX   #$90      ; beginning of 3rd line?
BEQ   BS2      ; yes, do nothing
DEX         ; no, backup one space
NOP
BS2:   STX   CURSOR      ; remember new location
STX   LCD0      ; set new location
JSR   LCDBUSY
LDA   #$20      ; print a space to overwrite old char
STA   LCD1
JSR   LCDBUSY
LDA   CURSOR      ; put cursor back on the space
STA   LCD0
JSR   LCDBUSY
RTS


ORG   $F950

DOKEY:
; handle an input char
PHA         ; save A
JSR   PRINTCH      ; print it
PLA         ; restore A
CMP   #$0D      ; <CR> ?
BNE   D1      ; no, we're done
JSR   PARSEK      ; yes, parse the input line
D1:   RTS

KBDINIT:
LDA     #$82            ; enable CA1 ints from 6522
; STA     IER
NOP
NOP
NOP
LDA     #$00
STA     DDRA            ; port A is input
STA     ORB             ; all 0's on output port
STA     INNDX           ; init inbuff index
LDA     #$FF
STA     DDRB            ; port B is output
RTS

CHKKBD:
; get the key (if pressed)
; the key is returned in A
; if no key pressed, return 00
LDY   #$FE      ; start with 11111110
C1:   STY   ORB      ; scan 1 of 8 rows
LDA   IRA      ; read the column
AND   #$07      ; lower 3 bits only
CMP   #$07      ; key ?
BNE   C2      ; yes, go decode it
TYA         ; no
BMI   C3      ; is it 01111111 yet?
LDA   #$00      ; yes, no key was pressed
RTS         ;   quit - return 00
C3:   SEC         ; no
ROL   A      ; shift left
TAY         ; put the pattern back in Y
CLC         ; go scan the next row
BCC   C1
C2:   STA   KEY      ; save the column info
TYA         ; get the row
LDY   #$00      ; init counter
C4:   INY         ; count
LSR   A      ; shift the row
BCS   C4      ; until the 0 falls out
TYA         ; get the row-count
ASL   A      ; move it to the higher bits
ASL   A
ASL   A
ORA   KEY      ; combine row and column
; decode the key value into ASCII
LDX   #$17
C6:   CMP   KEYTAB,X
BEQ   C5
DEX
BPL   C6
LDA   #$00
BEQ   C7
C5:   LDA   ASCTAB,X
C7:   STA   KEY      ; save the ASCII key
LDA   #$14      ; wait for debounce
JSR   DELAY
JSR   WAITNOKEY   ; wait for no key
LDA   #$30      ; wait for debounce
JSR   DELAY
JSR   WAITNOKEY   ; wait for no key
LDA   KEY      ; get the ASCII key char
RTS         ; return the char

WAITNOKEY:
; wait for no key being pressed
LDA   #$00
STA   ORB
W1:   LDA   IRA
AND   #$07
CMP   #$07
BNE   W1
RTS

KEYTAB:            ; keyboard codes
.BYTE   $0D
.BYTE   $3E
.BYTE   $3D
.BYTE   $26
.BYTE   $25
.BYTE   $36
.BYTE   $35
.BYTE   $2E
.BYTE   $2D
.BYTE   $46
.BYTE   $45
.BYTE   $16
.BYTE   $15
.BYTE   $1E
.BYTE   $1D
.BYTE   $0E
.BYTE   $23
.BYTE   $13
.BYTE   $2B
.BYTE   $0B
.BYTE   $33
.BYTE   $1B
.BYTE   $00   ; not used
.BYTE   $00   ; not used
ASCTAB:         ; ASCII codes
.BYTE   $30   ; 0
.BYTE   $31
.BYTE   $32
.BYTE   $33
.BYTE   $34
.BYTE   $35
.BYTE   $36
.BYTE   $37
.BYTE   $38
.BYTE   $39   ; 9
.BYTE   $41   ; A
.BYTE   $42
.BYTE   $43
.BYTE   $44
.BYTE   $45
.BYTE   $46   ; F
.BYTE   $00
.BYTE   $00
.BYTE   $00
.BYTE   $20   ; space
.BYTE   $08   ; backspace
.BYTE   $0D   ; <CR>
.BYTE   $00   ; not used
.BYTE   $00   ; not used



ORG   $FA30
PARSEK:
; Check the input line, see what to do.
; Process each token found on the line.
LDA   #$00      ; beginning of input line
STA   BUFFPTR      ; point to kybd buffer
STA   BUFFPTR+1
STA   TOKEN1
PK1:   JSR   GETTOKEN   ; get the next token
JSR   DOTOKEN      ; process it
BNE   PK1      ; more tokens?
RTS

NOP
NOP

NOP
NOP
NOP
NOP

GETTOKEN:
; identify the next token in the current buffer
; BUFFPTR points to the buffer
; max buffer length is 32.
; TOKEN1 points to first char in token.
; TOKEN2 points to first space after token.
LDY   TOKEN1      ; start from here
GTLOOP:   LDA   (BUFFPTR),Y   ; get a char
CMP   #$00      ; end of buffer marker?
BEQ   G3      ; yes - quit
CMP   #$20      ; space?
BNE   SYM      ; no - it's a token
INY         ; yes - skip it
CPY   #$20      ; end of buffer?
BNE   GTLOOP      ; no, loop
G3:   LDA   #$00      ; yes, report no more tokens
STA   TOKEN2
GTDONE:   RTS         ; return
SYM:   STY   TOKEN1      ; point to beginning of token
GTL2:   INY         ; next char
CPY   #$20      ; end of buffer?
BNE   G1      ; no
G2:   STY   TOKEN2      ; yes, point to end of token
RTS
G1:   LDA   (BUFFPTR),Y   ; get next char
CMP   #$20      ; space?
BNE   GTL2      ; no, loop
BEQ   G2      ; yes, quit

NOP
NOP
NOP
NOP
NOP
NOP

DOTOKEN:
; Process a token from the current buffer.
; Process each token based on it's length.
; Len of 1 is a 'kernel' command.
; Len of 3 is a command word.
; Len of 2 is a byte value.
; Len of 4 is a word value.
LDA   TOKEN2      ; was a token found?
BEQ   DT1      ; no, quit
SEC         ; yes, determine it's length
SBC   TOKEN1      ; subtract
STA   TKLEN      ; save the length
CMP   #$01
BNE   DT2
JSR   ONE
CLC
BCC   DT1
DT2:   LDA   TKLEN
CMP   #$02
BNE   DT3
JSR   TWO
CLC
BCC   DT1
DT3:   LDA   TKLEN
CMP   #$03
BNE   DT4
JSR   THREE
CLC
BCC   DT1
DT4:   LDA   TKLEN
CMP   #$04
BNE   DT5
JSR   FOUR
CLC
BCC   DT1
DT5:   LDA   #$00      ; unsupported token length
STA   TOKEN2
DT1:   LDA   TOKEN2
STA   TOKEN1
RTS

ONE:
; Is it a 'D'?
LDY   TOKEN1
LDA   (BUFFPTR),Y   ; get the letter
CMP   #'D
BEQ   DEPOSIT
CMP   #'E
BEQ   EXAMINE
RTS

TWO:
; push a byte onto the dstack
; convert ASCII byte to binary
LDY   TOKEN1      ; point to hi-order char
JSR   CVTBYT
JMP   PUSH      ; push onto dstack

THREE:
; It's a command 'word'. Execute it.
JMP   DOWORD

FOUR:
; Push a word onto the dstack. Do it hi-byte first,
; so that it can be used as a pointer directly.
LDY   TOKEN1
JSR   CVTBYT
NOP
NOP
NOP
NOP
JSR   PUSH
NOP
NOP
JSR   CVTBYT
JMP   PUSH      ; push lo byte

DEPOSIT:
JSR   POP
JSR   DUPW
JSR   WRITEA
LDA   #$01
JSR   DELAY
JSR   INCW
EXAMINE:
JSR   DUPW
JSR   PRINTW
JSR   READ
JSR   PRINTB
LDA   #'D
JSR   PRINTCH
; backup cursor 4 spaces
LDX   CURSOR
DEX
DEX
DEX
DEX
STX   CURSOR      ; remember new location
STX   LCD0      ; set new location
JSR   LCDBUSY
RTS

CVTBYT:
; Get an ASCII byte from the buffer, convert to binary.
LDA   (BUFFPTR),Y
JSR   BINBYT      ; convert to binary
ASL   A      ; move to upper nibble
ASL   A
ASL   A
ASL   A
STA   TEMP      ; save it
INY         ; get lo-order char
LDA   (BUFFPTR),Y
JSR   BINBYT      ; convert to binary
CLC         ; add upper nibble
ADC   TEMP
INY         ; point to next char
RTS

NOP
NOP
NOP
NOP
NOP
NOP


ORG   $FB80

PUSH:
; push A onto the dstack
LDX   DSP
STA   DSTACK,X
DEC   DSP
RTS

POP:
; pop A from the dstack
INC   DSP
LDX   DSP
LDA   DSTACK,X
RTS

INCW:
; Increment the word on the top of the stack,
; but don't pop it.
LDX   DSP
INX
INC   DSTACK,X
BNE   IW1
INX
INC   DSTACK,X
IW1:   RTS

WRITEA:
; Write A to the location indicated by the word
; on the top of the stack.
; Preserve A, pop the address.
PHA
LDA   #$8D
STA   ZPIND
JSR   POP
STA   ZPIND+1
JSR   POP
STA   ZPIND+2
LDA   #$60
STA   ZPIND+3
PLA
JSR   ZPIND
RTS

DUPW:
; Push a duplicate of the word that is already
; on the top of the stack.
; Preserve A.
PHA
LDY   DSP
INY
INY
LDA   DSTACK,Y
JSR   PUSH
DEY
LDA   DSTACK,Y
JSR   PUSH
PLA
RTS

READ:
; Read a memory location, specified by the addr
; on the stack. Pop the address, and push the
; result. Also return the result in A.
LDA   #$AD
STA   ZPIND
JSR   POP
STA   ZPIND+1
JSR   POP
STA   ZPIND+2
LDA   #$60
STA   ZPIND+3
JSR   ZPIND
JSR   PUSH
RTS

PRINTBNS:
; Print a byte value in hex at the
; current cursor location. Do NOT include a space.
JSR   POP
JSR   BYTEHEX
LDA   ASCBYT
JSR   PRINTCH
LDA   ASCBYT+1
JSR   PRINTCH
RTS

PRINTB:
; Print a byte value in hex at the
; current cursor location. Include a space.
JSR   PRINTBNS
LDA   #$20
JSR   PRINTCH
RTS

PRINTW:
; Print a word value in hex at the
; current cursor location. Include a space.
JSR   POP
PHA
JSR   PRINTBNS
PLA
JSR   PUSH
JSR   PRINTB
RTS

DOWORD:
; process a 3-letter word from the current buffer
; save the current BUFFPTR and TOKEN2
LDA   BUFFPTR
PHA
LDA   BUFFPTR+1
PHA
LDA   TOKEN2
PHA
; find the word's definition block
LDA   #$00      ; point to start of word defs
STA   EEPTR
LDA   #$C0
STA   EEPTR+1
DW3:   LDA   TOKEN1
STA   TKTMP
LDY   #$00      ; check block type
LDA   (EEPTR),Y
STA   BTYPE
BNE   DW1      ; deleted?
DW2:   LDA   EEPTR      ; yes - goto next block
CLC
ADC   #$20      ; blk size is 32.
STA   EEPTR
BCC   DW5
INC   EEPTR+1
LDA   EEPTR+1
CMP   #$D8      ; end of EE ?
BEQ   DWDONE
DW5:   CLC         ; loop back
BCC   DW3
DW1:   INY         ; check the symbol
LDA   (EEPTR),Y
STA   WLETT
TYA         ; save Y
TAX
LDY   TKTMP      ; get token letter
LDA   (BUFFPTR),Y
CMP   WLETT      ; letter match?
BNE   DW2      ; no - goto next block
TXA         ; restore Y
TAY
INC   TKTMP      ; yes - next letter
CPY   #$03      ; done?
BNE   DW1      ; no - next letter
; found matching word block!
LDA   BTYPE      ; check blk type
CMP   #$01      ; tokens?
BEQ   DW7      ; yes
CMP   #$02      ; code?
BEQ   DW4      ; yes
BNE   DWDONE      ; unknown - quit

NOP
NOP
DW7:
; found token block - point to 1st token
INY         ; point to space before token
INY         ; point to first letter of token
; process tokens
STY   TOKEN1
STY   TOKEN2
; set the BUFFPTR to match EEPTR
LDA   EEPTR
STA   BUFFPTR
LDA   EEPTR+1
STA   BUFFPTR+1
; recurse
DW6:   JSR   GETTOKEN   ; get a token
JSR   DOTOKEN      ; process it
BNE   DW6      ; more tokens?
DWDONE:
; restore the BUFFPTR and TOKEN1
PLA
STA   TOKEN2
STA   TOKEN1
PLA
STA   BUFFPTR+1
PLA
STA   BUFFPTR
RTS
DW4:   ; process code
LDA   #$4C      ; make a JMP in 0-page
STA   ZPIND
LDA   EEPTR      ; point to the block
CLC
ADC   #$04      ; the 4th byte is the start
STA   ZPIND+1
LDA   EEPTR+1
STA   ZPIND+2
JSR   ZPIND      ; make the call
CLC         ; done
BCC DWDONE

NOP
NOP
NOP
NOP
NOP
NOP

COMPILE:
; Compile a new word into EE. The definition is
; in (BUFFPTR)+TOKEN2 .
; find an unused or deleted block
LDA   #$00      ; EE starts at $C000
STA   COMPTR
LDA   #$C0
STA   COMPTR+1
COM1:   LDY   #$00      ; 1st byte of blk is type
LDA   (COMPTR),Y   ; get block type
CMP   #$01      ; token block?
BEQ   USEDBL      ; yes - find another
CMP   #$02      ; code block?
BEQ   USEDBL      ; yes - find another
; this block is free!
; (BUFFPTR)+TOKEN2 points to space before 1st token
LDA   #$00
STA   TKTMP
; (COMPTR)+TKTMP now points to block type
LDA   #$01      ; write the block type
JSR   EEWRT
COM2:   INC   TKTMP      ; next output index
INC   TOKEN2      ; next input index
LDY   TOKEN2      ; beyond end of input buffer?
CPY   #$20
BEQ   DONULL      ; yes
LDA   (BUFFPTR),Y   ; get a char from buffer
JSR   EEWRT      ; write the char
CLC         ; do next char
BCC   COM2
DONULL:   LDA   #$00      ; write a null terminator
JSR   EEWRT
COMDONE:
LDA   #$00      ; don't process rest of buffer
STA   TOKEN2
RTS         ; return
USEDBL:   LDA   COMPTR      ; goto next block
CLC
ADC   #$20      ; blk size is 32.
STA   COMPTR
BCC   COM1
INC   COMPTR+1
LDA   COMPTR+1
CMP   #$D8      ; limit of blocks ?
BEQ   COMDONE      ; yes - quit
BNE   COM1      ; no - try this block
EEWRT:   LDY   TKTMP      ; get index
STA   (COMPTR),Y   ; write the char
LDA   #$0D      ; wait 50ms for EE to finish
JSR   DELAY
RTS

ORG   $FDA0

BOOT:
;
; The Reset vector (FFFC) points here.
;
SEI                     ; no IRQ interrupts
CLD                     ; no Decimal mode
JSR   LCDINIT
JSR   KBDINIT
LDA   #$C0
STA     ARES            ; ACIA soft reset
LDA     #$1A            ; 8-N-1, 2400 baud
STA     ACTL
LDA   #$FF
STA   DSP
; LDA     #$89            ; enable ACIA xmtr/rcvr
; STA     ACMD
NOP
NOP
NOP
NOP
NOP
CLI         ; enable ints
; print "OK"
LDA   #$4F      ; 'O'
JSR   PRINTCH
LDA   #$4B      ; 'K'
JSR   PRINTCH
LDA   #$0D      ; <CR>
JSR   PRINTCH

; Go into the idle loop.
; Check the kbd, about 60 times/second.
; Any other work must be interrupt-driven.
IDLE:   JSR   CHKKBD
BEQ   IDLE      ; no key
JSR   DOKEY      ; handle the key
LDA   #$04      ; delay
JSR   DELAY
CLC         ; loop
BCC   IDLE



ORG   $FE00
BINBYT:
; Convert a single hex-ascii char to binary.
CMP     #$60
BMI     BB2
AND     #$DF
BB2:
SEC
SBC     #$30
CMP     #$11
BMI     BB1
SEC
SBC     #$07
BB1:
RTS

; Convert the value in A into two hex-ascii bytes, and store
; the two bytes in ASCBYT.
BYTEHEX:
PHA                     ; save the byte
LSR     A               ; do the high nibble first
LSR     A
LSR     A
LSR     A
CLC                     ; make it ascii
ADC     #$30
CMP     #$3A            ; if it's a letter, add a little more
BMI     BH3
CLC
ADC     #$07
BH3:
STA     ASCBYT        ; store the ascii byte
PLA                     ; get the original byte
AND     #$0F            ; do the low nibble
CLC                     ; make it ascii
ADC     #$30
CMP     #$3A
BMI     BH4
CLC
ADC     #$07
BH4:
STA     ASCBYT+1        ; store the 2nd ascii byte
RTS

; Time delay.
; Put the delay value in A. A value of 00 is 256 loops (max delay).
; With 256 loops, the number of machine cycles is: 329479+131072N,
; where N is the number of NOP instructions. If N is 5, and the
; clock is 1 Mhz, then the max delay is 0.985 seconds, or 3.85 ms
; per loop. If N is 12, and the clock is 1.8432 Mhz, then the max
; delay is 1.032 seconds, or 4.03 ms per loop.
; X and Y are preserved, Status is not.
; Location DLYCNT is used for temp storage.
DELAY:
STA DLYCNT
TXA             ; save X
PHA
LDX #$0 ; init X
DLOOP:
INX             ; waste time
NOP
NOP
NOP
NOP
NOP
BNE DLOOP       ; did X wrap?
DEC DLYCNT      ; yes, decrement counter
BNE DLOOP       ; are we done? No - loop again.
PLA             ; we're done - restore X
TAX
RTS             ; return

; Do longer delays, in multiples of about 1 second.
; Put the desired delay in A.
; A value of 00 will return immediately.
LDELAY:
BEQ LDELAY2     ; if 00, return right away
TAX             ; save the counter in X
LDELAY1:
LDA #$00        ; do a 1-second delay
JSR DELAY
DEX             ; DEC the counter
BNE LDELAY1     ; done?
LDELAY2:
RTS             ; yes - return

;
; NMI entry point.
;
NMI:
RTI

;
; IRQ entry point.
;
IRQ:
RTI         ; return from interrupt


; INTERRUPT AND RESTART VECTORS
ORG     $FFFA
.WORD   NMI             ; NMI vector
.WORD   BOOT            ; Cold start & reset vector
.WORD   IRQ             ; IRQ vector (and BRK vector)
.END    BOOT
}}}