This machine uses a single servo to simulate the motion of a children’s toy drinking bird (to see real life version in action, click here), where normally, traveling fluid in the body of toy causes it to tip back and forth in a distinct gesture.  This electric drinking bird was constructed from paper (for the body) and a hinged hook (for the neck).  Connecting the hook to the bird’s body allows for the drinking motion to be more accentuated–as the servo moves the bird closer to the ground, the neck will “flop” suddenly.  While the drinking motion is relatively slow and robotic, the back and forth gesture is adapted from the movement in a simple harmonic oscillator.

See below for code.  If questions, feel free to contact jkwang1@andrew.cmu.edu


// "Drinking Bird" by Jen Kwang
// moves a single servo to simulate the motion of a toy drinking bird.
// code below is altered examples from:
// https://courses.ideate.cmu.edu/16-223/f2018/text/days/day02.html

#include <Servo.h> 
const int SERVO_PIN = 9;
Servo svo;

// ================================================================
// constants for Simple Harmonic Motion oscillator movement (wobbling bird)

const float k    = 4*M_PI*M_PI;   // 1 Hz; freq = (1/2*pi) * sqrt(k/m); k = (freq*2*pi)^2
const float b    = 1.5;           // damping
const float q_d  = 90.0;          // neutral spring position
const float dt   = 0.02;          // integration time step

float q    = 0.0;         // initial position
float qd   = 300.0;         // initial velocity

bool turning = false;

// constants for bird drinking motion
const float center    = 90.0;     // in degrees
const float magnitude = 130.0;     // in degrees
const float period    =  10.0;     // in seconds, duration of cycle
const float interval  =  0.002;   // in seconds, duration of each step

// ================================================================
void setup(void){
  Serial.begin(115200);
  svo.attach(SERVO_PIN);
}
// ================================================================

void loop(){
  // "Drinking" motion ============================================
  if (turning == false) {
    int cycle_steps = period / interval;
    
    for (int step = 0; step < cycle_steps; step++){
      float phase = step * (M_PI/cycle_steps/1.1);
      float angle = center + magnitude * sin(phase);
      svo.write(angle);
      delay(500*interval);
      }
    turning = true;
    }
  // "Wobbling" motion ============================================
  while (turning){
  // calculate the derivatives
  float qdd = k * (q_d - q) - b * qd;
  // integrate one time step, update servo
  q  += qd  * dt;
  qd += qdd * dt;
  svo.write(q);

  // logic to reset the oscillator after a cycle has completed
  if (fabs(qd) < 0.1 && fabs(q_d - q) < 0.1) {
    q = 0.0;
    turning = false;
    }
  delay((int)(830*dt));
  }
}