User Tools

Site Tools


oak:tutorials:servo

Oak: Using a servo

tl;dr: The Arduino Servo library seems to work just fine.

Example: NextBus meter

The San Francisco public transit system (MUNI) makes realtime prediction data available through NextBus. For this example, we will mount a small servo with an arrow connected to it in a box, making a prediction display suitable for the home.

Components

qty. part
1 Oak (with soldered headers)
1 small servo motor
3 jumper wires

Wiring

Oak VCC → servo red
Oak Gnd → servo black
Oak P5 → servo white

NOTE: I chose to use a servo small enough to be safely powered by the 3.3v provided by the Oak. If you need a larger servo, you will need a few more components (like an optoisolator or a logic-level converter) to allow the 3.3v Oak to control a servo which requires higher voltage.

Oak code

We will code with the Arduino environment (because that is what's available today).

#include <Servo.h>

/*
 * A servo based count-down display for transit times.
 */

#define SERVO_PIN 5
#define SERVO_MIN 700
#define SERVO_MAX 2400

#define MINUTE(x) (x * 60 * 1000)

#define POS_GONE 180
#define POS_3_MINUTE 140
#define POS_6_MINUTE 30
#define POS_9_MINUTE 6

Servo s;
int servo_pos = POS_GONE;

long end_time = 0;

void setup() {
  Particle.function("countdown", set_remaining_ms);
}

void loop() {
  update_servo_pos();
  delay(10000);
}

int set_remaining_ms(String arg) {
  long remaining_ms = arg.toInt();
  end_time = millis() + remaining_ms;
  update_servo_pos();
  return 0;  // success (-1 means failure to Particle).
}

void update_servo_pos() {
  long remaining_ms = end_time - millis();
  if (remaining_ms < 0) {
    remaining_ms = 0;
  }

  // Do linear interpolations between labels on the physical device.
  if (remaining_ms > MINUTE(9)) {
    servo_pos = POS_GONE;
  } else if (remaining_ms > MINUTE(6)) {
    servo_pos = interpolate(remaining_ms, MINUTE(6), MINUTE(9), POS_6_MINUTE, POS_9_MINUTE);
  } else if (remaining_ms > MINUTE(3)) {
    servo_pos = interpolate(remaining_ms, MINUTE(3), MINUTE(6), POS_3_MINUTE, POS_6_MINUTE);
  } else {
    servo_pos = interpolate(remaining_ms, 0, MINUTE(3), POS_GONE, POS_3_MINUTE);
  }

  // Move the servo for 1s, then detach to avoid a prolonged buzzing noise.
  s.attach(SERVO_PIN, SERVO_MIN, SERVO_MAX);
  s.write(servo_pos);
  delay(1000);
  s.detach();
}

// Return a linear interpolation between outMin and outMax, given the input data and bounds.
long interpolate(long input, long inMin, long inMax, long outMin, long outMax) {
  long i = max(min(input, inMax), inMin);
  return outMin + (i - inMin) * (outMax - outMin) / (inMax - inMin);
}

Updating our meter

In order for this meter to be useful, we need to call it every once in a while with updated data.

export DEVICE_ID="<your_device_id>"
export PARTICLE_ACCESS_TOKEN="<your_access_token>"
curl https://api.particle.io/v1/devices/${DEVICE_ID}/countdown \
  -d access_token="${PARTICLE_ACCESS_TOKEN}" \
  -d "args=360000"

It is left as an exercise to the reader to figure out how to call this periodically. One method is to use a cron task on Google AppEngine to check with NextBus and update the device.

oak/tutorials/servo.txt · Last modified: 2016/04/10 18:12 by sir_buckyball