Hacker's Edge Game Guide: Programming/Example program: keyboard.s

Example program: keyboard.s
Nov. 15, 2017

This is an example program to highlight some recent updates, you can use this example code on a machine without an OS or kernel, such as the 6502 Monitor. Below the example is an example Makefile to show how you would assemble this program:

.import __IOBASE__

MONITOR = $f000

COUT = __IOBASE__+$d0
CIN = __IOBASE__+$e0
MIRQ = __IOBASE__+$dd
MBUT = __IOBASE__+$d9
MCOL = __IOBASE__+$da
MROW = __IOBASE__+$db
COL = __IOBASE__+$d7
ROW = __IOBASE__+$d6

.macro ptraxy
  sta ptr
  stx ptr+1
  LDY #0


    .byte "Keyboard test program!", $a,$0

    .byte "Masked input: ", $0

    .byte "Char input test!",$a
    .byte "Do you like this [Y/N]?",$0

    .byte "Mouse test!",$a
    .byte "Click anywhere to see where the mouse is!",$a
    .byte "Press ESC to stop.",$a,$0

    .byte $a,"Great to hear!",$a,$0

    .byte $a,"Please provide feedback on the forums.",$a,$0


ptr: .res 2, $00


.proc _mask_test: near
  ora #$10
  lda #mask_input
  jsr _print
: lda CIN
  cmp #$a
  beq :+
  bne :-
: jmp _return

.proc _getc_test: near
  ora #$20
  lda #getc_input
  jsr _print
: lda CIN
  cmp #'Y'
  beq :+
  cmp #'N'
  beq :+++
  bne :-
: lda #getc_yes
: jsr _print
  jmp _return
: lda #getc_no
  bne :--

.proc _mouse_test: near
  lda #mouse_irq
  sta MIRQ+1
  ora #($20 | $40)
  lda #mouse_test
  jsr _print
: lda CIN
  cmp #$1b
  beq :+
  bne :-
: jmp _return

.proc mouse_irq: near
  lda MROW
  sta ROW
  lda MCOL
  sta COL
  lda #'X'
  sta COUT

.proc _print: near
: lda (ptr),Y
  beq :+
  sta COUT
  bne :-
: rts

.proc _welcome: near
  lda #welcome
  jsr _print

.proc _return: near
  lda #0

.segment "STARTUP"

ldx #$FF
jsr _welcome
jsr _mask_test
jsr _getc_test
jsr _mouse_test

Let's start by exploring the code in more detail so that you can better understand how it's all put together. First, you will notice an __IOBASE__ import at the very top, this is provided at link time by the linker. It is highly recommended that you place this in your code in case you plan on using it on a machine where the I/O is mapped to a different area of memory. The next part are a bunch of variables, some in I/O mapped space, and one to tell us the address of the monitor, which is where we will JMP to after the program is done. Then we define a macro which we may use more than once, having this here doesn't make smaller binaries, but it makes for smaller and more organized source code. This macro here will take the registers and generate a pointer for us, and then set LDY to #0, so we can use it as a counter through the pointed address.

Finally we come to the heart of soul of an assembly program, the segments! The segments can appear in any order within the assembly source program, the linker will take care of all the details for you later. The first segment we define here is RODATA, which is where our static data which we do not plan on modifying at runtime lives. These can be anything from tables, to integers, and string data. The next segment we define is ZEROPAGE, these are addresses we can reserve in the zero page for pointers, small variables, or data which other programs may access after ours is done. The benefit of zeropage is that there are a lot of operations which are much quicker when accessing zeropage data, and some operations require a pointer in zeropage to function correctly. Use the zeropage wisely. Now finally we come to our CODE segment. This is where all your code should live, you are not required to use the .proc scoping command, but it can be useful. And finally we come to our STARTUP segment, this can include some basic bootstrapping code, to your entire main routine, it's entirely up to you. This segment is the entry point for the most part into your program. This segment is written at the beginning of your output binary image.

Now that we have all that explanation out of the way, here is the simple Makefile for this:

all: keyboard.bin

keyboard.bin: keyboard.o
	ld65 -C ../../src/he-rom.cfg -m keyboard.map -o keyboard.bin keyboard.o

keyboard.o: keyboard.s
	ca65 -l keyboard.lst keyboard.s

	rm -rf keyboard.bin keyboard.o keyboard.lst keyboard.map

It first uses the ca65 assembler to generate the object file, which we will then link using the ld65 linker. Another guide will be available soon which will provide the he-rom.cfg file you see listed here, plus an explanation on what it does.