John's Vademecum

Try to learn something about everything, and everything about something -Thomas Huxley “Darwin's bulldog” (1824-1895)

User Tools

Site Tools


public:computers:6502:monitor

6502 Project

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 UPShift+A UP
Shift+4 dec→hex Shift+5 GO Shift+6 BLOCK DOWNShift+B DOWN
7 8 Shift+9 auto-update Block view Shift+C CLS
Shift+E POKE 0 Shift+F BLOCKShift+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
  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
  lda #>mem_fail_msg_1
  sta MESSAGE_POINTER + 1
  jsr print
  jmp loop
 
mem_fail_2:
  lda #<mem_fail_msg_2
  sta MESSAGE_POINTER
  lda #>mem_fail_msg_2
  sta MESSAGE_POINTER + 1
  jsr print
  jmp loop
 
 
 
; go straight to MONITOR at startup
;  lda #<splash
;  sta MESSAGE_POINTER
;  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
  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
  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 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

public/computers/6502/monitor.txt · Last modified: 06/03/25 06:49 GMT by 127.0.0.1