We got our hands on a few small cheap stepper motors from technobots. Because these stepper motors are geared, they have quite a remarkable number of steps per revolution (2048 full steps, or 4096 microsteps) and come with a small driver board already. These drivers are a small single chip darlington array, and you simply make the 4 input pins high in order to pass current through each coil. This means that unlike more expensive stepper motor drivers you have to manage the coils in the stepper motor yourself, thankfully this is rather straight forward to do this for the Arduino.

I have taken inspiration from the post at engyfun.blogspot.com.au, but added the ability to switch between full stepping and micro stepping, and correcting for backlash. I have also added a simple serial interface to tell the stepper motor where to go.

/*
 *	StepperControlSerial.ino
 * 
 * It is licensed under the Creative Commons - GNU GPL license.
 * 
 * Simon Hammersley, BHivelabs.uk
 * 
 * Serial interface for controling small geared stepper motors with ULN2003A drivers
 * 
 * Current program provides a serial interface to move the stepper motor and change the drive parameters
 * 
 * 2016-01-30
 * 			Initial version				Simon 
*/

///variable declarations

int pins[4] = {9,10,11,12};    											//pins stepper motor is connected to
long CurrentPos = 0;            										//current step number for the motor
int StepsPerRev = 2048;        											//full steps per revolution of the motor
int CurrentCoil = 0;           											//current coil number (0-3 for full step, 0-7 for micro stepping)
unsigned long StepTime = 6000;           								//shortest time that a coil can be energised for in us
unsigned long RampSpeed = 2;             								//how many times StepTime to vary speed by during ramp
unsigned long RampDuration = 500;         								//number of steps during which to ramp
boolean microstepping = false; 											//microstepping on/off
int backlash = 500;											//Number of steps to use for backlash correction
int FullSteps[][4]= {{HIGH, LOW, LOW, LOW},								//stepping pattern for full step mode
                     {LOW, HIGH, LOW, LOW},
                     {LOW, LOW, HIGH, LOW},
                     {LOW, LOW, LOW, HIGH}};
int MicroSteps[][4]= {{HIGH, LOW, LOW, LOW},							//stepping pattern for half stepping mode
                      {HIGH, HIGH, LOW, LOW},
                      {LOW, HIGH, LOW, LOW},
                      {LOW, HIGH, HIGH, LOW},
                      {LOW, LOW, HIGH, LOW},
                      {LOW, LOW, HIGH, HIGH},
                      {LOW, LOW, LOW, HIGH},
                      {HIGH, LOW, LOW, HIGH}};
String inputString = "";         										// a string to hold incoming data
boolean stringComplete = false;  										// whether the string is complete and ready to be parsed

///function declarations
int GoTo(int Step)  													//Send stepper to possition Step return 0 if all is well, 1 if an error
{
  //Serial.print("Current Possition");									//debug output
  //Serial.println(CurrentPos);
  if(Step == CurrentPos)												//check if the motor is already at the target possition
  {
    //Serial.println("no Steps to move");								//debug output
    return 0;						
  }
  if(Step < CurrentPos)													//if you need to go to a lower step position
  {
    //Serial.print("Go Backwards");										//Debug output
    //Serial.println(CurrentPos-Step);
    
    GoBackwards((CurrentPos-Step)+backlash);
    return GoForwards(backlash);										//call GoBackwards and pass on the return status
  }
  if(Step > CurrentPos)													//if you need to go to a higher step possiton
  {
    //Serial.print("Go Forwards");										//debug output
    //Serial.println(Step-CurrentPos);
    return GoForwards(Step-CurrentPos);									//call GoForwards and pass on the return status
  }
  return 1;																//should not be able to get to this point, therefore return error
}

int GoBackwards(int Steps)												//Send stepper to a lower step number
{
  unsigned long delayTime = 0;											//initiallise value of delay
  int StepPattern[4];													//initiallise the step pattern array
  for(int i =0; i< Steps; i++)											//number of steps to make loop
  {
    if(i == 0)															//if 1st step
    {
      
      delayTime = RampSpeed*StepTime;									//set length of the 1st step to RampSpeed*StepTime
    }
    else if(i<= RampDuration)											//for ramp duration, bring down the step time slightly each step untill it reaches the minimum value			
    {
      if(delayTime > StepTime)
      {
        delayTime = delayTime - StepTime*(1.0*RampSpeed-1.0)/(1.0*RampDuration);
      }
      else
      {
        delayTime = StepTime;
      }
    }
    if(microstepping == false)											//check if microstepping, if not
    {
      if(CurrentCoil == 0)												//if we are on coil 0 now, set to coil 3
      {
        CurrentCoil = 3;				
      }
      else 																//otherwise decrement the current coil value
      {
        CurrentCoil--;
      }
      for(int j = 0; j<4; j++)											//copy the correct pattern of coils to energise into the step pattern array
      { 
        StepPattern[j] = FullSteps[CurrentCoil][j];
      }
    }
    else 																//if microstepping
    {
      if(CurrentCoil == 0)												//go from coil 0 to coil 7
      {
        CurrentCoil = 7;
      }
      else 																//otherwise decrement coil
      {
        CurrentCoil--;
      }
      for(int j = 0; j<4; j++)											//copy coil pattern into the pattern array
      { 
        StepPattern[j] = MicroSteps[CurrentCoil][j];
      }
    }
    for (int j=0; j<4; j++)												//energise the coils in the step pattern
    {
  	  digitalWrite(pins[j],StepPattern[j]);
    }
    delayMicroseconds(delayTime);										//wait step time
    for (int j=0; j<4; j++)												//turn off all coils
    {
  	  digitalWrite(pins[j],LOW);
    }
    CurrentPos--;														//complete step and record current possition
  }
  return 0;																//return that loop has completed ok
  
}

int GoForwards(int Steps)												//Send stepper to a higher step number
{
  unsigned long delayTime = 0;													//initialise delay time
  int StepPattern[4];													//initiallise step pattern array
  for(int i =0; i< Steps; i++)											//step loop
  {
    if(i == 0)															//if 1st step
    {
      
      delayTime = RampSpeed*StepTime;									//set length of the 1st step to RampSpeed*StepTime
    }
    else if(i<= RampDuration)											//for ramp duration, bring down the step time slightly each step untill it reaches the minimum value			
    {
      if(delayTime > StepTime)
      {
        delayTime = delayTime - StepTime*(1.0*RampSpeed-1.0)/(1.0*RampDuration);
      }
      else
      {
        delayTime = StepTime;
      }
    }
    if(microstepping == false)											//if not microstepping
    {
      if(CurrentCoil == 3)												//if on coil 3, go back to coil 0
      {
        CurrentCoil = 0;
      }
      else 																//otherwise increment the coil value
      {
        CurrentCoil++;
      }
      for(int j = 0; j<4; j++)											//copy across the Step pattern
      { 
        StepPattern[j] = FullSteps[CurrentCoil][j];
      }
    }
    else 																//if microstepping
    {
      if(CurrentCoil == 7)												//if at coil 7, go to coil 0
      {
        CurrentCoil = 0;
      }
      else 																//otherwise go increment coil
      {
        CurrentCoil++;
      }
      for(int j = 0; j<4; j++)											//copy correct step pattern
      { 
        StepPattern[j] = MicroSteps[CurrentCoil][j];
      }
    }
      for (int j=0; j<4; j++)											//energise the correct coils for this step
    {
  	  digitalWrite(pins[j],StepPattern[j]);
    }
    delayMicroseconds(delayTime);										//delay step time
    for (int j=0; j<4; j++)												//turn off coils
    {
  	  digitalWrite(pins[j],LOW);
    }
    CurrentPos++;														//finish step and increment the current step number
  }
  
  return 0;																//finish motion
  
}

void testMotors()														//test if the motors are working
{
  boolean before = microstepping;										//hold on to current setting for microstepping
  microstepping = false;												//turn off microstepping
  GoTo(0);																//Set Home possition
  Serial.println("Go Forwards 1/4 rev, Microstepping off");
  GoTo(StepsPerRev/4);													//make 1/4 turn using full steps
  delay(5000);  														//pause
  Serial.println("Go Backwards 1/4 rev, Microstepping off");			
  GoTo(0);																//go back to home using full steps
  delay(5000);															//pause
  microstepping = true;													//turn on microstepping
  Serial.println("Go Forwards 1/8 rev, Microstepping on");
  GoTo(StepsPerRev/4);													//make 1/8th turn using microsteps, number of microsteps per rev = 2*StepsPerRev
  delay(5000);  														//pause
  Serial.println("Go Backwards 1/8 rev, Microstepping on");
  GoTo(0);																//go back home using microsteps
  microstepping=before;													//return microstepping setting to value before test
}

void setup()															//Setup function
{
  Serial.begin(9600);													//start the serial port, baud rate 9600
  Serial.println("StepperControlSerial");								//Print to computer program name
  for(int i=0; i<4; i++)												//initialise all motor pins to be output pins
  {
    //Serial.println(pins[i]);											//debug line
    pinMode(pins[i],OUTPUT);
    
  }
  inputString.reserve(200);  											//save some space to listen to the serial port
  Serial.println("Ready");
  //GoTo(-2048);												
}


void loop()																//main loop
{
  if (stringComplete) 													//if '\n' is recieved
  {
    Serial.println(inputString);										//echo string back to user 
    switch(inputString[0])												//look at the first char of recieved command
    {
      case't':															//Run motor test on recieving t
        Serial.println("Run motor test");
        testMotors();
        Serial.println("Motor test complete");
        break;
      case 's':															//return current settings to user
        Serial.println("CurrentSettings\n");
        Serial.print("Motor Pins ");
        for(int i = 0; i< 4; i++)										//list all pins in the array
        {
          Serial.print(pins[i]);
          Serial.print(" ");
        }
        Serial.println("");
        Serial.print("Steps Per Revolution ");
        Serial.println(StepsPerRev);
        Serial.print("Step Time ");
        Serial.println(StepTime);
        Serial.print("Speed Ramp Factor ");
        Serial.println(RampSpeed);
        Serial.print("Number of steps for Speed Ramp ");
        Serial.println(RampDuration);
        Serial.print("Microstepping ");
        Serial.println(microstepping);
        break;
      case 'R':															//number of steps per revolution
        if(inputString[1] == '\n')										//just R, return current value
        {
          Serial.print("Steps per Revolution ");
          Serial.println(StepsPerRev);
        }
        else 															//if followed by a number, set the value fo steps per rev to that number
        {
          StepsPerRev = inputString.substring(1,inputString.length()-1).toInt();
          Serial.print("Steps per Revolution ");
          Serial.println(StepsPerRev);
        }
        break;
      case 'g':															//Goto
        if(inputString[1] == '\n')										//just g on its own, return current step possition
        {
          Serial.print("Current step number ");
          Serial.println(CurrentPos);
        }
        else 															//otherwise use the goto command to go to that step possition
        {
          int Target = inputString.substring(1,inputString.length()-1).toInt();
          Serial.print("Goto ");
          Serial.println(Target);
          GoTo(Target);
          Serial.println("Done");
        }
        break;
      case 'm':															//microstepping
        if(inputString[1] == '\n')										//return current microstepping setting
        {
          Serial.print("Microstepping ");
          if(microstepping)
          {
            Serial.println("on");
            break;
          }
          else
          {
            Serial.println("off");
            break;
          }
        }
        else 															//set microstepping to be on or off
        {
          if(inputString[1] == '1')
          {
            if(inputString[2]=='\n')
            {
              Serial.println("Microstepping on");
              microstepping = true;
              break;
            }
          }
          if(inputString[1]=='0')
          {
            if(inputString[2] =='\n')
            {
              Serial.println("Microstepping off");
              microstepping = false;
              break;
            }
          }        
        }
      default:															//if command not found tell user
        Serial.println("Command not recognised");
    }
    inputString = "";													//clear input string
    stringComplete = false;												//
  }

}

void serialEvent() 														//when new data arrives on serial port
{
  while (Serial.available()) {											//keep reading while there is data to read
    char inChar = (char)Serial.read(); 									
    inputString += inChar;												//add last char to the input string
    if (inChar == '\n') 												//if the new char is a new line character, set string complete so we can parse it
    {
      stringComplete = true;
    } 
  }
}