Arduino & Python Serial Communication

Hello, I’m able to send a string of code from python to my arduino to control a 3D printer but I’m having problems that the arduino isn’t reading the full length string i.e. if I sent “G01 X2 Y5” from python to my arduino it would process the start of the string “G01 X2” (i.e move the x axis 2mm) and stop. Then if I were to send another command “G01 X7 Y1” it would begin by finishing off the previous string “Y5” and then do the beginning of the new string “G01 X7”. I’m not sure why this is happening as I have sent commands from the arduino serial monitor and it processes the full string without any problems. Would anyone have any ideas on fixing this? I know there is code for processing gCode and universal gcode sender software but I need to create a simple version of this myself.

Arduino code:

#define X_STEP_PIN         54 //Printer Head/carriage
#define X_DIR_PIN          55
#define X_ENABLE_PIN       38
#define X_MIN_PIN           3

#define Y_STEP_PIN         60 //Printer Bed 
#define Y_DIR_PIN          61
#define Y_ENABLE_PIN       56
#define Y_MIN_PIN          14

#define Z_STEP_PIN         46
#define Z_DIR_PIN          48
#define Z_ENABLE_PIN       62
#define Z_MIN_PIN          18

typedef enum {  NONE, GOT_G, GOT_X, GOT_Y, GOT_Z } states; // the possible states of the state-machine

states state = NONE;  // current state-machine state
unsigned int currentValue;  // current partial number



void setup ()
{
  Serial.begin (115200);
  state = NONE;

  pinMode(X_STEP_PIN  , OUTPUT);
  pinMode(X_DIR_PIN    , OUTPUT);
  pinMode(X_ENABLE_PIN    , OUTPUT);
  pinMode(X_MIN_PIN, INPUT);
  pinMode(Y_STEP_PIN  , OUTPUT);
  pinMode(Y_DIR_PIN    , OUTPUT);
  pinMode(Y_ENABLE_PIN    , OUTPUT);
  pinMode(Y_MIN_PIN, INPUT);
  pinMode(Z_STEP_PIN  , OUTPUT);
  pinMode(Z_DIR_PIN    , OUTPUT);
  pinMode(Z_ENABLE_PIN    , OUTPUT);
  pinMode(Z_MIN_PIN, INPUT);
}  // end of setup


void gCode (const unsigned int value)
{
  if (value == 01) //linear motion
  {
  }
  else if (value == 92) //Homing command
  {
    while (digitalRead(X_MIN_PIN) == LOW)
    {
      digitalWrite(X_DIR_PIN, LOW);
      for (int x = 0; x < 1; x++)
      {
        digitalWrite(X_STEP_PIN, HIGH);
        delayMicroseconds(500);
        digitalWrite(X_STEP_PIN, LOW);
      }
    }
    while (digitalRead(Y_MIN_PIN) == LOW)
    {
      digitalWrite(Y_DIR_PIN, HIGH);
      for (int x = 0; x < 1; x++)
      {
        digitalWrite(Y_STEP_PIN, HIGH);
        delayMicroseconds(500);
        digitalWrite(Y_STEP_PIN, LOW);
      }
    }
    while (digitalRead(Z_MIN_PIN) == LOW)
    {
      digitalWrite(Z_DIR_PIN, LOW);
      for (int x = 0; x < 1; x++)
      {
        digitalWrite(Z_STEP_PIN, HIGH);
        delayMicroseconds(500);
        digitalWrite(Z_STEP_PIN, LOW);
      }
    }
  }
}


void xMotor (const unsigned int value)
{
  if (value > 0)
  {
    int xSteps = (value * 80); //converting mm to steps required (80 steps per mm)
    digitalWrite(X_DIR_PIN, HIGH);
    for (int x = 0; x < xSteps; x++)
    {
      digitalWrite(X_STEP_PIN, HIGH);
      delayMicroseconds(500);
      digitalWrite(X_STEP_PIN, LOW);
    }
  }
  else
  {
    int xSteps = (abs(value) * 80); //converting mm to steps required (80 steps per mm)
    digitalWrite(X_DIR_PIN, LOW);
    for (int x = 0; x < xSteps; x++)
    {
      digitalWrite(X_STEP_PIN, HIGH);
      delayMicroseconds(500);
      digitalWrite(X_STEP_PIN, LOW);
    }
  }
}


void yMotor (const unsigned int value)
{
  const int ySteps = (value * 80); //converting mm to steps requried (80 steps per mm)
  digitalWrite(Y_DIR_PIN, LOW);
  for (int x = 0; x < ySteps; x++)
  {
    digitalWrite(Y_STEP_PIN, HIGH);
    delayMicroseconds(500);
    digitalWrite(Y_STEP_PIN, LOW);

  }
}

void zMotor (const unsigned int value)
{
  const int zSteps = (value * 1280); //converting mm to steps requried (80 steps per mm)
  digitalWrite(Z_DIR_PIN, HIGH);
  for (int x = 0; x < zSteps; x++)
  {
    digitalWrite(Z_STEP_PIN, HIGH);
    delayMicroseconds(500);
    digitalWrite(Z_STEP_PIN, LOW);

  }
}


void handlePreviousState ()
{
  switch (state)
  {
    case GOT_G:
      gCode (currentValue);
      break;
    case GOT_X:
      xMotor (currentValue);
      break;
    case GOT_Y:
      yMotor (currentValue);
      break;
    case GOT_Z:
      zMotor (currentValue);
      break;
  }  // end of switch

  currentValue = 0;
}


void processIncomingByte (const byte c)
{
  if (isdigit (c))
  {
    currentValue *= 10;
    currentValue += c - '0';
  }  // end of digit
  else
  {

    // The end of the number signals a state change
    handlePreviousState ();

    // set the new state, if we recognize it
    switch (c)
    {
      case 'G':
        state = GOT_G;
        break;
      case 'X':
        state = GOT_X;
        break;
      case 'Y':
        state = GOT_Y;
        break;
      case 'Z':
        state = GOT_Z;
        break;
      default:
        state = NONE;
        break;
    }  // end of switch on incoming byte
  } // end of not digit

} // end of processIncomingByte

void loop ()
{
  while (Serial.available ())
    processIncomingByte (Serial.read());

}  // end of loop

Please note my python code is a bit messy and not complete.
Python Code:

from Tkinter import *
import serial

root = Tk()
root.title("3D Printer Control Software")
root.geometry("800x500")

ser = serial.Serial('com4', 115200)

def xClockStep():
    ser.write('G01 X1')

def xAntiStep():
    ser.write('')

def yClockStep():
    ser.write('G01 Y1')

def yAntiStep():
    ser.write('')

def zClockStep():
    ser.write('G01 Z1')

def zAntiStep():
    ser.write('')

def xClockRev():
    ser.write('G01 X10')

def xAntiRev():
    ser.write('')

def yClockRev():
    ser.write('G01 Y10')

def yAntiRev():
    ser.write('')

def zClockRev():
    ser.write('G01 Z10')

def zAntiRev():
    ser.write('')

def allHome():
    ser.write('G92')

label1 = Label(text="Printer Axis Controls:")
label1.grid(row=0, columnspan=4)

button1 = Button(text="X-Axis Clockwise \nStep", fg="white", bg="black", command=xClockStep)
button1.grid(row=1, column=1, pady=20)
button2 = Button(text="X-Axis Anticlockwise \nStep", fg="white", bg="black", command=xAntiStep)
button2.grid(row=1, column=0, pady=20)
button3 = Button(text="X-Axis Clockwise \nHalf Revolution", fg="white", bg="black", command=xClockRev)
button3.grid(row=1, column=3, stick=E, pady=20)
button4 = Button(text="X-Axis Anticlockwise \nHalf Revolution", fg="white", bg="black", command=xAntiRev)
button4.grid(row=1, column=2, pady=20)

button5 = Button(text="Y-Axis Clockwise \nStep", fg="white", bg="black", command=yClockStep)
button5.grid(row=2, column=1, pady=20)
button6 = Button(text="Y-Axis Anticlockwise \nStep", fg="white", bg="black", command=yAntiStep)
button6.grid(row=2, column=0, pady=20)
button7 = Button(text="Y-Axis Clockwise \nHalf Revolution", fg="white", bg="black", command=yClockRev)
button7.grid(row=2, column=3, stick=E, pady=20)
button8 = Button(text="Y-Axis Anticlockwise \nHalf Revolution", fg="white", bg="black", command=yAntiRev)
button8.grid(row=2, column=2, pady=20)

button9 = Button(text="Z-Axis Clockwise \nStep", fg="white", bg="black", command=zClockStep)
button9.grid(row=3, column=1, pady=20)
button10 = Button(text="Z-Axis Anticlockwise \nStep", fg="white", bg="black", command=zAntiStep)
button10.grid(row=3, column=0, pady=20)
button11 = Button(text="Z-Axis Clockwise \nRevolution", fg="white", bg="black", command=zClockRev)
button11.grid(row=3, column=3, stick=E, pady=20)
button12 = Button(text="Z-Axis Anticlockwise \nRevolution", fg="white", bg="black", command=zAntiRev)
button12.grid(row=3, column=2, pady=20)

button13 = Button(text="All Axes Home", fg="black", bg="green", command=allHome)
button13.grid(row=4, column=0)

root.mainloop()

I recommend that your Arduino receives the complete message before trying to parse any of it. Have a look at the 3rd example and the parse example in Serial Input Basics

Also have a look at this Python - Arduino demo which was written prior to the other stuff.

For example change this

ser.write('G01 Y1')

to

ser.write('<G01 Y1>')

It may also be wise for the Arduino to control the communication by sending (say) ‘M’ to the PC when it is ready for More data.

…R

Hi Robin2, thanks for the reply! I actually found your tutorial forum about this topic and I found it very useful.

Robin2:
For example change this

ser.write('G01 Y1')

to

ser.write('<G01 Y1>')

In regards to your advise, I have changed my code to suit with your tutorial examples and added my functions to perform the necessary actions required. However, my code compiles but with errors and it doesn’t work. My problem is the parsedata() function. I don’t understand what this line of code is actually doing (char * strtokIndx;) , I’ve googled it but yet I’m none the wiser.

Within my code I have split the string into three arrays and converted them to bytes as I don’t know how to call a function with an array. I’ve tried several different ways to process the separated strings but I’m having no joy.

Also when I generate a gCode file I don’t want to be manually adding ‘<’ ‘>’ these to the start and end of each line of code. A gCode file has a list of commands, with each new line consisting of a new string to be processed. How would I incorporate the idea of ‘<’ ‘>’ but with a return (’\r’) and a new line (’\n’)?

Here is my new code:

#define X_STEP_PIN         54 //Printer Head/carriage
#define X_DIR_PIN          55
#define X_ENABLE_PIN       38
#define X_MIN_PIN           3

#define Y_STEP_PIN         60 //Printer Bed 
#define Y_DIR_PIN          61
#define Y_ENABLE_PIN       56
#define Y_MIN_PIN          14

#define Z_STEP_PIN         46
#define Z_DIR_PIN          48
#define Z_ENABLE_PIN       62
#define Z_MIN_PIN          18

const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

char part1[numChars] = {0};
char part2[numChars] = {1};
char part3[numChars] = {2};

byte string1;
byte string2;
byte string3;

boolean newData = false;

typedef enum {NONE, GOT_G, GOT_X, GOT_Y, GOT_Z } states; // the possible states of the state-machine

states state = NONE;  // current state-machine state
unsigned int currentValue;  // current partial number


void setup() {
  Serial.begin(115200);
  Serial.println("Enter data in this style <G01 X12 Y300>  ");
  Serial.println();
  state = NONE;

  pinMode(X_STEP_PIN  , OUTPUT);
  pinMode(X_DIR_PIN    , OUTPUT);
  pinMode(X_ENABLE_PIN    , OUTPUT);
  pinMode(X_MIN_PIN, INPUT);
  pinMode(Y_STEP_PIN  , OUTPUT);
  pinMode(Y_DIR_PIN    , OUTPUT);
  pinMode(Y_ENABLE_PIN    , OUTPUT);
  pinMode(Y_MIN_PIN, INPUT);
  pinMode(Z_STEP_PIN  , OUTPUT);
  pinMode(Z_DIR_PIN    , OUTPUT);
  pinMode(Z_ENABLE_PIN    , OUTPUT);
  pinMode(Z_MIN_PIN, INPUT);

}

void loop() {
  recvWithStartEndMarkers();
  if (newData == true) {
    strcpy(tempChars, receivedChars);
    // this temporary copy is necessary to protect the original data
    //   because strtok() used in parseData() replaces the commas with \0
    parseData();
    newData = false;
  }
}

void recvWithStartEndMarkers() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '<';
  char endMarker = '>';
  char rc;

  if (Serial.available() > 0 && newData == false) {
    rc = Serial.read();

    if (recvInProgress == true) {
      if (rc != endMarker) {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx >= numChars) {
          ndx = numChars - 1;
        }
      }
      else {
        receivedChars[ndx] = '\0'; // terminate the string
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }
    else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
}

void parseData() {      // split the data into its parts

  char * strtokIndx; // this is used by strtok() as an index

  strtokIndx = strtok(tempChars, " ");      // get the first part - the string
  strcpy(part1, strtokIndx); // copy it to messageFromPC
  string1 = byte(part1);
  processIncomingByte(string1);

  strtokIndx = strtok(NULL, " "); // this continues where the previous call left off
  strcpy(part2, strtokIndx);     // convert this part to an integer
  string2 = byte(part2);
  processIncomingByte(string2);

  strtokIndx = strtok(NULL, " ");
  strcpy(part3, strtokIndx);     // convert this part to a float
  string3 = byte(part3);
  processIncomingByte(string3);

}

void gCode (const unsigned int value)
{
  if (value == 01) //linear motion
  {
  }
  else if (value == 92) //Homing command
  {
    while (digitalRead(X_MIN_PIN) == LOW)
    {
      digitalWrite(X_DIR_PIN, LOW);
      for (int x = 0; x < 1; x++)
      {
        digitalWrite(X_STEP_PIN, HIGH);
        delayMicroseconds(500);
        digitalWrite(X_STEP_PIN, LOW);
      }
    }
    while (digitalRead(Y_MIN_PIN) == LOW)
    {
      digitalWrite(Y_DIR_PIN, HIGH);
      for (int x = 0; x < 1; x++)
      {
        digitalWrite(Y_STEP_PIN, HIGH);
        delayMicroseconds(500);
        digitalWrite(Y_STEP_PIN, LOW);
      }
    }
    while (digitalRead(Z_MIN_PIN) == LOW)
    {
      digitalWrite(Z_DIR_PIN, LOW);
      for (int x = 0; x < 1; x++)
      {
        digitalWrite(Z_STEP_PIN, HIGH);
        delayMicroseconds(500);
        digitalWrite(Z_STEP_PIN, LOW);
      }
    }
  }
}


void xMotor (const unsigned int value)
{
  if (value > 0)
  {
    int xSteps = (value * 80); //converting mm to steps required (80 steps per mm)
    digitalWrite(X_DIR_PIN, HIGH);
    for (int x = 0; x < xSteps; x++)
    {
      digitalWrite(X_STEP_PIN, HIGH);
      delayMicroseconds(500);
      digitalWrite(X_STEP_PIN, LOW);
    }
  }
  else
  {
    int xSteps = (abs(value) * 80); //converting mm to steps required (80 steps per mm)
    digitalWrite(X_DIR_PIN, LOW);
    for (int x = 0; x < xSteps; x++)
    {
      digitalWrite(X_STEP_PIN, HIGH);
      delayMicroseconds(500);
      digitalWrite(X_STEP_PIN, LOW);
    }
  }
}


void yMotor (const unsigned int value)
{
  const int ySteps = (value * 80); //converting mm to steps requried (80 steps per mm)
  digitalWrite(Y_DIR_PIN, LOW);
  for (int x = 0; x < ySteps; x++)
  {
    digitalWrite(Y_STEP_PIN, HIGH);
    delayMicroseconds(500);
    digitalWrite(Y_STEP_PIN, LOW);
  }
}

void zMotor (const unsigned int value)
{
  const int zSteps = (value * 1280); //converting mm to steps requried (80 steps per mm)
  digitalWrite(Z_DIR_PIN, HIGH);
  for (int x = 0; x < zSteps; x++)
  {
    digitalWrite(Z_STEP_PIN, HIGH);
    delayMicroseconds(500);
    digitalWrite(Z_STEP_PIN, LOW);

  }
}

void processIncomingByte (byte c)
{
  if (isdigit (c))
  {
    currentValue *= 10;
    currentValue += c - '0';
  }  // end of digit
  else
  {

    // The end of the number signals a state change
    handlePreviousState ();

    // set the new state, if we recognize it
    switch (c)
    {
      case 'G':
        state = GOT_G;
        break;
      case 'X':
        state = GOT_X;
        break;
      case 'Y':
        state = GOT_Y;
        break;
      case 'Z':
        state = GOT_Z;
        break;
      default:
        state = NONE;
        break;
    }  // end of switch on incoming byte
  } // end of not digit

}

void handlePreviousState ()
{
  switch (state)
  {
    case 'GOT_G':
      gCode (currentValue);
      break;
    case 'GOT_X':
      xMotor (currentValue);
      break;
    case 'GOT_Y':
      yMotor (currentValue);
      break;
    case 'GOT_Z':
      zMotor (currentValue);
      break;
    default:
      state = NONE;
      break;
  }  // end of switch

  currentValue = 0;
}

rmcd06:
However, my code compiles but with errors and it doesn't work.

You have not posted the error message and I can't help until you do.

My problem is the parsedata() function. I don't understand what this line of code is actually doing (char * strtokIndx;) , I've googled it but yet I'm none the wiser.

That line just creates a variable that is used in conjunction with strtok() to hold the address of the character where the token was found. Then strcpy() or atoi() uses it to copy or convert that piece of the string.

I need to think a bit more about your other 2 questions

...R

I am a bit confused about your approach to parsing.

Take this as an example ser.write(’’)

There are 4 pieces of information here G 01 Y 1

If you split it into two char arrays using the space as a delimiter you will get G01 and Y1

You can then analyse both arrays the same way - take the first character as character and take the rest as a number

You should be able to analyse the parts like this

void analyse( char inarray[] ) {
   codeLetter = inarray[0];
   codeVal = atoi(inarray[1]);
}

by calling that function like

analyse(part1);

or

analyse(part2);

I am assuming you have two global variables defined as

char codeLetter;
int codeVal;

Then you can see what the code letter is and act on it with something like

switch(codeLetter) {
   case 'G'
     // do something using the value in codeVal
     break;
   case 'Y'
     // do something else
     break;

I probably don’t have the syntax exactly right but it should give the idea.

…R

rmcd06:
How would I incorporate the idea of ‘<’ ‘>’ but with a return (’\r’) and a new line (’\n’)?

If your Python program is reading the GCode file it should be straightforward to get it to insert a ‘<’ before each message and replace the \r\n with a ‘>’

But maybe I have misunderstood.

In future, one question at a time :slight_smile:

…R

Thanks for the help Robin2 but I have tried your advise of analysing the array after the full string has been split. To gain understanding of this step, I coded it to print the results into the serial monitor.

void parseData() {      // split the data into its parts

  char * strtokIndx; // this is used by strtok() as an index

  strtokIndx = strtok(tempChars, " ");      // get the first part - the string
  strcpy(part1, strtokIndx); // copy it to part1
  analyse(part1);

  strtokIndx = strtok(NULL, " "); // this continues where the previous call left off
  strcpy(part2, strtokIndx);     // copy it to part2
  analyse(part2);

  strtokIndx = strtok(NULL, " ");
  strcpy(part3, strtokIndx);     // copy it to part3
  analyse(part3);

}

void analyse (char inarray[]){
  codeLetter = inarray[0];
  codeVal = atoi(inarray[1]);
  Serial.println("letter is: ");
  Serial.println(codeLetter);
  Serial.println("number is: ");
  Serial.println(codeVal);
  
}

For instance, if I serial write , the serial monitor prints back:

letter is: 
G
number is: 
0
letter is: 
X
number is: 
0
letter is: 
Y
number is: 
0

For some reason it wont give me back the original number I typed in and am not to sure why? Any guidance would be appreciated.

I did say I might not have the syntax exactly right. I am not a C++ expert.

Try this

codeVal = atoi(& inarray[1]);

…R

Thank you so much Robin2, that’s it working now from both the serial monitor and from python. I’m going to try working on sending a gcode file from python and see if I can code it to automatically add the ‘<’ ‘>’ within python for each new line of code. Can’t thank you enough for the help! Greatly appreciated!

I have been bothered during the night because I forgot to suggest that you interpret the GCode on your PC and just send simple numbers to the Arduino.

I have a system for a small lathe in which the PC sends something like <ttttt, aaaa, bbbb, cccc> to the Arduino controlling the stepper motors. tttt is the total time (in microsecs) for a move. aaaa is the interval between steps (in µsecs) for motor A, bbbb and cccc are the intervals for the other motors.

All the Arduino needs to do is to check the time and every aaaa µsecs cause a pulse for motor A, every bbbb µsecs a pulse for motor B etc. And when tttt µsecs have elapsed it stops sending pulses and gets the next instruction which will be waiting in the serial input buffer.

…R