Processing and Arduino Serial Example

image

Hi @josephh, @glv, @PhilHaw, @TfGuy44,

I’ve included you specifically because I’ve seen you reply to Proceesing+Arduino Serial questions, but anyone else is welcome to join in.

I’m sure you’ve noticed that the same problems come up again and again. There must be about twenty common ways of getting the comms not to work. Little chances of anyone unfamiliar getting it to work quickly. The sketches in Examples are not bi-directional and not easily extensible with real values.

I’m wondering if we could make an example that would always work first time. Maybe pinned at the top of the section. As I see it the spec. is:

  • Values passed Processing to Arduino and Arduino to Processing.
  • Integers and floats.
  • Code as simple as possible.
  • Simple instructions.
  • Minimum user edits to make it work (COM port name in Processing).
  • Clear error message if COM port name is wrong.
  • Messages are text not binary, for easy printing.
  • Works first time, does not lock up, overrun, etc.
  • Some checking of received string (but not checksum/acknowledge/repeat).
  • No libraries required.
  • No need for extra hardware connected to Ardino.
  • Easy to add more values.

Instructions (not done yet)

  • how to run it the first time
  • how to add a value Processing-to-Ard
  • how to add a value Ard-to-Processing.
  • comment about merging it into your application.
  • comment about performance and speeding it up.

Please look through the code, maybe try it, suggest improvements. The Arduino code is more clunky that I wanted in places. I usually use sprintf and sscanf but on this occasion sscanf would not compile. A little Googling says it’s unreliable and makes the sketch significantly bigger.

Richard.

Processing:

// Serial Demo

import processing.serial.*;

// Change this com port name to the Arduino port name
// The same name as the port set in the Arduino IDE.  

final String portName = "COM4";


/*

Do not have this sketch running while the Arduino IDE is loading 
the sketch into the Arduino, or the Serial Monitor is open.

*/

final boolean T = true;
final boolean F = false;

color colrText  = color(225, 205,  0);
color colrBgnd  = color(20,   60, 80);

boolean ArdSent = F;    // true after something received from Arduino

Serial port;

void setup()
{
  frameRate(20);
  size(330, 240);
  port = new Serial(this, portName, 9600);  
}

void draw()
{
  
  String exArduino = ""; // Received from Arduino
  String sAry[];         // Received mess split into strings.
  
  // Values received from Arduino...
  long   ArdMillis;      // Millis
  long   ArdLoopCount;   // LoopCount
  int    ArdTemp;        // Temperature

  // Values from Processing returned from Arduino...
  int    ArdFrameCount;  // frameCount from Processing
  int    ArdMouseX;      // mouseX
  int    ArdMouseY;      // mouseY
  float  ArdSine;        // Sine

  String sfC;            // Temporary string frameCount
  int    mX, mY;         // Temporary copies of mouseX,Y
  float  fSine;          // Sine wave
  String sSine;          // Sine value for display.
  String msg = "";       // Message to Arduino. 

  int  iGX, iGY;         // coordinates for value display

  while (port.available() > 0)
  {
    // Get message from Arduino
    exArduino = port.readStringUntil('Z');
    if (exArduino == null) break;
    ArdSent = T;
    exArduino = trim(exArduino);  // remove the CR.
    // Print what we recived.
    println(String.format("%8d From Arduino: %s", frameCount, exArduino));

    // Process the message from Arduino
    sAry = split(exArduino, ",");
    if (sAry.length == 9 && sAry[0].equals("A") && sAry[8].equals("Z"))
    {
      ArdMillis     = parseInt(sAry[1]);
      ArdLoopCount  = parseInt(sAry[2]);
      ArdTemp       = parseInt(sAry[3]);
      ArdFrameCount = parseInt(sAry[4]);
      ArdMouseX     = parseInt(sAry[5]);
      ArdMouseY     = parseInt(sAry[6]);
      ArdSine       = parseFloat(sAry[7]);
    
      // Display the Arduino Values
      iGX = 310;
      iGY =  40;
      fill(colrBgnd);
      rect(220, iGY - 25, 100, 200);
      
      fill(colrText);
      text("Arduino"                         , iGX, iGY); iGY += 30;
      text(String.format("%d", ArdMillis)    , iGX, iGY); iGY += 20;
      text(String.format("%d", ArdLoopCount) , iGX, iGY); iGY += 20;
      text(ArdTemp                           , iGX, iGY); iGY += 30;
      text(ArdFrameCount                     , iGX, iGY); iGY += 20;
      text(ArdMouseX                         , iGX, iGY); iGY += 20;
      text(ArdMouseY                         , iGX, iGY); iGY += 20;
      text(String.format("%.1f", ArdSine)    , iGX, iGY); iGY += 20;
    }
  }

   
  // Display the data labels...
  iGY = 40;
  fill(colrBgnd);
  rect(5, iGY - 25, 210, 200); 
  fill(colrText);
  textAlign(LEFT);  
  textSize(16);
  text("Processing" , 120,  iGY); iGY += 30;
  text("Millis()"   ,  10,  iGY); iGY += 20;
  text("Loop Count" ,  10,  iGY); iGY += 20;
  text("Temperature",  10,  iGY); iGY += 30;
  text("FrameCount" ,  10,  iGY); iGY += 20;
  text("Mouse X"    ,  10,  iGY); iGY += 20;
  text("Mouse Y"    ,  10,  iGY); iGY += 20;
  text("Sine"       ,  10,  iGY); iGY += 20;
  textAlign(RIGHT);
   
  sfC = String.format("%d", frameCount);
  mX = mouseX;
  mY = mouseY;
  fSine = 50 * sin(frameCount * PI/3600);
  // (sine wave deliberately slow to see the decimals separately)
  sSine = String.format("%6.1f", fSine);

  // Display the Processing values...
  iGX = 200;
  iGY = 140;
  text(sfC  , iGX,  iGY); iGY += 20;
  text(mX   , iGX,  iGY); iGY += 20;
  text(mY   , iGX,  iGY); iGY += 20;
  text(sSine, iGX,  iGY); iGY += 20;
 
  // Send to the Arduino.
  // Send half as often as checking for receive, so there is no overrun. 
  if (ArdSent && frameCount % 2 == 0)
  {
    msg = String.format("A,%d,%d,%d,%.1f,Z", frameCount, mX, mY, fSine);
    print(String.format("%8d", frameCount)); print(" To Arduino:   "); print(msg); print(" "); println();
    port.write(msg);
  }  
}

Arduino:

#define nProcessingVals  4  // The number of values we're expecting from Processing

#define pM pinMode
#define dW digitalWrite

#define buffsize 50
char buffer[buffsize];

int   led1 = 13;      // Led shows recived message accepted.
long  frameCount;     // Values from Processing...
int   mouseX;
int   mouseY;
float sine;

long loopCount;       // Ardino loopCount.


// https://theorycircuit.com/arduino-internal-temperature-sensor/
// (copied from there and split to two functions)
double GetTemp0(void)
{
  // The internal temperature has to be used
  // with the internal reference of 1.1V.
  // Channel 8 can not be selected with
  // the analogRead function yet.

  // Set the internal reference and mux.
  ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));
  ADCSRA |= _BV(ADEN);  // enable the ADC

  //delay(20);            // wait for voltages to become stable.
}

double GetTemp1(void)
{
  unsigned int wADC;
  double t;

  ADCSRA |= _BV(ADSC);  // Start the ADC

  // Detect end-of-conversion
  while (bit_is_set(ADCSRA,ADSC));

  // Reading register "ADCW" takes care of how to read ADCL and ADCH.
  wADC = ADCW;

  // The offset of 324.31 could be wrong. It is just an indication.
  t = (wADC - 324.31 ) / 1.22;

  // The returned temperature is in degrees Celcius.
  return (t);
}

void setup()
{
  pM(led1, OUTPUT);
  loopCount = 0;
  Serial.begin(9600);
  Serial.setTimeout(100);
  delay(20);
  Serial.println(__FILE__);
  Serial.println(__DATE__);
  Serial.println(__TIME__);
  GetTemp0();
}

void loop()
{

  int     nRcvd;
  int     index;
  int     temp;
  boolean startA;
  int     commaCount = 0;
  
  if (Serial.available() > 0)
  {
    nRcvd = Serial.readBytesUntil('Z', buffer, buffsize);
    if (nRcvd > 0)
    {
      // Check the message begins with A, has expected commas
      // (we know it ends in Z as we read until Z.
      index = 0; 
      startA = buffer[index] == 'A';
      while ((commaCount < (nProcessingVals + 1)) && (index < nRcvd))
      {
        if (buffer[index++] == ',') {commaCount++;}
      }

      if (startA && commaCount == (nProcessingVals + 1))
      {
        index = 2;  // index of start of first value
        frameCount = atol(&buffer[index]); while (buffer[index++] != ',');
        mouseX     = atoi(&buffer[index]); while (buffer[index++] != ',');
        mouseY     = atoi(&buffer[index]); while (buffer[index++] != ',');
        sine       = atof(&buffer[index]); while (buffer[index++] != ',');
        dW(led1, 1);
      }
      else {dW(led1, 0);}
    }
    else {dW(led1, 0);}
  }
  else {dW(led1, 0);}

  if (loopCount % 2 == 0)
  {
    temp = GetTemp1();
    Serial.print("A")       ; Serial.print(",");
    Serial.print(millis())  ; Serial.print(",");
    Serial.print(loopCount) ; Serial.print(",");
    Serial.print(temp)      ; Serial.print(",");
    Serial.print(frameCount); Serial.print(",");
    Serial.print(mouseX)    ; Serial.print(",");
    Serial.print(mouseY)    ; Serial.print(",");
    Serial.print(sine)      ; Serial.print(",");
    Serial.print("Z");
    Serial.println();  // only for better format on serial monitor
  }
  delay(50);
  loopCount++;
}
3 Likes

nice work and good start to communicate with arduino

Very interesting ideas Richard which I will take a little time to study and mull over.

In the meantime, a couple of things I do in my own Arduino-Processing serial comms are:

  1. When Processing opens the COM port the Arduino should respond with an identifier string that the Processing code can recognize as a valid response. If it doesn’t get it (It should look for it in the serialEvent() handler) then an error message could be displayed. Actually what I am doing is having a text field in the app window that says “Not Connected” and this changes to “Connected” if the correct identifier was received.
  2. To allow the end user to select the correct COM port I populate a drop-list with the available COM ports on the system and let them choose hopefully the correct one. If they do select the right port then the “Not Connected” text can be changed to “Connected”.

Idea #2 breaks your suggested requirement of not requiring any libraries as I use @quark 's excellent G4P library to handle the drop-list and user’s selection. If you are aiming your idea at coders rather than end-users of the finished apps, then maybe it’s not necessary to let the user select the COM port and it can be hard-coded as in your example.

Regards

Phil.

Phil, Can you share your code for a drop-list with available com ports? I have tried now for a couple of days with no success.

Hi Matt, try this code snippet. If there is only one COM port found it will try to open it automatically.

I’m now mostly coding in Python so my Processing skills have got a little rusty :crazy_face:

import processing.serial.*;

// The serial port:
Serial myPort;       

// List all the available serial ports:

void getSerialPorts() {
  String portsList [] = {"No Serial Ports"};

  println("There is/are ", Serial.list().length, "COM Port(s)");

  switch (Serial.list().length) {

  case 0:
    println("No COM ports seen");
    COMList.setItems(portsList, 0);
    break;

  case 1:
    println("Only one COM port seen");

    COMList.setItems(Serial.list(), 0);
    COMList.insertItem(0, "Dummy");
    myPort = new Serial(this, Serial.list()[0], baudRate);
    PortOpen = true;
    myPort.bufferUntil('\n');
    break;

  default:
    println("Multiple COM ports seen");
    COMList.setItems(Serial.list(), 0);
    //   COMList.insertItem(0,"Dummy");
    //   myPort = new Serial(this, Serial.list()[0], baudRate);
    //   PortOpen = true;
    //   myPort.bufferUntil('\n');
    break;
  }
  println(Serial.list());
}

The COMList object is created in the G4P GUI section:

  // ============================== Serial Ports DropList ==================================
  COMList = new GDropList(this, 40, 324, 155, 100, 3, 24);
  COMList.setItems(loadStrings("list_292706"), 0);
  COMList.addEventHandler(this, "COMList_click1");
  label4 = new GLabel(this, 40, 292, 80, 22);
  label4.setText("Serial Port");
  label4.setTextBold();
  label4.setOpaque(false);
  label4.resizeToFit(true, true);

  // =========================== End of The Drop Lists Setup ================================