Hex Mode
Drawing the hex mode display is fairly straightforward. I (now) use an 80 byte array as a framebuffer and a variable as an index into the array for accessing specific screen positions. I also defined some handy constants to make getting around the display a little more convenient.
Code: Select all
PM_FB_LINE1 = 0
PM_FB_LINE2 = 20
PM_FB_LINE3 = 40
PM_FB_LINE4 = 60
PM_frameBuffer = $0300 ; - $034F (80 bytes)
PM_fbPtr = $0350 ; index into framebufferCode: Select all
_PM_PutChar:
phy
ldy PM_fbPtr
sta PM_frameBuffer,y
iny
cpy #80
bne .nowrap
ldy #0
.nowrap:
sty PM_fbPtr
ply
rtsOne thing we will need to do a lot of in hex mode is take a byte and print its value as two hexadecimal digits. My hex output routine is one of the very first things I wrote for my LCD library. This is an example of some old code that I'm still using because it works. It uses a small lookup table to do the conversion. I've since learned more compact ways to do this, but I'll go ahead and share this old version.
Code: Select all
HEX_TABLE: .asciiz "0123456789ABCDEF"
_PM_PutHex:
pha
phy ; save registers
pha
ror
ror
ror
ror
and #%00001111 ; get high nibble
tay
lda HEX_TABLE,y
jsr _PM_PutChar
pla
and #%00001111 ; get low nibble
tay
lda HEX_TABLE,y
jsr _PM_PutChar
ply ; restore registers
pla
rts ; _PM_PutHexIt's time for a brief excursion into stacks. I recently started using a zero-page data stack like the one Garth describes in the stack treatise. I'm still only using it in basic ways, and I don't have cool macros like Garth's, but I do use it for two very useful things in PAGIMON: dynamically allocating ZP space to use for temporary variables, and passing addresses (two-byte parameters) to subroutines. Here's how it works:
Code: Select all
STACK = 0
STACKL = 0
STACKH = 1
; These constants are used with the zero page, indexed .X addressing mode to access data on the stack.
ldx #$FF
txs ; set up hardware (call stack)
dex ; .X = $FE
; In the reset routine when the hardware stack is set up, .X is set to $FE. Stack cells will always be two bytes in size,
; so STACKL,x (= 0,x) will refer to $00FE while STACKH,x (= 1,x) will refer to $00FF. When we store (push) something
; onto the stack it looks like this:
lda #<A_STRING ; Low byte of A_STRING's address
sta STACKL,x
lda #>A_STRING ; High byte of A_STRING's address
sta STACKH,x
dex
dex ; push ADDRESS - assuming this is our first push of the day, now .X = $FC
; .X is kept pointing to the next empty cell, so whenever anything is stored on the stack .X is decremented two times. To
; pull something back off the stack you just reverse the process:
inx
inx ; .X = $FE again
lda STACKL,x
sta POINTER
lda STACKH,x
sta POINTER+1 ; pull ADDRESS and store it in POINTER to A_STRING
; Because .X is always pointing at hitherto unclaimed memory, this system also gives you immediate and constant access to two
; ZP bytes if you need access temporary storage for a few instructions. If you need more space, you can always get more space:
dex ; get 2 more bytes of temporary ZP storage
dex ; STACKL,x; STACKH,x; STACKL+2,x;STACKH+2,x are now available for scratchpad use
; Just be sure to clean up when you're done.
inx
inx ; No memory leaks!
; Another cool thing about this is that you can use zero page, pre-indexed indirect addressing to transparently access absolute
; memory locations:
lda (STACK,x) ; Print first character of A_STRING
jsr _PM_PutChar
; Unfortunately, there is no (STACK,x),y addressing mode to get to the second character of A_STRING. But, as forum member
; Martin_H pointed out to me, you can increment the pointer directly on the stack:
inc STACKL,x
bne .skipHB
inc STACKH,x
.skipHB:
lda (STACK,x)
jsr _PM_PutChar ; Print next character of A_STRING
etc
; I put all of these features to use to construct PAGIMON's hex-mode output screen.Code: Select all
_PM_hexMode_display:
pha
jsr _PM_ClearFB ; Clear the framebuffer
lda PM_UI_addressL
sta STACKL,x
lda PM_UI_addressH
sta STACKH,x
dex
dex ; Push current address on data stack
lda #PM_FB_LINE1
jsr _PM_RAMline
lda #PM_FB_LINE2
jsr _PM_RAMline
lda #PM_FB_LINE3
jsr _PM_RAMline
lda #PM_FB_LINE4
jsr _PM_RAMline ; dump dump dump dump!
inx
inx ; Pull address back off stack
pla
rts ; _PM_hexMode_displayCode: Select all
_PM_ClearFB:
pha
phy
ldy #0
lda #' '
.loop:
sta PM_frameBuffer,y
iny
cpy #80
bne .loop
ply
pla
rtsCode: Select all
_PM_RAMline:
phy
sta PM_fbPtr
lda #'$'
jsr _PM_PutChar ; Print the base (hexadecimal) signifier character
; I'm going to use STACKL,x as temporary storage soon, so rather than inx,inx I will access the passed address like this:
lda STACKH+2,x
jsr _PM_PutHex ; Print the high order byte of the offset
lda STACKL+2,x
jsr _PM_PutHex ; Print the low order byte of the offset
lda #':'
jsr _PM_PutChar ; Print the separator character
lda #7 ; We have to jump over some spaces to print the ASCII interpretation of the byte. Use STACKL to keep track.
sta STACKL,x ; Each hex byte takes two spaces, so we need to jump one fewer space each time around the loop.
ldy #4 ; print 4 bytes
.loop:
lda (STACK+2,x) ; get the byte
jsr _PM_PutHex ; print it
lda PM_fbPtr
pha ; save framebuffer pointer
clc
adc STACKL,x ; add offset to ASCII interpretation
sta PM_fbPtr
lda (STACK+2,x)
jsr _PM_PutChar ; print ASCII interpretation
pla ; restore frame buffer pointer
sta PM_fbPtr
inc STACKL+2,x
bne .skipHB
inc STACKH+2,x ; increment the address pointer directly on the stack
.skipHB:
dec STACKL,x ; decrement the offset into the ASCII interpretation
dey
bne .loop
ply
rts ; _PM_RAMline
Code: Select all
_PM_refresh:
pha
phy
lda #0
sta LCD_cursor
jsr _LCD_set_cursor
ldy #0
.loop:
lda PM_frameBuffer,y
jsr _LCD_chrout
iny
cpy #80
bne .loop
ply
pla
rts ; _PM_refreshAt what point should _PM_refresh be called? I added it to the main loop:
Code: Select all
_PM_main:
php
lda #PM_REG_MODE
sta PM_UI_mode
.loop:
lda PM_UI_mode
cmp #PM_REG_MODE
beq .regmode
cmp #PM_HEX_MODE
beq .hexmode
; If PM_UI_mode gets corrupted somehow, fall through (default) to register mode
.regmode:
jsr _PM_regMode_display
jsr _PM_refresh
jsr _PM_regMode_update
bra .loop
.hexmode:
jsr _PM_hexMode_display
jsr _PM_refresh
jsr _PM_hexMode_update
bra .loop
Part 5 will be the register mode display. I've saved it until now, because I've been working on updating it to incorporate Ed's suggestions from earlier - break out the status register, and include the stack register. I have managed to do these things, but some of the methods seem pretty inelegant to me. I'm hoping the experts can show some ways to improve it!