3d Digitzer Arm build

Hi, thanks for any help.
Question = How do I know a Processing app is getting a signal from the COMPORT?

I’m stuck with this project. https://www.youtube.com/watch?v=8feFq3ugSLI
I’ve built the hardware, Wired it, Installed the Processing, PeasyCam, Java and uploaded the Arduino sketch. The Processing software called “DEMO” runs on Windows, Arduino’s IDE serial monitor see this data from the rotary encoders via the Arduino (see attached).

The “DEMO” app does not seem to get a signal.

Make sure you are not running the serial monitor. If that doesn’t work, then you need to run a very very simple program where you tap into the serial data and output those values. That would be a good start before trying to figure it the code that you have there.

Kf

Iirc there are different com ports - I had to switch the right one

I even had to unplug and plug every time :wink:

Kfrajer, Dhrisir, thanks

Sadly it doesn’t work with the Arduino IDE serial monitor off, or the whole Arduino IDE not open.

I think this line in the Processing code says; Look to the Comport for anything greater than ‘0’ and use that data. So if the PC see’s the MEGA then the code should, right?

void serialEvent(Serial myPort)
{
while (myPort.available ()>0)

Question, In my screenshot of the serial monitor (the top right window, COM6) has some small square characters. Is this a Baud Rate issue? I guess I have to have the MEGA talking to the PC and the Processing code all at the same baud rate?

Again thanks all for your help.

Yes, the baud rate needs to be the same in both the Ardunio and Processing interfaces.

Please provide all your code. So far, the code you show above is not proper.

Kf

what’s the best way to include the code?

Processing Code has 4 tabs

/////////////////////////////////////////GUI2_demo4_plus Tab
import processing.serial.*;

Serial myPort;  // Create object from Serial class

float PX=0;
float PY=0;
float PZ=0;
float ROT=0;

float sx=0;
float sy=0;
float sz=0;
float sa=0;

char datatype;
byte [] inData = new byte[5];
int inptr=0;
int rstate=0;

float www=0;

void serialEvent(Serial myPort) 
{
  while (myPort.available ()>0)
  {
    switch(rstate)
    {
    case 0:
      datatype = myPort.readChar();
      if (datatype=='x'||datatype=='y'||datatype=='z'||datatype=='a')
      {
        rstate=1;
        inptr=0;
      }
      break;

    case 1:
      inData[inptr++]=(byte)myPort.readChar();
      if (inptr==4)
      {
        int intbit = 0;
        intbit = (inData[3] << 24) | ((inData[2] & 0xff) << 16) | ((inData[1] & 0xff) << 8) | (inData[0] & 0xff);
        float f = Float.intBitsToFloat(intbit);

        switch(datatype)
        {
        case 'x': 
          sx=f;
          break;
        case 'y': 
          sy=f;
          break;
        case 'z': 
          PX=sx;
          PY=sy;
          PZ=f;
          ROT=sa;
         
          break;
        case 'a':
          sa=f;
          break;
        }
        rstate=0;
      }        
      break;
    }
  }
}

class pointer
{
  PVector tip = new PVector(0, 0, 0);
  float rotation=0;
  boolean active=false;
  boolean demoMode=false;
  pointer(PApplet p,int index)
  {
    myPort =new Serial(p, Serial.list()[0], 19200);
    active=true;

  }

  void update()
  {
    if (demoMode)
    {
      tip.x=(mouseX-width/2)/4;
      tip.y=(mouseY-height/2)/4;
      tip.z=40+20*sin(www+=0.01);//random(20,50);//40.0;
    }
    else
    {
//      tip.set(PX,PY,PZ);
      tip.x=PX;
      tip.y=PY;
      tip.z=PZ;
      rotation=ROT;
    }
  }
};




///////////////////////////////////////////////////////////////////////////OBJECTS TAB

//============================================================
//  Measuring objects for 3D digitizer
//  Objects implements function for 3D and 2D screen drawing and
//  2D export.
//  (F)DZL 2015   
//============================================================
//============================================================
// Base class for all objects
//============================================================
public abstract class object
{
  abstract void draw();  //-Draw to screen (3D)
  abstract void modify(PVector p); //-Modify object
  abstract void project(); //-Draw to 2D
  abstract void export(); //-Export (draw .PDF compatible)

};
//============================================================
// Single 3D point (1mmm box)
//============================================================
class gpoint extends object
{
  PVector pos;
  color lineColor=color(255);
  color projectColor=color(100);
  color exportColor=color(0);
  gpoint(PVector p)
  {
    pos.x=p.x;
    pos.y=p.y;
    pos.z=p.z;
  }
  gpoint(float x, float y, float z)
  {
    pos=new PVector(x, y, z);
  }
  void draw()
  {
    stroke(lineColor);
    pushMatrix();
    translate(pos.x, pos.y, pos.z);
    box(1);
    popMatrix();
  }

  void project()
  {
    stroke(projectColor);
    line(pos.x-5, pos.y, pos.x+5, pos.y);
    line(pos.x, pos.y-5, pos.x, pos.y+5);
  }

  void export()
  {
    stroke(exportColor);
    line(pos.x-5, pos.y, pos.x+5, pos.y);
    line(pos.x, pos.y-5, pos.x, pos.y+5);
  }

  void modify(PVector p)
  {
    pos.x=p.x;
    pos.y=p.y;
    pos.z=p.z;
  }
}

//============================================================
// Single 10mm circle 
//============================================================
class gcircle extends object
{
  PVector pos;
  color projectColor=color(100);
  color exportColor=color(0);
  color lineColor=color(255);
  boolean filled=true;
  gcircle(PVector p)
  {
    pos.x=p.x;
    pos.y=p.y;
    pos.z=p.z;
  }
  gcircle(float x, float y, float z)
  {
    pos=new PVector(x, y, z);
  }
  void draw()
  {
    stroke(lineColor);
    pushMatrix();
    translate(pos.x, pos.y, pos.z);
    ellipse(0, 0, 10, 10);
    popMatrix();
  }
  void project()
  {
    stroke(projectColor);
    ellipse(pos.x, pos.y, 10, 10);
  }

  void export()
  {
    if (filled)
    {
      noStroke();
      fill(exportColor);
    } else
    {
      noFill();
      stroke(exportColor);
    }
    ellipse(pos.x, pos.y, 10, 10);
  }

  void modify(PVector p)
  {
    pos.x=p.x;
    pos.y=p.y;
    pos.z=p.z;
  }
}

//============================================================
//  Open loop feature
//============================================================
class gfeature extends object
{
  boolean filled=true;
  PVector pos=new PVector(0, 0, 0);
  color projectColor=color(100);
  color exportColor=color(100, 100, 0);
  color lineColor=color(255, 255, 0);
  color anchorColor=color(0, 255, 0);
  ArrayList<PVector> figure = new ArrayList<PVector>();
  gfeature(PVector p)
  {
    pos.x=p.x;
    pos.y=p.y;
    pos.z=p.z;
    figure.add(new PVector(pos.x, pos.y, pos.z));
  }
  gfeature(float x, float y, float z)
  {
    pos=new PVector(x, y, z);
    figure.add(new PVector(pos.x, pos.y, pos.z));
  }

  void draw()
  {
    pushMatrix();
    translate(pos.x, pos.y, pos.z);
    stroke(anchorColor);
    ellipse(0, 0, 5, 5);
    popMatrix();
    float x0=pos.x;
    float y0=pos.y;
    float z0=pos.z;

    stroke(lineColor);

    for (PVector p : figure)
    {
      line(x0, y0, z0, p.x, p.y, p.z);
      x0=p.x;
      y0=p.y;
      z0=p.z;
    }
    line(x0, y0, z0, pos.x, pos.y, pos.z);
  }

  void project()
  {
    stroke(projectColor);
    float x0=pos.x;
    float y0=pos.y;
    float z0=pos.z;
    for (PVector p : figure)
    {
      line(x0, y0, p.x, p.y);
      x0=p.x;
      y0=p.y;
      z0=p.z;
    }
    line(x0, y0, pos.x, pos.y);
  }

  void export()
  {
    if (filled)
    {
      noStroke();
      fill(exportColor);
    } else
    {
      noFill();
      stroke(exportColor);
    }

    PShape loop=createShape();
    loop.beginShape();

    for (PVector p : figure)
    {
      loop.vertex(p.x, p.y);
    }
    loop.vertex(pos.x, pos.y);
    loop.endShape();
    shape(loop);
  }

  void modify(PVector p)
  {
    figure.add(new PVector(p.x, p.y, p.z));
  }
}


////////////////////////////////////////////////////////////////////////////POINTER TAB
import processing.serial.*;

Serial myPort;  // Create object from Serial class

float PX=0;
float PY=0;
float PZ=0;
float ROT=0;

float sx=0;
float sy=0;
float sz=0;
float sa=0;

char datatype;
byte [] inData = new byte[5];
int inptr=0;
int rstate=0;

float www=0;

void serialEvent(Serial myPort) 
{
  while (myPort.available ()>0)
  {
    switch(rstate)
    {
    case 0:
      datatype = myPort.readChar();
      if (datatype=='x'||datatype=='y'||datatype=='z'||datatype=='a')
      {
        rstate=1;
        inptr=0;
      }
      break;

    case 1:
      inData[inptr++]=(byte)myPort.readChar();
      if (inptr==4)
      {
        int intbit = 0;
        intbit = (inData[3] << 24) | ((inData[2] & 0xff) << 16) | ((inData[1] & 0xff) << 8) | (inData[0] & 0xff);
        float f = Float.intBitsToFloat(intbit);

        switch(datatype)
        {
        case 'x': 
          sx=f;
          break;
        case 'y': 
          sy=f;
          break;
        case 'z': 
          PX=sx;
          PY=sy;
          PZ=f;
          ROT=sa;
         
          break;
        case 'a':
          sa=f;
          break;
        }
        rstate=0;
      }        
      break;
    }
  }
}

class pointer
{
  PVector tip = new PVector(0, 0, 0);
  float rotation=0;
  boolean active=false;
  boolean demoMode=false;
  pointer(PApplet p,int index)
  {
    myPort =new Serial(p, Serial.list()[0], 19200);
    active=true;

  }

  void update()
  {
    if (demoMode)
    {
      tip.x=(mouseX-width/2)/4;
      tip.y=(mouseY-height/2)/4;
      tip.z=40+20*sin(www+=0.01);//random(20,50);//40.0;
    }
    else
    {
//      tip.set(PX,PY,PZ);
      tip.x=PX;
      tip.y=PY;
      tip.z=PZ;
      rotation=ROT;
    }
  }
};



////////////////////////////////////////////////////////////////////////////////GUI TAB

//============================================================
//  Simple GUI controls
//  Controls are activated by key and mouse click
//  Contextual hels is displayed on mouse over
//  (F)DZL 2015
//============================================================

class control
{
  //Position and size
  int x=0;
  int y=0;
  int w=60;
  int h=35;
  //-Colors
  color faceColor=color(98, 206, 198);
  color textColor=color(75, 42, 0);
  //-Key to activate control
  char hotkey;
  //-Discriptive caption
  String caption;
  //-Mouse-over help 
  String help;
  //-Mouse and key state
  boolean clickState=false;
  boolean keyState=false;
  boolean mouseOver=false;
  //-Updated for redraw
  boolean change=false;
  //-Typematic control
  int timer=0;
  boolean typematic=false;
  //-Constructor
  control(int px, int py, char k, String c,String h)
  {
    x=px;
    y=py;
    hotkey=k;
    caption=c;
    help=h;
  }
  //-Draw
  void draw()
  {
    if (clickState||keyState)
      stroke(255, 0, 0);
    else
      stroke(50);
    fill(faceColor);
    rect(x, y, w, h);
    fill(textColor);
    textSize(20);
    text("-"+hotkey+"-", x+5, y+17);
    textSize(14);
    text(caption, x+8, y+33);
  }
  //-Draw help
  void drawHelp()
  {
    textSize(20);
    fill(200);
    text(help,x+w+5,y+20);
  }
  //-Update. Handles keys and mouse
  boolean update()
  {
    boolean result=false;
    mouseOver=false;
    if ((mouseX>x)&&(mouseX<(x+w)) && (mouseY>y)&&(mouseY<(y+h)))
    {
      mouseOver=true;
      if (mousePressed==true)
      {
        if (!clickState)
        {
          clickState=true;
          change=true;
        }
      } else
      {
        if (clickState==true)
        {
          result=true;
          clickState=false;
          change=true;
        }
      }
    } else
      if (mousePressed==false)
    {      
      clickState=false;
      change=true;
    }     


    if (key==hotkey)
    {
      if (keyPressed==true)
      {
              mouseOver=true;

        if (!keyState)
        {
          keyState=true;
          change=true;
          timer=millis()+500;
          result=true;
        } else
        {
          if (typematic)
          {
            if (millis()>timer)
            {
              timer+=10;
              result=true;
            }
          }
        }
      } else
      {
        if (keyState==true)
        {
          keyState=false;
          change=true;
        }
      }
    } else
      if (keyPressed==false)
    {
      keyState=false;
      change=true;
    }
    return result;
  }
};
Here's the Arduino Sketch:

///////////////////////////////////////////////digitizer2_2.ino

//*************************************************
//  Demo code for 3D digitizer
//  See description of project here:
//
//  (F)Dzl 2018  
//  
//*************************************************

#include "dzl_encoders.h"

IQencoder E0, E1, E2, E3;

//*************************************************
//  Mechanical set up:
//*************************************************
#define ARM1 176.38       // Arm E1-R2 length[mm.]
#define ARM2 176.38       // Arm E2-tip length[mm.]
//Offsets from mechanical set-up:
#define Z_OFFSET 80.782   // E1 axis height above table [mm.]
#define X_OFFSET -125.0   // Distance from E0 axis to preset position [mm.]
#define Y_OFFSET 125.0    // Distance from E0 axis to preset position [mm.]
//Angles from mechanical set-up:
#define E0_PRESET -45.0   
#define E1_PRESET 32.048
#define E2_PRESET -113.174
#define E3_PRESET 0.0

//*************************************************
//  Send coordinates to processing program
//*************************************************
void sendFloat(float f, unsigned t) {
  byte * b = (byte *) &f;
  Serial.write(t);
  Serial.write(b[0]);
  Serial.write(b[1]);
  Serial.write(b[2]);
  Serial.write(b[3]);
}

void setup()
{
  Serial.begin(19200);

  setEncoderRate(10000);
  
  //Attach encoders so anti-clockwise rotation is positive:
  E0.attach(9, 8);      //E0 (Q,I)  
  E1.attach(11, 10);    //E1 (I,Q)
  E2.attach(13, 12);    //E2 (I,Q)
  E3.attach(7, 6);      //tt (Q,I)

  delay(10);            //-Allow time to settle

  //Preset encoders:
  E0.setDegrees(E0_PRESET);     //Horizontal encoder (corner)
  E1.setDegrees(E1_PRESET);     //First vertical encoder
  E2.setDegrees(E2_PRESET);     //Second vertical encoder
  E3.setDegrees(E3_PRESET);     //Turntable
}

void loop()
{
  //Read encoders in radians
  double A = E1.getRadians();
  double B = E2.getRadians();
  double C = E0.getRadians();
  double D = E3.getRadians();

  //Calculate distance from E0 axis to tip 'r' and height above table 'z'
  double r = cos(A) * ARM1 + cos(A + B) * ARM2;
  double z = (sin(A) * ARM1) + (sin(A + B) * ARM2) + Z_OFFSET;

  //Calculate tip x,y
  double x = r * cos(C)+ X_OFFSET;
  double y = r * sin(C)+ Y_OFFSET;

/*
  //Print encoder angles:
  Serial.print(E0.getDegrees());
  Serial.print(",");
  Serial.print(E1.getDegrees());
  Serial.print(",");
  Serial.print(E2.getDegrees());
  Serial.print(",");
  Serial.println(E3.getDegrees());
*/

/*
  //Print coordinates:
  Serial.print(x);
  Serial.print(",");
  Serial.print(y);
  Serial.print(",");
  Serial.print(z);
  Serial.print(",");
  Serial.println(D);
*/

  //Send coordinates to Processing
  sendFloat(x, 'x');
  sendFloat(y, 'y');
  sendFloat(z, 'z');
  sendFloat(D, 'a');
  
  delay(100);

}


/////////////////////////////////////////////////////////dzl_encoders.h

//*****************************************************
//  Quadrature encoder library V1.0
//  (F) Dzl 2018
//  Drives encoders on ATMEGA328 based and compatible
//*****************************************************

#ifndef __ENCODERS
#define __ENCODERS

//*****************************************************
//  Settings
//*****************************************************
#define MAX_ENCODERS 8             //Restrict number of encoders
#define STATES_PER_REV 2400        //Lines per rev * 2
#define DEFAULT_RATE 10000         //[Hz]

volatile unsigned int timerIncrement = (16000000L / DEFAULT_RATE);

//*****************************************************
//  Some macros
//*****************************************************
#define SET(x,y) (x |=(1<<y))          //-Bit set/clear macros
#define CLR(x,y) (x &= (~(1<<y)))      // |
#define CHK(x,y) (x & (1<<y))          // |
#define TOG(x,y) (x^=(1<<y))           //-+

//*****************************************************
// Encoder state machine:
// encoder offset = encref [old state][encoder input]
// 128 -> state machine error (impossible state change)
//*****************************************************
volatile int encref[4][4] =
{
  //  0  1  2  3
  {
    0, 1, -1, 128
  }
  ,//0
  {
    -1, 0, 128, 1
  }
  ,//1
  {
    1, 128, 0, -1
  }
  ,//2
  {
    128, -1, 1, 0
  }// 3
};

int setEncoderRate(unsigned int rate)
{
  if ((rate <= 20000) && (rate >= 250))
  {
    timerIncrement = 16000000L / rate;
    return rate;
  }
  return -1;
}



volatile unsigned char attachedEncoders = 0;
class IQencoder* encoders[MAX_ENCODERS];

class IQencoder
{
  public:
    volatile int encoderCounter;
    unsigned char I_pin;
    unsigned char Q_pin;
    unsigned char state;

    void attach(unsigned char I, unsigned char Q)
    {
      I_pin = I;
      Q_pin = Q;
      state = 0;
      pinMode(I_pin, INPUT_PULLUP);
      pinMode(Q_pin, INPUT_PULLUP);
      encoderCounter = 0;
      if (attachedEncoders < MAX_ENCODERS)
        encoders[attachedEncoders++] = this;  //-Add encoder to sampling system

      if (attachedEncoders == 1)              //-First encoder starts sampling system
      {
        TCCR1A = 0x00;                        //-Timer 1 inerrupt
        TCCR1B = 0x01;                        // |
        TCCR1C = 0x00;                        // |
        SET(TIMSK1, OCIE1A);                  // |
        sei();                                //-+
      }
    }

    void setDegrees(float deg)
    {
      encoderCounter = (deg / 360.0) * (float)STATES_PER_REV;
    }
    void setRadians(float rad)
    {
      encoderCounter = (rad / M_PI * 2) * (float)STATES_PER_REV;
    }

    float getRadians()
    {
      return (double)encoderCounter * M_PI * 2 / (double)STATES_PER_REV;
    }
    float getDegrees()
    {
      return (double)encoderCounter * 360.0 / (double)STATES_PER_REV;
    }
};

//*****************************************************
//  Global encoder sampler timer interrupt
//*****************************************************
SIGNAL(TIMER1_COMPA_vect)
{
  OCR1A += timerIncrement;
  volatile unsigned char input;
  for (unsigned char i = 0; i < attachedEncoders; i++)
  {
    input = 0;
    if (digitalRead(encoders[i]->I_pin) == HIGH)
      input |= 0x02;
    if (digitalRead(encoders[i]->Q_pin) == HIGH)
      input |= 0x01;
    encoders[i]->encoderCounter += encref[encoders[i]->state][input];
    encoders[i]->state = input;
  }
}

#endif

Please format your code :blush:

It consist on these two steps:

  1. In your code editor (PDE, VS code, Eclipse, etc) ensure you execute the beautifier function. This function automatically indents your code. Auto-indenting makes your code easier to read and helps catching bugs due to mismatch parenthesis, for instance. In the PDE, you use the key combination: ctrl+t
  2. You copy and paste your code in the forum. Then you select the code and you hit the formatting button aka. the button with this symbol: </>

That’s it! Please notice you do not create a new post in case you need to format something you already posted. You can edit your post, copy the code to the PDE, indent the code properly there and then past it back here, format the code and >> save << the edits.

Extra info:

Formatting your code makes everybody’s life easier, your code looks much better plus it ensures your code integrity is not affected by the forum’s formatting (Do you know the forum processes markup code?) Please visit the sticky posts or the FAQ section/post to learn about this, other advantages and super powers you can get in this brand new forum.

Kf

1 Like

The following line (found in your code) will grab the first serial port it sees.

myPort =new Serial(p, Serial.list()[0], 19200);

If you know the Arduino is on “COM6” try this:

myPort = new Serial(p, "COM6", 19200);

To make the program more robust, search for the port name in the array returned by Serial.list(). That will also tell you if the port is available.

The blocks you were seeing in the serial monitor were almost certainly ‘printable’ representations of bytes, given the baud rates match between sources. Also, x, y and z were visible. :wink:

1 Like

noahbuddy,

DUDE, IT’S ALIVE. “myPort = new Serial(p, “COM6”, 19200);” did it, One of the encoders is out of place, but an easy fix. I will get it all running and make a video to share with you all. Thanks all for your Patient’s, Knowledge and Sharing your Skills. Mick

2 Likes

Very neat, it’s like 3d printing but in reverse. keep us posted:-)

2 Likes

Did you make the video?

Thanks!

I’m working on the same digitizer, and I’m not getting anywhere. I replaced the existing code with “myPort = new Serial(p, “COM9”, 19200);” (as my arduino is COM9) and all I’m getting is this:

“Error, disabling serialEvent() for COM9
null”

Any suggestions?

1 Like

Did you try that?

Chrisir

1 Like

I did, thanks. I ended up just going straight for it with myPort =new Serial(p, Serial.list()[2], 19200); I’m still waiting on the proper encoders to show up, so in the meantime, i just wanted to make sure Processing was indeed receiving serial data from the arduino, so i used the only two encoders I had, which are the KY-040’s that come with generic arduino sensor kits. The good news is that as I manually spun the encoders, the X and Y coordinates would respond in kind.

Because I’m keen on having these clouds outputted to a DXF file, I downloaded this from user Bryan1: https://github.com/BL-Shopwork/CCM-Arm

Now, the test encoders I’ve been using don’t register anything at all, so I’m not sure what to do about that. And yes, I did change the code in the new sketch to “myPort =new Serial(p, Serial.list()[2], 19200);”

If you can figure that one out, I’ll buy you dinner lol

By the way, I know it’s a hundred years later, but if you’re still interested and haven’t seen this, he ended up creating a DXF output: