3d stepper source

From Just in Time

;TITLE:         stepper.src
;PURPOSE:       control up to 3 stepper motors to move in straight lines
;		in space. Take commands from a serial port.
;AUTHOR:        Danny Havenith
;	Copyright (c) 2006 Danny Havenith
; Use, modification and distribution is subject to the Boost Software
; License, Version 1.0. (See copy at 
; UART VP and associated subroutines:
; Copyright © [01/26/1999] Scenix Semiconductor, Inc. All rights reserved.
; ra.0-2 3 keyboard row outputs
; rb.1-3 3 keyboard column inputs for keyboard matrix
;     ra.0 is the row output for the 'emergency keys' 
;     (min, max, emergency stop) and is always enabled (low-active) when motors move
;     This pin is also connected to a 'busy'-led.
; ra.3 serial out
; rb.0 serial in
; rb.4-7 Z-axis stepper motor output
; rc.0-3 X-axis stepper motor output
; rc.4-7 Y-axis stepper motor output
; Contains 2 VPs: 1) stepper motor controller 2) serial in/out

;-------------------------- DEVICE DIRECTIVES --------------------------



		RESET		Initialize
;------------------------------ CONSTANTS ------------------------------

;------------------------------ --------- ------------------------------
; The following constants are meant to be changed to configure for 
; diverse hardware.

Frequency		EQU	4_000_000 ; clock frequency
BaudRate		EQU	9600 	  ; serial port baudrate
InterruptsPerBit 	EQU	3	; samples per serial bit
HalfStepsPerSecond 	EQU 	350     ; stepper motor half steps/sec

; uncomment the following EQU if using a MAX232, since we need to
; reverse the rs-232 signals while not using a MAX232
UsingMAX232	EQU 1		; we're using a max232 

;------------------------------ --------- ------------------------------
; some derived constants (derived from the ones above)
; These are not meant to be changed manually.
		; clock ticks per interrupt
InterruptPeriod	EQU	Frequency/(InterruptsPerBit * BaudRate) + 1
		; value to put in W to obtain the clock ticks per interrupt
		; formulated in this particular way to get rid of 'Literal 
		; truncated to 8 bits' warning
RetiwValue	EQU 	256-InterruptPeriod 
		; Interrupts per stepper motor step
StepperDelay	EQU	Frequency/(HalfStepsPerSecond*InterruptPeriod)

; number of interrupts to pass between detection of the start bit ("flank") and
; the middle of the first bit, plus one (see code for that 'plus one').
StartDelay	EQU InterruptsPerBit/2 + InterruptsPerBit + 1

		; port definitions
OutputXY	EQU	rc ; port C outputs the signals for X(0-3) and Y(4-7) 
OutputZ		EQU	rb ; port B outputs the signals for Z on bits(0-3)
KeyboardRows	EQU	ra ; port A serves keyboard rows (outputs, a.0-a.2)
KeyboardRow0	EQU	ra.0
KeyboardRow1	EQU	ra.1
KeyboardRow2	EQU	ra.2
KeyboardColumns EQU	rb ; port B serves keyboard columns (inputs b.1-b.3)
KeyboardCol0	EQU	rb.1
KeyboardCol1	EQU	rb.2
KeyboardCol2	EQU	rb.3
KeyboardMask	EQU	%00000111

SerialOut	EQU	ra.3 ; pins ra.3 and rb.0 are serial output and
SerialIn	EQU	rb.0 ; input respectively.

;------------------------------ VARIABLES ------------------------------
			ORG	$08
; 'global' bank, registers that can be accessed regardles of the current
; bank.
ErrorTreshold	DS 2	; Error treshold (for Bresenhams)
Step1		DS 2	; Step size in the second dimension
Step2		DS 2	; Step size in the third dimension

; temporary register for interrupt routines
InterruptScratch DS 1
; this byte contains the sign bits for x (.0),y (.1) and z (.2)
Signs		DS 1

			ORG $10
; bank with registers for the line drawing algorithm
LineDrawingBank	EQU $

StepsToGo	DS 2 ; steps left on current line
Error2		DS 2 ; cumulative errr in the first dimension
Error1		DS 2	; cumulative error in the second dimension

; aliases for 
MovedX		EQU StepsToGo
MovedY		EQU StepsToGo
MovedZ		EQU StepsToGo

StepperDelayCount DS 2  ; timeout between steps
PosX		DS 1 ; X, Y and Z pos. 8 bits precision is more
PosY		DS 1 ; than enough.
PosZ		DS 1

Direction0	DS 1 ; mapping of dimensions (0, 1, 2) to 
Direction1	DS 1 ; axes (x, y, z) and direction (up or down).
Direction2	DS 1

StepperState	DS 1 ; current stepper state. see below.
StepperFlags	DS 1 ;
StepperKeyMoved EQU StepperFlags.0

; interruptable states, these are pre-empted by a move command
StepperIdle	 EQU 0 ; Do Nothing
StepperIdleKeys	 EQU 1 ; Handle keyboard
StepperCountKeys EQU 2 ; Timeout and handle keys

; non-interruptable states. No new vector can be programmed
; while the driver is in one of these states
StepperUpdatePos1 EQU 4 ; Update position 1
StepperUpdatePos2 EQU 5; Update position 2
StepperOutputPos EQU 6 ; latch x,y,z position to outputs
StepperCounting	 EQU 7 ; wait a while before moving to next position
			; update position 0 if wait ends

			ORG $30
		; this bank contains the results of the commands
		; more specifically, the x,y and z vector values
		; are kept here...
CommandBank	EQU $

Coordinates	EQU $
		;coordinates must start at an even address, see the parse function
GoX		DS 2
GoY		DS 2
GoZ		DS 2

		; command parser variables
CurrentNumber	DS 2 ; accumulator for the number we're currently parsing
ParserState	DS 1 ; current state
NextChar	DS 1 ; next character to parse
CurrentCoordinate DS 1; which coordinate we're parsing.
CurrentCommand	DS 1;
CommandFlags	DS 1;

; parser states
ParserExpectCommand	EQU 0
ParserExpectNumber	EQU 1
ParserInHexNumber	EQU 2
ParserExpectReturn	EQU 3

			ORG $50
SERIAL		=       $	;UART bank
tx_high		ds      1	;hi byte to transmit
tx_low		ds      1	;low byte to transmit
tx_count	ds      1	;number of bits sent
tx_divide	ds      1	;xmit timing (/16) counter
rx_count	ds      1	;number of bits received
rx_divide	ds      1	;receive timing counter
rx_byte		ds      1	;buffer for incoming byte
flags		ds	1	;only contains the rx_flag
rx_flag		EQU flags.0
string		ds	1	;used by send_string to store the address in memory
byte		ds	1	;used by serial routines

;---------------------------- SETTINGS ---------------------------

		FREQ	Frequency
WKED_W		equ	$0A		;Write MIWU/RB Interrupt edge setup, 0 = falling, 1 = rising
WKEN_W		equ	$0B		;Write MIWU/RB Interrupt edge setup, 0 = enabled, 1 = disabled
ST_W		equ	$0C		;Write Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
LVL_W		equ	$0D		;Write Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
PLP_W		equ	$0E		;Write Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
DDIR_W		equ	$0F		;Write Port Direction

 IFDEF UsingMAX232
RA_latch	equ	%00001111		;SX18/20/28/48/52 port A latch init
RA_latch	equ	%00000111		;SX18/20/28/48/52 port A latch init
RA_DDIR		equ	%11110000		;see under pin definitions for port A DDIR value
RA_LVL		equ	%00000000		;SX18/20/28/48/52 port A LVL value
RA_PLP		equ	%11111111		;SX18/20/28/48/52 port A PLP value

RB_latch	equ	%00000000		;SX18/20/28/48/52 port B latch init
RB_DDIR		equ	%00001111		;SX18/20/28/48/52 port B DDIR value
RB_ST		equ	%11111111		;SX18/20/28/48/52 port B ST value
RB_LVL		equ	%00000000		;SX18/20/28/48/52 port B LVL value
RB_PLP		equ	%11110001		;SX18/20/28/48/52 port B PLP value

RC_latch	equ	%00000000		;SX18/20/28/48/52 port C latch init
RC_DDIR		equ	%00000000		;SX18/20/28/48/52 port C DDIR value
RC_ST		equ	%11111111		;SX18/20/28/48/52 port C ST value
RC_LVL		equ	%00000000		;SX18/20/28/48/52 port C LVL value
RC_PLP		equ	%11111111		;SX18/20/28/48/52 port C PLP value

;-------------------------- MACRO DEFINITIONS --------------------------
; The following are mainly some macros that define 16 bit, little endian
; versions of the 8-bit instruction set that we all know and have grown to
; love.

; 16-bit little endian decrement
Dec16		MACRO address
		 sub address, #1 ; remember dec doesn't change C flag
		 dec address + 1

; 16-bit little endian addition
; This addition sets carry flag accordingly (but not the zero flag)
Add16		MACRO dest, src	; little endian 16 bit addition
		mov	W, src
		add	dest, W
		mov	W, src + 1
		snb	C
		movsz	W, ++src + 1
		add	dest + 1, W

; 16-bit little endian subtraction
Sub16		MACRO dest,src
		sub dest, src	; sub lsb
		subb dest + 1, /c   ; sub one extra if carry not set (= borrow)
		sub dest + 1, src + 1; subtract w from msb

; 16-bit, little endian compare, sets zero and carry flag
Cmp16		MACRO dest, src
		mov w, src + 1
		mov w, dest + 1 - w
		jnz	End   	; if msb equal...
		mov w, src	; lsb
		mov w, dest - w ; 
; It's 16 bit
; It's Little endian (or it doesn't matter)
; And, it copies.
Copy16		MACRO dest, src
		mov dest, src
		mov dest + 1, src + 1

; test a 16 bit value for zero, start with the msb, to safely test
; when another 'thread' (interrupt) is decreasing the value.
Test16		MACRO dest
		test dest + 1
		test dest

Clr16		MACRO dest
		clr dest
		clr dest + 1
; move a 16 bit literal into RAM
MovLit16	MACRO dest, literal16
		mov dest, #(literal16 // 256)
		mov dest + 1, #(literal16 / 256) 
; These macros control whether our serial line is high-active
; or low-active.
Rs232Up		MACRO pin
				setb pin
				clrb pin

Rs232Down	MACRO pin
				clrb pin
				setb pin

; transfer the state of the rs232 input pin to the carry flag
Rs232PinToCarry MACRO pin
		sb	pin			;get current rx bit
	        snb	pin			; 

; rather complex macro to generate code that 
; produces a line. It puts the absolute components
; in the GoX, GoY and GoZ registers and then composes
; the last three bits of the 'signs' register.
; the expression '||x' takes the absolute value of x.
DoLine		MACRO x,y,z
		bank CommandBank
		MovLit16 GoX, ||x
		MovLit16 GoY, ||y
		MovLit16 GoZ, ||z

		; the following uses the fact that the expression:
		; 	||(a+1) - ||a + 1
		; is zero for a < 0
		; is two otherwise. (for integer a)
		mov signs, # ( (||(x+1) - ||x + 1) / 2 + (||(y+1) - ||y + 1) + (||(z+1) - ||z + 1) * 2)
		call @Move	

; convert from ascii-coded hex digit to value
; jump if not a	true hex digit
; note: this code changes the contents of the fr register
; note: this could probably be optimized
ConvertHexOrJump MACRO fr,address
		LOCAL is_digit
		cjb fr, #'0', address
		cja fr, #'z', address
		cjbe fr, #'9', is_digit
		or fr, #$20
		cjb fr, #'a', address
		cja fr, #'f', address
		sub fr, #('a' - 10 - '0')
is_digit	sub fr, #'0'
;-------------------------- INTERRUPT ROUTINE --------------------------
		ORG	0

	; Virtual Peripheral: Universal Asynchronous Receiver Transmitter (UART) 
	; These routines send and receive RS232 serial data, and are currently
	; configured (though modifications can be made) for the popular
	; "No parity-checking, 8 data bit, 1 stop bit" (N,8,1) data format.
	; RECEIVING: The rx_flag is set high whenever a valid byte of data has been
	; received and it is the calling routine's responsibility to reset this flag
	; once the incoming data has been collected.
	; TRANSMITTING: The transmit routine requires the data to be inverted
	; and loaded (tx_high+tx_low) register pair (with the inverted 8 data bits
	; stored in tx_high and tx_low bit 7 set high to act as a start bit). Then
	; the number of bits ready for transmission (10=1 start + 8 data + 1 stop)
	; must be loaded into the tx_count register. As soon as this latter is done,
	; the transmit routine immediately begins sending the data.
	; This routine has a varying execution rate and therefore should always be
	; placed after any timing-critical virtual peripherals such as timers,
	; adcs, pwms, etc.
	; Note: The transmit and receive routines are independent and either may be
	;	removed, if not needed, to reduce execution time and memory usage,
	;	as long as the initial "BANK serial" (common) instruction is kept.
	;       Input variable(s) : tx_low (only high bit used), tx_high, tx_count
	;       Output variable(s) : rx_flag, rx_byte
	;       Variable(s) affected : tx_divide, rx_divide, rx_count
	;       Flag(s) affected : rx_flag
		bank	SERIAL			;switch to serial register bank
:transmit	decsz	tx_divide		;only execute the transmit routine
		jmp	:receive		; 
		mov	w,#InterruptsPerBit	;
		mov	tx_divide,w		; 
		test    tx_count		;are we sending?
		snz				; 
		jmp	:receive		;
		stc                             ;yes, ready stop bit
		rr      tx_high			; and shift to next bit
		rr      tx_low			; 
		dec     tx_count		;decrement bit counter
		snb	tx_low.6		;output next bit
		Rs232Up	SerialOut		; 
		sb	tx_low.6		; 
		Rs232Down SerialOut		; 
		; transfer input to carry flag.
:receive        Rs232PinToCarry SerialIn

		test    rx_count                ;currently receiving byte?
		sz				; 
		jmp	:rxbit                  ;if so, jump ahead
		mov     w,#9                    ;in case start, ready 9 bits
		sc				;skip ahead if not start bit
		mov     rx_count,w              ;it is, so renew bit count
		mov     w,#StartDelay		;ready 1.5 bit periods plus one
		mov     rx_divide,w		; 
:rxbit          decsz	rx_divide		;middle of next bit?
          	jmp	:rxdone			;
		mov	w,#InterruptsPerBit	;yes, ready 1 bit period
		mov	rx_divide,w		; 

		dec     rx_count                ;last bit?
		sz                              ;if not
		rr      rx_byte                 ; then save bit
		snz                             ;if so,
		setb    rx_flag                 ; then set flag
:rxdone						; else, exit

; Stepper motor controller.
; This VP controls up to 3 stepper motors. It implements Bresenhams algorithm
; to move in a straight line in 3D coordinate space.
		bank LineDrawingBank 
		mov w, StepperState
		jmp pc + w
		jmp :ZeroStepperOutputs ; idle, do noting
		jmp :DoKeyboard1 ; idle, handle keyboard
		jmp :DoKeyboard2 ; timeout while handling keyboard
		jmp :EndStepper ; state 3 not used

; non-interruptable states

		jmp :DoUpdatePos1 ; update pos1
		jmp :DoUpdatePos2 ; update pos2
		jmp :OutputStepper; latch x,y,z-positions to outputs

		; state #7 no jump, just start executing here 
		; wait a while before moving to next position
		Dec16 StepperDelayCount
		Test16 StepperDelayCount
		jnz :EndStepper

		; transition to update position state
		mov StepperState, #StepperUpdatePos1
		MovLit16 StepperDelayCount, StepperDelay

		; make a step in the first direction
		Dec16 StepsToGo
		mov w, Direction0
		call :MakeStep
		jmp :EndStepper

		Dec16 StepperDelayCount
		Test16 StepperDelayCount
		jnz :EndStepper; 2

		MovLit16 StepperDelayCount, StepperDelay ;2
		clr StepsToGo ; 1
		clr StepsToGo + 1 ;1

		; activate row 1
		mov m, #DDIR_W
		mov !KeyboardRows, #((RA_DDIR & ~KeyboardMask) | (KeyboardMask ^  (1 << . KeyboardRow1)))
		clrb KeyboardRow1 ; 1

		sb KeyboardCol0 ; 1
		call :Key10 ; 8
		sb KeyboardCol1 ;1
		call :Key11
		sb KeyboardCol2
		call :Key12
		setb KeyboardRow1
		mov StepperState, #StepperCountKeys
		jmp :EndStepper
		mov StepperState, #StepperIdleKeys
		; activate row 2
		mov m, #DDIR_W
		mov !KeyboardRows, #((RA_DDIR & ~KeyboardMask) | (KeyboardMask ^ (1 << . KeyboardRow2)))
		clrb KeyboardRow2		
		sb KeyboardCol0
		call :Key20
		sb KeyboardCol1
		call :Key21
		sb KeyboardCol2
		call :Key22
		setb KeyboardRow2

:EndKeyboard	test StepsToGo
		jnz :DoOutputStepper

		; set all stepper outputs to zero
		; this should decrease current when steppers are
		; idle.
		setb KeyboardRow0
		clr OutputXY
		mov w, OutputZ
		and w, #%00001111
		mov OutputZ, w
		jmp :EndStepper

:Key10		dec PosX
		setb StepsToGo.0
:Key11		dec PosY
		setb StepsToGo.0
:Key12		inc PosX
		setb StepsToGo.0
:Key20		inc PosY
		setb StepsToGo.0
:Key21		dec PosZ
		setb StepsToGo.0
:Key22		inc PosZ
		setb StepsToGo.0

; update position 1 state		
		Add16 Error1, Step1
		jc :Change1
		Cmp16 Error1, ErrorTreshold
		jnc :EndUpdatePos1
		Sub16 Error1, ErrorTreshold
		mov w, Direction1
		call :MakeStep	
		mov StepperState, #StepperUpdatePos2
		jmp :EndStepper

; update position 2 state
		Add16 Error2, Step2
		jc :Change2
		Cmp16 Error2, ErrorTreshold
		jnc :TransitionToOutputPos
		Sub16 Error2, ErrorTreshold
		mov w, Direction2
		call :MakeStep

		; transition to outputpos state
		mov StepperState, #StepperOutputPos
		jmp :EndStepper

:MakeStep	; increase or decrease an axis dimension, depending on the value of w
		; w.7 determines whether we increase or decrease (1 = increase)
		; w.1 and w.0 determine which axis (0 = x, 1 = y, 2 = z, 3 = crash horibly)
		mov InterruptScratch, w
		jb InterruptScratch.7,	:Increase
		and w, #3
		jmp PC + w
		jmp :DecreaseX
		jmp :DecreaseY
		jmp :DecreaseZ
:Increase	and w, #3
		jmp PC + w
		jmp :IncreaseX
		jmp :IncreaseY
		jmp :IncreaseZ

:IncreaseZ	inc PosZ
:IncreaseY	inc PosY
:IncreaseX	inc PosX
:DecreaseZ	dec PosZ
:DecreaseY	dec PosY
:DecreaseX	dec PosX

:ExpandPos	; convert from number (0-3) to 
		; bit pattern (0001, 0010, 0100, 1000)
		and w, #7
		jmp PC+w
;		retw %0001, %0100, %0010, %1000, %0001, %0100, %0010, %1000
		retw %0001, %0011, %0010, %0110, %0100, %1100, %1000, %1001
;		retw %0001, %0101, %0100, %0110, %0010, %1010, %1000, %1001

:ExpandPos2	; easiest way to create a bit pattern in upper
		; bits is to have another version of Expand
		; instead of swapping
		and w, #7
		jmp PC+w
;		retw %00010000, %00100000, %01000000, %10000000
		retw %00010000, %00110000, %00100000, %01100000, %01000000, %11000000, %10000000, %10010000

		ORG $0100
; output positions state		
:OutputStepper	; now translate x,y and z to stepper
		; line outputs

		; first move x and y to combined 
		; XY output (4 bits each)
		mov StepperState, #StepperCounting
		mov w, PosX
		call :ExpandPos
		mov InterruptScratch, w
		mov w, PosY
		call :ExpandPos2
		or w, InterruptScratch
		mov OutputXY, w
		mov w, PosZ
		call :ExpandPos2 ; translate z pos to high bits
		mov InterruptScratch, w
		mov w, OutputZ
		and w, #%00001111
		or w, InterruptScratch
		mov OutputZ, w

		Test16 StepsToGo
		jnz :EndStepper ; if steps to go is zero, transition to idle
		mov StepperState, #StepperIdle


EndInterrupt	mov w, #RetiwValue
;------------------------ INITIALIZATION ROUTINE -----------------------
		;<group comment>
		mov 	m, #ST_W			;point MODE to write ST register
		mov     w,#RB_ST            	;Setup RB Schmitt Trigger, 0 = enabled, 1 = disabled
		mov	!rb,w		
		mov     w,#RC_ST            	;Setup RC Schmitt Trigger, 0 = enabled, 1 = disabled
		mov	!rc,w	

		mov 	m, #LVL_W			;point MODE to write LVL register
		mov     w,#RA_LVL            	;Setup RA CMOS or TTL levels, 0 = TTL, 1 = CMOS
		mov	!ra,w		 
		mov     w,#RB_LVL            	;Setup RB CMOS or TTL levels, 0 = TTL, 1 = CMOS
		mov	!rb,w		
		mov     w,#RC_LVL            	;Setup RC CMOS or TTL levels, 0 = TTL, 1 = CMOS
		mov	!rc,w	

		mov 	m, #PLP_W			;point MODE to write PLP register
		mov 	w,#RA_PLP            	;Setup RA Weak Pull-up, 0 = enabled, 1 = disabled
		mov	!ra,w		 
		mov     w,#RB_PLP            	;Setup RB Weak Pull-up, 0 = enabled, 1 = disabled
		mov	!rb,w		
		mov     w,#RC_PLP            	;Setup RC Weak Pull-up, 0 = enabled, 1 = disabled
		mov	!rc,w	

		mov 	m, #DDIR_W			;point MODE to write DDIR register
		mov	w,#RA_DDIR		;Setup RA Direction register, 0 = output, 1 = input		
		mov	!ra,w	
		mov	w,#RB_DDIR		;Setup RB Direction register, 0 = output, 1 = input
		mov	!rb,w			
		mov	w,#RC_DDIR		;Setup RC Direction register, 0 = output, 1 = input
		mov	!rc,w			

		mov     w,#RA_latch          	;Initialize RA data latch
		mov     ra,w		
		mov     w,#RB_latch         	;Initialize RB data latch
		mov     rb,w		
		mov     w,#RC_latch          	;Initialize RC data latch
		mov     rc,w		

; zero all ram (SX28)
		clr	fsr			;reset all ram banks
:zero_ram	sb	fsr.4			;are we on low half of bank?
		setb	fsr.3			;If so, don't touch regs 0-7
		clr	ind			;clear using indirect addressing
		incsz	fsr			;repeat until done
		jmp	:zero_ram

		mov	!option,#%10011111	;enable rtcc interrupt

;---------------------------- MAIN PROGRAM -----------------------------

		bank LineDrawingBank
		mov StepperState, #StepperIdleKeys
		MovLit16 StepperDelayCount, StepperDelay

		bank SERIAL
		mov w, #OpeningMessage // 256; modulo to prevent warning
		call @send_string
		bank SERIAL
		call @get_byte		; read a byte from serial port
		call @send_byte		; echo that same byte
		mov w, byte		; re-retreive the received byte
		bank CommandBank
		mov NextChar, w
		call @ParseCharacter	; parse the byte and act
		test CommandFlags
		jmp MainLoop
		clr CommandFlags
		; for now, we only have 1 command, so assume
		; 'M' was issued.
		call @Move
		mov w, #Prompt // 256
		call @send_string
		jmp MainLoop
		ORG $200
SendPrompt	bank SERIAL
		mov w, #'>'
		call @send_byte
		mov w, #13
		call @send_byte
		mov w, #10
		call @send_byte

Move		; program the vector in GoX, GoY and GoZ
		; into the Bresenham registers and start the
		; algorithm.
		; If the Bresenham routine is busy, wait until it's 
		; finished
		bank LineDrawingBank	
		; wait until the stepper is interruptible	
:Wait		jb StepperState.2, :Wait	

		; now start to determine which value is the largest
		; Bresenhams algoritm is driven by the largest component
		; on every 'tick' a step is made in that direction and other
		; directions may or may not make a step in that same tick.
		bank CommandBank
		Cmp16 GoX, GoY
		jnc :YOrZIsLargest ; jump if (Y > X)
		Cmp16 GoX, GoZ	
		jnc :ZIsLargest	; jump if (Z > X)

:XIsLargest	; x is largest, order becomes x, y, z
		Copy16 ErrorTreshold, GoX
		Copy16 Step1, GoY 
		Copy16 Step2, GoZ

		bank LineDrawingBank

		; first set the direction registers
		; the direction registers map Bresenhams registers 
		; (0, 1, and 2) to the three axis (x, y and z)
		; these are coded 0 for x, 1 for y and 2 for z
		; we set them to a value 'shifted left' (times 2) 
		; because we're going to shift them to the right 
		; lateron to add the direction bits.
		mov Direction0, #0 ; 2 times 0, for x
		mov Direction1, #2 ; 2 times 1, for y
		mov Direction2, #4 ; 2 times 2, for z

		; now distribute the direction (up or down) bits
		rr Signs
		rr Direction0	; x to Direction0
		rr Signs
		rr Direction1	; y to Direction1
		rr Signs
		rr direction2   ; z to Direction2

		jmp :Finalize

		Cmp16 GoY, GoZ
		jnc :ZIsLargest
:YIsLargest	; y is largest, order becomes y, x, z
		Copy16 ErrorTreshold, GoY
		Copy16 Step1, GoX
		Copy16 Step2, GoZ
		bank LineDrawingBank

		; first set the direction registers
		; the direction registers map Bresenhams registers 
		; (0, 1, and 2) to the three axis (x, y and z)
		; these are coded 0 for x, 1 for y and 2 for z
		; we set them to a value 'shifted left' (times 2) 
		; because we're going to shift them to the right 
		; lateron to add the direction bits.
		mov Direction0, #2 ; 2 times 1, for y
		mov Direction1, #0 ; 2 times 0, for x
		mov Direction2, #4 ; 2 times 2, for z

		; now distribute the direction (up or down) bits
		rr Signs
		rr Direction1	; x to Direction1
		rr Signs
		rr Direction0	; y to Direction0
		rr Signs
		rr direction2   ; z to Direction2

		jmp :Finalize
:ZIsLargest	; z is largest, order becomes z, x, y
		Copy16 ErrorTreshold, GoZ
		Copy16 Step1, GoX
		Copy16 Step2, GoY
		bank LineDrawingBank

		; first set the direction registers
		; the direction registers map Bresenhams registers 
		; (0, 1, and 2) to the three axis (x, y and z)
		; these are coded 0 for x, 1 for y and 2 for z
		; we set them to a value 'shifted left' (times 2) 
		; because we're going to shift them to the right 
		; lateron to add the direction bits.
		mov Direction0, #4 ; 2 times 2, for z
		mov Direction1, #0 ; 2 times 0, for x
		mov Direction2, #2 ; 2 times 1, for y

		; now distribute the direction (up or down) bits
		rr Signs
		rr Direction1	; x to Direction1
		rr Signs
		rr Direction2	; y to Direction2
		rr Signs
		rr direction0   ; z to Direction0

		BREAK :Finalize
		; Error1 = ErrorTreshold/2
		Copy16 Error1, ErrorTreshold
		rr Error1 + 1 
		rr Error1
		; Error2 = Error1
		Copy16 Error2, Error1 

		; Set StepsToGo
		Copy16 StepsToGo, ErrorTreshold
		MovLit16 StepperDelayCount, StepperDelay

		; now set the state to 'output position'
		; this will activate the stepper.
		clrb KeyboardRow0
		mov StepperState, #StepperOutputPos

; parse an input character
; This routine is used to parse input command strings
; an example input string would be
;  'm 1ff, 0, FFFF' 
; which stands for 'Move 511, 0, 65535'

		bank CommandBank
		mov w, ParserState
		jmp PC + w
		jmp :DoExpectCommand						
		jmp :DoExpectNumber
		jmp :DoInNumber
		mov w, #13 ; carriage return
		mov w, NextChar - w
		sz ; do nothing unless we have a cr
		mov ParserState, #ParserExpectCommand
		or CommandFlags, #$01
		retp ; call the 'move' routine and return
		mov w, #'M'
		mov w, NextChar - w
		and w, #~$20 ; tolower
		sz ; return if it is not 'm' (the only command we support so far
		mov CurrentCommand, #'M'
		Clr16 GoX
		Clr16 GoY
		Clr16 GoZ
		mov Signs, #$07; all positive
		mov CurrentCoordinate, #1 ; start parsing x
		mov ParserState, #ParserExpectNumber
		ConvertHexOrJump NextChar, :EndExpectNumber
		mov ParserState, #ParserInHexNumber
		mov CurrentNumber, NextChar
		clr CurrentNumber + 1
		cse NextChar, #'-' ; return if not '-'...
		mov w, /CurrentCoordinate
		and Signs, w ; reset the corresponding sign bit

:DoInNumber	ConvertHexOrJump NextChar, :EndInNumber
		; now CurrentCoordinate = CurrentCoordinate * 16 + nextdigit
		; todo: optimize
		rl CurrentNumber
		rl CurrentNumber + 1
		rl CurrentNumber
		rl CurrentNumber + 1
		rl CurrentNumber
		rl CurrentNumber + 1
		rl CurrentNumber
		rl CurrentNumber + 1
		mov w, NextChar
		and w, #$0f
		or CurrentNumber, w

		; calculate the destination address
		mov w, #Coordinates
		add w, CurrentCoordinate

		and w, #$fe
		; copy the parsed number to the address
		mov FSR, w
		mov IND, CurrentNumber
		inc FSR
		mov IND, CurrentNumber + 1

		; if this was not the last coordinate, 
		; goto ExpectNumber state
		; else goto ExpectReturn state
		mov w, #ParserExpectNumber
		snb CurrentCoordinate.2
		mov w, #ParserExpectReturn
		mov ParserState, w

		rl CurrentCoordinate
		; immediately reparse this character
		jmp ParseCharacter
		ORG	$400
; UART Subroutines

	; Function: get_byte
	; Get byte via serial port and echo it back to the serial port
	;	-NONE
	;	-received byte in rx_byte
get_byte	bank 	SERIAL
		sb	rx_flag			;wait till byte is received
		jmp	get_byte
		clrb    rx_flag			;reset the receive flag
		mov     w,rx_byte		;store byte (copy using W)
		mov     byte,w			; & fall through to echo char back

	; Function: send_byte
	; Send byte via serial port
	;	w 	-	The byte to be sent via RS-232
	; 	outputs the byte via RS-232
send_byte    	bank    SERIAL

:wait        	test    tx_count                ;wait for not busy
		jmp	:wait                   ;

		mov     tx_high,w               ; store data byte
		clrb    tx_low.7                ; set up start bit
		mov     w,#10			;1 start + 8 data + 1 stop bit
		mov     tx_count,w
		retp                            ;leave and fix page bits

	; Function: send_string
	; Send string pointed to by address in W register
	;	w	-	The address of a null-terminated string in program
	;			memory
	; 	outputs the string via RS-232
send_string	bank	SERIAL
 		mov     string,w                ;store string address
:loop        	mov     w,string                ;read next string character
		mov     m,#(StringPage>>8)      ;with indirect addressing 
		iread                           ;using the mode register
		test    w                       ;are we at the last char?
		snz                             ;if not=0, skip ahead
		jmp	:exit			;yes, leave & fix page bits
		call    send_byte               ;not 0, so send character
		inc     string                  ;point to next character
		jmp     :loop                   ;loop until done
:exit		mov     m,#$0f                   ;reset the mode register

;		String constants
		org $500
StringPage	EQU $
OpeningMessage	DW '3D Stepper Control v1.00.01', 13
		DW 'J&J Productions 2006', 13 
		; notice the missing zero at the above line.
Prompt		DW '>', 0