Thursday, May 16, 2013

Stepper Motor XY Interweave


The final task that I played with before beginning to construct a device with stepper motors and boards was the concept of making two motors move simultaneously.  This is very important when playing with 3D printers because you often want to trace out specific geometric shapes which require two motors moving in the XY plane simultaneously.  I kept it pretty simple to start by looking at how to move the "tool" in a straight line from a start point to an ending point.  I used a concept I called interweave, which basically means that you move both motors sequentially in very small increments, going back and forth to "stairstep" your way to a line.  This is shown below... the yellow motions approximate the blue line.  In the limit that the number of staircases is increased, (or to use calculus times, in the limit that the yellow step size goes to zero), the staircase becomes the blue line.
The basic idea of interweave in the way I did it, was to calculate the slope of the line, and step in small increments dx and dy which reflect the slope of the line.  This is easy to do if the slope is a whole number.  For example, in moving from position (50, 100) to position (100, 200), the slope is dy/dx = (200-100)/(100-50) = 2.  So a simple program which interweaves would have the form:

void moveSlope2Steps100()
{
  for(int i=0; i<100; i++)
  {
      takeSingleStep(YstepPin); //One step up
      takeSingleStep(YstepPin); //Second step up
      takeSingleStep(XstepPin); //One step across
    }
}

Generalizing this form is not too hard as long as the slope turns out to be a whole number... We need to know if the slope is greater than or less than one, and then we'll move more steps in the y or x direction, respectively.  Note that there are no cases here for infinite or zero slopes (eg horizontal or vertical lines).


void lineInterweaveWholenumberSlope(int deltaX, int deltaY)
{
  if(deltaX > deltaY) 
  {
     int slope = deltaX/deltaY;
     for(int i=0; i<deltaY; i++)
     {        takeSingleStep(YstepPin); //One step up
        for(int j=0; j<slope; j++)
            takeSingleStep(XstepPin); //Across number of steps equal to slope                          
     }
  }

  else 
  {
     int slope = deltaY/deltaX;
     for(int i=0; i<deltaX; i++)
     {        takeSingleStep(XstepPin); //One step across
        for(int j=0; j<slope; j++)
            takeSingleStep(YstepPin); //Up number of steps equal to slope
     }
   }
}

An example is shown below.  Suppose deltaX = 12 and deltaY = 3.    The program reduces to:


lineInterweaveWholenumberSlope(12, 3)

{
  if(12 > 3) //YES!!!
  {
     int slope = 12/3;  //4
     for(int i=0; i<3; i++)  //repeat 3 times
     {        takeSingleStep(YstepPin); //One step up
        for(int j=0; j<4; j++)
            takeSingleStep(XstepPin);//Four steps over                          
     }
  }
}


If the slope is not a whole number, then we run into issues because we cannot do moves with less than a step.  There are a lot of approaches that I considered.  Most of the early ones involved trying to convert a decimal number into a fraction made of whole numbers.  But this doesn't work because the size of the yellow staircases quickly becomes huge, or because you cannot express irrational numbers as a fraction made of whole numbers.  So the approach that I have employed is to find the nearest whole number slope, and find the remainder (modulo).  Then we can move as though we had a whole number slope, but add in extra steps to compensate for the remainder.

As an example, consider that deltaX = 14 and deltaY = 3.  Fourteen divided by three is 4.666, or we can think of it as 4 with a remainder of 2.  If we execute the same motion as described in the previous example, BUT add an extra step in the x-direction for the first two iterations, we arrive at the endpoint.   The picture at right shows the idea, with the extra steps shown in red.

It's pretty easy to code this type of behavior, because the extra step just happens as many times as the remainder.  The remainder and the slope are both easily found using integer division (the remainder is removed so int[14/3] = 4), and modulo (which records the remainder so 14%3 = 2).  Sample code for the case where deltaX exceeds deltaY is shown below.


void lineInterweave(int deltaX, int deltaY)
{
  if(deltaX > deltaY) 
  {
     int slope = deltaX/deltaY;
     int remainder = deltaX%deltaY;
     for(int i=0; i<deltaY; i++)
     {        takeSingleStep(YstepPin); //One step up
        for(int j=0; j<slope; j++)
            takeSingleStep(XstepPin); //Across number of steps equal to slope
        if(i < remainder)                          
            takeSingleStep(XstepPin); // this is the step in red
     }
  }


One thing to point out is that the extra steps mean that the actual path traced out will deviate from the ideal path; in the example above the "tool" moves too far to the right early on, but ends up in the right place.  This type of deviation is exacerbated when a) the remainder is large and b) the number of steps is small.

I created an Excel spreadsheet to explore how far off the actual line would be from the theoretical line.  The spreadsheet shows a series of points on the left corresponded to the coded actual line (based on integer slopes and modulo calculated remainders) and the theoretical points based on floating point calculations.  You can play with the number of steps in the X and Y directions to get a better sense of the errors discussed in the previous paragraph.  Here is a link to the spreadsheet.

The complete code for interweaving is below:


void moveXY(int _Xsteps, int _Ysteps)
{
  int slope[2];
  if(_Xsteps > _Ysteps)
   {
    slope[0] = _Xsteps/_Ysteps;  //calculate slope to nearest integer
    slope[1] = _Xsteps%_Ysteps;  //calculate remainder
   }
   else
   {
    slope[0] = _Ysteps/_Xsteps;  //calculate slope to nearest integer
    slope[1] = _Ysteps%_Xsteps;  //calculate remainder
   }
   if(_Xsteps == 0)  //vertical line
    {
      for(int i=0; i<_Ysteps; i++)  {takeSingleStep(stepPinY); }
    }
    else if(_Ysteps == 0)  //horizontal line
    {
      for(int i=0; i<_Xsteps; i++)  {takeSingleStep(stepPinX); }
    }
    else if(_Xsteps > _Ysteps)
    {
      for(int i=0; i<_Ysteps; i++)
      {
        takeSingleStep(stepPinY);
        for(int j=0; j<slope[0]; j++)
            takeSingleStep(stepPinX);
        if(i < slope[1]) // this is where we compensate for the remainder
            takeSingleStep(stepPinX);  //take one more step each round 

                                       //until we've taken as many as  
                                       //the remainder
      }
    }
    else
    {
      for(int i=0; i<_Xsteps; i++)
      {
        takeSingleStep(stepPinX);
        for(int j=0; j<slope[0]; j++)
            takeSingleStep(stepPinY);
        if(i < slope[1])
            takeSingleStep(stepPinY);
      }
    }
}