The final program for the robot combines the building blocks we've worked on in the tutorial:

  • Using the Circuit Playground library for microphone, NeoPixel, and switch functions
  • Taking FFT information and looking for our command sounds
  • Moving the robot based on the command sounds 

You might want to review how to program Circuit Playground. And definitely use a good USB data cable and not a charging cable (even I have done this and said "doh!").

Here is the code to run on the Circuit Playground for the tone controlled robot:

// 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. Sound processing is done with the
//  Circuit Playground FFT library function for AVR microcontrollers.

// The fast Fourier transform (FFT) algorithm converts a signal from the
// time domain to the frequency domain -- here, turning a specific audio
// signal into a command to the robot.

// Note the Circuit Playground slide switch when set to "-" stops the motors
//  It is not a battery off switch so unplug or turn off the battery to save
//  power when not in use.

// By Anne Barela for Adafruit Industries September, 2016  Version 1.0

#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");
  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.

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

    if( maxBins > 3 ) {  // Some loud broad spectrum sound, we don't want to act on that
      Serial.println("\nMore than 3 bins are maxed out");
      return;
    }

    // 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 10:              // 8000  hertz
       case 11: unused();    // 7822 hertz
                break;
       case 18:
       case 19: CircuitPlayground.strip.setPixelColor(0,0,240,0); // 2795 hertz
                forward();   // forward
                break;
       case 20:              // 2957 hertz
       case 21: unused();    // 3094 hertz  
                break;
       case 22: CircuitPlayground.strip.setPixelColor(1,0,240,0); // 3250 hertz
                turnLeft();
                break;
       case 23:             // 3436 hertz
       case 24: unused();   // 3605 hertz
                break;
       case 25: CircuitPlayground.strip.setPixelColor(2,0,240,0); // 3700 hertz
                turnRight();
                break;
       case 26:             // 3876 hertz
       case 27: unused();   // 4046 hertz
                break;
       case 28: CircuitPlayground.strip.setPixelColor(3,0,240,0); // 4192 hertz
                stopRobot();
                break;
       case 29:             // 4339 hertz
       case 30: unused();   // 4517 hertz
                break;
       case 31: CircuitPlayground.strip.setPixelColor(4,0,240,0); // 4640 hertz
                // reverse();
                break;
       default: CircuitPlayground.strip.setPixelColor(9,200,0,0); // if any other bin, light NeoPixel #9 red
                stopRobot(); 
                break;
     } // end switch
     CircuitPlayground.strip.show();  // show the neopixel assigned to the bin
  } // end if
} // end function loop

// Servo motion 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);
}

void unused() {   // Frequency not used at present
  CircuitPlayground.strip.setPixelColor(8,0,0,255); // display a blue light on pixel 8
}

Do you wheels turn when in the robot stop mode? You need to be sure the calibration calues for your servos are determined as on the previous page. I doubt the values I have will be the same as yours. They should be between 83 and 97.

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

This page (Tone Controlled Robot) was last updated on Oct 03, 2016.

Text editor powered by tinymce.