5.b. OrangutanBuzzer - High-Level Buzzer Control Library

Overview

This library allows you to easily control the piezo buzzer on the 3pi robot, Orangutan SV-xx8, and Orangutan LV-168. It gives you the option of playing either a note or a frequency for a specified duration at a specified volume, or you can use the play() method to play an entire melody in the background. Buzzer control is achieved using one of the Timer1 PWM outputs, and duration timing is performed using a Timer1 overflow interrupt, so this library will conflict with any other libraries that rely on or reconfigure Timer1. For example, the Arduino function analogWrite() will not work on the Timer1 PWM output pins once you have started to use this library in your sketch.

The benefit to this approach is that you can play notes on the buzzer while leaving the CPU mostly free to execute the rest of your code. This means you can have a melody playing in the background while your Orangutan or 3pi does its main task. You can poll the isPlaying() method to determine when the buzzer is finished playing.

You do not need to initialize your OrangutanBuzzer object before use. All initialization is performed automatically when needed.

All of the methods in this class are static; you should never have more than one instance of an OrangutanBuzzer object in your sketch.

OrangutanBuzzer Methods

Complete documentation of this library’s methods can be found in Section 3 of the Pololu AVR Library Command Reference.

Usage Examples

This library comes with three example sketches that you can load by going to File > Examples > OrangutanBuzzer.

1. OrangutanBuzzerExample

Demonstrates one way to use this library’s playNote() method to play a simple melody stored in RAM. It should immediately start playing the melody, and you can use the top user pushbutton to stop and replay the melody. The example is structured so that you can add your own code to loop() and the melody will still play normally in the background, assuming your code executes quickly enough to avoid inserting delays between the notes. You can use this same technique to play melodies that have been stored in EEPROM (the mega168 has enough room in EEPROM to store 170 notes).

#include <OrangutanLCD.h>
#include <OrangutanPushbuttons.h>
#include <OrangutanBuzzer.h>

/*
 * OrangutanBuzzerExample: for the Orangutan SV-xx8, Orangutan LV-168,
 *    and 3pi robot
 *
 * This example uses the OrangutanBuzzer library to play a series of notes on
 * the buzzer.  It also uses the OrangutanLCD library to display the notes it is
 * playing, and it uses the OrangutanPushbuttons library to allow the user to
 * stop/reset the melody with the top pushbutton.
 *
 * http://www.pololu.com/docs/0J17/5.b
 * http://www.pololu.com
 * http://forum.pololu.com
 */

#define MELODY_LENGTH 95

// These arrays take up a total of 285 bytes of RAM (out of a 1k limit)
unsigned char note[MELODY_LENGTH] = 
{
  NOTE_E(5), SILENT_NOTE, NOTE_E(5), SILENT_NOTE, 
  NOTE_E(5), SILENT_NOTE, NOTE_C(5), NOTE_E(5),
  NOTE_G(5), SILENT_NOTE, NOTE_G(4), SILENT_NOTE,

  NOTE_C(5), NOTE_G(4), SILENT_NOTE, NOTE_E(4), NOTE_A(4), 
  NOTE_B(4), NOTE_B_FLAT(4), NOTE_A(4), NOTE_G(4),
  NOTE_E(5), NOTE_G(5), NOTE_A(5), NOTE_F(5), NOTE_G(5), 
  SILENT_NOTE, NOTE_E(5), NOTE_C(5), NOTE_D(5), NOTE_B(4),

  NOTE_C(5), NOTE_G(4), SILENT_NOTE, NOTE_E(4), NOTE_A(4), 
  NOTE_B(4), NOTE_B_FLAT(4), NOTE_A(4), NOTE_G(4),
  NOTE_E(5), NOTE_G(5), NOTE_A(5), NOTE_F(5), NOTE_G(5), 
  SILENT_NOTE, NOTE_E(5), NOTE_C(5), NOTE_D(5), NOTE_B(4),

  SILENT_NOTE, NOTE_G(5), NOTE_F_SHARP(5), NOTE_F(5),
  NOTE_D_SHARP(5), NOTE_E(5), SILENT_NOTE, NOTE_G_SHARP(4),
  NOTE_A(4), NOTE_C(5), SILENT_NOTE, NOTE_A(4), NOTE_C(5), NOTE_D(5),

  SILENT_NOTE, NOTE_G(5), NOTE_F_SHARP(5), NOTE_F(5),
  NOTE_D_SHARP(5), NOTE_E(5), SILENT_NOTE,
  NOTE_C(6), SILENT_NOTE, NOTE_C(6), SILENT_NOTE, NOTE_C(6),

  SILENT_NOTE, NOTE_G(5), NOTE_F_SHARP(5), NOTE_F(5),
  NOTE_D_SHARP(5), NOTE_E(5), SILENT_NOTE,
  NOTE_G_SHARP(4), NOTE_A(4), NOTE_C(5), SILENT_NOTE, 
  NOTE_A(4), NOTE_C(5), NOTE_D(5),

  SILENT_NOTE, NOTE_E_FLAT(5), SILENT_NOTE, NOTE_D(5), NOTE_C(5)
};

unsigned int duration[MELODY_LENGTH] =
{
  100, 25, 125, 125, 125, 125, 125, 250, 250, 250, 250, 250,

  375, 125, 250, 375, 250, 250, 125, 250, 167, 167, 167, 250, 125, 125,
  125, 250, 125, 125, 375,

  375, 125, 250, 375, 250, 250, 125, 250, 167, 167, 167, 250, 125, 125,
  125, 250, 125, 125, 375,

  250, 125, 125, 125, 250, 125, 125, 125, 125, 125, 125, 125, 125, 125,

  250, 125, 125, 125, 250, 125, 125, 200, 50, 100, 25, 500,

  250, 125, 125, 125, 250, 125, 125, 125, 125, 125, 125, 125, 125, 125,

  250, 250, 125, 375, 500
};

OrangutanLCD lcd;
OrangutanPushbuttons buttons;
OrangutanBuzzer buzzer;
unsigned char currentIdx;

void setup()                    // run once, when the sketch starts
{
  currentIdx = 0;
  lcd.print("Music!");
}

void loop()                     // run over and over again
{
  // if we haven't finished playing the song and 
  // the buzzer is ready for the next note, play the next note
  if (currentIdx < MELODY_LENGTH && !buzzer.isPlaying())
  {
    // play note at max volume
    buzzer.playNote(note[currentIdx], duration[currentIdx], 15);
    
    // optional LCD feedback (for fun)
    lcd.gotoXY(0, 1);                           // go to start of the second LCD line
    lcd.print((unsigned int)note[currentIdx]);  // print integer value of current note
    lcd.print("  ");                            // overwrite any left over characters
    currentIdx++;
  }

  // Insert some other useful code here...
  // the melody will play normally while the rest of your code executes
  // as long as it executes quickly enough to keep from inserting delays
  // between the notes.
  
  // For example, let the top user pushbutton function as a stop/reset melody button
  if (buttons.isPressed(TOP_BUTTON))
  {
    buzzer.stopPlaying(); // silence the buzzer
    if (currentIdx < MELODY_LENGTH)
      currentIdx = MELODY_LENGTH;        // terminate the melody
    else
      currentIdx = 0;                    // restart the melody
    buttons.waitForRelease(TOP_BUTTON);  // wait here for the button to be released
  }
}

2. OrangutanBuzzerExample2

Demonstrates how you can use this library’s play() method to start a melody playing. Once started, the melody will play all the way to the end with no further action required from your code, and the rest of your program will execute as normal while the melody plays in the background. The play() method is driven entirely by the Timer1 overflow interrupt. The top user pushbutton will play a fugue by Bach from program memory, the middle user pushbutton will quietly play the C major scale up and back down from RAM, and the bottom user pushbutton will stop any melody that is currently playing or play a single note if the buzzer is currently inactive.

#include <OrangutanLCD.h>
#include <OrangutanPushbuttons.h>
#include <OrangutanBuzzer.h>

/*
 * OrangutanBuzzerExample2: for the Orangutan SV-xx8, Orangutan LV-168,
 *    and 3pi robot
 *
 * This example uses the OrangutanBuzzer library to play a series of notes on
 * the buzzer.  It uses the OrangutanPushbuttons library to allow the user
 * select which melody plays.
 *
 * This example demonstrates the use of the OrangutanBuzzer::play() method,
 * which plays the specified melody entirely in the background, requiring
 * no further action from the user once the method is called.  The CPU
 * is then free to execute other code while the melody plays.
 *
 * http://www.pololu.com/docs/0J17/5.b
 * http://www.pololu.com
 * http://forum.pololu.com
 */

OrangutanLCD lcd;
OrangutanPushbuttons buttons;
OrangutanBuzzer buzzer;

#include <avr/pgmspace.h>  // this lets us refer to data in program space (i.e. flash)
// store this fugue in program space using the PROGMEM macro.  
// Later we will play it directly from program space, bypassing the need to load it 
// all into RAM first.
const char fugue[] PROGMEM = 
  "! O5 L16 agafaea dac+adaea fa<aa<bac#a dac#adaea f"
  "O6 dcd<b-d<ad<g d<f+d<gd<ad<b- d<dd<ed<f+d<g d<f+d<gd<ad"
  "L8 MS <b-d<b-d MLe-<ge-<g MSc<ac<a ML d<fd<f O5 MS b-gb-g"
  "ML >c#e>c#e MS afaf ML gc#gc# MS fdfd ML e<b-e<b-"
  "O6 L16ragafaea dac#adaea fa<aa<bac#a dac#adaea faeadaca"
  "<b-acadg<b-g egdgcg<b-g <ag<b-gcf<af dfcf<b-f<af"
  "<gf<af<b-e<ge c#e<b-e<ae<ge <fe<ge<ad<fd"
  "O5 e>ee>ef>df>d b->c#b->c#a>df>d e>ee>ef>df>d"
  "e>d>c#>db>d>c#b >c#agaegfe f O6 dc#dfdc#<b c#4";


void setup()                    // run once, when the sketch starts
{
  lcd.print("Press a");
  lcd.gotoXY(0, 1);
  lcd.print("button..");
}

void loop()                     // run over and over again
{
  // wait here for one of the three buttons to be pushed
  unsigned char button = buttons.waitForButton(ALL_BUTTONS);
  lcd.clear();
  
  if (button == TOP_BUTTON)
  {
    buzzer.playFromProgramSpace(fugue);

    lcd.print("Fugue!");
    lcd.gotoXY(0, 1);
    lcd.print("flash ->");
  }
  if (button == MIDDLE_BUTTON)
  {
    buzzer.play("! V8 cdefgab>cbagfedc");
    lcd.print("C Major");
    lcd.gotoXY(0, 1);
    lcd.print("RAM ->");
  }
  if (button == BOTTOM_BUTTON)
  {
    if (buzzer.isPlaying())
    {
      buzzer.stopPlaying();
      lcd.print("stopped");
    }
    else
    {
      buzzer.playNote(NOTE_A(5), 200, 15);
      lcd.print("note A5"); 
    }
  }
}

3. OrangutanBuzzerExample3

Demonstrates the use of this library’s playMode() and playCheck() methods. In this example, automatic play mode is used to allow the melody to keep playing while it blinks the red user LED. Then the mode is switched to play-check mode during a phase where we are trying to accurately measure time. There are three #define macros that allow you to run this example in different ways and observe the result. Please see the comments at the top of the sketch for more detailed information.

#include <OrangutanLCD.h>
#include <OrangutanLEDs.h>
#include <OrangutanBuzzer.h>

/*
 * OrangutanBuzzerExample3: for the Orangutan LV-168, Orangutan SV-xx8,
 *    or 3pi robot
 *
 * This example uses the OrangutanBuzzer library to play a series of notes on
 * the target's piezo buzzer.
 *
 * This example demonstrates the use of the OrangutanBuzzer::playMode()
 * and OrangutanBuzzer::playCheck() methods, which allow you to select
 * whether the melody sequence initiated by OrangutanBuzzer::play() is
 * played automatically in the background by the Timer1 interrupt, or if
 * the play is driven by the playCheck() method in your main loop.
 *
 * Automatic play mode should be used if your code has a lot of delays
 * and is not time critical.  In this example, automatic mode is used
 * to allow the melody to keep playing while we blink the red user LED.
 *
 * Play-check mode should be used during parts of your code that are
 * time critical.  In automatic mode, the Timer1 interrupt is very slow
 * when it loads the next note, and this can delay the execution of your.
 * Using play-check mode allows you to control when the next note is
 * loaded so that it doesn't occur in the middle of some time-sensitive
 * measurement.  In our example we use play-check mode to keep the melody
 * going while performing timing measurements using Timer2.  After the
 * measurements, the maximum time measured is displayed on the LCD.
 *
 * Immediately below are three #define statements that allow you to alter
 * the way this program runs.  You should have one of the three lines
 * uncommented while commenting out the other two:  
 *
 * If only WORKING_CORRECTLY is uncommented, the program should run in its
 * ideal state, using automatic play mode during the LED-blinking phase
 * and using play-check mode during the timing phase.  The maximum recorded
 * time should be 20, as expected.
 *
 * If only ALWAYS_AUTOMATIC is uncommented, the program will use automatic
 * play mode during both the LED-blinking phase and the timing phase.  Here
 * you will see the effect this has on the time measurements (instead of 20,
 * you should see a maximum reading of around 27 or 28).
 *
 * If only ALWAYS_CHECK is uncommented, the program will be in play-check
 * mode during both the LED-blinking phase and the timing phase.  Here you
 * will see the effect that the LED-blinking delays have on play-check
 * mode (the sequence will be very choppy while the LED is blinking, but
 * sound normal during the timing phase).  The maximum timing reading should
 * be 20, as expected.
 */
 
// *** UNCOMMENT ONE OF THE FOLLOWING PRECOMPILER DIRECTIVES ***
// (the remaining two should be commented out)
#define WORKING_CORRECTLY    // this is the right way to use playMode()
//#define ALWAYS_AUTOMATIC   // playMode() is always PLAY_AUTOMATIC (timing is inaccurate)
//#define ALWAYS_CHECK       // playMode() is always PLAY_CHECK (delays interrupt the sequence)

OrangutanLEDs leds;
OrangutanBuzzer buzzer;
OrangutanLCD lcd;

#include <avr/pgmspace.h>
const char rhapsody[] PROGMEM = "O6 T40 L16 d#<b<f#<d#<f#<bd#f#"
  "T80 c#<b-<f#<c#<f#<b-c#8"
  "T180 d#b<f#d#f#>bd#f#c#b-<f#c#f#>b-c#8 c>c#<c#>c#<b>c#<c#>c#c>c#<c#>c#<b>c#<c#>c#"
  "c>c#<c#>c#<b->c#<c#>c#c>c#<c#>c#<b->c#<c#>c#"
  "c>c#<c#>c#f>c#<c#>c#c>c#<c#>c#f>c#<c#>c#"
  "c>c#<c#>c#f#>c#<c#>c#c>c#<c#>c#f#>c#<c#>c#d#bb-bd#bf#d#c#b-ab-c#b-f#d#";


void setup()                    // run once, when the sketch starts
{
  TCCR2A = 0;         // configure timer2 to run at 78 kHz
  TCCR2B = 0x06;      // and overflow when TCNT2 = 256 (~3 ms)
  buzzer.playFromProgramSpace(rhapsody);
}

void loop()                     // run over and over again
{
  // allow the sequence to keep playing automatically through the following delays
#ifndef ALWAYS_CHECK
  buzzer.playMode(PLAY_AUTOMATIC);
#else
  buzzer.playMode(PLAY_CHECK);
#endif
  lcd.gotoXY(0, 0);
  lcd.print("blink!");
  int i;
  for (i = 0; i < 8; i++)
  {
#ifdef ALWAYS_CHECK
    buzzer.playCheck();
#endif
    leds.red(HIGH);
    delay(500);
    leds.red(LOW);
    delay(500);
  }
  
  lcd.gotoXY(0, 0);
  lcd.print("timing");
  lcd.gotoXY(0, 1);
  lcd.print("        ");    // clear bottom LCD line
  // turn off automatic playing so that our time-critical code won't be interrupted by
  // the buzzer's long timer1 interrupt.  Otherwise, this interrupt could throw off our
  // timing measurements.  Instead, we will now use playCheck() to keep the sequence
  // playing in a way that won't throw off our measurements.
#ifndef ALWAYS_AUTOMATIC
  buzzer.playMode(PLAY_CHECK);
#endif
  unsigned char maxTime = 0;
  for (i = 0; i < 8000; i++)
  {
    TCNT2 = 0;
    while (TCNT2 < 20)    // time for ~250 us
      ;
    if (TCNT2 > maxTime)
      maxTime = TCNT2;    // if the elapsed time is greater than the previous max, save it
#ifndef ALWAYS_AUTOMATIC
    buzzer.playCheck();   // check if it's time to play the next note and play it if so
#endif
  }
  lcd.gotoXY(0, 1);
  lcd.print("max=");
  lcd.print((unsigned int)maxTime);
  lcd.print(' ');  // overwrite any left over characters
}