Code For Stepper Driver of an CnC apllication...

Hello,

as I have promised in another thread: Arrays - Programming Questions - Arduino Forum I will post a part of the code for my CnC application here...

the problem which I currently have, is that I use a SD-Card to load the "G-Code" and after each 512 Bytes the SD-Card has to reload data, which causes a 1mS break. Does anyone know a workaround?

It has not a big impact on the CnC performamce - even tough I want to solve this issue somehow...

This is the stepper driver which I am using:

Picture of the cnc hardware prototype in action:

Testing the CnC hardware:

The self made CnC software (windows):

The code which I am going to post is merely related to read the data from the SD-Card and to feed the stepping devices.

There are two reasons why I post the code,

a) first I want to help others developing similar things and

b) maybe some of you have ideas how to improve performance...

Here is the code:

// Execute the CnC Project.
void MyProject() {
  
  // Delay for the high Phase (used to trigger the stepper).
  int highPhaseDelay = 0;
  
  // Delay for the low Phase (used to trigger tge stepper).
  int lowPhaseDelay = 0;
  
  // Open the file that contains the CnC code...  Format [...x156Xy-45Ya>A...] ... the x156X means 156 steps in the positive direction of the x axis, the y-45Y means 45 steps in the negative direction of the y axis the a>A indicates weather a realay gers triggerd or not...
  File myFile = SD.open("pc.txt");
  
  // If there is an file...
  if (myFile) {
  
    // Loops as long till the file is at its end.
    while (myFile.available()) {
      
      // Extracts all the data to execute one reference point (x, y coordinates and the state of the relay)...
      if (MyGetDataMapFromFile(char(myFile.read())) == true) {
            
        // Checks weather there is a change for the relay or not to the former iteration.
        if (globalPlasmaActiv != globalPlasmaActivTemp) {
          
          // Activates the relay
          if (globalPlasmaActiv == '<') {
            
            // Sets the stepping speed.
            highPhaseDelay = globalTimePerHighPhaseForPlasmaActiv;
            
            // Sets the stepping speed.
            lowPhaseDelay = globalTimePerLowPhaseForPlasmaActiv;
            
            // Led indication that the relay is activ.
            digitalWrite(Visual_Plasma_Activ, HIGH);
            
            // Activates the relay, that activates milling or laser cuttion or plasma cutting or what ever...
            digitalWrite(Relai_PlasmaActiv, HIGH);
            
            // start deleay time.            
            delay(globalStartDelayTime);
          }
          
          // Deactivate the relay.
          if (globalPlasmaActiv == '>') {
            
            // Sets the stepping speed.
            highPhaseDelay = globalTimePerHighPhaseForPlasmaInactiv;
            
            // Sets the stepping speed.
            lowPhaseDelay = globalTimePerLowPhaseForPlasmaInactiv;
            
            // Switch of the led.
            digitalWrite(Visual_Plasma_Activ, LOW);
            
            // Switch of the relay.
            digitalWrite(Relai_PlasmaActiv, LOW);
          }
        }
        
        // Trigger the Stepper for the X-Axis.
        MyStepperX(globalStepsOnXAxis, highPhaseDelay, lowPhaseDelay);
        
        // Trigger the Stepper for the Y-Axis. 
        MyStepperY(globalStepsOnYAxis, highPhaseDelay, lowPhaseDelay);
        
        // Used for the programm logic...
        globalPlasmaActivTemp = globalPlasmaActiv;
      
        // Sends a sign to the windows application, to indicate that one reference point is done.
        Serial.println("U");
      }
    }
    
    // Deactivate the relay, if it is still activ.
    digitalWrite(Relai_PlasmaActiv, LOW);
    
    // Deactivate the led if it is still activ.
    digitalWrite(Visual_Plasma_Activ, LOW);
    
    // Close the file.
    myFile.close();
    
    // Sends a Sign to the windows application, that the projejct is finished.
    Serial.println("L");

    // Project is finished - do nothing...
    loop();
  }
}

// Extract the coordinates form the file.
boolean MyGetDataMapFromFile(char tempReceivedChar) {
  
  // Start receiving coordinates for the X-Axis.
  if (tempReceivedChar == 'X') {

	// It is not finished for X.
    globalMyGetDataMapFromFileXFinish = false;
  }
  
  // End receiving coordinates for the X-Axis.
  if (tempReceivedChar == 'x') {
    
    // Convert String to an Char Array.
    globalMyGetDataMapFromFileCharacterString.toCharArray(globalMyGetDataMapFromFileCharArray, 10);

    // Convert Char array to an integer.
    globalStepsOnXAxis = atoi(globalMyGetDataMapFromFileCharArray);
    
    // Reset the array.
    memset(globalMyGetDataMapFromFileCharArray, 0, 10);
    
    // Reset the string.
    globalMyGetDataMapFromFileCharacterString = "";
    
    // Coordinates for X-Axis were extracted.
    globalMyGetDataMapFromFileXFinish = true;
  }
      
  // Same as for X...
  if (tempReceivedChar == 'Y') {
     
    // Same as for X...
    globalMyGetDataMapFromFileYFinish = false;
  }
  
  // Same as for X...
  if (tempReceivedChar == 'y') {
    
    // Same as for X...
    globalMyGetDataMapFromFileCharacterString.toCharArray(globalMyGetDataMapFromFileCharArray, 10);

    // Same as for X...
    globalStepsOnYAxis = atoi(globalMyGetDataMapFromFileCharArray);
    
    // Same as for X...
    memset(globalMyGetDataMapFromFileCharArray, 0, 10);
    
    // Same as for X...
    globalMyGetDataMapFromFileCharacterString = "";
    
    // Same as for X...
    globalMyGetDataMapFromFileYFinish = true;
  }
  
  // Receiving the sign, that triggers the relay.
  if (globalMyGetDataMapFromFilePlasmaActiveInit == true) {
    globalMyGetDataMapFromFilePlasmaActiveInit = false;
    globalPlasmaActiv = tempReceivedChar;
  } 
  
  // Receiving the sign, that triggers the relay.
  if (tempReceivedChar == 'A') {
    globalMyGetDataMapFromFileAFinish = false;
    globalMyGetDataMapFromFilePlasmaActiveInit = true;
  }
  
  // Receiving the sign, that triggers the relay.
  if (tempReceivedChar == 'a') {
    globalMyGetDataMapFromFileAFinish = true;
  }
     
  // If the received character is a number.
  if (tempReceivedChar == '1' || tempReceivedChar == '2' || tempReceivedChar == '3' || tempReceivedChar == '4' || tempReceivedChar == '5' || tempReceivedChar == '6' || tempReceivedChar == '7' || tempReceivedChar == '8' || tempReceivedChar == '9' || tempReceivedChar == '0' || tempReceivedChar == '-') {
    
    // Convert Char Array to an string.
    globalMyGetDataMapFromFileCharacterString.concat(tempReceivedChar); 
  }
  
  // If all data for a reference point is received.
  if (globalMyGetDataMapFromFileXFinish == true && globalMyGetDataMapFromFileYFinish == true && globalMyGetDataMapFromFileAFinish == true) {
    
    // Prepare for next reference point.
    globalMyGetDataMapFromFileXFinish = false;
    globalMyGetDataMapFromFileYFinish = false;
    globalMyGetDataMapFromFileAFinish = false;
   
    // Current reference point finished.
    return true;
  }
  else {
        
    // Current reference point not finished.
    return false;
  }
}

Kind Regards,

Andreas

More Code...

// Trigger stepper for X-Axis.
void MyStepperX(int steps, int highPhaseDelay, int lowPhaseDelay) {
    
  // If one of the end-switches is activ - they protect the CnC hardware.
  if (digitalRead(DSUB_EndSwitch_XPosLeft) == HIGH && digitalRead(DSUB_EndSwitch_XPosRight) == HIGH && digitalRead(DSUB_EndSwitch_XNegLeft) == HIGH && digitalRead(DSUB_EndSwitch_XNegRight) == HIGH && digitalRead(DSUB_EndSwitch_YPosLeft) == HIGH && digitalRead(DSUB_EndSwitch_YPosRight) == HIGH && digitalRead(DSUB_EndSwitch_YNegLeft) == HIGH && digitalRead(DSUB_EndSwitch_YNegRight) == HIGH) {

    // If the button stop was pressed.
    if (digitalRead(ButtonStopProjectExecution) == LOW) {
      
      // Loop
      while(true) {
        
        // If the rela was activ.
        if (globalPlasmaActiv == '<') {
         
          globalPlasmaActiv == '#';
          
          // Switch of the relay.
          digitalWrite(Relai_PlasmaActiv, LOW);
          
          // Switch off the led.
          digitalWrite(Visual_Plasma_Activ, LOW);
        }
          
        // Loop till continue is pressed.   
        if (digitalRead(ButtonContinueProjectExecution) == LOW) {
          
          // Endlosschleife beenden.
          break;          
        }
      }
    }
    
    // Steps in the positiv direction of the x-axis.
    if (steps > 0) {
      
      // If the dir pin is low.
      if (digitalRead(StepperDirX) == LOW) {
        
        // Set the dir pin to high.
        digitalWrite(StepperDirX, HIGH);
        
        // 5µ Sekunden delay. 
        delayMicroseconds(5);
      }
    }
    
    // Steps in the negative direction of the x-axis.
    if (steps < 0) {
      
      // convert to an positive integer.
      steps = steps * (-1);
      
      // If the pin is high.
      if (digitalRead(StepperDirX) == HIGH) {
        
        // Set the dir pin to low.
        digitalWrite(StepperDirX, LOW);
        
        // 5µ Sekunden delay. 
        delayMicroseconds(5);
      }
    }
 
    // Trigger the stepper with the right speed.
    for (int i=0; i < steps; i++) {
      
      // positive pulse.
      digitalWrite(StepperPulX, HIGH);
      
      // Delay.
      delayMicroseconds(lowPhaseDelay);
      
      // Negativ Pulse.
      digitalWrite(StepperPulX, LOW);
      
      // Delay.
      delayMicroseconds(highPhaseDelay);
    }
  }
  else {
    
    // Led shows that an end switch is activ.
    digitalWrite(Visual_OutOfAreaError, HIGH);
    
    // Deactivate the relay.
    digitalWrite(Relai_PlasmaActiv, LOW);
    
    // Report the windows application which end switch was activ and stop...
    if (digitalRead(DSUB_EndSwitch_XPosLeft) == LOW) {
      Serial.println("A"); 
      while(1) { delay(1); }
    }
    
    // Report the windows application which end switch was activ and stop...
    if (digitalRead(DSUB_EndSwitch_XPosRight) == LOW) {
      Serial.println("C");
      while(1) { delay(1); }
    }
    
    // Report the windows application which end switch was activ and stop...
    if (digitalRead(DSUB_EndSwitch_XNegLeft) == LOW) {
      Serial.println("D");
      while(1) { delay(1); }
    }
    
    // Report the windows application which end switch was activ and stop...
    if (digitalRead(DSUB_EndSwitch_XNegRight) == LOW) {
      Serial.println("E");
      while(1) { delay(1); }
    }
    
    // Report the windows application which end switch was activ and stop...
    if (digitalRead(DSUB_EndSwitch_YPosLeft) == LOW) {
      Serial.println("I");
      while(1) { delay(1); }
    }
    
    // Report the windows application which end switch was activ and stop...
    if (digitalRead(DSUB_EndSwitch_YPosRight) == LOW) {
      Serial.println("J");
      while(1) { delay(1); }
    }
    
    // Report the windows application which end switch was activ and stop...
    if (digitalRead(DSUB_EndSwitch_YNegLeft) == LOW) {
      Serial.println("K");
      while(1) { delay(1); }
    }
    
    // Report the windows application which end switch was activ and stop...
    if (digitalRead(DSUB_EndSwitch_YNegRight) == LOW) {
      Serial.println("M");
      while(1) { delay(1); }
    }
  }
}

// Same as for MyStepperX...
void MyStepperY(int steps, int highPhaseDelay, int lowPhaseDelay) {
  
  // Same as for MyStepperX...
  if (digitalRead(DSUB_EndSwitch_XPosLeft) == HIGH && digitalRead(DSUB_EndSwitch_XPosRight) == HIGH && digitalRead(DSUB_EndSwitch_XNegLeft) == HIGH && digitalRead(DSUB_EndSwitch_XNegRight) == HIGH && digitalRead(DSUB_EndSwitch_YPosLeft) == HIGH && digitalRead(DSUB_EndSwitch_YPosRight) == HIGH && digitalRead(DSUB_EndSwitch_YNegLeft) == HIGH && digitalRead(DSUB_EndSwitch_YNegRight) == HIGH) {

    // Same as for MyStepperX...
    if (digitalRead(ButtonStopProjectExecution) == LOW) {
            
      // Same as for MyStepperX...
      while(true) {
        
        // Same as for MyStepperX...
        if (globalPlasmaActiv == '<') {
          
          // Same as for MyStepperX...
          globalPlasmaActiv == '#';
          
          // Same as for MyStepperX...
          digitalWrite(Relai_PlasmaActiv, LOW);
          
          // Same as for MyStepperX...
          digitalWrite(Visual_Plasma_Activ, LOW);
        }
  
        // Same as for MyStepperX...   
        if (digitalRead(ButtonContinueProjectExecution) == LOW) {
          
          // Same as for MyStepperX...
          break;          
        }
      }
    }
    
    // Same as for MyStepperX...
    if (steps > 0) {
      
      // Same as for MyStepperX...
      if (digitalRead(StepperDirY) == LOW) {
        
        // Same as for MyStepperX...
        digitalWrite(StepperDirY, HIGH);
        
        // Same as for MyStepperX...
        delayMicroseconds(5);
      }
    }
    
    // Same as for MyStepperX...
    if (steps < 0) {
      
      // Same as for MyStepperX...
      steps = steps * (-1);
      
      // Same as for MyStepperX...
      if (digitalRead(StepperDirY) == HIGH) {
        
        // Same as for MyStepperX...
        digitalWrite(StepperDirY, LOW);
        
        // Same as for MyStepperX...
        delayMicroseconds(5);
      }
    }
  
    // Same as for MyStepperX...
    for (int i=0; i < steps; i++){
      
      // Same as for MyStepperX...
      digitalWrite(StepperPulY, HIGH);
      
      // Same as for MyStepperX...
      delayMicroseconds(lowPhaseDelay);
      
      // Same as for MyStepperX...
      digitalWrite(StepperPulY, LOW);
      
      // Same as for MyStepperX...
      delayMicroseconds(highPhaseDelay);
    } 
  }
  else {
    
    // Same as for MyStepperX...
    digitalWrite(Visual_OutOfAreaError, HIGH);
    
    // Same as for MyStepperX...
    digitalWrite(Relai_PlasmaActiv, LOW);
        
    // Same as for MyStepperX...
    if (digitalRead(DSUB_EndSwitch_XPosLeft) == LOW) {
      Serial.println("A"); 
      while(1) { delay(1); }
    }
    
    // Same as for MyStepperX...
    if (digitalRead(DSUB_EndSwitch_XPosRight) == LOW) {
      Serial.println("C");
      while(1) { delay(1); } 
    }
    
    // Same as for MyStepperX...
    if (digitalRead(DSUB_EndSwitch_XNegLeft) == LOW) {
      Serial.println("D"); 
      while(1) { delay(1); }
    }
    
    // Same as for MyStepperX...
    if (digitalRead(DSUB_EndSwitch_XNegRight) == LOW) {
      Serial.println("E");
      while(1) { delay(1); }
    }
    
    // Same as for MyStepperX...
    if (digitalRead(DSUB_EndSwitch_YPosLeft) == LOW) {
      Serial.println("I");
      while(1) { delay(1); }
    }
    
    // Same as for MyStepperX...
    if (digitalRead(DSUB_EndSwitch_YPosRight) == LOW) {
      Serial.println("J");
      while(1) { delay(1); }
    }
    
    // Same as for MyStepperX...
    if (digitalRead(DSUB_EndSwitch_YNegLeft) == LOW) {
      Serial.println("K");
      while(1) { delay(1); }
    }
    
    // Same as for MyStepperX...
    if (digitalRead(DSUB_EndSwitch_YNegRight) == LOW) {
      Serial.println("M");
      while(1) { delay(1); }
    }
  }
}

// Calculate the stepping speed of "rapid positioning"...
void MyCalculateSpeedForPlasmaInactiv() {
  double tempRevulutionsPerSecond = globalRPMWhilePlasmaInactiv / 60;
  double tempStepsPerSecond = tempRevulutionsPerSecond * globalStepsPerRevulution;
  double tempTimePerStep = 1 / tempStepsPerSecond;
  double tempTPLP = (tempTimePerStep / 2) * 1000000;
  double tempTPHP = (tempTimePerStep / 2) * 1000000;
  globalTimePerLowPhaseForPlasmaInactiv = (int) tempTPLP;
  globalTimePerHighPhaseForPlasmaInactiv = (int) tempTPHP;
}

// Calculate the stepping speed of "linear interpolation"...
void MyCalculateSpeedForPlasmaActiv() {
  double tempRevulutionsPerSecond = globalRPMWhilePlasmaActiv / 60;
  double tempStepsPerSecond = tempRevulutionsPerSecond * globalStepsPerRevulution;
  double tempTimePerStep = 1 / tempStepsPerSecond;
  double tempTPLP = (tempTimePerStep / 2) * 1000000;
  double tempTPHP = (tempTimePerStep / 2) * 1000000;
  globalTimePerLowPhaseForPlasmaActiv = (int) tempTPLP;
  globalTimePerHighPhaseForPlasmaActiv = (int) tempTPHP;
}

The way to hide the latency of SDcard access is to queue motor commands and have
an interrupt routine interpret these and output steps. If the commands in the queue
are high level (such as a linear or circular movement) then you won't need a very
big queue to keep the interrupt routine busy for 1ms worth of steps.

You might get away with just queuing individual steps (longer queue needed, RAM
usage could be an issue). That makes the interrupt routine much simpler.

Hello,

I know what you mean.

Tough this would mean a significant change in my code - it is worth to think about.

The SRAM on the DUE is to small - but i thaught about a ram extension (must be up to 10 Mb) - I dont know if such an extension kit exist!?

Thx

On the Due you can probably can access SDcard in native mode I believe (the chip is
capable, not sure if all the right pins are brought out) - in theory it would be a lot faster.

Why does the 1mS delaay matter?

If you organize things so that a full movement is complete can't it just wait until the next movement starts?

...R

Hi,

@MarkT: Do you have an tutorial for doing this?

@Robin2: This is the question - in fact I dont know if the 1 mS delay does matter...

It is long enough that I can hear the delay from the stepper noise, when I just perform a streight movement with one stepper...

For slow CnC operations, it does not matter for sure - for fast applications like laser engravings - who knows...

I am just trying to find and fix problems as long as I am in the developing phase...

Kind Regards, and thx

I asked why the delay would matter because I am planning to write some CNC code myself (the project is on the back-burner right now) and my plan is to send the data from my PC to the Arduino in chunks which represent a complete movement on all axes (perhaps a very small movement) on the assumption (hope?) that it won't matter if the cutter waits a while before receiving the next movement command.

Actually, as far as I recall, my idea is that at the start of a movement (the data already being in the Arduino) the Arduino will request data from the PC for the next movement. The PC will send the data and the Arduino USART will receive it in the background while the movement is happening. At the end of the movement the Arduino will read the available serial data, request some more, and then implement the next movement.

I plan to do most of the GCode interpreting on the PC so that it only needs to send the minimum amount of data to the Arduino and the Arduino will have very little calculation to do - and certainly no floating point maths.

...R

Hello Robin,

my plan is to send the data from my PC to the Arduino in chunks which represent a complete movement on all axes

this was exactly how I did the first version of my CnC - I was transmitting data from the pc over the serial interface to the arduino for one reference point, after this was finished, the arduino requested the next reference point.

However, it turned out that this is not an optimal way in doing this - the serial communication was taking to long (could take a few mS) and the file transfer was not stable in terms of transmission time... Imagine your pc gets stuck for a few milli sekonds, because some other application is consuming a peak of processing power - you wont even realize it, but you will hear it from the noise of the steppers, if you perform a streigt cutting operation....

Thats the reason why I buffered the data on an SD-Card... This has also the advantage, that you can build a redo-project function, because the data is on the SD-Card and you do not need to transmitt it again.

Receiving data while the movement is happening is a smart idea... it may work...

I plan to do most of the GCode interpreting on the PC so that it only needs to send the minimum amount of data to the Arduino and the Arduino will have very little calculation to do

This is the same as I have done, I spent almost one year to write an application in visual basic, that can do G-Code interpretation and do some more stuff...

Andreas1984:
Imagine your pc gets stuck for a few milli sekonds, because some other application is consuming a peak of processing power - you wont even realize it, but you will hear it from the noise of the steppers, if you perform a streigt cutting operation....

I'm following this up to get the benefit of your experience ...

Apart from there being a change in the noise of the stepper motors is there any other consequence of the delay?

And ... my notion is that a straight cut would be accomplished as a single movement with just a single set of data sent from the PC - the number of steps for each motor and the total time in which the steps are to take place. The time is just a number. To give a simple example using 2 motors ... if motor1 is to move 100 steps and motor2 is to move 66 steps I would send the numbers 100, 66, 6600 and 10. In a loop from 0 to 6600 motor1 would step every 66 iterations and motor2 would step every 100 iterations. 10 is the "delay" between each iteration which governs the speed of the steps (10 is probably not a realistic number).

...R

Hi,

Apart from there being a change in the noise of the stepper motors is there any other consequence of the delay?

Well, I can't really tell right now - The only operations which I have done on my CnC Hardware right now is to draw shapes using a pencil. For a operation like this the little delays do not matter, because the shape gets drawn, no matter how many and how long the delays are.

My intention is to build a plasma cutter in the first step and as a second step a laser cutter - Depandand on the power and speed you are cutting or engraving, problems could appear trough delays.

At the moment my only delay problem is the 1mS that the SD-Card take to reload data - I still dont know how big or if it will be a serious problem for some tasks - but I think, if you can hear the noise you will also be able to see it for example in an laser engraving...

Plasma Cutting is way slower, I dont see a problem here...

The preparation of the data for MyStepperX or MyStepperY (functions to trigger the stepping engines) do take about 30µS - this does not matter for the performance...

my notion is that a straight cut would be accomplished as a single movement with just a single set of data sent from the PC

It depends on how you interpret the G-Code...

If you are using plain G-Code this is correct - even tough in my application this works a little bit different - the user has to specify the "distance between reference points" (usually 0,1 or so...) - so even a streight cut is consisting of a set of reference points...

I did this, because I found it easyier to implement all the mathematical functions, when using a fixed distance between reference points...

Here are some pictures of the windows application: Maybe you can find some inspiration for your own CnC app.

I want my system to drive a small lathe possibly in 3 axes and I want to use the lathe structure as the basis for a simple (and cheap) 3D printer. I've also come across EDM on the Reprap forum and that looks interesting. Laser cutting card for model buildings is another idea. Maybe none of them will get done.

How did you get the G Code for the snowflakes that have curves?

...R

Hello,

There is a plugin for Inkscape called gcodetools - this way you can draw or vectorize everything and create G-Code from it...

I have written an module, that can interpret this G-Code and to merge it to a format that my CnC application can work with it.

By the way, I have withdrawn the idea to implement a bigger SRAM, to buffer the "G-Code" on it... It seems to be to much effort at the current stage...

Instead I am trying to squeze as much performance out of the SD-Card as possible.

Thereby I will check two things:

a) If I get a performance increase by using a faster SD-Card ... I have ordered this one -> SanDisk MicroSDHC 8GB Extreme Pro

b) If I get a performance increase by using the newest sdfatlib... But this will be trickie - till now I havent had good luck by fooling around with librarys which are not by default provided from the Arduino IDE...