When working with what frequencies the robot responds to, the temptation is to whistle into the microphone and see what it does. This is what I did in uploading the Circuit Playground sample program vu meter which uses the microphone as a sound level meter. I did get some multicolor sounds with different whistles.

But do whistles allow you to control the robot? Maybe, if you are good at your whistles. Through testing the various bins my whistling fell in, I could get decent start and stop whistles. If I whistle in a frequency filling bins 5, 6, or 7, the robot moves forwards. A different, higher whistle filling one of the bins 8, 9, or 10, the robot stops. My whistles are not varied enough to get left and right turns. If you have some range of tones, you can change which bins make which motion in the program's switch statement.

For the most flexible sound control, consider the tone controlled robot on the next page.

Here is the code for a two tone whistle bot. A lower tone starts the robot moving forward, a higher whistle stops the robot.

Download: file
// A sound controlled robot for the Adafruit Circuit Playground board
//  This uses two continuous rotation servos for movement and listens to
//  commands via the built-in mic. 
// This version is designed to respond to two whistle tones for 
//   start and stop.
// By Mike Barela for Adafruit Industries September, 2016  

#include <Adafruit_CircuitPlayground.h>   // Circuit Playground library
#include <Servo.h>                        // Audiono servo control library

// Global Definitions ------------------------------------------------------------
//  Fourier Transform 
#define BINS        32    // FFT output is output in this many bins
#define FRAMES       4    // This many FFT cycles are averaged for leveling
#define THRESHOLD  150    // Max bin value to say a tone was detected

//  Servo objects
Servo servoLeft;             // Define left servo
#define leftStopAngle  96.2  // Angle that left servo will stop (calibrated)
Servo servoRight;            // Define right servo
#define rightStopAngle 95.3  // Angle that right servo will stop (calibrated)
uint8_t moving = 0;          // Is the robot currently moving value (0 = no, 1 = yes)

// SETUP FUNCTION - this runs once to set the robot up when it is powered on

void setup() {
  servoLeft.attach(12);                 // Set left servo to digital pin 12
  servoRight.attach(6);                 // Set right servo to digital pin 6
  CircuitPlayground.begin();            // Initialize the CP library
  CircuitPlayground.setBrightness(20);  // NeoPixels are another visual que on what is happening
  CircuitPlayground.clearPixels();      // Ensure all NeoPixels are off at start
  Serial.begin(9600);                   // Set up the USB port for serial information for users
  Serial.println("\n\nAdafruit Circuit Playground Robot using Whistle Commands");
  stopRobot();
}

// LOOP FUNCTION - runs over and over to check what to do ---------------------

void loop() {
  uint8_t  i,j;                        // loop index values
  uint16_t spectrum[BINS];             // FFT spectrum output buffer
  uint16_t avg[BINS];                  // The avaerage FFT values over FRAMES iterations
  int16_t  maxVal = 0, maxIndex = 0;   // The maximum value and the FFT bin of that value
  int8_t   maxBins;

  if( !CircuitPlayground.slideSwitch() ) {  // if slide switch is moved to "-", shut robot down
     if(moving) {
        stopRobot();                        // stop the robot
        CircuitPlayground.clearPixels();    // turn all pixels off in low power mode
     }
     Serial.println("\nRobot stopped due to slide switch, move to '+' to resume");
     return;                                //  and go back to top of loop
  }

  for(j=1; j <= FRAMES; j++) {         // Gather FRAMES samples of audio from the microphone
     CircuitPlayground.mic.fft(spectrum);  // This function gathers an audio sample and does FFT
     for(i=0; i < BINS; i++) {             // Add values to perform a simple average
       if(spectrum[i] > 255) spectrum[i] = 255; // in library, sometimes values get "huge"
       if(i == 0)
         avg[i] = spectrum[i];
       else
         avg[i] = avg[i] + spectrum[i];
     }
  } 
  for(i=0; i < BINS; i++) {       // For each output bin average
     avg[i] = avg[i] / FRAMES;    //   calculate the average (unweighted)
  }

  maxBins = 0;
  for(i=0; i < BINS; i++) {   // Search for the highest value and the FFT bin it's in
     if(avg[i] > 255) avg[i] = 255;   // HACK 4 NOW
     if(avg[i] >= maxVal) {
       maxVal = avg[i];       // Important note: If there is an equal max value in higher bins
       maxIndex = i;          //   note the later index (helps when recognizing a sound)
     }
     if(avg[i] >= 254) maxBins++;
  }
  // avg[] is now FRAME averaged FFT output, 32 bins.

     if( CircuitPlayground.leftButton() ) {    // use onboard buttons to change behavior 
        maxIndex = 9; // stop
        maxVal = THRESHOLD;
     }
     if( CircuitPlayground.rightButton() ) {
        maxIndex = 7; // forward
        maxVal = THRESHOLD;
     }
    
  // A detection is defined as a bin reaching a value of at least THRESHOLD
  if( maxVal >= THRESHOLD ) {    

     // For visual review of the values the FFT has produced, print the FFTs 32 bins
     for( uint8_t j=0; j < 32; j++) {
       Serial.print(avg[j]);
       Serial.print(" ");
     }
     Serial.println("");
     // maxVal is the biggest bin, maxIndex is the index of that value
     Serial.print("\nMax Value = "); Serial.print(maxVal);
     Serial.print(", Index of Max Value = "); Serial.println(maxIndex);

     CircuitPlayground.clearPixels();  // clear old pixel values
     switch( maxIndex ) {  // based on which bin had the detection, act on it
       case  0:
       case  1:
       case  2:
                CircuitPlayground.strip.setPixelColor(0,0,240,0);
                // you can put a movement function call here
                break;       
       case  3:
       case  4:
                CircuitPlayground.strip.setPixelColor(1,0,240,0);
                // you can put a movement function call here
                break;       
       case  5:
       case  6:
       case  7:
                CircuitPlayground.strip.setPixelColor(2,10,225,10); // Low whistle (3rd LED Green)
                forward();   // forward
                break;       
       case  8:
       case  9:
       case 10:  
                CircuitPlayground.strip.setPixelColor(3,225,00,10); // higher whistle (4th LED red)
                stopRobot();
                break;     
       case 11:              // 7822 hertz
                CircuitPlayground.strip.setPixelColor(3,0,240,0);
                // you can put a movement function call here
                break;
       case 18:
       case 19: CircuitPlayground.strip.setPixelColor(4,0,240,0); // 2795 hertz
                // you can put a movement function call here
                break;
       case 20:              // 2957 hertz
       case 21:              // 3094 hertz  
       case 22: CircuitPlayground.strip.setPixelColor(5,0,240,0); // 3250 hertz
                // you can put a movement function call here
                break;
       case 23:             // 3436 hertz
       case 24:             // 3605 hertz
                
       case 25: CircuitPlayground.strip.setPixelColor(6,0,240,0); // 3700 hertz
                // you can put a movement function call here
                break;
       case 26:             // 3876 hertz
       case 27:             // 4046 hertz
       case 28: CircuitPlayground.strip.setPixelColor(7,0,240,0); // 4192 hertz (motor noise?)
                // you can put a movement function call here
                break;
       case 29:             // 4339 hertz
       case 30:             // 4517 hertz
       case 31: CircuitPlayground.strip.setPixelColor(8,0,240,0); // 4640 hertz (often goes here)
                // you can put a movement function call here
                break;
       default: CircuitPlayground.strip.setPixelColor(9,200,0,0); // if any other bin, light NeoPixel #9 red
                break;
     } // end switch
     CircuitPlayground.strip.show();  // show the neopixel assigned to the bin
  } // end if
} // end function loop

// Servo motion function routines for robot movement forward, reverse, turns, and stop

void stopRobot() {
  moving = 0;                        // let program know we are stopped
  servoLeft.write(leftStopAngle);    // Get these values from a calibration routine
  servoRight.write(rightStopAngle);  //  as continuous servos don't stop at exactly 90.0
  Serial.println("Stopped");
}

void forward() {
  if( moving == 1 ) {  // if the robot currently is moving  NEEDED?
    stopRobot();       // stop it 
  }
  else {
    moving = 1;        // flag we are going to move
  }
  servoLeft.write(0);
  servoRight.write(180);
}

void reverse() {
  if( moving == 1 ) {  // if the robot currently is moving
    stopRobot();       // stop it 
  }
  else {
    moving = 1;        // flag we are going to move
  }
  servoLeft.write(180);
  servoRight.write(0);
}

void turnRight() {
  if( moving == 1 ) {  // if the robot currently is moving
    stopRobot();       // stop it 
  }
  else {
    moving = 1;        // flag we are going to move
  }
  servoLeft.write(180);
  servoRight.write(180);
}

void turnLeft() {
  if( moving == 1 ) {  // if the robot currently is moving
    stopRobot();       // stop it 
  }
  else {
    moving = 1;        // flag we are going to move
  }
  servoLeft.write(0);
  servoRight.write(0);
}

Using the Whistle Bot

Use a lower tone whistle to start it forward, a higher whistle to stop. You'll probably need to experiment with your whistle tones to find the right ones. It currently is calibrated for more of a male whistle (I'm told by my wife).

Whistle and see which NeoPixels light up. For me it's the 4th and 5th pixels (#3 and #4). If you see something like the second and third (NeoPixels #2 and #3), you can change where the movement function calls are in the switch..case statement (there are comments where to put the calls in).

The left and right pushbuttons perform the same two functions (start & stop) if you would like to manually change the mode. If you change the switch..case statement, put the case numbers in the if statements that check the left and right buttons on Circuit Playground.

Here is a video of the whistle bot in action:

Variations on the Whistle Bot

You could use a first low tone for start and a second low tone for stop. You would test if variable moving is 1 then you's call stopRobot instead of forward. Then you have the higher tone to do something like calling reverse.

If you can whistle in three tones, you can probably call all of the control functions with some variables which record what state you are in and what state you want the robot to change to when a whistle is received.

On the next page you can build an inexpensive tone generator to save you from having to whistle so precisely.

This guide was first published on Oct 12, 2016. It was last updated on Oct 12, 2016.

This page (Whistle Bot) was last updated on Nov 24, 2020.