3.b. Orangutan Buzzer Control Functions

Overview

These functions allow you to easily control the buzzer on the 3pi robot, Orangutan SV, Orangutan SVP and Orangutan LV-168 . You have 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 Timer 1 PWM outputs, and duration timing is performed using a Timer 1 overflow interrupt.

Note: The OrangutanServos and OrangutanBuzzer libraries both use Timer 1, so they will conflict with each other and any other code that relies on or reconfigures Timer 1.

This library is incompatible with some older releases of WinAVR. If you experience any problems when using this library, make sure that your copy of the compiler is up-to-date. We know that it works with WinAVR 20080610.

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 does its main task. You can poll the isPlaying() method to determine when the buzzer is finished playing.

C++ users: See Section 5.b of Programming Orangutans and the 3pi Robot from the Arduino Environment for examples of this class in the Arduino environment, which is almost identical to C++.

Complete documentation of the functions can be found in Section 3 of the Pololu AVR Library Command Reference.

Usage Examples

This library comes with three examples in libpololu-avr\examples.

1. buzzer1

Demonstrates one way to use this library’s play_note() 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 the main 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; the mega328 has enough room in EEPROM to store 340 notes).

#include <pololu/orangutan.h>
/*
 * buzzer1:
 *
 * This example uses the OrangutanBuzzer library to play a series of notes
 * on the Orangutan's/3pi's buzzer.  It also uses the OrangutanLCD library
 * to display the notes its 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/0J20
 * 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] = 
{
  E(5), SILENT_NOTE, E(5), SILENT_NOTE, E(5), SILENT_NOTE, C(5), E(5),
  G(5), SILENT_NOTE, G(4), SILENT_NOTE,

  C(5), G(4), SILENT_NOTE, E(4), A(4), B(4), B_FLAT(4), A(4), G(4),
  E(5), G(5), A(5), F(5), G(5), SILENT_NOTE, E(5), C(5), D(5), B(4),

  C(5), G(4), SILENT_NOTE, E(4), A(4), B(4), B_FLAT(4), A(4), G(4),
  E(5), G(5), A(5), F(5), G(5), SILENT_NOTE, E(5), C(5), D(5), B(4),

  SILENT_NOTE, G(5), F_SHARP(5), F(5), D_SHARP(5), E(5), SILENT_NOTE,
  G_SHARP(4), A(4), C(5), SILENT_NOTE, A(4), C(5), D(5),

  SILENT_NOTE, G(5), F_SHARP(5), F(5), D_SHARP(5), E(5), SILENT_NOTE,
  C(6), SILENT_NOTE, C(6), SILENT_NOTE, C(6),

  SILENT_NOTE, G(5), F_SHARP(5), F(5), D_SHARP(5), E(5), SILENT_NOTE,
  G_SHARP(4), A(4), C(5), SILENT_NOTE, A(4), C(5), D(5),

  SILENT_NOTE, E_FLAT(5), SILENT_NOTE, D(5), 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
};

unsigned char currentIdx;

int main()                    // run once, when the sketch starts
{
  currentIdx = 0;
  print("Music!");

  while(1)                     // 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 && !is_playing())
    {
      // play note at max volume
      play_note(note[currentIdx], duration[currentIdx], 15);
      
      // optional LCD feedback (for fun)
      lcd_goto_xy(0, 1);                           // go to start of the second LCD line
	  if(note[currentIdx] != 255) // display blank for rests
        print_long(note[currentIdx]);  // print integer value of the current note
      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 (button_is_pressed(TOP_BUTTON))
    {
      stop_playing(); // silence the buzzer
      if (currentIdx < MELODY_LENGTH)
        currentIdx = MELODY_LENGTH;        // terminate the melody
      else
        currentIdx = 0;                    // restart the melody
      wait_for_button_release(TOP_BUTTON);  // wait here for the button to be released
    }
  }

  return 0;
}
2. buzzer2

Demonstrates how you can use this library’s play() function 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() function 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 <pololu/orangutan.h>

/*
 * buzzer2:
 *
 * This example uses the OrangutanBuzzer functions to play a series of notes
 * on the Orangutan's/3pi's buzzer.  It uses the OrangutanPushbuttons
 * library to allow the user select which melody plays.
 *
 * This example demonstrates the use of the 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/0J20
 * http://www.pololu.com
 * http://forum.pololu.com
 */

#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 loop()                     // run over and over again
{
  // wait here for one of the three buttons to be pushed
  unsigned char button = wait_for_button(ALL_BUTTONS);
  clear();
  
  if (button == TOP_BUTTON)
  {
    play_from_program_space(fugue);

    print("Fugue!");
    lcd_goto_xy(0, 1);
    print("flash ->");
  }
  if (button == MIDDLE_BUTTON)
  {
    play("! V8 cdefgab>cbagfedc");
    print("C Major");
    lcd_goto_xy(0, 1);
    print("RAM ->");
  }
  if (button == BOTTOM_BUTTON)
  {
    if (is_playing())
    {
      stop_playing();
      print("stopped");
    }
    else
    {
      play_note(A(5), 200, 15);
      print("note A5"); 
    }
  }
}

int main()                    // run once, when the program starts
{
  print("Press a");
  lcd_goto_xy(0, 1);
  print("button..");

  while(1)
  	loop();

  return 0;
}
3. buzzer3

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.

/*
 * buzzer3: for the Orangutan LV, SV, SVP, X2, and 3pi robot.
 *
 * This example program is indended for use on the Orangutan LV, SV,
 * SVP, X2, and 3pi robot and will NOT work under the Arduino environment.
 * It uses the OrangutanBuzzer functions to play a series of notes on
 * the Orangutan's buzzer.
 *
 * This example demonstrates the use of the play_mode()
 * and play_check() methods, which allow you to select
 * whether the melody sequence initiated by play() is
 * played automatically in the background by the Timer1 interrupt, or if
 * the play is driven by the play_check() 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.
 *
 * http://www.pololu.com/docs/0J20
 * http://www.pololu.com
 * http://forum.pololu.com
 */

#include <pololu/orangutan.h>
 
// *** UNCOMMENT ONE OF THE FOLLOWING PREPROCESSOR DIRECTIVES ***
// (the remaining two should be commented out)
#define WORKING_CORRECTLY  // this is the right way to use playMode()
//#define ALWAYS_AUTOMATIC // mode is always PLAY_AUTOMATIC, timing is inaccurate
//#define ALWAYS_CHECK     // mode is always PLAY_CHECK, delays interrupt the sequence

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


int main()
{
  TCCR0A = 0;         // configure timer0 to run at 78 kHz
  TCCR0B = 0x04;      // and overflow when TCNT0 = 256 (~3 ms)
  play_from_program_space(rhapsody);

  while(1)
  {
    // allow the sequence to keep playing automatically through the following delays
#ifndef ALWAYS_CHECK
    play_mode(PLAY_AUTOMATIC);
#else
    play_mode(PLAY_CHECK);
#endif
    lcd_goto_xy(0, 0);
    print("blink!");
    int i;
    for (i = 0; i < 8; i++)
    {
#ifdef ALWAYS_CHECK
      play_check();
#endif
      red_led(1);
      delay_ms(500);
      red_led(0);
      delay_ms(500);
    }
  
    lcd_goto_xy(0, 0);
    print("timing");
    lcd_goto_xy(0, 1);
    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
    play_mode(PLAY_CHECK);
#endif
    unsigned char maxTime = 0;
    for (i = 0; i < 8000; i++)
    {
      TCNT0 = 0;
      while (TCNT0 < 20)    // time for ~250 us
        ;
      if (TCNT0 > maxTime)
        maxTime = TCNT0;    // if the elapsed time is greater than the previous max, save it
#ifndef ALWAYS_AUTOMATIC
      play_check();   // check if it's time to play the next note and play it if so
#endif
    }
    lcd_goto_xy(0, 1);
    print("max=");
    print_long((unsigned int)maxTime);
    print(" ");  // overwrite any left over characters
  }
}