Cross Development Notes

Much as I love developing for the Apple II, developing on the Apple II can be tedious. Fortunately, it is easy to do Apple II cross development on a modern operating system using 6502 cross-development tools tools like a65, cc65, the command-line version of AppleCommander, and your favorite emulator. The simple "Hello, world!" program in Listing 1 will serve as a starting point.

Listing 1: hello.txt.

   1         0300               org   $300
   2         FD8E      crout    equ   $FD8E
   3         FDED      cout     equ   $FDED
   4  0300   20 8E FD           jsr   crout
   5  0303   A2 00              ldx   #0
   6  0305   BD 16 03  print    lda   msg,x
   7  0308   F0 08              beq   prln
   8  030A   09 80              ora   #$80
   9  030C   20 ED FD           jsr   cout
  10  030F   E8                 inx
  11  0310   D0 F3              bne   print
  12  0312   20 8E FD  prln     jsr   crout
  13  0315   60                 rts
  14  0316   48 65 6C  msg      .byte "Hello, world!",$0
      0319   6C 6F 2C  
      031C   20 77 6F  
      031F   72 6C 64  
      0322   21 00     

$ ac -g hello ~/kegs/disks/Dev.2mg | hd
000000: 20 8e fd a2 00 bd 16 03 f0 08 09 80 20 ed fd e8   ........... ...
000010: d0 f3 20 8e fd 60 48 65 6c 6c 6f 2c 20 77 6f 72  .. ..`Hello, wor
000020: 6c 64 21 00                                      ld!.

Using a65.

The makefile rule in Listing 2 shows a series of commands suitable for assembling and copying the example to an Apple II disk image. The a65 command assembles the source hello.s, sending a listing to hello.txt and writing the object code to a text file named 65c02.out. Because the output is text, the java command invokes the MkObj program (below) to convert the file to binary; the subsequent hd command does a hex dump for quick visual inspection. The first ac command removes any previous version of the object file. The second one actually copies the binary to the target disk. The ac command itself is just a short script that invokes the command-line version of AppleCommander, passing along any command-line parameters (Listing 3). Finally, the disk_conf file is updated to tell this particular emulator (kegs-osx) to re-read the revised disk image.

Listing 2: Makefile.

hello: hello.s
  a65 -n -o hello.s > hello.txt
  java MkObj > hello
  hd hello
  ac -d ~/kegs/disks/Dev.2mg hello
  cat hello | ac -p ~/kegs/disks/Dev.2mg hello bin 2048
  touch ~/kegs/disk_conf


Listing 3: ac.

$ cat ~/bin/ac #/bin/sh java -jar ~/bin/ac.jar $@ 

Converting object file formats.

The default output of a65 is a text file named 65c02.out. It is suitable for use with the EXEC command under DOS or ProDOS. The java program in Listing 4 reads the file 65c02.out, parses the ASCII hex bytes and writes the equivalent binary to standard output, where is can be conveniently directed to a file.

Listing 4: MkObj.java.

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;

/*
 * MkObj: Convert 65c02.out to binary via System.out
 * copyright (C) 2006; distribution per GPL
 * @author John B. Matthews
 */

public class MkObj {

    public static void main(String[] args) {
        try {
            if (args.length == 0) {
                Convert("65c02.out");
            } else if (args.length == 1 && "-h".equalsIgnoreCase(args[0])) {
                help();
            } else if (args.length > 1) {
                help();
            } else {
                Convert(args[0]);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            help();
        }
    }

    private static void Convert(String inName) throws IOException {
        File inputFile = new File(inName);
        BufferedInputStream in = new BufferedInputStream(
            new FileInputStream(inputFile));
        int value;

        value = skip(in, (int) ':'); //skip to the first byte
        while ((value = getByte(in)) != -1) {
            if (value > -1) {
                System.out.write(value);
            }
        }

        in.close();
        System.out.flush();
    }

    private static int skip(InputStream in, int to) throws IOException {
        int c;
        do {
            c = in.read();
        } while (!(c == -1 || c == to));
        return c;
    }

    private static int getByte(InputStream in) throws IOException {
        StringBuffer s = new StringBuffer();
        int c = in.read();
        if (c == -1) {
            return -1;
        } else if ((c == 10) || (c == 13)) {
            c = skip(in, (int) ':');
            return -2;
        } else if (c == (int) ' ') {
            return -3;
        } else {
            s.append((char) c);
            s.append((char) in.read());
            return hexStringToInt(s.toString());
        }
    }

    private static int hexStringToInt(String s) {
        int i = 0;
        try {
            i = Integer.parseInt(s, 16);
        } catch (NumberFormatException e) {
        }
        return i;
    }

    private static void help() {
        System.err.println("MkObj: Convert a65 -o to binary on System.out");
        System.err.println("java MkObj [-h] [filename] [> destination]");
        System.err.println("if no filename, default to 65c02.out.");
    }
}

Adding macros.

While a65 is a fine assembler, it lacks macro capability. Fortunately, the GNU macro processor m4 (available on many platforms) can be used to solve the problem. Listing 5 is an m4 macro file named macro.m4. The command "m4 macro.m4 > macro.s" can be added to the makefile rule above. Invoking m4 expands the macros contained in that file to produce the source for the assembly program shown in Listing 6. With some care, the resulting source and listings can be made quite readable.

The macro pshlabel allows each invocation of a macro to have a unique series of globally accessible labels. The macro poplabel allows a macro to return to a previously generated label in the series. The macros stadr, incw, decw, pradr, prchr and prdec are typical 6502 macros; the code itself is mostly a series of macro invocations. Because m4 is quite general in its approach, it can be used in the back-end of a compiler or simply to write assembly language programs directly.

Listing 5: macros.m4.

divert(-1) dnl Don't include macros in output
changequote([,]) dnl rarely used characters for quotes
define([labelnum], 1) dnl initialize label counter
define([pshlabel], [ dnl create a new, unique label
  pushdef([$1], [$1]labelnum) define([labelnum], incr(labelnum))]) dnl
define([poplabel], [popdef([$1])]) dnl return to a previous level
define(stadr, [
;Store $1 in $2
 LDA #$1%256
 STA $2
 LDA #$1/256
 STA $2+1])
define(incw, [pshlabel([INCW])
;Increment word $1^
 INC $1
 BNE INCW
 INC $1+1
INCW:])
define(decw, [pshlabel([DECW])
;Decrement word $1^
 LDA $1
 BNE DECW
 DEC $1+1
DECW DEC $1])
define(pradr, [
;Print $1 as hex
 LDX $1
 LDA $1+1
 JSR PRNTAX])
define(prdec, [
;Print $1 as decimal
 LDX $1
 LDA $1+1
 JSR PRDEC])
define(prchr, [
;Print $1 as char
 LDA #'$1'
 ORA #%10000000
 JSR COUT])
divert(0)dnl
;Test macros
PTR EQU $6
PRDEC EQU $ED24
PRNTAX EQU $F941
CROUT EQU $FD8E
COUT EQU $FDED
 ORG $800
 stadr($FFFF, PTR)
 incw(PTR)
 decw(PTR)
 decw(PTR)
 pradr(PTR)
 prchr(=)
 prdec(PTR)
 JSR CROUT
 RTS

Listing 6: macros.txt.

   1                   ;Test macros
   2         0006      PTR      EQU   $6
   3         ED24      PRDEC    EQU   $ED24
   4         F941      PRNTAX   EQU   $F941
   5         FD8E      CROUT    EQU   $FD8E
   6         FDED      COUT     EQU   $FDED
   7         0800               ORG   $800
   8                            
   9                   ;Store $FFFF in PTR
  10  0800   A9 FF              LDA   #$FFFF%256
  11  0802   85 06              STA   PTR
  12  0804   A9 FF              LDA   #$FFFF/256
  13  0806   85 07              STA   PTR+1
  14                            
  15                   ;Increment word PTR^
  16  0808   E6 06              INC   PTR
  17  080A   D0 02              BNE   INCW1
  18  080C   E6 07              INC   PTR+1
  19         080E      INCW1:
  20                            
  21                   ;Decrement word PTR^
  22  080E   A5 06              LDA   PTR
  23  0810   D0 02              BNE   DECW2
  24  0812   C6 07              DEC   PTR+1
  25  0814   C6 06     DECW2    DEC   PTR
  26                            
  27                   ;Decrement word PTR^
  28  0816   A5 06              LDA   PTR
  29  0818   D0 02              BNE   DECW3
  30  081A   C6 07              DEC   PTR+1
  31  081C   C6 06     DECW3    DEC   PTR
  32                            
  33                   ;Print PTR as hex
  34  081E   A6 06              LDX   PTR
  35  0820   A5 07              LDA   PTR+1
  36  0822   20 41 F9           JSR   PRNTAX
  37                            
  38                   ;Print = as char
  39  0825   A9 3D              LDA   #'='
  40  0827   09 80              ORA   #%10000000
  41  0829   20 ED FD           JSR   COUT
  42                            
  43                   ;Print PTR as decimal
  44  082C   A6 06              LDX   PTR
  45  082E   A5 07              LDA   PTR+1
  46  0830   20 24 ED           JSR   PRDEC
  47  0833   20 8E FD           JSR   CROUT
  48  0836   60                 RTS

Using cc65.

Using cc65 is easier than ever with AppleCommander's -cc65 command-line option, as shown in Listing 7. Note that the starting address is not required. The second example shows how to link the lo-res library statically using the co65 utility.

Listing 7: cc65.

HELLO=hello
ROD=rod
...
${HELLO}: ${HELLO}.c
  cl65 -O -t apple2enh ${HELLO}.c
  ac -d p1.po ${HELLO}
  ac -cc65 p1.po ${HELLO} bin < ${HELLO}

${ROD}: ${ROD}.c
  co65 --code-label _a2e_lo_install a2e.lo.tgi
  ca65 a2e.lo.s
  cl65 -T -l -O -t apple2enh ${ROD}.c --obj a2e.lo.o
  ac -d p1.po ${ROD}
  ac -cc65 p1.po ${ROD} bin < ${ROD}

Using Aztec C.

Visit Bill Buckel's Apple Oldies at the Aztec C Museum.

Now you have several fewer excuses for not writing Apple II programs!

Copyright 2006, 2008 John B. Matthews

Distribution permitted under the terms of the GPL: http://www.gnu.org/copyleft/gpl.html.

Last updated 7-Jan-2010