-[[.:start]]
====== Monitor ======
** My first attempts at a Monitor program **
===== Features =====
* RAM is tested at power-up- writing and checking with 2 different bit patterns, then zeroing RAM if successful (avoiding ZeroPage and Stack areas)
* display a "Mission Elapsed Time" counter and reset to zero with a button press
* step forward and backward through memory locations
* display hex data and its ASCII equivalent for each memory location (''PEEK'')
* enter a 2-digit hex value and write it into the currently selected memory location (''POKE'')
* enter a 4-digit hex value and move to that chosen memory address
* display an 8-byte block of memory data on one line, starting at the currently selected address
* display an 8-byte block of memory data and have it auto-refresh at 1 second intervals
* move to a pre-determined address ($3F00) in memory and begin executing code from that point
* this allows machine code to be entered directly from $3F00 onwards and then executed. It's cumbersome, but makes the machine programmable!
Data/addresses are entered on the keypad. Commands are entered by pressing the "SHIFT/INSTRUCT" button at the same time as one of the pre-set instructions.
|shift+1 ''MET CLOCK''| 2 |shift+3 ''BLOCK UP''|shift+'A' ''UP''|
| shift+4 ''dec->hex'' | shift+5 ''GO'' |shift+6 ''BLOCK DOWN''|shift+B ''DOWN''|
| 7 | 8 | shift+9 ''auto-update Block view'' |shift+'C' ''CLS''|
|shift+E ''POKE''| 0 |shift+F ''BLOCK''|shift+D ''PEEK''|
*shift+1 = ''MET Clock''
*shift+2
*shift+3 = ''Display the next 8-byte block up'' - aka "page Up"
*shift+'A' = ''Memory selected = UP one''
*shift+4 = ''Convert an entered decimal number to its HEX equivalent''
*shift+5 = ''GO!'' (jsr $3F00)
*shift+6 = ''Display the next 8-byte block down'' - aka "page Down"
*shift+B = ''Memory selected = DOWN one''
*shift+7
*shift+8
*shift+9 = ''Update 8-byte block view at 1-second intervals''
*shift+'C' = ''Clear Screen''
*shift+E = ''Enter data byte into selected memory address'' - aka ''POKE''
*shift+0
*shift+F = ''Display an 8-byte block of data, starting from current memory address''
*shift+D = ''move to the new memory location provided in last 4 digits entered''
===== monitor_dev.s =====
.localchar '@'
;.SEGMENT "ZEROPAGE"
.zeropage
DUMP_POINTER: .res 2
FLAGS: .res 1
TOGGLE_TIME: .res 1
CLOCK_LAST: .res 1
MESSAGE_POINTER: .res 2
TICKS: .res 4
CENTISEC: .res 1
HUNDRED_HRS: .res 1
TEN_HRS: .res 1
HRS: .res 1
TEN_MINUTES: .res 1
MINUTES: .res 1
TEN_SECONDS: .res 1
SECONDS: .res 1
MEM_POINTER: .res 2
;.SEGMENT "BSS"
.bss
INKEY: .res 1
ASCII: .res 4
BYTE: .res 2
TENS: .res 1
HUNDREDS: .res 1
HEX: .res 2
HEXB: .res 2
TEMP: .res 1
TEMP2: .res 1
.include "../includes/ioports.inc"
.include "../includes/lcd.inc"
.include "../includes/getkey.inc"
.include "../includes/functions.inc"
.include "../includes/rtc.inc"
.code
reset:
ldx #$ff
txs
cli ; interrupts ON
jsr via_1_init ; set-up VIA_1 for LCD/Keypad
jsr lcd_init ; set-up 4-bit mode
jsr lcd_start ; set-up various features of lcd
init_variables:
stz TICKS
stz TICKS + 1
stz TICKS + 2
stz TICKS + 3
stz DUMP_POINTER
stz DUMP_POINTER + 1
stz MESSAGE_POINTER
stz MESSAGE_POINTER + 1
stz TOGGLE_TIME
stz CLOCK_LAST
stz CENTISEC
stz FLAGS
stz SECONDS
stz TEN_SECONDS
stz MINUTES
stz HRS
stz TEN_HRS
stz TEN_MINUTES
stz HUNDRED_HRS
stz TEMP
stz TEMP2
stz TENS
stz MEM_POINTER
stz MEM_POINTER + 1
;; test then clear RAM between
;; $0200 - $3FFF - avoids the ZP and STACK areas
ram_clear:
lda #$02 ; start at $0200
sta MEM_POINTER + 1
ldy #$00
loop_ram:
lda #$AA
sta (MEM_POINTER),y
lda #$FF
lda (MEM_POINTER),y
cmp #$AA
bne mem_fail_1
lda #$55
sta (MEM_POINTER),y
lda #$FF
lda (MEM_POINTER),y
cmp #$55
bne mem_fail_2
lda #$00
sta (MEM_POINTER),y
iny
beq next_page
jmp loop_ram
next_page:
lda MEM_POINTER + 1
inc
cmp #$40
beq done_ram
sta MEM_POINTER + 1
jmp loop_ram
done_ram:
lda #mem_pass_msg
sta MESSAGE_POINTER + 1
jsr print
smb5 FLAGS
jmp loop
mem_fail_1:
lda #mem_fail_msg_1
sta MESSAGE_POINTER + 1
jsr print
jmp loop
mem_fail_2:
lda #mem_fail_msg_2
sta MESSAGE_POINTER + 1
jsr print
jmp loop
; go straight to MONITOR at startup
; lda #splash
; sta MESSAGE_POINTER + 1
; jsr new_address
; main loop
loop:
wai
jsr check_flags
jmp loop
;;;;;;;;;;;;; FUNCTIONS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;
check_flags:
bbs0 FLAGS, update_block_address
bbs5 FLAGS, clock_time
; check other flags... other actions....
rts
update_block_address:
sec
lda TICKS
sbc TOGGLE_TIME
cmp #$32
bcc exit_update_block
jsr block_address
lda TICKS
sta TOGGLE_TIME
exit_update_block:
rts
clock_time:
sec
lda TICKS
sbc CLOCK_LAST
cmp #$32
bcc exit_clock
jsr lcd_cursor_off
jsr lcd_home
lda HUNDRED_HRS
jsr bintoascii
lda TEN_HRS
jsr bintoascii
lda HRS
jsr bintoascii
lda #':'
jsr print_char
lda TEN_MINUTES
jsr bintoascii
lda MINUTES
jsr bintoascii
lda #':'
jsr print_char
lda TEN_SECONDS
jsr bintoascii
lda SECONDS
jsr bintoascii
lda #' '
jsr print_char
lda TICKS
sta CLOCK_LAST
exit_clock:
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; update screen when new memory location is selected
;;
;;
new_address:
jsr lcd_clear
jsr lcd_cursor_on
print_address:
lda #'$'
jsr print_char
lda DUMP_POINTER + 1
jsr bintohex
lda DUMP_POINTER
jsr bintohex
lda #' '
jsr print_char
print_data:
ldy #$00
lda (DUMP_POINTER),y
jsr bintohex
lda #' '
jsr print_char
lda (DUMP_POINTER),y
jsr print_char
message_end:
jsr print ; add second line (cursor) after re-writing the top line
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; display 8 bytes of data for a "block" of memory
;;
;;
block_address:
jsr lcd_clear
ldy #$00
print_block_address:
lda #'$'
jsr print_char
lda DUMP_POINTER + 1
jsr bintohex
lda DUMP_POINTER
jsr bintohex
jsr lcd_line_2
print_block:
lda (DUMP_POINTER),y
jsr bintohex
lda (DUMP_POINTER),y
iny
cpy #$08
bne print_block
block_message_end:
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; re-draw line 2 cursor
;;
;;
print:
jsr lcd_line_2
ldy #0
line1:
lda (MESSAGE_POINTER),y
beq end_print
jsr print_char
iny
jmp line1
end_print:
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Monitor function - decrement the selected address
;;
;;
decrement_address:
sec
lda DUMP_POINTER
sbc #$01
sta DUMP_POINTER
sta BYTE
lda DUMP_POINTER + 1
sbc #$00
sta DUMP_POINTER + 1
sta BYTE + 1
dec_ok:
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Monitor function - increment the selected address
;;
;;
increment_address:
clc
lda DUMP_POINTER
adc #$01
sta DUMP_POINTER
sta BYTE
bcc inc_ok
inc DUMP_POINTER + 1
lda DUMP_POINTER + 1
sta BYTE + 1
inc_ok:
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Monitor function - increment the selected block of addresses by 8
;;
;;
increment_block:
clc
lda DUMP_POINTER
adc #$08
sta DUMP_POINTER
sta BYTE
lda DUMP_POINTER + 1
adc #$00
sta DUMP_POINTER + 1
sta BYTE + 1
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Monitor function - decrement the selected block of addresses by 8
;;
;;
decrement_block:
sec
lda DUMP_POINTER
sbc #$08
sta DUMP_POINTER
sta BYTE
lda DUMP_POINTER + 1
sbc #$00
sta DUMP_POINTER + 1
sta BYTE + 1
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; use last 4 entered ASCII characters from the keypad and convert
;; them to TWO 8-bit binary bytes in RAM
;;
;;
ascii_byte:
lda ASCII + 1
jsr ascii_bin
clc
asl
asl
asl
asl
sta BYTE
lda ASCII
jsr ascii_bin
ora BYTE
sta BYTE
lda ASCII + 3
jsr ascii_bin
clc
asl
asl
asl
asl
sta BYTE + 1
lda ASCII + 2
jsr ascii_bin
ora BYTE + 1
sta BYTE + 1
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; toggle the display/update of Clock on each appropriate keypress
;;
show_clock:
bbs5 FLAGS, reset_bit5
smb5 FLAGS
jmp exit_show_clock
reset_bit5:
rmb5 FLAGS
exit_show_clock:
rts
;jmp debounce
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; toggle the automatic update view of the "8-byte memory block"
;;
show_block:
bbs0 FLAGS, reset_bit0
smb0 FLAGS
jmp exit_show_block
reset_bit0:
rmb0 FLAGS
exit_show_block:
rts
;jmp debounce
;debounce:
; ldx #$ff
; ldy #$ff
;delay:
; nop
; dex
; bne delay
; dey
; bne delay
; rts
;;;;;;;;;;;;;;;;;; INTERRUPT HANDLERS ;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; CB1 : reset & restart timer
;;
cb1_handler:
stz HUNDRED_HRS
stz TEN_HRS
stz TEN_MINUTES
stz TEN_SECONDS
stz HRS
stz MINUTES
stz SECONDS
smb5 FLAGS
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; CB2 : lap-time pause timer
;;
cb2_handler:
jsr show_clock
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; MONITOR / KEYPAD
;;
;;
keypad_handler:
jsr get_key ; READs from PORTA which also re-sets VIA's Interrupt flag
sta INKEY ; put the ASCII value of input into RAM ( $00 )
lda PORTB_1 ; check for SHIFT/INSTRUCTION button
and #%10000000
beq check_keypress ; done this way to get around the limit in size of branch jumps....
jmp handle_new_char
check_keypress:
lda INKEY
; choose action of "SHIFTed" key-press
check_a:
cmp #'A'
; move up one memory address and display contents
bne check_b
jsr increment_address
jsr new_address
jmp exit_key_irq
check_b:
cmp #'B'
; move down one memory address and display contents
bne check_c
jsr decrement_address
jsr new_address
jmp exit_key_irq
check_c:
cmp #'C'
; return to MONITOR
bne check_d
rmb5 FLAGS
jsr lcd_clear
lda #splash
sta MESSAGE_POINTER + 1
jsr new_address
jmp exit_key_irq
check_d:
cmp #'D'
; move monitor to entered 4-digit memory address
bne check_e
lda BYTE
sta DUMP_POINTER
lda BYTE + 1
sta DUMP_POINTER + 1
jsr new_address
jsr print
jmp exit_key_irq
check_e:
cmp #'E'
; insert (POKE) byte of data in to current memory address, then increment to next address
bne check_f
lda BYTE
ldy #$00
sta (DUMP_POINTER),y
jsr increment_address
jsr new_address
jsr print
jmp exit_key_irq
check_f:
cmp #'F'
; show 8-byte wide block of memory
bne check_1
ldy #$00
lda BYTE
sta DUMP_POINTER
lda BYTE + 1
sta DUMP_POINTER + 1
jsr block_address
jmp exit_key_irq
check_1:
cmp #'1'
; show/auto-update clock
bne check_3
jsr lcd_clear
lda #emt
sta MESSAGE_POINTER + 1
jsr print
smb5 FLAGS
;jsr show_clock
jmp exit_key_irq
check_3:
cmp #'3'
bne check_6
ldy #$00
jsr increment_block
jsr block_address
jmp exit_key_irq
check_6:
cmp #'6'
bne check_9
ldy #$00
jsr decrement_block
jsr block_address
jmp exit_key_irq
check_9:
cmp #'9'
bne check_4
jsr show_block
jmp exit_key_irq
check_4:
cmp #'4'
bne check_5
lda BYTE
sta HEXB
lda BYTE + 1
sta HEXB + 1
jsr byte_to_hex
jmp exit_key_irq
check_5:
cmp #'5'
bne exit_key_irq
jsr $3F00
jmp exit_key_irq
handle_new_char:
lda ASCII + 2
sta ASCII + 3
lda ASCII + 1
sta ASCII + 2
lda ASCII
sta ASCII + 1
lda INKEY ; get the new ASCII keypress value and...
sta ASCII
jsr print_char ; and print it on LCD
jsr ascii_byte ; convert the rolling 4-byte ASCII character data into two binary bytes
exit_key_irq:
jsr scan ; re-enable keypad
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
nmi:
rti
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; interrupt is triggered by HIGH edge on VIA CA1 pin
;; PORTA low nibble (keypad columns) inputs are diode ORed to CA1
;;
irq:
; put registers on the stack while handling the IRQ
pha
phx
phy
; find responsible hardware
; Is it VIA_1?
lda IFR_1 ; if IFR_1 has Bit7 set (ie sign=NEGATIVE) then it IS the source of the interrupt
bpl next_device ; if it's not set (ie sign=POSITIVE) then branch to test the next possible device
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; IFR Flags
;; B7 B6 B5 B4 B3 B2 B1 B0
;; IRQ TI1 TI2 CB1 CB2 SR CA1 CA2
;;
;; Interrupt source is found by sequentially shifting IFR bit left to put bit-of-interest into the CARRY place
;; and then branching based on whether CARRY is SET or not
;;
;; Only add tests for IRQ sources in use, and adjust the ASLs in each test as necessary
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
test_timer1:
asl ; shift IFR left twice puts the TI1 bit into CARRY....
asl
bcc test_cb1 ; carry clear = next test
bit T1CL_1 ; clear not clear = handle the TIMER interrupt
jsr rtc
jmp exit_irq
test_cb1:
asl
asl
bcc test_cb2
bit PORTB_1
jsr cb1_handler
jmp exit_irq
test_cb2:
asl
bcc test_ca1
bit PORTB_1
jsr cb2_handler
jmp exit_irq
test_ca1:
asl ; shift CA1 bit into the CARRY bit & test
asl
bcc exit_irq ; carry clear = leave
jsr keypad_handler ; carry not clear = handle the CA1 interrupt (keypad)
jmp exit_irq
next_device:
exit_irq:
ply
plx
pla
rti
emt: .asciiz "hhh mm ss MET"
splash: .asciiz "cmon> "
error_message: .asciiz "Not Decimal"
mem_pass_msg: .asciiz "RAM Test Pass"
mem_fail_msg_1: .asciiz "RAM Test 1 Fail"
mem_fail_msg_2: .asciiz "RAM Test 2 Fail"
; Reset/IRQ vectors
.segment "VECTORS"
.word nmi
.word reset
.word irq
See [[public:computers:6502:ca65_setup|]] for the details of the additional ''includes'' files needed to assemble the code.
===== Notes =====
The idea of basic features came from
[[http://www.suppertime.co.uk/blogmywiki/2021/03/6502-breadboard-computer-part-7-working-monitor/]]
[[https://www.youtube.com/watch?v=TK2JIZREgYM]]
My version is similar, but different.
The Keypad scanning routine came from :
[[https://www.asinine.nz/2022-05-01/6502-part4/]]
I wanted to write a routine to poll the 16-key pad and needed some hints. I understood what was needed, but was uncertain about the best way to achieve the correct results, so I looked at various bits of code online. When I worked through this snippet I understood what it was doing, and could perhaps in future come up with a similar working solution myself. It's all a matter of studying, understanding and then using the new knowledge. In the meantime this snippet of code works well enough that I see no point in re-inventing the wheel, so I'm just using it verbatim.
To allow key presses to trigger interrupts I have to set the 4 row-outputs (PA4-PA7) high and then diode-OR the switch's column lines, feeding this OR'd signal in to CA1 on the 65c22. Pressing any key pulls CA1 high, triggering an interrupt, and the interrupt routine then calls the keypad reading function itself which then determines which key was pressed.
--- //John Pumford-Green 27/08/22 09:24//
===== Further Information =====
{{tag>6502 assembly}}