Cross Development Notes
Apple II 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