Robot Drawing Arm

Printer ink is the most expensive commodity in the world (by weight, or volume.. apparently). I’m always running out of it just when I need to print out a boarding pass, or some other essential printed document. So wouldn’t it be great if you could print things out using a biro?

Here is my version 0 attempt at such a device in action at Lift’12. It actually has a lot of similarities to this drawing automaton, although I only realised that after I’d finished and the video blogger Nicolas Charbonnier pointed it out to me! Nicolas did a video interview with me at Lift’12 where I had the chance to show off my creation a little. You can watch the video (which includes a fair bit of discussion about my day job at CERN) here.

Basic idea: A robot arm with two drive servos and one more to move a pen (or pencil) up and down onto standard A5 paper. Plugs into a computer for the power and the drawing data. Draws following the mouse or via a crude black & white filter on a JPEG image.

Ingredients:

Arduino (I used my trusty old duemilanove), USB lead to connect it to a computer, three Futaba S3003 servos (or equivalent), a few bits of wire, and some stationary from Muji (one A5 file box, two aluminium rulers, one wooden ruler and one acryllic one to be precise). A biro (one with four colours if you are feeling fancy!). Software: Processing and some jpg’s (+a bit of patience)!

Building it was fairly straight foreward, with the most complicated part making a cam to lift the pen up/down. I did this by drilling 1/2 of an acryllic ruler so that the pen can pass through it, with the pen attached using a single thin screw to an extremity of a servo horn. Fixing the acrylic ruler to the up/down servo didn’t work very well, hence the blue ‘LIFT’ tape in the photos.

Once assembled it should look like this – obviously you can modify the hardware build as you wish.Image

You can see the coins I added (wrapped in masking tape) as a counterweight on the left. It helped to resolve some issues with the lack of co-planar motion in the two arm segments.

After trying to write my own code I gave up and used the excellent Firmata library for the arduino/processing link (my own code was doing strange things…). The computer communicates serially to the plotter, sending positions for each of the three servos.

When I was writing the initial processing sketch, I controlled the arm directly for the first few passes. This was disasterous as I managed to snap teeth off two out of my three servos (originally they were super-micro lightweight ones… after the damage I decided to upgrade to the S3003’s). This lead me to develop my own ’emulator’ for the arm, so that I could solve the mathematical and physical constraints without breaking anything else. You can see an early demo of the arm here.

Here is the processing source code:

import processing.serial.*;
import cc.arduino.*;
Arduino arduino;
int countstart = 0;
int servo1Pin = 9; // Control pin for servo motor
int servo2Pin = 10; // Control pin for servo motor
int servo3Pin = 11; // Control pin for servo motor
int armposn;// = 0;
int digitposn;// = 0;
//int penposn;//= 0;
int penstate = 0;

int xcent = 300;
int ycent = 300;
//int targetX= 400;
//int targetY= 200;
int line1 = 130;
int line2 = 130;
int minimumlength = 40;
color black = color(0);
PImage img;
PImage edgeImg ;

void setup(){
float[][] kernel = { { -1, -1, -1 },
{ -1, 9, -1 },
{ -1, -1, -1 } };

size (600, 600);
background(255);

img = loadImage(“ben.jpg”); // Load the original image

img.loadPixels();
edgeImg = createImage(img.width, img.height, RGB);

// Loop through every pixel in the image.
for (int y = 1; y < img.height-1; y++) { // Skip top and bottom edges
for (int x = 1; x < img.width-1; x++) { // Skip left and right edges
float sum = 0; // Kernel sum for this pixel
for (int ky = -1; ky <= 1; ky++) {
for (int kx = -1; kx <= 1; kx++) {
// Calculate the adjacent pixel for this kernel point
int pos = (y + ky)*img.width + (x + kx);
// Image is grayscale, red/green/blue are identical
float val = red(img.pixels[pos]);
// Multiply adjacent pixels based on the kernel values
sum += kernel[ky+1][kx+1] * val;
}
}
// For this pixel in the new image, set the gray value
// based on the sum from the kernel
edgeImg.pixels[y*img.width + x] = color(sum);
}
}
// State that there are changes to edgeImg.pixels[]
edgeImg.updatePixels();

arduino = new Arduino(this, Arduino.list()[1]);
arduino.pinMode(servo1Pin, Arduino.OUTPUT);
arduino.pinMode(servo2Pin, Arduino.OUTPUT);
arduino.pinMode(servo3Pin, Arduino.OUTPUT);

arduino.analogWrite(servo1Pin, 70);
arduino.analogWrite(servo2Pin, 170);
arduino.analogWrite(servo3Pin, 50); // the servo moves to the horizontal location of the mouse

// note – we are setting a digital pin to output
background(255);

}

void draw()
{
if (countstart == 1){
makepic();
arduino.analogWrite(servo1Pin, 70);
arduino.analogWrite(servo2Pin, 170);
arduino.analogWrite(servo3Pin, 50);
}
countstart = countstart +1;
}

//void draw(){
void makepic(){
background(255);
//fill(50);
stroke(0);
//rect(350+30,320-10,130,130);
image(img, 0, 0, 130, 130); // Displays the image from point (0,0)

image(img, 130, 0, 130, 130); // Draw the new image
filter(POSTERIZE,4);
filter(THRESHOLD);
colorMode(HSB);

for (int xcycle = 130; xcycle < 259; xcycle = xcycle+2)
{
for (int ycycle = 0; ycycle < 129; ycycle++)
{
if (get(xcycle,ycycle)<-2)
{
set(xcycle+350-130+30,ycycle+320-10,black);
markpaper(xcycle+350-130+30,ycycle+320-10,110);//changed pen variable 110
delay(20);
markpaper(xcycle+350-130+30,ycycle+320-10,170);//comment this line and the one further below out to draw lines not dots
//delay(20);
}
else
{
set(xcycle,ycycle,0);
markpaper(xcycle+350-130+30,ycycle+320-10,170);
//delay(50);
}
}
for (int ycycle = 129; ycycle > 0; ycycle–)
{
if (get(xcycle+1,ycycle)<-2)
{
set(xcycle+350-130+1+30,ycycle+320-10,black);
markpaper(xcycle+350-130+1+30,ycycle+320-10,110);
delay(20);
markpaper(xcycle+350-130+1+30,ycycle+320-10,170);//likewise comment this line out for lines not dots on your paper as well as the one further up

}
else
{
set(xcycle+1,ycycle,0);
markpaper(xcycle+350-130+1+30,ycycle+320-10,170);
//delay(50);
}
}
}

}

void markpaper(int targetX, int targetY, int penposn){
//print(“xposn” + targetX);
//println(” yposn” + targetY);
float angle1 = atan2((targetX – xcent), (targetY – ycent));
float sectorlength = sqrt(sq(targetX – xcent)+sq(targetY – ycent));

//if it’s out of reach, shorten the length in the same direction
if (sectorlength > (line1+line2))
{
sectorlength = line1+line2;
targetX = int(xcent + sin(angle1)*sectorlength);
targetY = int(ycent + cos(angle1)*sectorlength);
}

if (sectorlength < minimumlength)
{
sectorlength = minimumlength;
targetX = int(xcent + sin(angle1)*sectorlength);
targetY = int(ycent + cos(angle1)*sectorlength);
}

float internangle = acos((sq(sectorlength)-sq(line2)-sq(line1))/(2*line1*line2));

float sendangle1st = (angle1+(internangle/2));
if (degrees(sendangle1st) < 0 )
{
sendangle1st = radians(0);
}
if (degrees(sendangle1st) > 180 )
{
sendangle1st = radians(180);
}

int line1X = int(xcent+ sin(sendangle1st)*line1);
int line1Y = int(ycent+ cos(sendangle1st)*line1);
int line2X = line1X + int(sin(((angle1+(internangle/2) – internangle)))*line2);
int line2Y = line1Y + int(cos(((angle1+(internangle/2) – internangle)))*line2);
stroke(0,9);
line(xcent, ycent, line1X, line1Y);
line(line1X, line1Y, line2X-1, line2Y-1);
// if (penposn == 150)
// {
// set(line2X,line2Y,black);
// println(“TEXT”);
// }
// set(mouseX,mouseY,black);

//if it’s too close set the minimum threshold in the same direction

int sendangle1 = round(degrees(sendangle1st));
int sendangle2 = round(degrees((radians(180) – internangle)-radians(35)));
//println(“Send1>”+ sendangle1 + ” Send2 >” + sendangle2);
digitposn = sendangle2;
armposn = sendangle1;

arduino.analogWrite(servo1Pin, digitposn);
arduino.analogWrite(servo2Pin, penposn);
arduino.analogWrite(servo3Pin, armposn);
if (penstate != penposn)
{
delay(300);
}
else
{
delay(1);
}// the servo moves to the horizontal location of the mouse
penstate = penposn;
}

//end of source\\

I should add that this code is very rough, and contains hard-coded boundary limits for the drawing area which are specific to the physical configuration of my hardware (and painstakingly obtained by moving the arm and looking at where the pen is). This code is also set to draw dots rather than lines, as people generally indicated a preference for this kind of output.

Performance is ‘interesting’. I’ve set it to draw an approx 160×160 matrix, one pixel at a time. Depending upon the number of dark pixels this can take up to 1.5 hours per image. If you want to draw lines that go between all the connected pixels the time per image comes down to <20 minutes. This is mostly a function of delays added in the code to cope with mechanical oscillations in the two arms and the pen.

Things that could be improved:

  • The code is very ropey! Like my flat, it would benefit from a tidy and the hoovering up of any stray variables.
  • The drawing area is still a bit sub-optimal. The arm can cover almost 100% of the space of an A5 sheet, however I’m only using about 65% for drawing at the moment. The boundary conditions for the angles would need to be carefully adjusted for this.
  • Worst of all the image processing is very crude (simple cut for black and white). This is mostly because I’ve been lazy (it WORKS, right?) and wish to avoid re-inventing the wheel when there are so many excellent image processing suites out there. If I have time it would be great to code something to vectorise JPG’s properly rather than my current ‘quick and dirty’ approach.
  • Colour! My pen has Red, Green, Blue and Black ink.. mostly I use black or blue for clarity, but it would be very interesting to come up with a multi-pass colour image reproduction system.
  • Good projects are never quite finished 😉

Image

A picture of Ben Bashford drawn from a JPG photo, live on the stage of Lift’12.

I hope you liked my creation – please feel free to comment, suggest and contribute. I’d like to thank Amy Shen for being my first drawing subject and for helping me to figure out the maths of converting an XY co-ordinate space into a two polar variables.

Tagged , , , , , , , , , , , , , , , ,

37 thoughts on “Robot Drawing Arm

  1. […] James Devine made this automatic pen printer device as his hobby. It took him about 3 days of work, programming the Arduino, the motors, putting stuff together, now it can print any black and white picture automatically. It reminds me of the totally amazing 1810 Henri Maillardet Automaton. James Devine works as a electrical engineer at CERN, standby to work on electrical systems for all the experiments at the Large Hadron Collider. James Devine released the source code and info about his robot drawing arm at his blog. […]

  2. […] James Devine made this automatic pen printer device as his hobby. It took him about 3 days of work, programming the Arduino, the motors, putting stuff together, now it can print any black and white picture automatically. It reminds me of the totally amazing 1810 Henri Maillardet Automaton. James Devine works as a electrical engineer at CERN, standby to work on electrical systems for all the experiments at the Large Hadron Collider. James Devine released the source code and info about his robot drawing arm at his blog. […]

  3. v v l swamy says:

    hai friend this is swamy i have seen ur project and it was quite impressive and i am new with aurduino and i didn’t know any thing about processing i searched in net and downloaded the processing and i dont understand what to do with the processing weather i should load it to my controller or i should load it in my pc can u please send my the detailed description what to do and how to do
    THANKING YOU
    HOPE FOR THE SPEED REPLY
    REGARDS,
    V V L SWAMY
    MY EMAIL ID : swamy.vonumu@gmail.com

    • pingu98 says:

      Processing goes on the PC! It send the commands via serial over USB to the arduino. The enviornments for processing and arduino look similar as the arduino IDE is basically a sub-set of processing. Good luck!

  4. Gerhard says:

    When trying to upload code I get error message “unexpected char:’/'” in the line for (int ycycle = 129; ycycle>0; ycycle-)

    Thanks

    • pingu98 says:

      I think it might be the – ? The code worked fine in my example… perhaps you’ve had a copy/paste error with the cursor floating somewhere. I would suggest you try to re-paste it.

  5. samson says:

    Hi , I have build your robot as you have describe below , but when tryng to upload the code , I get error message I’ don’t know why , please have you install some libraries on processing ?

    • pingu98 says:

      Samson, I had another go with Version 2 of my arm recently and there’s a problem with the firmata libraries I used. Apparently they haven’t been updated for Processing 2.0. I think the most recent version works with Processing 1.5 only – though I haven’t had time to get my installation working properly yet. Good luck!

  6. Erwin says:

    Hai,

    Can I have the code for the arduino please

    Kind Regards
    Erwin

  7. Erwin says:

    Hai ,

    Thanks for the fast reply

    I have one more question please :
    I get an error in Processing “cc.does not exist” where can I find the library

    Is this oke :
    Servo1 = Shoulder
    Servo2 = Elbow
    Servo3 = Up/down

    Glad you help me thanks

    Kind Regards
    Erwin

    • pingu98 says:

      Hi Erwin, I think you’re missing the arduino library from processing (line import cc.arduino etc.) you should go to the processing site and download it. As for which servo does which thing, I think you’re correct – you can also check out this other script I wrote, that gives manual control over the arm using the mouse. Should help you to debug the hardware –


      import cc.arduino.*;
      import processing.serial.*;
      Arduino arduino;
      int servo1Pin = 9; // Control pin for servo motor
      int servo2Pin = 10; // Control pin for servo motor
      int servo3Pin = 11; // Control pin for servo motor
      int penposn;
      void setup(){
      size (180, 180);
      background(255);
      arduino = new Arduino(this, Arduino.list()[1]);
      arduino.pinMode(servo1Pin, Arduino.OUTPUT);
      arduino.pinMode(servo2Pin, Arduino.OUTPUT);
      arduino.pinMode(servo3Pin, Arduino.OUTPUT);
      arduino.analogWrite(servo3Pin, 50); // the servo moves to the horizontal location of the mouse
      penposn = 50;
      }
      void draw(){
      arduino.analogWrite(servo1Pin, mouseX);
      arduino.analogWrite(servo2Pin, mouseY);
      if (mousePressed)
      {
      penposn = 23;
      }
      else
      {penposn = 50;}
      arduino.analogWrite(servo3Pin, penposn);
      }

  8. Mayur Bille says:

    Hi, pingu98
    When I run your code in processing 1.5.1,
    I get the following errors and warning. can you suggest a possible course of action.

    Stable Library
    =========================================
    Native lib Version = RXTX-2.2pre2
    Java lib Version = RXTX-2.1-7
    WARNING: RXTX Version mismatch
    Jar version = RXTX-2.1-7
    native lib Version = RXTX-2.2pre2
    Exception in thread “Animation Thread” java.lang.ArrayIndexOutOfBoundsException: 1
    at project.setup(project.java:87)
    at processing.core.PApplet.handleDraw(Unknown Source)
    at processing.core.PApplet.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:662)
    Kind regards and thanks,
    Mayur Bille.

    • pingu98 says:

      Hello Mayur, Did you take the code from Github? If you copied and pasted it there might be non-unicode characters. Otherwise it sounds like you have an issue with your Java installation on your computer as well, if you google around there’s probably a way to make the two libraries match up (possibly updating your OS Java). You might also want to check out line 87 of your code, to see if there is anything suspicious, assuming that project.java:87 refers to a line… that isn’t already changed due to compilation in Java. Best of luck and let me know how you get on! You could also try the Arduino/Firmata SERVO example, which I used as inspiration – and if your system is working correctly should enable you to get something moving! James

  9. Erwin says:

    Hai James

    Sorry to bother you so much

    I have all versions (2.2.1 / 1.5.1 / 3.0a5) of processing dowload and tried and I always get the same error
    “Cc.arduino doe not exist” and “no library fond”
    Dowloaded , unpack , and start
    I have searched the Internet for “cc.arduino” but nothing found
    I have tried on two computers, one vista, and the other windows 8.1, no result

    hardware:
    http://www.thingiverse.com/thing:384850

    This is about my setup, see site above
    I installed the pencil to a servo and I use the Arduino Mega 2560
    The pin I use are 8,9,10 and this I change in the program

    What do I wrong?????

    Kind Regards
    Erwin

    • pingu98 says:

      Nice hardware! You need the Arduino/Firmata library which you can find here: http://playground.arduino.cc/Interfacing/Processing make sure you follow the instructions regarding installing it. It’s been a while but I think you had to put it into the correct directory (which you might have to create) and then restart processing. Should work fine in versions 1.5 and 2, provided Firmata isn’t broken… (which it has been from time to time). Best of luck and please let me know how you get on!
      James

  10. Erwin says:

    Hai ,

    Hai

    Thanks for the explanation I’m going to watch it quietly

    Between () Standard Version Processor 1.5.1, is STANDARD ( you can see that upper right )
    version 2.2.1 = JAVA
     

    mvg
    Erwin

  11. Erwin says:

    Hai James,

    There is already movement in the robot arm , thank you
    I think I have now installed the labrary good (Labraries)

    I used you control prog
    1: arduino = new Arduino(this, Arduino.list()[1]); the 1 I changed to 0 and then the prog works
    2: I go to the left with the mouse, go to the robot arm to the right
         when I use move the mouse to upsite the robot arm go under

    Can this solved in the software ?????

    May I also ask for the “James.jpg” file, I can not find him

    Thanks for helping my on way

    Kind regards
    Erwin

  12. Mayur Bille says:

    Hi pingu98,
    I have made the robotic arm using three servos and arduino uno.
    My arduino runs standard firmata.
    i used your processing code, but when i run the code in processing after about a second the serial communication stops. and the arduino resets itself and the RxTx leds switches off. I have installed the arduino library correctly. the processing sketch gives no errors but arduino stops getting data. Can you help and tell me exactly where i am going wrong.

  13. pingu98 says:

    Hi Mayur,

    Sounds like you have a hardware communication issue, if the Arduino is resetting. It could also be an illegal code sent from the PC, or a short circuit somewhere. I would suggest you run a serial Rx/Tx routine with your computer over a few minutes to check the stability of the connection.

    When you’ve got that stable, try running the code from this gist, which I used to calibrate my own arm.


    import cc.arduino.*;
    import processing.serial.*;
    Arduino arduino;
    int servo1Pin = 9; // Control pin for servo motor
    int servo2Pin = 10; // Control pin for servo motor
    int servo3Pin = 11; // Control pin for servo motor
    int penposn;
    void setup(){
    size (180, 180);
    background(255);
    arduino = new Arduino(this, Arduino.list()[1]);
    arduino.pinMode(servo1Pin, Arduino.OUTPUT);
    arduino.pinMode(servo2Pin, Arduino.OUTPUT);
    arduino.pinMode(servo3Pin, Arduino.OUTPUT);
    arduino.analogWrite(servo3Pin, 50); // the servo moves to the horizontal location of the mouse
    penposn = 50;
    }
    void draw(){
    arduino.analogWrite(servo1Pin, mouseX);
    arduino.analogWrite(servo2Pin, mouseY);
    if (mousePressed)
    {
    penposn = 23;
    }
    else
    {penposn = 50;}
    arduino.analogWrite(servo3Pin, penposn);
    }

    The arm should move following your mouse pointer once you configure it correctly. When it works okay, then try moving back to the original processing sketch. I assume you already got the non-unicode characters out if you did a copy/paste from my blog.

    Best of luck!
    James

  14. preeti says:

    hello sir,i m making this project as my 7th sem project…i hav followed all your instructions but when the 3 servo motor are connected to the external power supply of 5v ,they are not working..please do reply asap.

    • pingu98 says:

      Check the data line to the servos. If no PWM is provided to the servo’s then they won’t move. You can test it with a simple PWM signal generated from an arduino PWM output, also worth double checking the power – both the wiring and the servo by running it off the Arduino 5V supply as a test. Best of luck!

  15. Dimitrios says:

    hallo sir and congratulations for your great build.
    I am trying to build the robotic-arm but i have this output. (photo ending)
    https://drive.google.com/file/d/0B13HDerpRZ5WaVJOczJoNG1lOUk/view?usp=sharing

    photo of processing output end image (it seems the down right image is wrong)
    https://drive.google.com/file/d/0B13HDerpRZ5WS1ZWV3FjdS1LaFE/view?usp=sharing

    i also get a warning but the processing sketch runs
    https://drive.google.com/file/d/0B13HDerpRZ5WekJpaHB4YXFlYmc/view?usp=sharing

    The setup i am usingProcessing 1.5 + arduino IDE 1.05. r2 + micro servos 9g
    black+white pic “sq.jpg” 180X180 px +win 8.1 64bit

    can you please help me sort the problem?

  16. hallo sir and congratulations for your great build.
    I am trying to build the robotic-arm but i have this output. (photo ending)
    https://drive.google.com/file/d/0B13HDerpRZ5WaVJOczJoNG1lOUk/view?usp=sharing

    photo of processing output end image (it seems the down right image is wrong)
    https://drive.google.com/file/d/0B13HDerpRZ5WS1ZWV3FjdS1LaFE/view?usp=sharing

    i also get a warning but the processing sketch runs
    https://drive.google.com/file/d/0B13HDerpRZ5WekJpaHB4YXFlYmc/view?usp=sharing

    The setup i am usingProcessing 1.5 + arduino IDE 1.05. r2 + micro servos 9g
    black+white pic “sq.jpg” 180X180 px +win 8.1 64bit

    can you please help me sort the problem?

    and some video of the process
    https://drive.google.com/file/d/0B13HDerpRZ5WWXdsdjhRcVJ1eTg/view?usp=sharing

  17. Dimitrios says:

    Processing is awesome! for the callibration the following processing sketch can be used: http://users.sch.gr/dimpapadop/arkas.html (in greek but you’ll understand)

    int sx,sy,ex,ey,hx,hy,hxo,hyo;
    int armLength,ua,la;
    float uad, lad;
    void setup(){
    size(500,500);
    background(255, 224, 150);
    sx = width/2;
    sy = height/2;
    armLength = int(width/5);
    }

    void draw(){
    fill(255);
    rect(0,0,width,height);
    upperArm();
    }

    void upperArm(){
    int dx = mouseX – sx;
    int dy = mouseY – sy;
    float distance = sqrt(dx*dx+dy*dy);

    int a = armLength;
    int b = armLength;
    float c = min(distance, a + b);

    float B = acos((b*b-a*a-c*c)/(-2*a*c));
    float C = acos((c*c-a*a-b*b)/(-2*a*b));

    float D = atan2(dy,dx);
    float E = D + B + PI + C;

    ex = int((cos(E) * a)) + sx;
    ey = int((sin(E) * a)) + sy;
    print(“UpperArm Angle= “+degrees(E)+” “);

    hx = int((cos(D+B) * b)) + ex;
    hy = int((sin(D+B) * b)) + ey;
    println(“LowerArm Angle= “+degrees((D+B)));

    stroke(255,0,0,100);
    fill(240,0,0,200);
    ellipse(sx,sy,10,10);
    ellipse(ex,ey,8,8);
    ellipse(hx,hy,6,6);
    stroke(0);
    line(sx,sy,ex,ey);
    line(ex,ey,hx,hy);
    }

  18. pingu98 says:

    Thanks for sharing your calibration code! I’m sure it will be useful to others in the future as well 🙂 I hope you’ve got your arm drawing correctly now.

    • Dimitrios says:

      yes, i have. I just need to help me with the arm length – geometry. Are the 130 values? mine are 100mm=10cm shall i go with 100px? (so to be able not to draw outside… 🙂 the image comes ok now! Thank you again

  19. Hallo Dimitrios,

    I’ve seen all your photos and videos and I have to say I just got the same result
    This is my setup http://www.thingiverse.com/thing:1165504, I’ve posted a picture and I wonder, why is there this double picture, and how does that black spot over there

    May I ask if you’ve already solved the problem and possibly how

    Thank you in advance

  20. I’ve seen all your photos and videos and I have to say I just got the same result
    This is my setup http://www.thingiverse.com/thing:1165504, I’ve posted a picture and I wonder, why is there this double picture, and how does that black spot over there

    May I ask if you’ve already solved the problem and possibly how

    Thank you in advance

Leave a reply to pingu98 Cancel reply