Skip navigation
Published Friday, July 4, 2014 at 1:23 PM

Our First Macro

Last night I got string output working. But what if I want to make string output generic? I want to write a STOUT (STring OUT) subroutine that can take the address of any null-terminated string and print it to the console, but there's a problem: The 6502 is an 8-bit machine, so passing a 16-bit address as an argument takes some wrangling.

To get around this, I've designated two locations in the precious Zero Page (arbitrarily choosing $20 and $21) to store the locations of the low byte and high byte of the string's address, respectively.

;;; ----------------------------------------------------------------------
;;; Memory Definitions
;;; ----------------------------------------------------------------------

;;; Zero Page
        STRLO   = $20           ; Low byte of STOUT STRING
        STRHI   = $21           ; Hi byte of STOUT STRING

Now I can write my STOUT subroutine. The change here is that I'm going to use the Indirect Indexed addressing mode to look up the characters of the string from the zero-page addresses, instead of the Absolute,X addressing that I used in my last post.

;;; ----------------------------------------------------------------------
;;; Print the null-terminated string located at STRLO,STRHI
;;; ----------------------------------------------------------------------

STOUT:  LDY     #$00            ; Initialize string pointer
@loop:  LDA     (STRLO),Y       ; Get character
        BEQ     @done           ; If char == 0, we're done
        JSR     COUT            ; Otherwise, print it
        INY                     ; Increment pointer
        BNE     @loop           ; Continue
@done:  RTS                     ; Return

But calling this subroutine is still a little weird. Each time I want to print a string, I need to put the low byte of the string's address in $20, the high byte in $21, and then call STOUT. It's just slightly unwieldy, so… enter our first assembler macro!

The ca65 assembler supports putting a series of instructions together into a macro call, sort of like an inline function in C, so that code can be re-used without the overhead of a subroutine call. I've created a macro named STR that takes a 16-bit address and prints the string to the console.

;;;
;;; STR <ADDR>
;;;
;;; Print out the null-terminated string located at address <ADDR>
;;;
;;; Modifies: Accumulator, STRLO, STRHI
;;;
.macro  STR     ADDR
        LDA     #<ADDR          ; Grab the low byte of the address
        STA     STRLO
        LDA     #>ADDR          ; ... and the high byte
        STA     STRHI
        JSR     STOUT           ; then call STOUT.
.endmacro

Great. Now all I have to do is call STR to print my "HELLO, 6502 WORLD!" string over and over again.

LOOP:   STR     HELLO
        JMP     LOOP

HELLO:  .byte   "HELLO, 6502 WORLD! ",0

With this slight diversion out of the way, next up will be console input, which I promised to talk about today. But that's a subject for another post.