Actions

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

From Just in Time

m (18 revisions: copying content from old site)
 
(15 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
Status so far:
 
Status so far:
  
* 3D Bresenham line drawing algorithm has been programmed on-chip.  
+
* 3D [http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm Bresenham line drawing algorithm] has been programmed on-chip.  
 
* text parser has been implemented
 
* text parser has been implemented
* serial port VP has '''not''' been integrated yet
+
* serial port VP has been integrated
* keyboard and switch driver has '''not''' been implemented yet.
+
* keyboard and switch driver has been implemented
 +
* "emergency stop" and end-keys have been implemented in a way. Currently movement just stops if one of these keys is pressed.
  
See the source code below.
+
You can view the source [[3d stepper source|here]].
  
;=======================================================================
+
 
;TITLE:         stepper.src
+
== So What Does It Do ==
;
+
 
;PURPOSE:      control up to 3 stepper motors to move in straight lines
+
The firmware assumes the following:
; in space. Take commands from a serial port.
+
* Three stepper motors connected to rc (x and y) and rb.4-7 (z)
;
+
* Some serial communications device attached to ra.3 (SX sending) and rb.0 (SX receiving). The software is configurable to work either through a MAX232 ic or by connecting the pins directly to some rs-232 device. In the latter case, out- and input actions become active-low.
;AUTHOR:        Danny Havenith
+
 
; Copyright (c) 2006 Danny Havenith
+
The firmware communicates through the serial port and assumes a 9600bps 8N1 connection (this is configurable, but must be tuned with the clock frequencies, because some some bitrates are not possible with some clock speeds).
; Use, modification and distribution is subject to the Boost Software
+
 
; License, Version 1.0. (See copy at
+
At boot, the SX will send a welcome message and a prompt. The other end of the communciations line (the driver) is supposed to send ascii commands over the serial line, separated by newlines (ascii code 13). The SX will only accept commands when it has sent a prompt charachter ('>'). The driver should therefore always wait for a prompt before sending a command. Internally, there is one command buffer, so that the SX can execute one command and receive the next command at the same time.  
; http://www.boost.org/LICENSE_1_0.txt)
+
 
;
+
The ultimate (and currently the only-) command is the 'move' command:
; UART VP and associated subroutines:
+
 
; Copyright © [01/26/1999] Scenix Semiconductor, Inc. All rights reserved.
+
  m shhhh,shhhh,shhhh
;
+
 
;REVISIONS:
+
'm' denotes the 'move' command and directs the controller to perform a number of half-steps on each axis. 's' stands for an optional sign character, 'h' means an optional hex digit. Instead of comma's, any non-hex character can be used. There is no need for whitespace after the command, or in fact at any location at all. Examples of valid move commands are:
;
+
 
;CONNECTIONS:
+
  m0,ff,c0
; ra.0-2 3 keyboard row outputs
+
  M1FX2fX3f
; rb.1-3 3 keyboard column inputs for keyboard matrix
+
  m  de,-10,-ff
;    ra.0 is the row output for the 'emergency keys'
+
  mde-10-ff
;    (min, max, emergency stop) and is always enabled when motors move
+
 
;    This pin is also connected to a 'busy'-led.
+
 
; ra.3 serial in
+
Movement is specified in half-steps, relative to the current position.
; 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
 

Latest revision as of 22:07, 12 July 2010

Status so far:

  • 3D Bresenham line drawing algorithm has been programmed on-chip.
  • text parser has been implemented
  • serial port VP has been integrated
  • keyboard and switch driver has been implemented
  • "emergency stop" and end-keys have been implemented in a way. Currently movement just stops if one of these keys is pressed.

You can view the source here.


So What Does It Do

The firmware assumes the following:

  • Three stepper motors connected to rc (x and y) and rb.4-7 (z)
  • Some serial communications device attached to ra.3 (SX sending) and rb.0 (SX receiving). The software is configurable to work either through a MAX232 ic or by connecting the pins directly to some rs-232 device. In the latter case, out- and input actions become active-low.

The firmware communicates through the serial port and assumes a 9600bps 8N1 connection (this is configurable, but must be tuned with the clock frequencies, because some some bitrates are not possible with some clock speeds).

At boot, the SX will send a welcome message and a prompt. The other end of the communciations line (the driver) is supposed to send ascii commands over the serial line, separated by newlines (ascii code 13). The SX will only accept commands when it has sent a prompt charachter ('>'). The driver should therefore always wait for a prompt before sending a command. Internally, there is one command buffer, so that the SX can execute one command and receive the next command at the same time.

The ultimate (and currently the only-) command is the 'move' command:

m shhhh,shhhh,shhhh

'm' denotes the 'move' command and directs the controller to perform a number of half-steps on each axis. 's' stands for an optional sign character, 'h' means an optional hex digit. Instead of comma's, any non-hex character can be used. There is no need for whitespace after the command, or in fact at any location at all. Examples of valid move commands are:

m0,ff,c0
M1FX2fX3f
m   de,-10,-ff
mde-10-ff


Movement is specified in half-steps, relative to the current position.