Friday, November 26, 2010

Arduino, Steppers, EasyDriver and Python

After playing around with an Arduino and an EasyDriver (v43) I came up with the following solution. It is mostly inspired by Dan Thompson's Tutorial.

I tried unsuccessfully to use a binary communication scheme and ended up switching to ASCII and upping the speed to 115200 bauds. The Arduino code now uses the Messenger library which works quite well. 

 Here is a really ugly picture of the assembly: (stepper not included in picture) 

 Here is the Arduino sketch: 

#include 
Messenger message = Messenger();

////// ED_v4  Step Mode Chart //////
//                                //
//   MS1 MS2 Resolution           //
//   L   L   Full step (2 phase)  //
//   H   L   Half step            //
//   L   H   Quarter step         //
//   H   H   Eighth step          //
//                                //
////////////////////////////////////

const int DIR_PIN = 3;          // PIN  3 = DIR
const int STEP_PIN = 2;        // PIN  2 = STEP
const int MS1_PIN = 12;        // PIN 13 = MS
const int MS2_PIN = 9;         // PIN  9 = MS2
const int SLEEP_PIN = 11;      // PIN 12 = SLP
const int LED_ERR = 7;

unsigned int DIV=8;
unsigned int DELAY=1600/DIV;
int POSITION=0;
int TARGET=0;
int QUANTA = 10;
int DIR=1;
int MOVING=0;
boolean bEnabled=false;

int iPos=0;
int iWait=0;
void SetDivider( int div );
void SendSteps( int nb=1);
void SetDirection( int dir );

void setup()
{
 pinMode(DIR_PIN, OUTPUT);   // set pin 3 to output
 pinMode(STEP_PIN, OUTPUT);  // set pin 2 to output
 pinMode(MS1_PIN, OUTPUT);   // set pin 13 to output
 pinMode(MS2_PIN, OUTPUT);   // set pin 9 to output
 pinMode(SLEEP_PIN, OUTPUT); // set pin 12 to output
 pinMode(LED_ERR, OUTPUT);

 DIV=8;
 DELAY=1600/DIV;

 SetDivider( DIV );

 Serial.begin(115200);     // open the serial connection at 9600bps
 message.attach(messageCompleted);

}

void loop()
{
 // The following line is the most effective way of
 // feeding the serial data to Messenger
 while ( Serial.available() )
 {
  message.process( Serial.read() );
 }

 MoveQuanta();
}

void SendSteps( int nb)
{
 for( int i=0; i<nb; ++i )
 {
  // This LOW to HIGH change is what creates the..
  digitalWrite(STEP_PIN, LOW);
  // .."Rising Edge" so the easydriver knows to when to step.
  digitalWrite(STEP_PIN, HIGH);
  delayMicroseconds(DELAY);
 }
}

void SetDirection( int dir )
{
 if( dir>0 )
 {
  // CLOCKWISE
  DIR=1;
  digitalWrite(DIR_PIN, LOW);
 }
 else
 {
  // COUNTER-CLOCKWISE
  DIR=-1;
  digitalWrite(DIR_PIN, HIGH);
 }

}

void SetDivider( int div )
{
 switch( div )
 {
  case 1:
  digitalWrite(MS1_PIN, LOW);
  digitalWrite(MS2_PIN, LOW);
  break;
  case 2:
  digitalWrite(MS1_PIN, HIGH);
  digitalWrite(MS2_PIN, LOW);
  break;
  case 4:
  digitalWrite(MS1_PIN, LOW);
  digitalWrite(MS2_PIN, HIGH);
  break;
  case 8:
  digitalWrite(MS1_PIN, HIGH);
  digitalWrite(MS2_PIN, HIGH);
  break;
 }
}

void MoveQuanta()
{
 if( bEnabled )
 {
  if( TARGET > POSITION )
  {
   MOVING=1;
   SetDirection( 1 );
   int steps = min(TARGET-POSITION, QUANTA);
   SendSteps( steps );
   POSITION += steps;
  }
  else if( TARGET < POSITION )
  {
   MOVING=1;
   SetDirection( -1 );
   int steps = min(POSITION-TARGET, QUANTA);
   SendSteps( steps );
   POSITION -= steps;
  }
  else
  {
   MOVING=0;
  }
 }
}

// Define messenger function
void messageCompleted()
{
 //  commands:
 //  e  Enable Drives
 //  d  Disable Drives
 //  sd999  Set Divider <int>
 //  sw999  Set Wait <int>
 //  gp  Get Position
 //  gd  Get Divider
 //  gw  Get Wait
 //  ma999  Move Absolute <int>
 //  mr999  Move Relative <int>
 //  rp  Reset Position
 //  x  Stop All Movements
 if ( message.checkString("e") )
 {
  /////////////////////////////////////// Enable
  digitalWrite(SLEEP_PIN, HIGH);
  bEnabled=true;
 }
 else if ( message.checkString("d") )
 {
  /////////////////////////////////////// Disable
  digitalWrite(SLEEP_PIN, LOW);
  bEnabled=false;
 }
 if ( message.checkString("sd") )
 {
  /////////////////////////////////////// Set Div
  int div = message.readInt();
  if( div == 1 ||
  div == 2 ||
  div == 4 ||
  div == 8 )
  {
   DIV=div;
   SetDivider( DIV );
  }
 }
 else if ( message.checkString("sw") )
 {
  /////////////////////////////////////// Set Wait
  DELAY = message.readInt();
 }
 else if ( message.checkString("sq") )
 {
  /////////////////////////////////////// Set Quanta
  QUANTA = message.readInt();
 }
 if ( message.checkString("im") )
 {
  /////////////////////////////////////// Is Moving
  Serial.println( MOVING, DEC );
 }
 if ( message.checkString("gd") )
 {
  /////////////////////////////////////// Get Div
  Serial.println( DIV, DEC );
 }
 else if ( message.checkString("gw") )
 {
  /////////////////////////////////////// Get Wait
  Serial.println( DELAY, DEC );
 }
 else if ( message.checkString("gp") )
 {
  /////////////////////////////////////// Get Position
  Serial.println( POSITION, DEC );
}
 else if ( message.checkString("gq") )
 {
  /////////////////////////////////////// Get Quanta
  Serial.println( QUANTA, DEC );
 }
 else if ( message.checkString("gt") )
 {
  /////////////////////////////////////// Get Target
  Serial.println( TARGET, DEC );
 }
 if ( message.checkString("ma") )
 {
  /////////////////////////////////////// Move Absolute
  if( bEnabled )
  {
   int pos = message.readInt();
   TARGET = pos;
  }
 }
 else if ( message.checkString("mr") )
 {
  /////////////////////////////////////// Move Relative
  if( bEnabled )
  {
   int pos = message.readInt();
   TARGET += pos;
  }
 }
 else if ( message.checkString("rp") )
 {
  /////////////////////////////////////// Reset Position
  POSITION=0;
  TARGET=0;
 }
 else if ( message.checkString("x") )
 {
  /////////////////////////////////////// STOP!
  // stop on next MoveQuanta
  TARGET=POSITION;
 }
 else
 {
  digitalWrite( LED_ERR, HIGH );
 }

}


 And here is the Python Stepper class:


# -*- coding: utf-8 -*-
import serial
import time
##  The arduino can be reset by putting the DTR pin high, then low.
##  This is normally done on connection by the PySerial module (on windows).
##  This means that everytime a connection is made to the arduino, a reset is sent
##  and about 5 seconds are necessary before sending commands. To prevent this, the
##  line 61 in C:\Python26\Lib\site-packages\serial\serialwin32.py must be changed
##  from:
##    self._dtrState = win32file.DTR_CONTROL_ENABLE
##  to:
##    self._dtrState = win32file.DTR_CONTROL_DISABLE

class Stepper:

    def __init__( self, COMPort ):
        self.COMMAND_ENABLE = "e"               # no parameters
        self.COMMAND_DISABLE = "d"              # no parameters
        self.COMMAND_SET_DIV = "sd "             # <int>
        self.COMMAND_SET_WAIT= "sw "             # <int>
        self.COMMAND_SET_QUANTA= "sq "             # <int>
        self.COMMAND_GET_DIV = "gd"             # no parameters
        self.COMMAND_GET_WAIT = "gw"            # no parameters
        self.COMMAND_GET_QUANTA = "gq"            # no parameters
        self.COMMAND_GET_TARGET = "gt"            # no parameters
        self.COMMAND_GET_POSITION = "gp"        # no parameters
        self.COMMAND_MOVE_ABS = "ma "            # <int>
        self.COMMAND_MOVE_REL = "mr "            # <int>
        self.COMMAND_RESET_POSITION = "rp"      # no parameters
        self.COMMAND_STOP = "x"                 # no parameters
        self.COMMAND_IS_MOVING = "im"                 # no parameters


        self.Arduino = serial.Serial(COMPort, 115200, 8, 'N', 1 )
        self.Arduino.open()

    def __del__(self):
        pass
        self.Disable()
        #self.Arduino.close()

    def _SendCommmand(self, command ):
        self.Arduino.write( command + "\r" )

    def Enable(self):
        self._SendCommmand( self.COMMAND_ENABLE )

    def Disable(self):
        self._SendCommmand( self.COMMAND_DISABLE )

    def SetDiv(self, div):
        if div in [1,2,4,8]:
            self._SendCommmand( self.COMMAND_SET_DIV + str( div ) )

    def GetDiv(self):
        self._SendCommmand( self.COMMAND_GET_DIV )
        ret = self.Arduino.readline()
        return int(ret)

    def SetWait(self, wait):
        self._SendCommmand( self.COMMAND_SET_WAIT + str( wait ) )

    def GetWait(self):
        self._SendCommmand( self.COMMAND_GET_WAIT )
        ret = self.Arduino.readline()
        return int(ret)

    def SetQuanta(self, wait):
        self._SendCommmand( self.COMMAND_SET_QUANTA + str( wait ) )

    def GetQuanta(self):
        self._SendCommmand( self.COMMAND_GET_QUANTA )
        ret = self.Arduino.readline()
        return int(ret)

    def GetTarget(self):
        self._SendCommmand( self.COMMAND_GET_TARGET )
        ret = self.Arduino.readline()
        return int(ret)

    def MoveRel(self, position):
        self._SendCommmand( self.COMMAND_MOVE_REL + str( position ) )

    def MoveAbs(self, position):
        self._SendCommmand( self.COMMAND_MOVE_ABS + str( position ) )

    def GetPosition(self):
        self._SendCommmand( self.COMMAND_GET_POSITION )
        ret = self.Arduino.readline()
        return int(ret)

    def ResetPosition(self):
        self._SendCommmand( self.COMMAND_RESET_POSITION )

    def Stop(self):
        self._SendCommmand( self.COMMAND_STOP )

    def IsMoving(self):
        self._SendCommmand( self.COMMAND_IS_MOVING )
        ret = self.Arduino.readline()
        return int(ret) == 1

    def Reset(self):
        self.Arduino.setDTR( True )
        self.Arduino.setDTR( False )
        #should wait a few seconds after this!
        time.sleep(2)