User login

Powered by Drupal, an open source content management system

Theory and Practice of Tangible User Interfaces

iPhone controlled servo-crawler

Submitted by npdoty on Wed, 10/22/2008 - 23:17

Assignment: Servo Motor: Actuation Assignment 2

Collaborators:

Using two servo motors, two binder clips and just enough packaging material to keep it off the ground, this little servo-crawler rather awkwardly slides across the table (until he starts to reach the end of the wire).  The triangular clips lead to a "two-steps forward, one-step back" approach to movement.

As an extra touch, an iPhone can wirelessly start and stop the movement of the crawler.  When a Processing sketch is running in the background, touching a button on the iPhone (using the TouchOSC application) immediately starts the crawler moving.  Touching the button again will stop the crawling (though not immediately -- a bug somewhere).

Arduino Code


/*
 * Servo with iPhone Control
 * (modified by npdoty off of original code from TUI class potentiometer controlled servo)
 */

int servoPin = 7;      // Control pin for servo motor
int servoPin2 = 8;

int pulseWidth = 2100;    // Amount to pulse the servo
int pulseWidth2 = 700;
long lastPulse = 0;    // the time in millisecs of the last pulse
int refreshTime = 20;  // the time in millisecs needed in between pulses

//beginning and end positions of each servo (they need to move different amounts)
int beginW = 2100;
int endW = 1300;
int beginW2 = 700;
int endW2 = 1800;

//target position of each servo
int targetPulseWidth = endW;
int targetPulseWidth2 = endW2;

//difference between current position and target position
int difference = 0;
int difference2 = 0;

//number of steps to divide up the movement of each 'step'
int numSteps = 10;
  
int minPulse = 500;   // minimum pulse width

boolean move = false;    //should the crawler move?

void setup() {
  pinMode(servoPin, OUTPUT);  // Set servo pin as an output pin
  pinMode(servoPin2, OUTPUT);  // Set servo pin as an output pin
  
  pulseWidth = minPulse;      // Set the motor position to the minimum
  Serial.begin(9600);         // connect to the serial port
  Serial.println("servo_serial_better ready");
}

void loop() {
  if (Serial.available())    //read input from Processing (the iPhone)
  {
    char c = Serial.read();
    if (c == '1')
    {
      move = true;
    }
    else
    {
     move = false; 
    }
  }
  
  if (move)
  {
      pulseWidth = pulseWidth + difference;
      pulseWidth2 = pulseWidth2 + difference2;
  
      //debug info
      Serial.print("servo1: "); Serial.print(pulseWidth); Serial.print(" servo2: "); Serial.println(pulseWidth2);
      Serial.print("diff: "); Serial.print(difference); Serial.print(" diff2: "); Serial.println(difference2);
      Serial.print("target: "); Serial.print(targetPulseWidth); Serial.print(" target2: "); Serial.println(targetPulseWidth2);  
  
      updateServo();   // update servo position
  
      //start back the other way when you've finished
      if (pulseWidth <= endW)
      {
         targetPulseWidth = beginW; 
         targetPulseWidth2 = beginW2;
     
         difference = (targetPulseWidth - pulseWidth) / numSteps;
         difference2 = (targetPulseWidth2 - pulseWidth2) / numSteps;
      }
      else if (pulseWidth >= beginW)
      {
        targetPulseWidth = endW;
        targetPulseWidth2 = endW2; 
    
        difference = (targetPulseWidth - pulseWidth) / numSteps;
        difference2 = (targetPulseWidth2 - pulseWidth2) / numSteps;
      }
  }
}

// called every loop(). 
void updateServo() {
  // pulse the two servos again if rhe refresh time (20 ms) have passed:
  if (millis() - lastPulse >= refreshTime) {
    digitalWrite(servoPin, HIGH);   // Turn the motor on
    delayMicroseconds(pulseWidth);  // Length of the pulse sets the motor position
    digitalWrite(servoPin, LOW);    // Turn the motor off
    
    digitalWrite(servoPin2, HIGH);   // Turn the motor on
    delayMicroseconds(pulseWidth2);  // Length of the pulse sets the motor position
    digitalWrite(servoPin2, LOW);    // Turn the motor off
    
    lastPulse = millis();           // save the time of the last pulse
  }
}


Processing Code


/**
 * TouchOSC
 * 
 * Example displaying values received from
 * the "Simple" layout (Page1 only so far)
 * http://hexler.net/touchosc
 * modified by npdoty for sending messages over a serial port to an Arduino
 */

import oscP5.*;
import netP5.*;

import processing.serial.*;
// Change this to the portname your Arduino board
String portname = "/dev/tty.usbserial-A7006ysf"; // or "COM5"
Serial port;
String buf="";
int cr = 13;  // ASCII return   == 13
int lf = 10;  // ASCII linefeed == 10

OscP5 oscP5;

float v_fader1 = 0.0f;
float v_fader2 = 0.0f;
float v_fader3 = 0.0f;
float v_fader4 = 0.0f;
float v_fader5 = 0.0f;
float v_toggle1 = 0.0f;
float v_toggle2 = 0.0f;
float v_toggle3 = 0.0f;
float v_toggle4 = 0.0f;

void setup() {
  size(320,440);
  frameRate(25);
  /* start oscP5, listening for incoming messages at port 8000 */
  oscP5 = new OscP5(this,8000);
  port = new Serial(this, portname, 9600); 
}

void oscEvent(OscMessage theOscMessage) {

    String addr = theOscMessage.addrPattern();
    float  val  = theOscMessage.get(0).floatValue();
    
    if(addr.equals("/1/fader1"))        { v_fader1 = val; }
    else if(addr.equals("/1/fader2"))   { v_fader2 = val; }
    else if(addr.equals("/1/fader3"))   { v_fader3 = val; }
    else if(addr.equals("/1/fader4"))   { v_fader4 = val; }
    else if(addr.equals("/1/fader5"))   { v_fader5 = val; }
    else if(addr.equals("/1/toggle1"))  { v_toggle1 = val; }
    else if(addr.equals("/1/toggle2"))  { v_toggle2 = val; }
    else if(addr.equals("/1/toggle3"))  { v_toggle3 = val; }
    else if(addr.equals("/1/toggle4"))  { v_toggle4 = val; }
}

void draw() {
    background(0);

    // fader5 + toggle 1-4 outlines
    fill(0);
    stroke(0, 196, 168);  

    rect(17,21,287,55);
    rect(17,369,60,50);
    rect(92,369,60,50);
    rect(168,369,60,50);
    rect(244,369,60,50);

    // fader5 + toggle 1-4 fills
    fill(0, 196, 168);
    rect(17,21,v_fader5*287,55);
    if(v_toggle1 == 1.0f) 
    {
        port.write('1');    //turn on crawler
        rect(22,374,50,40);
    }
    else
    {
        port.write('0');    //turn off crawler
        fill(0);
        rect(22,374,50,40);
    }
    if(v_toggle2 == 1.0f) rect(97,374,50,40);
    if(v_toggle3 == 1.0f) rect(173,374,50,40);
    if(v_toggle4 == 1.0f) rect(249,374,50,40);
    
    // fader 1-4 outlines
    fill(0);
    stroke(255, 237, 0);
    rect(17,95,60,255);
    rect(92,95,60,255);
    rect(168,95,60,255);
    rect(244,95,60,255);
    
    // fader 1-4 fills
    fill(255, 237, 0);
    rect(17,95 + 255 - v_fader1*255,60,v_fader1*255);
    rect(92,95 + 255 - v_fader2*255,60,v_fader2*255);
    rect(168,95 + 255 - v_fader3*255,60,v_fader3*255);
    rect(244,95 + 255 - v_fader4*255,60,v_fader4*255);
}