Here is the code that makes your robot go. You'll need to have a few libraries installed in the Arduino IDE.


When you're ready to go wireless, cut and tin the leads of one of the 9V battery clips and insert the wires into the PWM shield PC terminal.

The other battery clip is for the Arduino... via the barrel connector.

Download: file
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
#include <Adafruit_NeoPixel.h>
#include "Adafruit_LEDBackpack.h"
#include "Adafruit_GFX.h"

Adafruit_NeoPixel mouth = Adafruit_NeoPixel(8, 7, NEO_GRB + NEO_KHZ800);
Adafruit_PWMServoDriver servos = Adafruit_PWMServoDriver();
Adafruit_8x8matrix eyes = Adafruit_8x8matrix();

// Eye bitmaps are stored in program memory.
static uint8_t PROGMEM
blinkImg[][8] = {
  { 
    B00111100,
    B01111110,
    B11111111,
    B11111111,
    B11111111,
    B11111111,
    B01111110,
    B00111100 }
  ,
  { 
    B00000000,
    B01111110,
    B11111111,
    B11111111,
    B11111111,
    B11111111,
    B01111110,
    B00111100 }
  ,
  { 
    B00000000,
    B00000000,
    B00111100,
    B11111111,
    B11111111,
    B11111111,
    B00111100,
    B00000000 }
  ,
  { 
    B00000000,
    B00000000,
    B00000000,
    B00111100,
    B11111111,
    B01111110,
    B00011000,
    B00000000 }
  ,
  { 
    B00000000,
    B00000000,
    B00000000,
    B00000000,
    B10000001,
    B01111110,
    B00000000,
    B00000000 } 
};

// Analog threshold used to determine when FOB buttons are pressed.
const byte BUTTON_THRESHOLD = 50;

uint8_t
blinkIndex[] = { 1, 2, 3, 4, 3, 2, 1 }, // Blink bitmap sequence.
blinkCountdown = 100,  // Countdown to next blink (in frames).
gazeCountdown  =  75,  // Countdown to next eye movement.
gazeFrames     =  50;

int8_t
eyeX = 3, eyeY = 3,     // Current eye position.
newX = 3, newY = 3,     // Next eye position.
dX   = 0, dY   = 0;  

unsigned long tme = 0;  // Last time we processed a mouth animation.
unsigned long slc = 50; // Milliseconds between mouth animations.
unsigned long ms = 0;   // Used to track a millis() time-slice.

int stt = 0;            // Revolving start position for the neck servo.
int pos = 0;            // Current 'moving' position of the neck servo.
int dst = 0;            // Random destination for the neck servo.
int dur = 0;            // Number of frames used by the easing function.
int fme = 0;            // Animation frame counter.

void setup() {
  // Make sure to adjust the address jumpers on the shield, and 
  // in Adafruit_PWMServoDriver.h on line ~48 -- I've used 0x43.
  eyes.begin(0x73);
  // Brightness is set _very_ low to conserve power.
  eyes.setBrightness(1);

  // We're using a Neo Pixel Stick for the mouth.
  mouth.begin();
  mouth.show();

  // We have two servos in the feet and one in the neck.
  servos.begin();
  servos.setPWMFreq(60);
}

void loop() {

  // Every few milliseconds we will animate the mouth.
  ms = millis();
  if ( tme + slc < ms ) {
    tme = ms;

    // 10% chance that the mouth will animate.
    if ( random( 1, 10 ) == 1 ) {
      // LEDs are set to ~1% brightness.
      animateMouth(mouth.Color(0, 0, 5), 15);
    } 
    else {
      // LEDs are set to ~10% brightness.
      animateMouth(mouth.Color(0, 0, 25), 15);
    }

    // Every few milliseconds we will update the neck position.
    if ( pos != dst && fme < dur )
    {
      // Update position until we arrive at dst.
      pos = (int)easeInOut( fme, stt, ( dst - stt ), dur );
      fme += 1;
      servos.setPWM(2, 0, pos);
    } 
    else {
      // Advance to the new destination.
      stt = pos;
      // Adjust this range if necessary.
      dst = random(150, 350);
      fme = 0;
      dur = 50;
    }
  }

  // Mirror animation of each 8x8 eye matrix.
  eyes.clear();
  eyes.drawBitmap(0, 0,
  // Blinking? Yes, look-up bitmap #. No, show bitmap 0.
  blinkImg[ (blinkCountdown < sizeof(blinkIndex)) ? blinkIndex[blinkCountdown] : 0 ], 8, 8, LED_ON);
  // Decrement blink counter.  At end, set random time for next blink.
  if(--blinkCountdown == 0) blinkCountdown = random(5, 180);

  // Add a pupil (2x2 black square) atop the blinky eyeball bitmap.
  // Periodically, the pupil moves to a new position...
  if(--gazeCountdown <= gazeFrames) {
    // Eyes are in motion - draw pupil at interim position.
    eyes.fillRect(
    newX - (dX * gazeCountdown / gazeFrames),
    newY - (dY * gazeCountdown / gazeFrames),
    2, 2, LED_OFF);
    if(gazeCountdown == 0) {    // Last frame?
      eyeX = newX; 
      eyeY = newY; // Yes.  What's new is old, then...
      do { // Pick random positions until one is within the eye circle.
        newX = random(7); 
        newY = random(7);
        dX   = newX - 3;  
        dY   = newY - 3;
      } 
      while((dX * dX + dY * dY) >= 10);        // Thank you Pythagoras.
      dX            = newX - eyeX;             // Horizontal distance to move.
      dY            = newY - eyeY;             // Vertical distance to move.
      gazeFrames    = random(3, 15);           // Duration of eye movement.
      gazeCountdown = random(gazeFrames, 120); // Count to end of next movement.
    }
  } 
  else {
    // Not in motion yet -- draw pupil at current static position.
    eyes.fillRect(eyeX, eyeY, 2, 2, LED_OFF);
  }
  eyes.writeDisplay();

  // Now, lets check to see if any of the buttons on the FOB are pressed.
  byte a = analogRead(3); // forward right
  byte b = analogRead(2); // forward left
  byte c = analogRead(1); // reverse right
  byte d = analogRead(0); // reverse left

  if ( ( b > BUTTON_THRESHOLD && d > BUTTON_THRESHOLD ) || ( b < BUTTON_THRESHOLD && d < BUTTON_THRESHOLD ) ) {
    // Turn servo 0 off if FOB buttons B + D are both pressed or released.
    servos.setPWM(0, 0, 0);
  } 
  else {
    if ( b > BUTTON_THRESHOLD ) {
      // FOB button B is pressed... forward left.
      servos.setPWM(0, 0, 400);
    }
    if ( d > BUTTON_THRESHOLD ) {
      // FOB button D is pressed... reverse left.
      servos.setPWM(0, 0, 350);
    }
  }

  if ( ( a > BUTTON_THRESHOLD && c > BUTTON_THRESHOLD ) || ( a < BUTTON_THRESHOLD && c < BUTTON_THRESHOLD ) ) {
    // Turn servo 1 off if FOB buttons A + C are both pressed or released.
    servos.setPWM(1, 0, 0);
  } 
  else {
    if ( a > BUTTON_THRESHOLD ) {
      // FOB button A is pressed... forward left.
      servos.setPWM(1, 0, 350);
    }
    if ( c > BUTTON_THRESHOLD ) {
      // FOB button C is pressed... reverse left.
      servos.setPWM(1, 0, 400);
    }
  }
}

void animateMouth(uint32_t c, uint8_t wait) {
  // Quick loop that does a symmetric animation on a single Neo Pixel Strip.
  for(uint16_t i=( mouth.numPixels() / 2 ); i<mouth.numPixels(); i++) {
    // Pixels 3, 2, 1, and 0.
    mouth.setPixelColor(mouth.numPixels() - i - 1, c);
    // Pixels 4, 5, 6, and 7.
    mouth.setPixelColor(i, c);
    mouth.show();
    delay(wait);
  }
}

float easeInOut( float t, float b, float c, float d )
{
  // Function used to smooth servo movements.
  if ((t/=d/2) < 1) 
    return c/2*t*t*t + b;

  return c/2*((t-=2)*t*t + 2) + b;
}

This guide was first published on Feb 17, 2014. It was last updated on Feb 17, 2014.

This page (Source Code) was last updated on Nov 18, 2020.