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.

About these ads

8 Responses to 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!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 790 other followers

%d bloggers like this: