Saturday, 3 April 2010

Arduino - Fancy Stepper Timer

I got distracted into writing an Arduino 2-channel stepper motor controller program for telescope tracking. It's unrelated to cnc, but it's a small exercise in Arduino programming and might be useful for others...

Ladyada.net has a motor shield for Arduinos that can be used to drive stepper motors (and other things). It comes with a program to drive the stepper motors, but it is blocking while it takes the delay between steps for one stepper motor, so the other stepper motor can't run concurrently.

Busy with searching Virtual Easter Eggs, I was too lazy to put together a proper interrupt-driven solution, so my Fancy Timer class is a quick work-around. If there is enough interest, I can put together an ISR solution.

So, here it goes: The following Arduino program lets you use 2 stepper motors simultaneously with the ladyada.net motor shield.

Limitation: As it's not interrupt-driven, it uses the built-in Arduino timer function, which has a granularity of 1ms, so it will all work fine for reasonably slow motor speeds. I suggest 200 to 300 pulses per second will be fine.

// FancyTimer - Lets you track multiple timers
// or stepper motors in parallel
// Copyright (C) 2010 www.cncorbust.com
// This program is free software: you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation, either version 3 of the License, or (at
// your option) any later version.
// This program is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE.  See the GNU General Public
// License for more details.
// You should have received a copy of the GNU General Public
// License along with this program.  If not, see
// <http://www.gnu.org/licenses/>.
//
// General idea:
// Class FancyTimer takes care of determining when an action
// (step) needs to be taken.
// You 'constantly' poll the class instance in loop().
// The poll method returns 'true' whenever you need to take
// action.
// You can create multiple instances of this class to track
// multiple timers/stepper motors at different speeds
// You can change the speed of each timer/stepper motor
// at any time and independently.
//
// Caveat:
// This only works if the motors run slowly enough, eg,
// up to maybe 200 steps per second
// Don't 'pause' calling the poll method or you will get a
// quick sequence of steps to "catch up"
// The method fails when the in-built timer wraps around
// to zero (after 50 days).
//
// ===============================================
// ============= The fancy timer. ================
// ===============================================
class FancyTimer
{
  public:

   // "steps per hour" because it keeps it precise
  unsigned long stepsPerHour;

  // how many steps we should be taking by now
  // in multiples of 1/3600000
  unsigned long current;

  // last time we were called to check
  unsigned long lastMillis;
 
  FancyTimer()
  {
    stepsPerHour = 0;
    current = 0;
    lastMillis = 0;
  }
 
  void setSpeed(unsigned long stepsPerHour)
  {
    this->stepsPerHour = stepsPerHour;
  }
 
  long getSpeed()
  {
    return this->stepsPerHour;
  }
 
  // returns true if it is time to take action
  boolean checkTime()
  {
    unsigned long oldMillis = lastMillis;
    lastMillis = millis();
    current += (lastMillis-oldMillis) * stepsPerHour;
   
    if(current>=3600000L)
    {
      current -= 3600000L;
     return true;
    }
    return false;
  }
};

// ================================================
// Here begins YOUR program
// This is an example to drive 2 stepper motors with
// afmotor.h from ladyada.
// ================================================

#include <AFMotor.h>

AF_Stepper motor1(48, 1);
AF_Stepper motor2(48, 2);

FancyTimer fancy1;
FancyTimer fancy2;

int motor1Direction = FORWARD;
int motor2Direction = FORWARD;

void setup()
{
}

void loop()
{
  // check condition / user input
  // and set motor speed and direction for each motor.
  //
  // We don't do this for this example, and simply
  // set the speed and direction for both motors.
  // Motor 1: 20 steps per second
  fancy1.setSpeed(20*3600L);
  motor1Direction = BACKWARD;

  // 13.521 steps pers second, ie, 13.521 * 3600 = 48675.6
  fancy2.setSpeed(48676L);
  motor2Direction = FORWARD;

  // Now poll timer to check if we need to take action:
  if( fancy1.checkTime() )
  {
    motor1.onestep(motor1Direction,INTERLEAVE);
  }

  // Now poll timer 2. For this example, we also switch off
  // the motor if it's not moving
  if( fancy2.checkTime() )
  {
    motor2.onestep(motor2Direction,DOUBLE);
  }
  else if( fancy2.getSpeed() == 0L )
  {
    motor2.release();
  }  
}