• Commodore Free Magazine, Issue 82 - Part 15

    From Stephen Walsh@39:901/280 to All on Fri Aug 15 17:15:55 2014

    ;to SET cursor
    JSR PLOT ;aka $FFF0
    LDY #3 ;first real text char
    ;is at string posn 2
    ;(INY:INY does same)
    PRIM2 =* ;top of the loop
    LDA (PTRSTR),Y ;get char from string
    BEQ PRIM3 ;exit when 0 reached
    JSR CHROUT ;print the char in .A
    INY ;increment index/len
    BNE PRIM2 ;branch to loop top
    ;(256 chars max!)

    So, you see that with just a bit of additional code we made ourselves a
    very nice string printing routine! The tricky part (not too bad, though)
    is possessing the knowledge that the Kernal PLOT function has mixed-up
    cursor coordinates with respect to registers X and Y, but we will assume
    that as good assembly language programmers you took the time to look up the function before using it, right?

    CLEANUP TIME

    The final thing we have to do is get the program counter "fixed up" so that
    we can return to the main program. Once again we will make a few changes
    to the C-128 method in order to save some space and time. If you choose
    not to add the PlotString mod you won't need to get the stack pointer with
    TSX as we did not modify .X anywhere in the original PRIMM-only function.

    We will take our length counter (in .Y), add it to the pointer located at $BC-$BD, and place it back on the stack where the original return address
    is located. This has the net effect of adding the number of string
    characters to the program counter - skipping the string (and the optional screen coordinates) altogether. And finally, make absolutely certain you
    pull the registers from the stack in the REVERSE order. Failure to do this could result in a hard-to-find bug later. Also, since this function
    modifies the stack to get at the program counter so you won't be able to
    JMP to it. Instead, remember to always JSR.

    C-128 Version

    PRIM3 =* ;all done printing
    TYA ;put the char count in .A
    TSX ;get the stack pointer
    INX ;increment the index
    INX ;four times to account
    INX ;for pushed A/X/Y
    INX ;and 6502 stack return
    ;address peculiarity
    CLC
    ADC $BC ;add count to string
    ;location (-1)
    STA $0100,X ;and store it where
    ;return address is
    LDA #$00 ;LDA #$00 accounts for
    ;word-sized addr
    ADC $BD ;do the hi byte
    INX
    STA $0100,X ;and store it
    PLA ;pull the registers
    TAX ;in REVERSE order
    PLA
    TAY
    PLA
    RTS ;return to main program

    Our Version

    PRIM3 =* ;all done printing
    TYA ;put char count in .A
    TSX ;only necessary if
    ;PLOT mod is used
    CLC
    ADC PTRSTR ;add count to string
    ;location (-1)
    STA RETADDR,X ;and store it where
    ;return address is
    LDA #$00 ;LDA #$00 accounts
    ;for word-sized addr
    ADC PTRSTR+1 ;do the hi byte
    STA RETADDR+1,X ;and store it
    PLA ;all done, so restore
    TAY ;the modified regs
    PLA ;in the REVERSE order
    TAX
    PLA
    RTS ;cpu will add a byte
    ;with RTS

    And that's that.

    BONUS: 65816 VERSION

    Look below to see how we might do the same thing on the SuperCPU. Notice
    how much shorter and less complicated the function becomes. I have
    included a few directives and instructions to pre-set the environment for native, 16-bit mode, although later we will make some register size
    adjustments in order to accommodate 8-bit Kernal requirements. Although
    not included in this sample, it is very easy to extend this function to
    accept strings of any length up to 65536 bytes.

    .65816
    .al (longa)
    .xl (longi)
    ptrstr = $bc
    PLOT = $fff0
    CHROUT = $ffd2
    ;-----
    ;SETUP ;this can be done earlier
    ;-----
    clc
    xce ;65816 native mode
    rep #%00110000 ;16-bit A/XY
    jsr PlotString
    .byte X,Y ;replace X (0-39),
    ;Y (0-24) with the
    ;actual screen
    ;coordinates you need
    .text "Hello World"
    .byte 0
    rts
    PlotString =*
    pha ;save .A on the stack
    phx ;save .X on the stack
    phy ;save .Y on the stack
    lda 1,s ;get return address
    ;(stack relative
    ;addressing)
    sta ptrstr ;store into DP
    ;(Direct Page) ptr
    sep #%00010000 ;go to 8-bit XY
    lda (ptrstr) ;get x/y-coordinates
    ;both at once (DP
    ;Indirect addressing)
    tay ;Kernal PLOT requires
    ;x-coordinate in .Y
    xba ;swap A/B registers
    tax ;Kernal PLOT requires
    ;y-coordinate in .X
    clc ;Kernal PLOT requires
    ;carry=0 to SET crsr
    jsr PLOT ;aka $FFF0
    sep #%00100000 ;go to 8-bit A
    ldy #2 ;string length count
    ;first real text char
    ;is at string posn 2
    prim2 =* ;top of the loop
    lda (ptrstr),y ;get char from string
    beq prim3 ;exit when 0 reached
    jsr CHROUT ;print the char in .A
    iny ;increment index/len
    bne prim2 ;branch to loop top
    ;(256 chars max!)
    prim3 =* ;all doneprinting
    rep #%00110000 ;go to 16-bit A/XY
    tya ;copy string len to A
    clc
    adc 1,s ;add return address
    ;stored on stack
    ;(stack relative
    ;addressing)
    sta 1,s ;store new, adjusted
    ;return addr on stack
    ;(stack relative
    ;addressing)
    ply ;all done, so
    plx ;restore A/X/Y
    pla ;in the REVERSE order
    rts ;back to main program
    ;cpu will add a byte
    ;to the return addr
    ;with rts

    IN CONCLUSION

    While thinking of ways to present today's topic I consulted several
    different sources to see how others have approached this in the past,
    primarily because my own projects have managed (over time) to take a very straight-forward function such as PRIMM and turn it into something very different from the code you see here, and it has all been based on my own
    needs in different situations. You will find the same will happen to you
    as you find new ways to expand and enhance your own efforts. These days I
    work almost exclusively on the SuperCPU, and with that comes liberties (primarily in the form of a greatly enhanced instruction set) which tend to spoil the assembly language programmer. So something like this became
    somewhat of a challenge as I had to think only in terms of 8-bit 6502. I
    was amazed to see the number of ways this function has been written - some good, some not as good. In the end I had to dig deep into the bowels of an
    old hard drive to find an untarnished source copy from a long time ago, one which I wasn't sure would even work as written! I had to type the whole
    thing into CBM prg Studio to make sure it would do what it is supposed to
    do. To my surprise, the only register you can push onto the stack is the Accumulator! Well, okay, it wasn't quite that bad, but it was sort of fun
    to work in 8-bits again.

    I hope you can make use of this technique - it's a fast (and efficient) way
    to get a screen up and running. While nothing earth-shaking, PRIMM does demonstrate how to access the stack and manipulate the program counter.
    Also, I included the bonus 65816 code for those who are interested in programming the SuperCPU. The VICE SuperCPU emulator is impressive and a
    lot of fun - give it a try! Arthur Jordison's CBM prg Studio now includes
    a 65816 assembler which makes writing programs for the SuperCPU a breeze.
    We have an entire section devoted to SuperCPU coding over at
    www.Melon64.com - and all are welcome. The admin allows uploads, so you
    are free to exhibit your code!

    See you next month...

    Please send errors, omissions, or suggestions to bert@winc64.com or on
    Lemon64, username satpro, or at www.melon64.com, username satpro.


    =====================================



    --- MBSE BBS v1.0.01 (GNU/Linux-i386)
    * Origin: Dragon's Lair ---:- bbs.vk3heg.net -:--- (39:901/280)