Thursday 29 October 2015

Arduino: Move 4 Stepper Motors Synchronously

...but, theoretically, it would work for an n number of motors.

Part of the machine inside FreeCAD
This is the algorithm that plans the trajectory and syncs the movements of the stepper motors, in the new CNC machine that I'm designing (and building!)

The input is an int array { MA, MB, MC, MD } where each int means the number of steps (positive or negative).

The stepper movement logic is stored into a boolean[8] array { SA, DA, SB, DB.... } where SA means step for motor A and DA is the direction bit of the motor A (for a 2 wire stepper driver, like the a4988). This array is erased and re-computed every loop cycle with the next movement, so real motor stepping has to take place inside this loop.

The image below (created with FreeCAD) shows a representation of the algorithm output for a requested movement of ( 5, 15, 25, 40 ) steps.

Input ( 5, 15, 25, 40 ), step bit only

It would be possible to do some kind of buffer by translating that boolean array into an int inside an int array. This should be a more-less memory efficient way of decoupling motion logic and motion execution, something to try in a future.

The code:

/*
 * JMG October 2015
 * NiCr Stepper Sync Algorithm V1
 * Tested on Arduino Nano (Ide 1:635)
 * 
 */

// stepper_instruction { Step1A, Dir1A, Step1B, Dir1B, Step2A, Dir2A, Step2B, Dir2B }
bool stepper_instruction[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };

void moveAB( int Adx, int Ady, int Bdx, int Bdy )
{
  int delta[4] = { Adx, Ady, Bdx, Bdy };
  int aux = 0;
  for( int i = 0; i < 4; i++ )
  {
    if( abs(delta[i]) > aux )
    {
      aux = abs(delta[i]);
    }
  }
  float R[4] = { 0, 0, 0, 0 };
  for( int i = 0; i < 4; i++ )
  {
    R[i] = (float)delta[i] / (float)aux;
  }
  int inc[4] = { 0, 0, 0, 0 };
  int acc[4] = { 0, 0, 0, 0 };
  int j = 0;
  while( ( acc[0] != Adx )||( acc[1] != Ady )||( acc[2] != Bdx )||( acc[3] != Bdy ) )
  {
    j++;
    for( int i = 0; i < 4; i++ )
    {
      inc[i] = round( R[i]*j - acc[i] );
      acc[i] = acc[i] + inc[i];
      stepper_instruction[2*i] = abs( inc[i] );
      if( inc[i] < 0 ) { stepper_instruction[2*i+1] = 1; }
      else { stepper_instruction[2*i+1] = 0; }
    }
    for( int i = 0; i < 7; i++ )
    {
      Serial.print( stepper_instruction[i] );
    }
    Serial.println( stepper_instruction[7] );
    for( int i = 0; i < 8; i++ )
    {
      stepper_instruction[i] = 0;
    }
  }
  for( int i = 0; i < 4; i++ )
  {
    Serial.print( acc[i] );
  }
  Serial.println();
  for( int i = 0; i < 4; i++ )
  {
    Serial.print( delta[i] );
  }
}
void setup()
{
  Serial.begin( 115200 );
  delay( 500 );
  moveAB( 100, -5000, 780, 25 );
}
void loop()
{
}

It prints by serial (115200 baud) the values of stepper_instruction at each loop cycle, and the counted steps vs the requested steps at the end of the loop.
I'm sure this is not the optimal way of doing it, but it works.

Update: the algorithm in action

PS:
I will reveal more info about the machine soon. NiCr

Friday 23 October 2015

Arduino: Read Instruction From Serial

When using Arduino, parsing serial data and converting it to something usable is way harder than it seems.


While this functionality is specially useful as it allows your computer to talk with the Arduino easily, a quick search on google will give you thousands of very different solutions. The one exposed here is just another one that is working perfectly for me.

The instruction:

The idea is to set the position of three servos by serial and also be able to change other parameters.
The instruction, then, must be composed from an instruction name and the parameters, for example, to move the three servos:

MOVE 120 23 90

And, if we also want to switch on a light, the instruction could be:

LIGHT ON

How do we get the Arduino to know that we want the servos to move and then switch on a light?

 

Arduino Read Instruction:

Code:
/*
JMG OCTOBER 2015
This script is made up from snippets found in stackoverflow and other
google results
*/

void setup()
{
  // open serial port at 115200 baud
  Serial.begin(115200);
}

String complete_instruction[4];  // will contain the decoded instruction (4 fields)
void loop()
{
  
  while(!Serial.available()) {}  // if there is nothing on serial, do nothing
  int  i = 0;
  char raw_instruction[25];
  while (Serial.available())
  {  // if something comes from serial, read it and store it in raw_instruction char array
    delay(10); // delay to allow buffer to fill
    if (Serial.available() > 0)
    {
      raw_instruction[i] = Serial.read();
      i++;
    }
  }
  if( strlen( raw_instruction ) > 0 )  // if a new raw_instruction has been read
  {
    // clean raw_instruction before decoding (overwrite non filled array positions with empty spaces)
    for( int n = i; n < 25; n++ ) { raw_instruction[n] = ' '; }
    // decode the instruction (4 fields) (iterator n = field, iterator j = character)
    int j = 0;
    for( int n = 0; n < 4; n++ )
    { 
      while( j < 25 )
      {
        if( raw_instruction[j] == ' ' )
        {
          j++;
          break;
        }
        else
        {
          complete_instruction[n] += raw_instruction[j];
        }
        j++;
      }
    }
    // print decoded instruction by serial
    for( int n = 0; n<4; n++ )
    {
      Serial.println( complete_instruction[n] );
      // clear field after using it
      complete_instruction[n]="";
    }
  }
  delay(50);
}

The summary is that you can send by serial something like "A B C D" (using space as delimiter) and the variable complete_instruction will contain {A, B, C, D}. A,B,C,D can be any kind of data of any length you want, for the instruction MOVE 120 23 90, it will return {"MOVE", "120", "23", "90"}. For the second instruction LIGHT ON, it will return {"LIGHT", "ON", "", "" }.

This way, now you just need to set several if conditions to check if the instruction says "MOVE", "LIGHT", "KILL" or whatever, and if it does, check the remaining parameters (you can use toInt() or toFloat() for numeric values).

My experience with this script is that it works as intended and does not cause memory problems. But I do not have enough knowledge about C to assure that it will not crash your script (from what I've read, String variables together with low RAM space seem to give problems.) Just take it into account if something very weird is happening. (code updated, memory problem solved?)


Bye!