6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Tue May 14, 2024 4:33 pm

All times are UTC




Post new topic Reply to topic  [ 34 posts ]  Go to page Previous  1, 2, 3  Next
Author Message
PostPosted: Sun Feb 12, 2023 9:09 pm 
Offline

Joined: Fri Mar 18, 2022 6:33 pm
Posts: 443
Part 4

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:
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 framebuffer
To illustrate how it works, I'll bring back the _PM_PutChar routine from part one. The caller passes the character to print in the .A register. This version of _PM_PutChar uses the absolute, indexed .Y addressing mode to store the character in the framebuffer at the location currently pointed to by fbPtr, then it increments fbPtr so that it points to the next location. There are only 80 characters in the buffer (characters number 0 through 79) so if, after incrementing, fbPtr is equal to 80 it is set to 0, wrapping back to the start of the display.
Code:
_PM_PutChar:
    phy
    ldy     PM_fbPtr
    sta     PM_frameBuffer,y
    iny
    cpy     #80
    bne     .nowrap
    ldy     #0
.nowrap:
    sty     PM_fbPtr
    ply
    rts
With _PM_PutChar we can randomly access any location on the screen, just by changing the value of PM_fbPtr and loading the character we want to output into the .A register.

One 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:
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_PutHex
To make this work with the framebuffer I only had to replace two calls to the LCD character output routine with calls to _PM_PutChar. (Also, while I was in there I updated the tya,pha/pla,tay register saving/restoring blocks to use CMOS instructions.)

It'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:
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.


Since, as shown in a previous post, the output format is the same for each line of the hex mode display, _PM_hexMode_display doesn't actually have very much to do. It just clears the framebuffer and calls the subroutine that prints a line.
Code:
_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_display
_PM_ClearFB is not particularly interesting, but I'll include it for the sake of completeness:
Code:
_PM_ClearFB:
    pha
    phy
    ldy     #0
    lda     #' '
.loop:
    sta     PM_frameBuffer,y
    iny
    cpy     #80
    bne     .loop
    ply
    pla
    rts
_PM_RAMline is where all the real work gets done. It expects the caller to send it the address to dump from on the data stack, and the character cell to start printing from (0, 20, 40, or 60) in the .A register. It increments the address directly on the stack as it prints. When it's finished it leaves the address on the stack, so you can call it over and over again and it will pick up where it left off.
Code:
_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
One thing we still don't have is a way to transfer the framebuffer to the LCD. That's what _PM_refresh does:
Code:
_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_refresh
_PM_refresh is the only other one of PAGIMON's routines aside from _PM_GetChar that needs to call my local routines. To make it work with a "stock" Ben Eater 6502 it would have to be modified, but not too much. I'm really pleased that using the framebuffer let me move all the connections with the "outside world" into two simple subroutines.

At what point should _PM_refresh be called? I added it to the main loop:
Code:
_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
Now the overall structure for both modes is: 1 - construct the display (in the framebuffer), 2 - send the framebuffer to the LCD, 3 - dispatch user input.

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!

_________________
"The key is not to let the hardware sense any fear." - Radical Brad


Top
 Profile  
Reply with quote  
PostPosted: Mon Feb 13, 2023 11:18 am 
Offline

Joined: Sat Oct 09, 2021 11:21 am
Posts: 704
Location: Texas
Paganini wrote:
Ken Skier wrote:
Since the video display need not be refreshed (redisplayed within a given time) by the processor, the UPDATE routine need not return within a given amount of time. The UPDATE routine, therefore, can wait indefinitely for a new character from the keyboard, and then take appropriate action.
I disagree with this. If we just circle waiting for a keypress without updating the display we won't see any changes that may have taken place since the last keypress - if we're examining a VIA input or output port, for example, we'll miss any activity. So PAGIMON will always execute the main loop and refresh the display, even if no key has been pressed.


Hm! That is a neat feature, to update the display even without asking for it. And yes, useful for the VIA in particular. When having a LOT more displayed at once, say on the TV typewriter or my own board, it might not be as practical. But since you have 1-16 bytes of info to update, well, that makes sense.

Question: Do you have pictures of this working on the LCD yet? Have you been using a simulator/emulator to test this code?

Good updates here, glad you are working well on it!

Chad


Top
 Profile  
Reply with quote  
PostPosted: Mon Feb 13, 2023 4:23 pm 
Offline

Joined: Fri Mar 18, 2022 6:33 pm
Posts: 443
Paganini wrote:
Question: Do you have pictures of this working on the LCD yet? Have you been using a simulator/emulator to test this code?


I don't have any pictures. I was thinking maybe a video to show it in action? I'd have to take it with my phone, though. I haven't been using a simulator. When I'm ready to test I just burn an eeprom and give it a spin. :)

_________________
"The key is not to let the hardware sense any fear." - Radical Brad


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 17, 2023 5:43 pm 
Offline

Joined: Fri Mar 18, 2022 6:33 pm
Posts: 443
Part 5

Register mode display

The original VISIMON only had 3 output lines. First, a constant string to label the displayed registers. Second, the variable output field line displaying the currently selected address, its contents as a two-digit hex byte, the same contents as a character, and the four registers (.A, .X, .Y and .P). Third, and last, the "arrow" line that prints an arrow pointing to the currently selected field. It simply calls a separate subroutine to display each line.

PAGIMON has more output fields, more labels, and is arranged differently in its smaller display space, but it uses essentially the same strategy to draw the UI:
Code:
_PM_regMode_display:
    jsr     _PM_clearFB
    jsr     _PM_line1
    jsr     _PM_line2
    jsr     _PM_line3
    jsr     _PM_line4
    jsr     _PM_status
    jsr     _PM_placePrompt
    rts     ; _PM_regMode_display
PAGIMON's line 1 contains the address, byte, and character fields, along with a byte display of the .P register. It also displays a static string label "NV-B" for 4 of the processor status bits that will be broken out on line 2.
Code:
_PM_line1:
    lda     #PM_FB_LINE1+1  ; Go to 2nd cell of line 1
    sta     PM_fbPtr
    lda     PM_UI_addressH
    jsr     _PM_PutHex
    lda     PM_UI_addressL
    jsr     _PM_PutHex      ; Display current address, high byte first
    inc     PM_fbPtr
    inc     PM_fbPtr        ; move forward 2 spaces
    jsr     _PM_getAdByte
    jsr     _PM_PutHex      ; get value of currently selected byte and display it
    inc     _PM_fbPtr
    inc     _PM_fbPtr       ; move forward 2 spaces
    jsr     _PM_PutChar     ; display the same byte as a character
    inc     PM_fbPtr        ; move forward one space
    lda     PM_rP
    jsr     _PM_PutHex      ; display the status register byte field
    inc     PM_fbPtr        ; move forward one space
    lda     #<PM_status
    sta     STACKL,x
    lda     #>PM_status
    sta     STACKH,x        ; transfer the address of the status register label string to the data stack
.loop:
    lda     (STACK,x)
    beq     .done           ; if NULL, we've reached the end of the string
    jsr     _PM_PutChar     ; string printing routine - I didn't make a subroutine for it, but I probably should
    inc     STACKL,x        ; go to next character
    bne     .skipHB
    inc     STACKH,x        ; 16-bit inc
.skipHB:
    bra     .loop
.done:
    rts     ; _PM_line1
PM_status:  .asciiz     "NV-B"
Line 2 will have 4 bits of the processor status register broken out, but I will take care of that in a separate subroutine. Eventually I'd like to display the program counter on line 2, for single-step mode. For now, _PM_line2 is just a stub that does nothing.
Code:
_PM_line2:
    rts     ; _PM_line2
_PM_line3 holds the static string label for the rest of the fields - the registers .A .X .Y .S and the DIZC label for the rest of the status bits.
Code:
_PM_line3:
    lda     #PM_FB_LINE3
    sta     PM_fbPtr        ; go to the start of line 3
    lda     #<PM_label
    sta     STACKL,x
    lda     #>PM_label
    sta     STACKH,x        ; use data stack as temporary variable
.loop:
    lda     (STACK,x)
    beq     .done
    jsr     _PM_PutChar
    inc     STACKL,x
    bne     .skipHB
    inc     STACKH,x
.skipHB:
    bra     .loop           ; this is the same string printing code as before
.done:
    rts     ; _PM_line3
PM_label: .asciiz " .A  .X  .Y  .S DIZC"
_PM_line4 prints the register images. The rest of the status bits will also be here, but they will be printed in a separate subroutine.
Code:
_PM_line4:
    lda     #PM_FB_LINE4+1  ; 2nd location on line 4
    sta     PM_fbPtr
    ldy     #1              ; PM_registers,0 is the status register, so we start with .Y = 1
.print_regs:
    lda     PM_registers,y
    jsr     _PM_PutHex
    inc     PM_fbPtr
    inc     PM_fbPtr        ; move forward 2 spaces
    iny
    cpy     #5              ; loop 4 times
    bne     .print_regs     ; print register images .A, .X, .Y, .S
    rts     ; _PM_line4
_PM_status breaks out the status bits from the status register. This one was tricky for me; I don't know if what I came up with is the best way. Suggestions welcomed!
Code:
_PM_status:
    lda     PM_rP
    ldy     #0
.loop:
    asl
    bcc     .zero
; bcs .one
; .one:
    pha
    phy
    lda     PM_statusPos,y
    tay
    lda     #'1'
    sta     PM_frameBuffer,y
    ply
    pla
    iny
    cpy    #8
    bne     .loop
    bra     .done
.zero:
    pha
    phy
    lda     PM_statusPos,y
    tay
    lda     #'0'
    sta     PM_frameBuffer,y
    ply
    pla
    iny
    cpy     #8
    bne     .loop
.done:
    rts     ; _PM_status
PM_statusPos:  .byte   36,37,38,39,76,77,78,79
My strategy is to loop 8 times; .Y = 0 - 7. Each time through the loop I shift one status bit into the carry. I then branch to the appropriate code to print a '1' or a '0.' I use the value of the loop counter as an index into a little byte array that contains the screen positions for the various bits (4 on line 2, and 4 on line 4).

The last thing that needs doing is displaying the prompt for the currently selected field. I used a similar strategy to the status bits - a little byte array with all the prompt positions that is indexed by the current field.
Code:
_PM_placePrompt:
    ldy     PM_UI_field     ; check current field
    sec                     ; if it is out of bounds set it to default field (address field)
    cpy     #PM_NUM_FIELDS
    bcc     .fieldOK
    ldy     #PM_FIELD_ADDRESS
    sty     PM_UI_field
.fieldOK:
    lda     PM_fields,y
    sta     PM_fbPtr
    lda     #':'        ; print prompt character
    jsr     _PM_PutChar
    rts     ; _PM_placePrompt
PM_fields: .byte 0,6,10,15,60,64,68,72
There wasn't room to display a '.P' for the status register byte, so my solution was to display the prompt to the *right* of the status register byte, next to the label for the broken out bits. Hopefully this will make their association clear enough!

_PM_getAdByte (called by _PM_line1) gets the value stored at the currently selected address. It uses the top of the data stack as a temporary variable, because we need to use a ZP addressing mode.
Code:
_PM_getAdByte:
    lda     PM_UI_addressL
    sta     STACKL,x
    lda     PM_UI_addressH
    sta     STACKH,x
    lda     (STACK,x)
    rts     ; _PM_getAdByte
To finish up, I need to mention a few data items.
Code:
; PAGIMON uses this structure to store the register images
; _PM_line4 uses a loop counter to index into this structure to print the register image values
PM_registers:   = $0351
PM_rP           = $0351     ; Status register image
PM_rA           = $0352     ; .A image
PM_rX           = $0353     ; .X image
PM_rY           = $0354     ; .Y image
PM_rS           = $0355     ; Stack pointer image

; This variable tracks the currently selected field (0..7)
PM_UI_field     = $0358

; These constants make working with fields easier
; Not all of them are used at the moment
PM_NUM_FIELDS       = 8
PM_FIELD_ADDRESS    = 0
PM_FIELD_BYTE       = 1
PM_FIELD_CHAR       = 2
PM_FIELD_rP         = 3
PM_FIELD_rA         = 4
PM_FIELD_rX         = 5
PM_FIELD_rY         = 6
PM_FIELD_rS         = 7

Part 6 will cover the most complicated part of PAGIMON: the register mode dispatcher. For now, here are a couple of pictures of the output modes in operation:
Attachment:
20230217_123832.jpg
20230217_123832.jpg [ 3.73 MiB | Viewed 3708 times ]
Attachment:
20230217_123853.jpg
20230217_123853.jpg [ 3.79 MiB | Viewed 3708 times ]

_________________
"The key is not to let the hardware sense any fear." - Radical Brad


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 17, 2023 6:00 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10800
Location: England
I think you can fit a x y s and also p on lines 3 and 4 if your field widths are 3 instead of 4


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 17, 2023 6:48 pm 
Offline

Joined: Fri Mar 18, 2022 6:33 pm
Posts: 443
I could, but I was reluctant to have the prompt character displayed with no space on either side of it. It seemed like it would make it less clear which field it was indicating. Here's a mockup, what do you think?
Attachment:
20230217_134604.jpg
20230217_134604.jpg [ 3.64 MiB | Viewed 3701 times ]
Maybe if I changed the prompt character from ':' to '>' it would work?

_________________
"The key is not to let the hardware sense any fear." - Radical Brad


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 17, 2023 7:00 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10800
Location: England
I see what you mean. I think > should work. Maybe even also a < on the other side?


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 17, 2023 7:03 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10800
Location: England
BTW this might or might not appeal, but rendering NV-BDIZC in mixed case to indicate 1 or 0 means it only takes 8 characters instead of 16. I think it's what we do in visual6502.
PC:0000 A:aa X:00 Y:00 SP:fd nv‑BdIZc


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 17, 2023 9:00 pm 
Offline

Joined: Mon Jan 19, 2004 12:49 pm
Posts: 684
Location: Potsdam, DE
Personally, I find the mixed case status confusing; instead I simply use (for the example above) ---B-IZ- so that only set flags are shown. YMMV of course.

Does the LCD chipset offer an underline option? I have a vague memory that some do, but it's been a long time... alternatively, could you flash the cursor (requiring multiple writes of course)?

Neil


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 17, 2023 10:14 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8433
Location: Southern California
barnacle wrote:
Personally, I find the mixed case status confusing; instead I simply use (for the example above) ---B-IZ- so that only set flags are shown. YMMV of course.

Does the LCD chipset offer an underline option? I have a vague memory that some do, but it's been a long time... alternatively, could you flash the cursor (requiring multiple writes of course)?

$5F is the underline.  But again, replying to both comments above, you can have up to eight custom characters in the LCD at once.  These will take the "ASCII" (not really ASCII of course, but in the table) positions 00 through 07.  This means you could also make tiny custom capital letters for cleared bits and use the regular capitals for set bits, or add some sort or other mark in the character box to indicate the set or clear status.  I made the following custom characters to display battery status on a project eight years ago:
Attachment:
DispBattStat5.jpg
DispBattStat5.jpg [ 17.36 KiB | Viewed 3680 times ]
(This was just to show them all at once.  In the final application, only one battery-level character would show, way over at the right end, and only when the space was available.)

For another project in the late 1980's, I had dozens of custom characters, which is not a problem as long as you don't need more than eight different ones in the display at once.  So when a custom character was needed, the LCD routine would examine a cache variable array in RAM to see if the character was recently defined in the LCD's character-generator RAM.  If it found it, it would use that number (in the range of 0 to 7); otherwise it would replace the least-recently-used custom character number with the new one.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Sat Feb 18, 2023 2:10 pm 
Offline

Joined: Sat Oct 09, 2021 11:21 am
Posts: 704
Location: Texas
Cool pictures! I do like the "display 16 bytes" window a lot, but I see your point to have a type of 'user interface' section as well.

Your board's setup looks so organized :) Honestly.

Ever think of doing the PS/2 Keyboard decoding in software? Would save a ton of pins and 3 IC's. Sorry, off topic, was just wondering.

Thanks for the pictures!

Chad


Top
 Profile  
Reply with quote  
PostPosted: Sat Feb 18, 2023 4:06 pm 
Offline

Joined: Fri Mar 18, 2022 6:33 pm
Posts: 443
sburrow wrote:
Ever think of doing the PS/2 Keyboard decoding in software? Would save a ton of pins and 3 IC's. Sorry, off topic, was just wondering.


I've thought of it in the sense that I know it's possible. :) I haven't tried actually doing it! The little blue keyboard card is pretty well Ben Eater's PS/2 circuit. It would be nice to be unshackled from it, but I anticipate that that's a project I won't get around to tackling for quite a while, since the little card works.

_________________
"The key is not to let the hardware sense any fear." - Radical Brad


Top
 Profile  
Reply with quote  
PostPosted: Tue Feb 28, 2023 4:31 pm 
Offline

Joined: Fri Mar 18, 2022 6:33 pm
Posts: 443
Part 6

Register mode dispatcher

Well, it's taken me a bit longer to get back to this project than I'd planned. But the delay was for a good reason (extra work!) and not an unpleasant one.

_PM_regMode_update is the most like the original VISIMON. Honestly, it's a little convoluted. One thing I really like about it is the "roll-in" method of input. It's much simpler than the command-string parser I was trying to write for the old PAGMON.
Code:
_PM_regMode_update:
    jsr     _PM_GetChar     ; Get the next keycode
    cmp     #NULL           ; If it's a NULL we don't need to do all the dispatching...
    bne     .case1
    rts                     ; ...but we do need to go refresh the display

.case1:
    cmp     #RIGHT_ARROW    ; Right arrow?
    bne     .case2
    inc     PM_UI_field     ; select next field
    lda     PM_UI_field     ; if cursor was at last field...
    cmp     #PM_NUM_FIELDS
    bne     .exit1
    lda     #0              ; ...wrap it to first field
    sta     PM_UI_field
.exit1:
    rts     ; _PM_regMode_update

.case2:
    cmp     #LEFT_ARROW     ; Left arrow?
    bne     .case3
    dec     PM_UI_field     ; Select previous field
    bpl     .exit2
    lda     #PM_NUM_FIELDS-1
    sta     PM_UI_field     ; If cursor was at first field wrap it to last field
.exit2:
    rts     ; _PM_regMode_update

.case3:
    cmp     #DOWN_ARROW     ; Down arrow?
    bne     .case4
    inc     PM_UI_addressL  ; Go forward one address
    bne     .exit3
    inc     PM_UI_addressH  ; 16-bit inc
.exit3:
    rts     ; _PM_regMode_update

.case4:
    cmp     #UP_ARROW       ; Up arrow?
    bne     .case5
    lda     PM_UI_addressL  ; Go back one address
    bne     .next4
    dec     PM_UI_addressH
.next4:
    dec     PM_UI_addressL  ; 16-bit dec
    rts     ; _PM_regMode_update

.case5:
    ldy     PM_UI_field
    cpy     #PM_FIELD_CHAR  ; Is character field selected?
    bne     .case6
    jsr     _PM_putAdByte   ; Write byte in .A to selected address
    rts     ; _PM_regMode_update

.case6:
    cmp     #'G'            ; Is it 'G' for GO?
    bne     .case7
    phx                     ; save data stack pointer
    ldx     PM_rS
    txs                     ; set up call stack from image
    lda     PM_rP
    pha                     ; put status image on (new) call stack

    ldy     PM_rY
    ldx     PM_rX
    lda     PM_rA           ; Load registers from images
    plp                     ; load the status image from the call stack

    jsr     _PM_exec        ; jump to the code

    php                     ; Save status, registers to images
    sta     PM_rA
    stx     PM_rX
    sty     PM_rY
    pla
    sta     PM_rP
    tsx
    stx     PM_rS

    plx                     ; Restore data stack pointer
    rts     ; _PM_regMode_update

.case7:
    pha                     ; Save the character
    jsr     _charToDigit    ; Convert it to binary
    cmp     #FF             ; I added this cmp. How come a simple 'bmi' doesn't work here? (Better check _char_To_Digit)
    beq     .case8          ; If it's not a hex digit go to the next case
    sta     STACK,x         ; Save the binary digit (TOS is scratchpad)
    pla                     ; Clean up the call stack
    lda     STACK,x         ; Binary digit -> .A again
    ldy     PM_UI_field     ; Now we have to decide what to do with it
    bne     .notaddr        ; Address field is not selected - skip ahead
; Address field is selected
    ldy     #3              ; .Y is index, loop 4 times
.loop7:                     ; Roll the hex digit in .A into the address field
    clc
    asl     PM_UI_addressL
    rol     PM_UI_addressH
    dey
    bpl     .loop7
    lda     STACK,x
    ora     PM_UI_addressL
    sta     PM_UI_addressL
    rts     ; _PM_regMode_update

.notadr:
    cpy     #PM_FIELD_BYTE  ; Is the byte field selected?
    bne     .regfld         ; If not, a register field must be selected
    and     #$0F            ; Roll the 4 LSB in .A into the currently selected address
    pha                     ; Save the new LSB for later
    jsr     _PM_getAdByte   ; Get currently selected byte
    asl
    asl
    asl
    asl                     ; Shift it left 4 times
    and     #$F0
    sta     STACK,x         ; Use TOS as temp again
    pla                     ; Later is now!
    ora     STACK,x         ; OR the new LSB with the now shifted contents of the current address
    jsr     _PM_putAdByte   ; Store the result
    rts     ; _PM_regMode_update

.regfld:                    ; At this point, .Y is 3 - 7 (one of the registers)
    dey
    dey
    dey                     ; Decrement .Y 3 times so we can use it as an index into the register structure
    phx                     ; Borrow .X from data stack so we can use it as an index into the register structure

    phy
    plx                     ; tyx - this little judo move is necessary because asl doesn't work with abs,y addressing

    ldy     #3              ; Roll hex digit from .A into selected register field
.loop8:
    clc
    asl     PM_registers,x
    dey
    bpl     .loop8          ; Is this really better than just doing asl PM_registers,x 4 times?
    ora     PM_registers,x
    sta     PM_registers,x
    plx                     ; Give .X back to the data stack
    rts     ; _PM_regMode_update

.case8:
    pla                     ; Restore the character we saved way back at the beginning of case 7
    cmp     #'Q'            ; Is it 'Q' for QUIT?
    bne     .case9
    pla
    pla                     ; Discard the return address for _PM_update
    plp                     ; This is the status register we saved waaay back at the beginning of _PM_main
    rts    ; _PM_main       ; Return to the caller of PAGIMON

.case9:
    cmp     #'H'            ; Is it 'H' for HEXDUMP?
    bne     .case10
    lda     #PM_HEX_MODE
    sta     PM_UI_mode
    rts     ; _PM_regMode_update

.case10:                     ; Just a stub. Add more cases here as necessary
    rts     ; _PM_regMode_update

; 'jmp'ing to the code from a subroutine lets the code 'rts' to PAGIMON
_PM_exec:
    jmp     (PM_UI_address)

; Sadly, we can't just sta     (PM_UI_address). We need a zero-page addressing mode
_PM_putAdbyte:
    pha
    lda     PM_UI_addressL
    sta     STACKL,x
    lda     PM_UI_addressH
    sta     STACKH,x
    pla
    sta     (STACK,x)
    rts     ; _PM_putAdByte

; This is old code from my LCD library. It searches the lookup table for a match. There must be a more efficient way to do it,
; but since this works I haven't bothered to replace it yet.
_PM_charToDigit:
    phy
    ldy     #$00
.next_upper:
    cmp     HEX_TABLE,y
    beq     .found
    iny
    cpy     #$10
    beq    .try_lower
    bra     .next_upper

.try_lower:
    ldy     #$0A
.next_lower:
    cmp     hex_table,y
    beq     .found
    cpy     #$10
    beq     .not_digit
    bra     .next_lower

.not_digit:
    lda     #$FF

    ply     ; aha this must be why I had to add that 'cmp.' If I switch plces with lda above, it should work.
    rts     ; _charToDigit

.found:
    tya
    ply
    rts     ; _charToDigit

_________________
"The key is not to let the hardware sense any fear." - Radical Brad


Top
 Profile  
Reply with quote  
PostPosted: Tue Feb 28, 2023 4:56 pm 
Offline

Joined: Fri Mar 18, 2022 6:33 pm
Posts: 443
Part 7

Housekeeping

That mostly concludes PAGIMON. However, we need to revisit _PM_main. VISIMON does no initialization, so when first started all of its fields contain garbage. I would like PAGIMON to be callable, so that, say, a BRK handler could use it for single stepping.
Code:
_PM_main:
    php                     ; Save caller's status flags
    php                     ; Save them again for the display
    sta     PM_rA           ; Initialize register display to sane values
    stx     PM_rX           ; .X is particularly important if we want to be
    sty     PM_rY           ; able to call routines from PAGIMON without trashing the data stack
    pla
    sta     PM_rP           ; Save status flags to image

    jsr     _PM_rS_init     ; Save stack in image

    lda     #PM_REG_MODE
    sta     PM_UI_mode      ; Default to register display 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

_PM_rS_init:
    phx                     ; This is a bit of a hack. We need the displayed call stack pointer
    tsx                     ; to be from within a subroutine, or we'll return to the
    stx     PM_rS           ; wrong place after a 'G'O. The user can still mess with the stack
    plx                     ; register if they want.
    rts     ; _PM_rS_init
So, PAGIMON is operational. Let's call it version 0.1. :D I would like to rejigger the display to reflect some of Ed's suggestions (using smaller field widths, changing the prompt character). I'd also like to add a "disassembly" view mode, and a "single step" mode. I'll keep adding to this thread whenever I make an update.

_________________
"The key is not to let the hardware sense any fear." - Radical Brad


Top
 Profile  
Reply with quote  
PostPosted: Thu Jun 08, 2023 3:30 pm 
Offline

Joined: Thu Dec 26, 2002 12:29 pm
Posts: 51
Location: Occitanie, France
Hi,
Thanks for the write-up - have you made any further improvements ?
You asked about software implementation of the PS2 keyboard interface - I have an example (way back from 2003 !) implemented on a PIC micro-controller. The technique is fairly simple but is done with lots of wait loops to detect transitions on CLK or DAT lines.
If you are interested, I can give you my code, and a MicroChip application note from that era that describes some of the principles. I also have other documents in paper format (I don't think I have the original files anymore) with other details and ideas. It should be quite simple to implement an efficient version with just two I/O pins - as long as one of the pins can generate an interrupt. Let me know.

For your monitor, I'm very interested in what you've done - can you share your code in a downloadable format ?

Thanks, in advance,
Glenn


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 34 posts ]  Go to page Previous  1, 2, 3  Next

All times are UTC


Who is online

Users browsing this forum: Yahoo [Bot] and 14 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: