Code archives/User Input/Stepper Motor Control

This code has been declared by its author to be Public Domain code.

Download source code

Stepper Motor Control by jfk EO-11110
(Posted 19 years ago)
Get the sourcecode together with the required DLPORTIO.DLL and dlportio.decls from here:

http://www.melog.ch/cnc/smc800_io.zip
; ****************************************************************************************************
; ********************* CALLING THE SMC800 STEPPERMOTOR CONTROLLER FROM BLITZ3D **********************
; ****************************************************************************************************

; This Program shows how to call the SMC800 Steppermotor Controller Card trough the Parallel Port.
; Other Controllers of the SMC Series may work with it too (SMC1500 etc.). See www.emis.com.

; The SMC can control 3 Stepper Motors and one contact switch. The contact switch is used
; to self-calibrate the Machine. Each axis can travel to its Zero Point, defined by a contact switch.
; After reaching the contact switch it has to move back a certain amount of mm (again out of the contact
; zone), so it's possible to start each row of a CNC Raster Milling Process at pretty much
; the same loaction. The 3 Switches are simply wired parallel and connected to the SMC800.
; This allows to "home" each Motor individually. Warning: using the Function "MotorHome()" 
; without working contact switches may cause your motors to seek for contact 
; forever. 

; Unlike many other Control Cards, the SMC800 expects a rudimentary Amplitude Sinus Modulation for the
; two phases of the Steppermotors. It's really a good thing to study and understand stepper Motors.
; The card offers 800mA for each Phase only, the SMC1500 allows 1.5Amp. Motors must be Two Phase
; Stepper Motors, operating at the same voltage that the SMC's power supply provides, this may be
; 12 To 30 Volts.

; The phase modulation can have a frequency up to 5 kHz. Due to the lack of programmable timers
; in Blitz3D we will use a Polling system here, based on the 1 kHz System Timer.


; The Comments in the Program explain the basic concept of calling the card in order to run
; a 3 Axis CNC Router/Locator. The following XYZ Convention is used:

; X = Motor 0 (left / right)
; Y = Motor 1 (down / up)
; Z = Motor 2 (back / forward)

; CNC People often name the vertical axis "Z", which is wrong from a scientific point of view. 
; In this Demo "Y" is the vertical axis. You may alter this in the code if you prefere Z for the
; vertical axis.



; In Addition to Blitz3D, a DLL is used, "DLPORTIO.dll" and also the blitz userlib "dlportio.decls",
; containing the following declarations:

;.lib "dlportio.dll"

;DlPortReadPortUchar%( port% )
;DlPortReadPortUshort%( port% )
;DlPortReadPortUlong%( port% )

;DlPortReadPortBufferUchar( port%, buffer*, count% )
;DlPortReadPortBufferUshort( port%, buffer*, count% )
;DlPortReadPortBufferUlong( port%, buffer*, count% )

;DlPortWritePortUchar( port%, value% )
;DlPortWritePortUshort( port%, value% )
;DlPortWritePortUlong( port%, value% )

;DlPortWritePortBufferUchar( port%, buffer*, count% )
;DlPortWritePortBufferUshort( port%, buffer*, count% )
;DlPortWritePortBufferUlong( port%, buffer*, count% )

; Special Thanks to the author of PortIO.dll for BLitz3D, right now I cannot find his name, not even
; in the original portio.zip, so please excuse. 



; This Demo Program is not a functional CNC Tool, it only shows how to call the
; Stepper Motors. Implementing a simple CNC Mill is pretty straight forward when
; you consider X and Z as the working space, and Y as the heights taken from a heightmap,
; pixel by pixel, or from a 3D scene using LinePick or CameraPick on a certain field of view.

; You may however add usage of Start- and Stop-Speed for the stepper motors, because this
; will allow to operate a machine with less vibration and less material stress, including 
; stress for the motors. There are several books and Websites about CNC programming.


Graphics 800,600,32,2 ; set up graphics
SetBuffer BackBuffer()



Global max_motor=2  ;(0=x, 1=y, 2=z)


Dim invert_axis(max_motor) ; here you can easily reverse a motors logic (0=normal operation, 1=reverse operation)
invert_axis(0)=1
invert_axis(1)=0
invert_axis(2)=0
;(these are the proxxon mf70 cnc settings)


Global LPT=$378 ; this is the parallel port of your machine. The number may differ, see "Resources" in the device manager's parallel port settings.
                ; and/or Bios. Note: you may have to disable the "bidirectional" parallel port mode in the bios by choosing eg. "unidirectional".


Dim phase_A(max_motor,8)    ; In these arrays we'll store the amplitude modulation curves
Dim phase_B(max_motor,8)
Dim phase_count#(max_motor) ; Used as modulation curves index





; The SMC800 allows for 3 levels of power: 20%, 50% and 100%
; In fact this will only set the curves to something more flat.
SetMotorPower(0,100) ; use 20, 50 or 100
SetMotorPower(1,100)
SetMotorPower(2,100)









Global Special_msg$="" ; used to tell the user some infos







; Defining some standard speeds, where only the Reference Travel speed is used in this example.
cnc_MainSpeed#=0.25
cnc_StartSpeed#=0.125
cnc_StopSpeed#=0.125
cnc_RefSpeed#=0.5
; Note. these speeds represent the frequency in kHz. 1.0 is the max! Use a fraction of 1.0 (1.0, 0.5, 0.25, 0.2,  0.125 ...)






;************************************     Main Test Loop     ***************************************
While KeyDown(1)=0
 Cls
 Text 0,0, "Cursor= left right, back , forward"
 Text 0,16,"PgUp/Dn= up, down"
 Text 0,32,"H= Home all Motors"
 Text 0,48,"Esc= Exit"
 Text 0,64, Special_msg$ ;  ; (Number of pulses for last MotorHome(): always Z axis in this demo, because it was the last one)
 Flip

 ; manual control of the 3 Motors
 k=203
 If KeyDown(k)
  While KeyDown(k)
   sync_timer(cnc_RefSpeed#)
   status = call_motor(0, 1) ; x minus
  Wend
 EndIf
 k=205
 If KeyDown(k)
  While KeyDown(k)
   sync_timer(cnc_RefSpeed#)
   status = call_motor(0, 0) ; x plus
  Wend
 EndIf
 k=208
 If KeyDown(k)
  While KeyDown(k)
   sync_timer(cnc_RefSpeed#)
   status = call_motor(2, 1) ; z minus
  Wend
 EndIf
 k=200
 If KeyDown(k)
  While KeyDown(k)
   sync_timer(cnc_RefSpeed#)
   status = call_motor(2, 0) ; z plus
  Wend
 EndIf
 k=209
 If KeyDown(k)
  While KeyDown(k)
   sync_timer(cnc_RefSpeed#)
   status = call_motor(1, 1) ; y minus
  Wend
 EndIf
 k=201
 If KeyDown(k)
  While KeyDown(k)
   sync_timer(cnc_RefSpeed#)
   status = call_motor(1, 0) ; y plus
  Wend
 EndIf

 ; home...
 k=35
 If KeyHit(35) Then
  ; (Note: it's neccessary to first Home the vertical motor!)
  MotorHome(1,cnc_RefSpeed#)
  MotorHome(0,cnc_RefSpeed#)
  MotorHome(2,cnc_RefSpeed#)
 EndIf

Wend
End














Function call_motor(motor, direction)

 ; motor choice: 0,1,2
 ; direction: 0 (away from home) or 1 (back to home)


 STB = LPT + 2 ; parallel port status port
 stb_byte = inp(STB) ; (we need this later for a printerport handshake)

 ;*** assembling the data byte for the SMC ***
 pc=Floor(phase_count(motor))
 byte=(phase_A(motor,pc) Or phase_B(motor,pc))  Or (motor Shl 6)


 ;*** Sending Data to Controller ***
 outp(LPT, (byte))   ; Actually send the Amplitude data and Motor Selection


 ; *** LPT Handshake *** 
 ;The following low level handshake may be a relict of needle printer times, 
 ;but it seems it's still neccessary with a lot of of parallel port devices, 
 ;such as this control card.
 outp(STB, stb_byte Or 1) ; gotta do a quick handshake to the "printer"
 ; eventually have to add a small delay here: (tho seems to work without it)
 ; For i=0 To 1000
 ; Next
 outp(STB, stb_byte And (~1)) ; note: "~1" means "Not(1)", or simply "254"
 ; end of LPT handshake


 ; Increment/Loop Phase curve index
 If direction=invert_axis(motor)
  phase_count(motor)=phase_count(motor)+1.0
  If phase_count(motor)>=8.0 Then phase_count(motor)=phase_count(motor)-8.0
 Else
  phase_count(motor)=phase_count(motor)-1.0
  If phase_count(motor)<0.0 Then phase_count(motor)=phase_count(motor)+8.0
 EndIf



 ; *** Checking the Contact Switch ***
 ; At the IO Port LPT+1 we read the control byte of the parallel port. Bit number 8
 ; will signal if the contact switch is closed or not:
 ; Bit is set: no contact
 ; Bit is zero: contact!

 CTR = LPT + 1 ; parallel port control port
 ctr_byte = inp(CTR)
 Return ctr_byte  ; bit 8 of this byte is the low active contact switch
End Function







; Functions to utilize BIOS input/output, to access the printer port on the hardware level.
Function outp(adress, value)
 DlPortWritePortUchar(adress , value )
End Function


Function inp(adress)
 value = DlPortReadPortUchar%( adress )
 Return value
End Function









Function MotorHome(motor,speed#=0.5)
 reached_ref=0
 count_pulses=0
 While reached_ref=0
  ; drive slide to contact switch:
  code=sync_timer(speed#) ; this will sync the motor calling with a certain frequency (Hz = speed * 1000)
  If code=777 Then:Return code:EndIf ; (aborted by user?)
  status = call_motor(motor, 1)
  count_pulses=count_pulses+1
  If (status And 128)=0  ;(reached contact switch ?)
   While (status And 128)=0 ; drive a little back, again out of contact 
    code=sync_timer(speed#)
    If code=777 Then:Return code:EndIf
    status = call_motor(motor, 0)
    count_pulses=count_pulses-1
   Wend
   counter=0
   While counter<1600 ; add some extra mm (eg. ~2 turns with 1.8deg steppermotors and M5 lathe axis)
    code=sync_timer(speed#)
    If code=777 Then:Return code:EndIf
    status = call_motor(motor, 0)
    counter=counter+1
    count_pulses=count_pulses-1
   Wend
   reached_ref=1
  EndIf
 Wend
 Special_msg$="Last Motor Home required: "+ count_pulses +" Pulses." ; this is useful for metric calibrations!
 Return 0
End Function






; This function will wait for a certain while in order to synchronize
; the calling of the motors with the choosen speed (Hz=speed*1000)
Function sync_timer(local_speed#=1.0)
 secs=(1.0/local_speed#)-1
 t=MilliSecs()+secs
 While t >= MilliSecs()
  If KeyDown(57) ; allow space key to abort things
   Delay 200
   FlushKeys()
   Return 777  ; return some abort code
  EndIf
 Wend
 Return 0 ; return zero for "successful execution"
End Function





Function SetMotorPower(motor,power)
 ; (For the Bits Description see also SMC800 Manual)
 If motor<0 Then motor=0
 If motor>max_motor Then motor=max_motor

 ; 20% power (set default anyway)
 phase_A(motor,0)= %111
 phase_A(motor,1)= %110
 phase_A(motor,2)= %110
 phase_A(motor,3)= %110
 phase_A(motor,4)= %011
 phase_A(motor,5)= %010
 phase_A(motor,6)= %010
 phase_A(motor,7)= %010
 phase_A(motor,8)= phase_A(motor,0)

 phase_B(motor,0)= %110000
 phase_B(motor,1)= %110000
 phase_B(motor,2)= %011000
 phase_B(motor,3)= %010000
 phase_B(motor,4)= %010000
 phase_B(motor,5)= %010000
 phase_B(motor,6)= %111000
 phase_B(motor,7)= %110000
 phase_B(motor,8)= phase_B(motor,0)

 If power=50 Then
  ; 50% power
  phase_A(motor,0)= %111
  phase_A(motor,1)= %101
  phase_A(motor,2)= %101
  phase_A(motor,3)= %101
  phase_A(motor,4)= %011
  phase_A(motor,5)= %001
  phase_A(motor,6)= %001
  phase_A(motor,7)= %001
  phase_A(motor,8)= phase_A(motor,0)

  phase_B(motor,0)= %101000
  phase_B(motor,1)= %101000
  phase_B(motor,2)= %011000
  phase_B(motor,3)= %001000
  phase_B(motor,4)= %001000
  phase_B(motor,5)= %001000
  phase_B(motor,6)= %111000
  phase_B(motor,7)= %101000
  phase_B(motor,8)= phase_B(motor,0)
 EndIf
 If power=100 Then
  ; 100% power
  phase_A(motor,0)= %111
  phase_A(motor,1)= %100
  phase_A(motor,2)= %100
  phase_A(motor,3)= %100
  phase_A(motor,4)= %011
  phase_A(motor,5)= %000
  phase_A(motor,6)= %000
  phase_A(motor,7)= %000
  phase_A(motor,8)= phase_A(motor,0)

  phase_B(motor,0)= %100000
  phase_B(motor,1)= %100000
  phase_B(motor,2)= %011000
  phase_B(motor,3)= %000000 
  phase_B(motor,4)= %000000
  phase_B(motor,5)= %000000
  phase_B(motor,6)= %111000
  phase_B(motor,7)= %100000
  phase_B(motor,8)= phase_B(motor,0)
 EndIf

End Function