Actions

Difference between revisions of "SX firmware to control 3 stepper motors"

From Just in Time

Line 19: Line 19:
 
  ; License, Version 1.0. (See copy at  
 
  ; License, Version 1.0. (See copy at  
 
  ; http://www.boost.org/LICENSE_1_0.txt)
 
  ; http://www.boost.org/LICENSE_1_0.txt)
 +
;
 +
; UART VP and associated subroutines:
 +
; Copyright © [01/26/1999] Scenix Semiconductor, Inc. All rights reserved.
 
  ;
 
  ;
 
  ;REVISIONS:
 
  ;REVISIONS:
Line 49: Line 52:
 
  RESET Initialize
 
  RESET Initialize
 
  ;------------------------------ CONSTANTS ------------------------------
 
  ;------------------------------ CONSTANTS ------------------------------
Frequency EQU 4_000_000 ; clock frequency
 
BaudRate EQU 9600   ; serial port baudrate
 
SamplesPerBit EQU 3 ; samples per serial bit
 
HalfStepsPerSecond EQU 80    ; stepper motor half steps/sec
 
 
   
 
   
 +
;------------------------------ --------- ------------------------------
 +
; 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 20    ; 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)
 
  ; some derived constants (derived from the ones above)
 +
; These are not meant to be changed manually.
 +
;
 
  ; clock ticks per interrupt
 
  ; clock ticks per interrupt
  InterruptPeriod EQU Frequency/(SamplesPerBit * BaudRate) + 1
+
  InterruptPeriod EQU Frequency/(InterruptsPerBit * BaudRate) + 1
 
  ; value to put in W to obtain the clock ticks per interrupt
 
  ; value to put in W to obtain the clock ticks per interrupt
 
  ; formulated in this particular way to get rid of 'Literal  
 
  ; formulated in this particular way to get rid of 'Literal  
Line 63: Line 78:
 
  ; Interrupts per stepper motor step
 
  ; Interrupts per stepper motor step
 
  StepperDelay EQU Frequency/(HalfStepsPerSecond*InterruptPeriod)
 
  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
 
  ; port definitions
Line 124: Line 143:
 
   
 
   
 
  Coordinates EQU $
 
  Coordinates EQU $
  ;coordinates must start at an even address see the parse function
+
  ;coordinates must start at an even address, see the parse function
 
  GoX DS 2
 
  GoX DS 2
 
  GoY DS 2
 
  GoY DS 2
Line 135: Line 154:
 
  CurrentCoordinate DS 1; which coordinate we're parsing.
 
  CurrentCoordinate DS 1; which coordinate we're parsing.
 
  CurrentCommand DS 1;
 
  CurrentCommand DS 1;
 +
CommandFlags DS 1;
 +
 
   
 
   
TextIndex DS 1;
 
 
  ; parser states
 
  ; parser states
 
  ParserExpectCommand EQU 0
 
  ParserExpectCommand EQU 0
Line 144: Line 164:
 
   
 
   
 
   
 
   
 +
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
 
   
 
   
 
   
 
   
  ;---------------------------- DEBUG SETTINGS ---------------------------
+
  ;---------------------------- SETTINGS ---------------------------
 
   
 
   
 
  FREQ Frequency
 
  FREQ Frequency
 
 
 
 
; WATCH <Symbol>,<bit count>,<format>
 
 
 
  WKED_W equ $0A ;Write MIWU/RB Interrupt edge setup, 0 = falling, 1 = rising
 
  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
 
  WKEN_W equ $0B ;Write MIWU/RB Interrupt edge setup, 0 = enabled, 1 = disabled
Line 160: Line 191:
 
   
 
   
 
  RA_latch equ %00000000 ;SX18/20/28/48/52 port A latch init
 
  RA_latch equ %00000000 ;SX18/20/28/48/52 port A latch init
  RA_DDIR equ %11110111 ;see under pin definitions for port A DDIR value
+
  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_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
 
  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_latch equ %00000000 ;SX18/20/28/48/52 port B latch init
  RB_DDIR equ %00000001 ;SX18/20/28/48/52 port B DDIR value
+
  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_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_LVL equ %00000000 ;SX18/20/28/48/52 port B LVL value
Line 243: Line 274:
 
  MovLit16 MACRO dest, literal16
 
  MovLit16 MACRO dest, literal16
 
  mov dest, #(literal16 // 256)
 
  mov dest, #(literal16 // 256)
  mov dest + 1, #((literal16 / 256) // 256)
+
  mov dest + 1, #(literal16 / 256)  
  ENDM
+
ENDM
 +
 +
; These macros control whether our serial line is high-active
 +
; or low-active.
 +
Rs232Up MACRO pin
 +
IFDEF UsingMAX232 THEN
 +
setb pin
 +
ELSE
 +
clrb pin
 +
ENDIF
 +
ENDM
 +
 +
Rs232Down MACRO pin
 +
IFDEF UsingMAX232 THEN
 +
clrb pin
 +
ELSE
 +
setb pin
 +
ENDIF
 +
ENDM
 +
 +
; transfer the state of the rs232 input pin to the carry flag
 +
Rs232PinToCarry MACRO pin
 +
sb pin ;get current rx bit
 +
IFDEF UsingMAX232 THEN
 +
        clc
 +
ELSE
 +
stc
 +
ENDIF
 +
        snb pin ;
 +
IFDEF UsingMAX232 THEN
 +
stc
 +
ELSE
 +
        clc
 +
ENDIF
 +
  ENDM
 
   
 
   
 
  ; rather complex macro to generate code that  
 
  ; rather complex macro to generate code that  
Line 285: Line 350:
 
   
 
   
 
  Interrupt
 
  Interrupt
 +
;*********************************************************************************
 +
; 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
 +
;
 +
;*********************************************************************************
 +
SerialVP
 +
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.
 
  ; Stepper motor controller.
Line 495: Line 627:
 
   
 
   
 
  Main
 
  Main
 +
bank SERIAL
 +
mov w, #OpeningMessage // 256; modulo to prevent warning
 +
call @send_string
 +
 +
 +
MainLoop
 +
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
 
  bank CommandBank
 
mov TextIndex, #TestCommands
 
:nextchar
 
mov m, #TestCommands >> 8
 
mov w, TextIndex
 
iread
 
or w, #0
 
jz MainLoop
 
 
  mov NextChar, w
 
  mov NextChar, w
  call @ParseCharacter
+
  call @ParseCharacter ; parse the byte and act
  bank  CommandBank
+
  test CommandFlags
  inc TextIndex
+
  snz
  jmp :nextchar
+
  jmp MainLoop
 
MainLoop 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
 
 
 
 
 
TestCommands
 
dw 'm200,200,200', 13
 
dw 'M0,-200,0', 13
 
dw 'm-200,200,2000', 13
 
dw 'm0,-200,0', 13
 
dw 0
 
 
  ORG $200
 
  ORG $200
 +
SendPrompt bank SERIAL
 +
mov w, #'>'
 +
call @send_byte
 +
mov w, #13
 +
call @send_byte
 +
mov w, #10
 +
call @send_byte
 +
retp
 +
 
  Move ; program the vector in GoX, GoY and GoZ
 
  Move ; program the vector in GoX, GoY and GoZ
 
  ; into the Bresenham registers and start the
 
  ; into the Bresenham registers and start the
Line 653: Line 794:
 
  ; which stands for 'Move 511, 0, 65535'
 
  ; which stands for 'Move 511, 0, 65535'
 
  ParseCharacter
 
  ParseCharacter
; parser states
 
;ParserExpectCommand EQU 0
 
;ParserExpectCoordinate EQU 1
 
;ParserInCoordinate EQU 2
 
 
   
 
   
 
  bank CommandBank
 
  bank CommandBank
Line 670: Line 807:
 
  retp
 
  retp
 
  mov ParserState, #ParserExpectCommand
 
  mov ParserState, #ParserExpectCommand
  jmp Move ; call the 'move' routine and return
+
  or CommandFlags, #$01
 +
retp ; call the 'move' routine and return
 
  :DoExpectCommand
 
  :DoExpectCommand
 
  mov w, #'M'
 
  mov w, #'M'
Line 677: Line 815:
 
  sz ; return if it is not 'm' (the only command we support so far
 
  sz ; return if it is not 'm' (the only command we support so far
 
  retp
 
  retp
 +
mov CurrentCommand, #'M'
 
  Clr16 GoX
 
  Clr16 GoX
 
  Clr16 GoY
 
  Clr16 GoY
Line 743: Line 882:
 
 
 
 
 
 
 
 
 +
ORG $400
 +
;*****************************************************************************************
 +
; UART Subroutines
 +
;*****************************************************************************************
 +
 +
;*********************************************************************************
 +
; Function: get_byte
 +
; Get byte via serial port and echo it back to the serial port
 +
; INPUTS:
 +
; -NONE
 +
; OUTPUTS:
 +
; -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
 +
retp
 
   
 
   
 
+
  ;*********************************************************************************
;----------------------------- SUBROUTINES -----------------------------
+
; Function: send_byte
 +
; Send byte via serial port
 +
; INPUTS:
 +
; w - The byte to be sent via RS-232
 +
; OUTPUTS:
 +
; outputs the byte via RS-232
 +
;*********************************************************************************
 +
send_byte    bank    SERIAL
 +
 +
:wait        test    tx_count                ;wait for not busy
 +
sz
 +
jmp :wait                  ;
 +
 +
;not    w                      ;ready bits (inverse logic)
 +
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
 +
; INPUTS:
 +
; w - The address of a null-terminated string in program
 +
; memory
 +
; OUTPUTS:
 +
; 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
 +
retp
 +
 +
org $500
 +
StringPage EQU $
 +
OpeningMessage DW '3D Stepper Control v1.0', 13
 +
DW 'J&J Productions 2006', 13
 +
 +
Prompt DW ">", 0

Revision as of 11:59, 3 December 2006

Status so far:

  • 3D Bresenham line drawing algorithm has been programmed on-chip.
  • text parser has been implemented
  • serial port VP has not been integrated yet
  • keyboard and switch driver has not been implemented yet.

See the source code below.

;=======================================================================
;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 
; http://www.boost.org/LICENSE_1_0.txt)
;
; UART VP and associated subroutines:
; Copyright © [01/26/1999] Scenix Semiconductor, Inc. All rights reserved.
;
;REVISIONS:
;
;CONNECTIONS:
; 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 when motors move
;     This pin is also connected to a 'busy'-led.
; ra.3 serial in
; rb.0 serial out
; rb.4-7 Z-axis stepper motor output
; rc.0-3 X-axis stepper motor output
; rc.4-7 Y-axis stepper motor output
;
;
;DETAILS:
; Contains 2 VPs: 1) stepper motor controller 2) serial in/out
;=======================================================================


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

		DEVICE		SX28,OSCHS1,TURBO
		DEVICE		STACKX, OPTIONX

		IRC_CAL		IRC_SLOW

		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 	20     ; 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)
KeyboardColumns	EQU	ra ; port A serves keyboard columns (in a.0-a.2)
KeyboardRows    EQU	rb ; port B serves keyboard rows (in b.1-b.3)
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
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.

; 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

RA_latch	equ	%00000000		;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	%11111111		;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
		 sc
		 dec address + 1
		ENDM

; 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
		ENDM

; 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
		ENDM
		

; 16-bit, little endian compare, sets zero and carry flag
Cmp16		MACRO dest, src
		LOCAL End
		mov w, src + 1
		mov w, dest + 1 - w
				
		jnz	End   	; if msb equal...
		mov w, src	;...compare lsb
		mov w, dest - w ; 
End
		ENDM
		
; 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
		ENDM

; 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
		snz
		test dest
		ENDM

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

Rs232Down	MACRO pin
			IFDEF UsingMAX232 THEN
				clrb pin
			ELSE
				setb pin
			ENDIF
		ENDM

; transfer the state of the rs232 input pin to the carry flag
Rs232PinToCarry MACRO pin
		sb	pin			;get current rx bit
		IFDEF UsingMAX232 THEN
	        	clc
		ELSE
			stc
		ENDIF
	        snb	pin			; 
		IFDEF UsingMAX232 THEN
			stc
		ELSE
	        	clc		
		ENDIF
		ENDM

; 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	
		ENDM

; 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'
		ENDM
		
;-------------------------- INTERRUPT ROUTINE --------------------------
		ORG	0

Interrupt
	;*********************************************************************************
	; 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
	;
	;*********************************************************************************
SerialVP
		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.
Stepper	
		bank LineDrawingBank 
		mov w, StepperState
		jmp pc + w
		jmp :ZeroStepperOutputs ; idle, do noting
		jmp :EndStepper ; idle, handle keyboard
		jmp :EndStepper ; 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 sstart 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

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


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


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

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


; 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 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
		mov StepperState, #StepperCounting

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

:EndStepper		

EndInterrupt	mov w, #RetiwValue
		retiw


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
		retp
:IncreaseY	inc PosY
		retp
:IncreaseX	inc PosX
		retp
:DecreaseZ	dec PosZ
		retp
:DecreaseY	dec PosY
		retp
:DecreaseX	dec PosX
		retp

ExpandPos	; convert from number (0-3) to 
		; bit pattern (0001, 0010, 0100, 1000)
		and w, #7
		jmp PC+w
;		retw %0001, %0010, %0100, %1000
		retw %0001, %0011, %0010, %0110, %0100, %1100, %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

		
;------------------------ INITIALIZATION ROUTINE -----------------------

Initialize
		;<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 	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 -----------------------------

Main		
		bank SERIAL
		mov w, #OpeningMessage // 256; modulo to prevent warning
		call @send_string
		
		
MainLoop	
		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
		snz
		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
		retp

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)
:XOrZIsLargest	
		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

:YOrZIsLargest	
		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
: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.
		mov StepperState, #StepperOutputPos
		retp

; 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'
ParseCharacter	

		bank CommandBank
		mov w, ParserState
		jmp PC + w
		jmp :DoExpectCommand						
		jmp :DoExpectNumber
		jmp :DoInNumber
:DoExpectReturn
		mov w, #13 ; carriage return
		mov w, NextChar - w
		sz ; do nothing unless we have a cr
		retp
		mov ParserState, #ParserExpectCommand
		or CommandFlags, #$01
		retp ; call the 'move' routine and return
:DoExpectCommand
		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
		retp
		mov CurrentCommand, #'M'
		Clr16 GoX
		Clr16 GoY
		Clr16 GoZ
		mov Signs, #$07; all positive
		mov CurrentCoordinate, #1 ; start parsing x
		mov ParserState, #ParserExpectNumber
		retp
:DoExpectNumber
		ConvertHexOrJump NextChar, :EndExpectNumber
		mov ParserState, #ParserInHexNumber
		mov CurrentNumber, NextChar
		clr CurrentNumber + 1
:EndExpectNumber 
		cse NextChar, #'-' ; return if not '-'...
		retp
		mov w, /CurrentCoordinate
		and Signs, w ; reset the corresponding sign bit
		retp		

:DoInNumber	ConvertHexOrJump NextChar, :EndInNumber
		; now CurrentCoordinate = CurrentCoordinate * 16 + nextdigit
		; todo: optimize
		clc
		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
		retp

:EndInNumber	
		; 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

		clc
		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
	; INPUTS:
	;	-NONE
	; OUTPUTS:
	;	-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
		retp

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

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

		;not     w                       ;ready bits (inverse logic)
		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
	; INPUTS:
	;	w	-	The address of a null-terminated string in program
	;			memory
	; OUTPUTS:
	; 	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
		retp

		org $500
StringPage	EQU $
OpeningMessage	DW '3D Stepper Control v1.0', 13
		DW 'J&J Productions 2006', 13 

Prompt		DW ">", 0