Final Project: Motion controlled drawing arm

My final project is a servo motor drawing arm controlled by an arduino using motion. The idea is for the arm to follow motion from your hand, which then gets drawn out. A secondary part of the project also gives a digital representation of your motion controlled drawing on processing.

Materials:

  • Circuit Playground Express
  • 2 Servo Motors
  • 3D Printed Chassis
  • Acrylic Board (Or similar)
  • Writing/Drawing Utensil

Building the drawing arm

To build the drawing arm, 3D print the chassis for the motors.

Attach one servo to the baseplate, and the second to the arm. Glue in the servo attachments as well.

Attach the baseplate to a sheet of acrylic

Assemble the arms

Optional: Add a counterweight, here I used a few coins

Connect the servos to the circuit playground

Add a drawing utensil, and its done!

Arduino Code


#include <Servo.h>
#include <Adafruit_CircuitPlayground.h>

// Configuration values to adjust the sensitivity and speed of the mouse.
// X axis (left/right) configuration:
#define XACCEL_MIN 0.1 // Minimum range of X axis acceleration, values below
// this won’t move the mouse at all.
#define XACCEL_MAX 12.0 // Maximum range of X axis acceleration, values above
// this will move the mouse as fast as possible.
#define XMOUSE_RANGE 8.0 // Range of velocity for mouse movements. The higher
// this value the faster the mouse will move.
#define XMOUSE_SCALE -1 // Scaling value to apply to mouse movement, this is
// useful to set to -1 to flip the X axis movement.

// Y axis (up/down) configuration:
// Note that the meaning of these values is exactly the same as the X axis above,
// just applied to the Y axis and up/down mouse movement. You probably want to
// keep these values the same as for the X axis (which is the default, they just
// read the X axis values but you can override with custom values).
#define YACCEL_MIN XACCEL_MIN
#define YACCEL_MAX XACCEL_MAX
#define YMOUSE_RANGE XMOUSE_RANGE
#define YMOUSE_SCALE 1

Servo myServo, myServo2;
int pos = 90;
int pos2 = 90;
int last_x = 0;
int last_y = 0;

// Floating point linear interpolation function that takes a value inside one
// range and maps it to a new value inside another range. This is used to transform
// each axis of acceleration to mouse velocity/speed. See this page for details
// on the equation: https://en.wikipedia.org/wiki/Linear_interpolation
float lerp(float x, float x0, float x1, float y0, float y1) {
// Check if the input value (x) is outside its desired range and clamp to
// those min/max y values.
if (x <= x0) {
return y0;
}
else if (x >= x1) {
return y1;
}
// Otherwise compute the value y based on x’s position within its range and
// the desired y min & max.
return y0 + (y1-y0)*((x-x0)/(x1-x0));
}

void setup() {
Serial.begin(9600);
CircuitPlayground.begin();
myServo.attach(9); // A2
myServo2.attach(10); // A3
}

void loop() {
if (!CircuitPlayground.slideSwitch()) {
return;
}

// Grab x, y acceleration values (in m/s^2).
float x = CircuitPlayground.motionX();
float y = CircuitPlayground.motionY();
// Serial.print(“X velocity: “);
// Serial.println(x);
// Serial.print(“Y velocity: “);
// Serial.println(y);

// Use the magnitude of acceleration to interpolate the mouse velocity.
float x_mag = abs(x);
float x_mouse = lerp(x_mag, XACCEL_MIN, XACCEL_MAX, 0.0, XMOUSE_RANGE);
float y_mag = abs(y);
float y_mouse = lerp(y_mag, YACCEL_MIN, YACCEL_MAX, 0.0, YMOUSE_RANGE);

// Change the mouse direction based on the direction of the acceleration.
if (x < 0) {
x_mouse *= -1.0;
}
if (y < 0) {
y_mouse *= -1.0;
}

// Values for processing sketch
float x_move = floor(x_mouse*XMOUSE_SCALE);
float y_move = floor(y_mouse*YMOUSE_SCALE);

Serial.print(x_move);
Serial.print(“,”);
Serial.print(y_move);
Serial.print(“,”);
Serial.print(CircuitPlayground.leftButton());
Serial.print(“,”);
Serial.println(CircuitPlayground.rightButton());

// Map translated acceleration values to motor range of motion
float x_pos = map(x_mouse, XMOUSE_RANGE * -1, XMOUSE_RANGE, 0, 180);
float y_pos = map(y_mouse, YMOUSE_RANGE * -1, YMOUSE_RANGE, 0, 180);
// Serial.print(“X Pos: “);
// Serial.println(x_pos);
// Serial.print(“Y Pos: “);
// Serial.println(y_pos);

if ( (pos >= 0 && pos <= 180) && (pos2 >= 0 && pos2 <= 180)) {
if (x_move > 0) { // move right
pos-=abs(x_move)*2;
pos2+=abs(x_move);
myServo.write(pos);
myServo2.write(pos2);
if (pos < 10) { pos = 10; }
if (pos2 > 105) { pos2 = 105; }
}
else if (x_move < 0) { //move left
pos+=abs(x_move)*2;
pos2-=abs(x_move);
if (pos > 90) { pos = 90; }
if (pos2 < 50) { pos2 = 50; }
myServo.write(pos);
myServo2.write(pos2);
}
else if (y_move > 0) { // up
pos+=abs(y_move);
pos2-=abs(y_move)*2;
if (pos > 100) { pos = 100; }
if (pos2 > 130) { pos2 = 130; }
myServo.write(pos);
myServo2.write(pos2);
}
else if (y_move < 0) { // down
pos-=abs(y_move);
pos2+=abs(y_move)*2;

if (pos > 180) { pos = 180; }
if (pos2 < 20) { pos2 = 20; }
myServo.write(pos);
myServo2.write(pos2);
}
}
// Serial.print(“pos1: “);
// Serial.println(pos);
// Serial.print(“pos2: “);
// Serial.println(pos2);

if (pos < 10) { pos = 10; }
if (pos > 180) { pos = 180; }
if (pos2 < 10) { pos2 = 10; }
if (pos2 > 180) { pos2 = 180; }

delay(50);
}

Processing Sketch


import processing.serial.*;

Serial myPort;
float inByte = 0;
float inByte2 = 0;
boolean clearSc = false;
int moveX = height/2;
int moveY = height/2;

void setup()
{
size(800, 800);
myPort = new Serial(this, Serial.list()[2], 9600);
myPort.bufferUntil(‘\n’);
noStroke();
smooth(8);
frameRate(60);
fill(random(255), random(255), random(255));
background(255);
}
void draw()
{
if (clearSc) {
background(255);
}
if (moveX >= 0 && moveX <= height) {
moveX += inByte2*2;
if (moveX > height) {
moveX = height;
}
if (moveX < 0) {
moveX = 0;
}
}
if (moveY >= 0 && moveY<= height) {
moveY += inByte*2;
if (moveY > height) {
moveY = height;
}
if (moveY < 0) {
moveY = 0;
}
}
print(“moveX:”);
print(moveX);
print(“moveY:”);
print(moveY);
ellipse(moveY, moveX, 15, 15);
}

void serialEvent (Serial myPort) {
// get the ASCII string:
String inString = myPort.readStringUntil(‘\n’);
String[] vals = split(inString, ‘,’);

String s = vals[0];
if (s != null) {
// trim off any whitespace:
s = trim(s);
inByte = float(s);
//println(inByte);
}

s = vals[1];
if (s != null) {
// trim off any whitespace:
s = trim(s);
inByte2 = float(s);
//println(inByte2);
}

s = vals[2];
if (s != null) {
// trim off any whitespace:
s = trim(s);
// convert to boolean, store as clear screen value
if (int(s) == 1) {
clearSc = true;
moveX=moveY=height/2;
} else {
clearSc = false;
}
//println(s);
}

s = vals[3];
if (s != null) {
// trim off any whitespace:
s = trim(s);
// convert to boolean, store as clear screen value
if (int(s) == 1) {
fill(random(255), random(255), random(255));
}
//println(s);
}
}