mirror of
				https://github.com/KevinMidboe/Arduino.git
				synced 2025-10-29 17:40:11 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			743 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			743 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
| #ifndef F_CPU
 | |
| #define F_CPU 16000000UL  // Standard Arduinos run at 16 MHz
 | |
| #endif //!F_CPU
 | |
| 
 | |
| #include <avr/interrupt.h>
 | |
| #include <avr/pgmspace.h>
 | |
| #include "ZumoBuzzer.h"
 | |
| 
 | |
| #ifdef __AVR_ATmega32U4__
 | |
| 
 | |
| // PD7 (OC4D)
 | |
| #define BUZZER_DDR  DDRD
 | |
| #define BUZZER      (1 << PORTD7)
 | |
| 
 | |
| #define TIMER4_CLK_8  0x4 // 2 MHz
 | |
| 
 | |
| #define ENABLE_TIMER_INTERRUPT()   TIMSK4 = (1 << TOIE4)
 | |
| #define DISABLE_TIMER_INTERRUPT()  TIMSK4 = 0
 | |
| 
 | |
| #else // 168P or 328P
 | |
| 
 | |
| // PD3 (OC2B)
 | |
| #define BUZZER_DDR  DDRD
 | |
| #define BUZZER      (1 << PORTD3)
 | |
| 
 | |
| #define TIMER2_CLK_32  0x3  // 500 kHz
 | |
| 
 | |
| static const unsigned int cs2_divider[] = {0, 1, 8, 32, 64, 128, 256, 1024};
 | |
| 
 | |
| #define ENABLE_TIMER_INTERRUPT()   TIMSK2 = (1 << TOIE2)
 | |
| #define DISABLE_TIMER_INTERRUPT()  TIMSK2 = 0
 | |
| 
 | |
| #endif
 | |
| 
 | |
| unsigned char buzzerInitialized = 0;
 | |
| volatile unsigned char buzzerFinished = 1;  // flag: 0 while playing
 | |
| const char *buzzerSequence = 0;
 | |
| 
 | |
| // declaring these globals as static means they won't conflict
 | |
| // with globals in other .cpp files that share the same name
 | |
| static volatile unsigned int buzzerTimeout = 0;    // tracks buzzer time limit
 | |
| static char play_mode_setting = PLAY_AUTOMATIC;
 | |
| 
 | |
| extern volatile unsigned char buzzerFinished;  // flag: 0 while playing
 | |
| extern const char *buzzerSequence;
 | |
| 
 | |
| 
 | |
| static unsigned char use_program_space; // boolean: true if we should
 | |
|                     // use program space
 | |
| 
 | |
| // music settings and defaults
 | |
| static unsigned char octave = 4;        // the current octave
 | |
| static unsigned int whole_note_duration = 2000;  // the duration of a whole note
 | |
| static unsigned int note_type = 4;              // 4 for quarter, etc
 | |
| static unsigned int duration = 500;        // the duration of a note in ms
 | |
| static unsigned int volume = 15;        // the note volume
 | |
| static unsigned char staccato = 0;       // true if playing staccato
 | |
| 
 | |
| // staccato handling
 | |
| static unsigned char staccato_rest_duration;  // duration of a staccato
 | |
|                         //  rest, or zero if it is time
 | |
|                         //  to play a note
 | |
| 
 | |
| static void nextNote();
 | |
| 
 | |
| #ifdef __AVR_ATmega32U4__
 | |
| 
 | |
| // Timer4 overflow interrupt
 | |
| ISR (TIMER4_OVF_vect)
 | |
| {
 | |
|   if (buzzerTimeout-- == 0)
 | |
|   {
 | |
|     DISABLE_TIMER_INTERRUPT();
 | |
|     sei();                                    // re-enable global interrupts (nextNote() is very slow)
 | |
|     TCCR4B = (TCCR4B & 0xF0) | TIMER4_CLK_8;  // select IO clock
 | |
|     unsigned int top = (F_CPU/16) / 1000;     // set TOP for freq = 1 kHz: 
 | |
|     TC4H = top >> 8;                          // top 2 bits... (TC4H temporarily stores top 2 bits of 10-bit accesses)
 | |
|     OCR4C = top;                              // and bottom 8 bits
 | |
|     TC4H = 0;                                 // 0% duty cycle: top 2 bits...
 | |
|     OCR4D = 0;                                // and bottom 8 bits
 | |
|     buzzerFinished = 1;
 | |
|     if (buzzerSequence && (play_mode_setting == PLAY_AUTOMATIC))
 | |
|       nextNote();
 | |
|   }
 | |
| }
 | |
| 
 | |
| #else
 | |
| 
 | |
| // Timer2 overflow interrupt
 | |
| ISR (TIMER2_OVF_vect)
 | |
| {
 | |
|   if (buzzerTimeout-- == 0)
 | |
|   {
 | |
|     DISABLE_TIMER_INTERRUPT();
 | |
|     sei();                                    // re-enable global interrupts (nextNote() is very slow)
 | |
|     TCCR2B = (TCCR2B & 0xF8) | TIMER2_CLK_32; // select IO clock
 | |
|     OCR2A = (F_CPU/64) / 1000;                // set TOP for freq = 1 kHz
 | |
|     OCR2B = 0;                                // 0% duty cycle
 | |
|     buzzerFinished = 1;
 | |
|     if (buzzerSequence && (play_mode_setting == PLAY_AUTOMATIC))
 | |
|       nextNote();
 | |
|   }
 | |
| }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| 
 | |
| // constructor
 | |
| 
 | |
| ZumoBuzzer::ZumoBuzzer()
 | |
| {
 | |
| }
 | |
| 
 | |
| // this is called by playFrequency()
 | |
| inline void ZumoBuzzer::init()
 | |
| {
 | |
|   if (!buzzerInitialized)
 | |
|   {
 | |
|     buzzerInitialized = 1;
 | |
|     init2();
 | |
|   }
 | |
| }
 | |
| 
 | |
| // initializes timer4 (32U4) or timer2 (328P) for buzzer control
 | |
| void ZumoBuzzer::init2()
 | |
| {
 | |
|   DISABLE_TIMER_INTERRUPT();
 | |
|   
 | |
| #ifdef __AVR_ATmega32U4__
 | |
|   TCCR4A = 0x00;  // bits 7 and 6 clear: normal port op., OC4A disconnected
 | |
|                   // bits 5 and 4 clear: normal port op., OC4B disconnected
 | |
|                   // bit 3 clear: no force output compare for channel A
 | |
|                   // bit 2 clear: no force output compare for channel B
 | |
|                   // bit 1 clear: disable PWM for channel A
 | |
|                   // bit 0 clear: disable PWM for channel B
 | |
|   
 | |
|   TCCR4B = 0x04;  // bit 7 clear: disable PWM inversion
 | |
|                   // bit 6 clear: no prescaler reset
 | |
|                   // bits 5 and 4 clear: dead time prescaler 1
 | |
|                   // bit 3 clear, 2 set, 1-0 clear: timer clock = CK/8
 | |
|   
 | |
|   TCCR4C = 0x09;  // bits 7 and 6 clear: normal port op., OC4A disconnected
 | |
|                   // bits 5 and 4 clear: normal port op., OC4B disconnected
 | |
|                   // bit 3 set, 2 clear: clear OC4D on comp match when upcounting, 
 | |
|                   //                     set OC4D on comp match when downcounting
 | |
|                   // bit 1 clear: no force output compare for channel D
 | |
|                   // bit 0 set: enable PWM for channel 4
 | |
| 
 | |
|   TCCR4D = 0x01;  // bit 7 clear: disable fault protection interrupt
 | |
|                   // bit 6 clear: disable fault protection mode
 | |
|                   // bit 5 clear: disable fault protection noise canceler
 | |
|                   // bit 4 clear: falling edge triggers fault
 | |
|                   // bit 3 clear: disable fault protection analog comparator
 | |
|                   // bit 2 clear: fault protection interrupt flag
 | |
|                   // bit 1 clear, 0 set: select waveform generation mode,
 | |
|                   //    phase- and frequency-correct PWM, TOP = OCR4C,
 | |
|                   //    OCR4D set at BOTTOM, TOV4 flag set at BOTTOM
 | |
| 
 | |
|   // This sets timer 4 to run in phase- and frequency-correct PWM mode,
 | |
|   //    where TOP = OCR4C, OCR4D is updated at BOTTOM, TOV1 Flag is set on BOTTOM.
 | |
|   //    OC4D is cleared on compare match when upcounting, set on compare
 | |
|   //    match when downcounting; OC4A and OC4B are disconnected.
 | |
|   
 | |
|   unsigned int top = (F_CPU/16) / 1000; // set TOP for freq = 1 kHz: 
 | |
|   TC4H = top >> 8;                      // top 2 bits...
 | |
|   OCR4C = top;                          // and bottom 8 bits
 | |
|   TC4H = 0;                             // 0% duty cycle: top 2 bits...
 | |
|   OCR4D = 0;                            // and bottom 8 bits
 | |
| #else
 | |
|   TCCR2A = 0x21;  // bits 7 and 6 clear: normal port op., OC4A disconnected
 | |
|                   // bit 5 set, 4 clear: clear OC2B on comp match when upcounting, 
 | |
|                   //                     set OC2B on comp match when downcounting
 | |
|                   // bits 3 and 2: not used
 | |
|                   // bit 1 clear, 0 set: combine with bit 3 of TCCR2B...
 | |
|                   
 | |
|   TCCR2B = 0x0B;  // bit 7 clear: no force output compare for channel A
 | |
|                   // bit 6 clear: no force output compare for channel B
 | |
|                   // bits 5 and 4: not used
 | |
|                   // bit 3 set: combine with bits 1 and 0 of TCCR2A to
 | |
|                   //    select waveform generation mode 5, phase-correct PWM,
 | |
|                   //    TOP = OCR2A, OCR2B set at TOP, TOV2 flag set at BOTTOM
 | |
|                   // bit 2 clear, 1-0 set: timer clock = clkT2S/32
 | |
|                   
 | |
|   // This sets timer 2 to run in phase-correct PWM mode, where TOP = OCR2A,
 | |
|   //    OCR2B is updated at TOP, TOV2 Flag is set on BOTTOM. OC2B is cleared
 | |
|   //    on compare match when upcounting, set on compare match when downcounting;
 | |
|   //    OC2A is disconnected.
 | |
|   // Note: if the PWM frequency and duty cycle are changed, the first
 | |
|   //    cycle of the new frequency will be at the old duty cycle, since
 | |
|   //    the duty cycle (OCR2B) is not updated until TOP.
 | |
|   
 | |
| 
 | |
|   OCR2A = (F_CPU/64) / 1000;  // set TOP for freq = 1 kHz
 | |
|   OCR2B = 0;                  // 0% duty cycle
 | |
| #endif
 | |
|   
 | |
|   BUZZER_DDR |= BUZZER;    // buzzer pin set as an output
 | |
|   sei();
 | |
| }
 | |
| 
 | |
| 
 | |
| // Set up timer 1 to play the desired frequency (in Hz or .1 Hz) for the
 | |
| //   the desired duration (in ms). Allowed frequencies are 40 Hz to 10 kHz.
 | |
| //   volume controls buzzer volume, with 15 being loudest and 0 being quietest.
 | |
| // Note: frequency*duration/1000 must be less than 0xFFFF (65535).  This
 | |
| //   means that you can't use a max duration of 65535 ms for frequencies
 | |
| //   greater than 1 kHz.  For example, the max duration you can use for a
 | |
| //   frequency of 10 kHz is 6553 ms.  If you use a duration longer than this,
 | |
| //   you will cause an integer overflow that produces unexpected behavior.
 | |
| void ZumoBuzzer::playFrequency(unsigned int freq, unsigned int dur, 
 | |
|                      unsigned char volume)
 | |
| {
 | |
|   init(); // initializes the buzzer if necessary
 | |
|   buzzerFinished = 0;
 | |
|   
 | |
|   unsigned int timeout;
 | |
|   unsigned char multiplier = 1;
 | |
|   
 | |
|   if (freq & DIV_BY_10) // if frequency's DIV_BY_10 bit is set
 | |
|   {                     //  then the true frequency is freq/10
 | |
|     multiplier = 10;    //  (gives higher resolution for small freqs)
 | |
|     freq &= ~DIV_BY_10; // clear DIV_BY_10 bit
 | |
|   }
 | |
| 
 | |
|   unsigned char min = 40 * multiplier;
 | |
|   if (freq < min) // min frequency allowed is 40 Hz
 | |
|     freq = min;  
 | |
|   if (multiplier == 1 && freq > 10000)
 | |
|     freq = 10000;      // max frequency allowed is 10kHz
 | |
| 
 | |
| #ifdef __AVR_ATmega32U4__
 | |
|   unsigned long top;
 | |
|   unsigned char dividerExponent = 0;
 | |
|   
 | |
|   // calculate necessary clock source and counter top value to get freq
 | |
|   top = (unsigned int)(((F_CPU/2 * multiplier) + (freq >> 1))/ freq);
 | |
|   
 | |
|   while (top > 1023)
 | |
|   {
 | |
|     dividerExponent++;
 | |
|     top = (unsigned int)((((F_CPU/2 >> (dividerExponent)) * multiplier) + (freq >> 1))/ freq);
 | |
|   }
 | |
| #else   
 | |
|   unsigned int top;
 | |
|   unsigned char newCS2 = 2; // try prescaler divider of 8 first (minimum necessary for 10 kHz)
 | |
|   unsigned int divider = cs2_divider[newCS2];
 | |
| 
 | |
|   // calculate necessary clock source and counter top value to get freq
 | |
|   top = (unsigned int)(((F_CPU/16 * multiplier) + (freq >> 1))/ freq);
 | |
|   
 | |
|   while (top > 255)
 | |
|   {
 | |
|     divider = cs2_divider[++newCS2];
 | |
|     top = (unsigned int)(((F_CPU/2/divider * multiplier) + (freq >> 1))/ freq);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   // set timeout (duration):
 | |
|   if (multiplier == 10)
 | |
|     freq = (freq + 5) / 10;
 | |
| 
 | |
|   if (freq == 1000)
 | |
|     timeout = dur;  // duration for silent notes is exact
 | |
|   else
 | |
|     timeout = (unsigned int)((long)dur * freq / 1000);
 | |
|   
 | |
|   if (volume > 15)
 | |
|     volume = 15;
 | |
| 
 | |
|   DISABLE_TIMER_INTERRUPT();      // disable interrupts while writing to registers 
 | |
|   
 | |
| #ifdef __AVR_ATmega32U4__
 | |
|   TCCR4B = (TCCR4B & 0xF0) | (dividerExponent + 1); // select timer 4 clock prescaler: divider = 2^n if CS4 = n+1
 | |
|   TC4H = top >> 8;                                  // set timer 1 pwm frequency: top 2 bits...
 | |
|   OCR4C = top;                                      // and bottom 8 bits
 | |
|   unsigned int width = top >> (16 - volume);        // set duty cycle (volume):
 | |
|   TC4H = width >> 8;                                // top 2 bits...
 | |
|   OCR4D = width;                                    // and bottom 8 bits
 | |
|   buzzerTimeout = timeout;                          // set buzzer duration
 | |
|   
 | |
|   TIFR4 |= 0xFF;  // clear any pending t4 overflow int.      
 | |
| #else
 | |
|   TCCR2B = (TCCR2B & 0xF8) | newCS2;  // select timer 2 clock prescaler
 | |
|   OCR2A = top;                        // set timer 2 pwm frequency
 | |
|   OCR2B = top >> (16 - volume);       // set duty cycle (volume)
 | |
|   buzzerTimeout = timeout;            // set buzzer duration
 | |
| 
 | |
|   TIFR2 |= 0xFF;  // clear any pending t2 overflow int.     
 | |
| #endif
 | |
| 
 | |
|   ENABLE_TIMER_INTERRUPT();  
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| // Determine the frequency for the specified note, then play that note
 | |
| //  for the desired duration (in ms).  This is done without using floats
 | |
| //  and without having to loop.  volume controls buzzer volume, with 15 being
 | |
| //  loudest and 0 being quietest.
 | |
| // Note: frequency*duration/1000 must be less than 0xFFFF (65535).  This
 | |
| //  means that you can't use a max duration of 65535 ms for frequencies
 | |
| //  greater than 1 kHz.  For example, the max duration you can use for a
 | |
| //  frequency of 10 kHz is 6553 ms.  If you use a duration longer than this,
 | |
| //  you will cause an integer overflow that produces unexpected behavior.
 | |
| void ZumoBuzzer::playNote(unsigned char note, unsigned int dur,
 | |
|                  unsigned char volume)
 | |
| {
 | |
|   // note = key + octave * 12, where 0 <= key < 12
 | |
|   // example: A4 = A + 4 * 12, where A = 9 (so A4 = 57)
 | |
|   // A note is converted to a frequency by the formula:
 | |
|   //   Freq(n) = Freq(0) * a^n
 | |
|   // where
 | |
|   //   Freq(0) is chosen as A4, which is 440 Hz
 | |
|   // and
 | |
|   //   a = 2 ^ (1/12)
 | |
|   // n is the number of notes you are away from A4.
 | |
|   // One can see that the frequency will double every 12 notes.
 | |
|   // This function exploits this property by defining the frequencies of the
 | |
|   // 12 lowest notes allowed and then doubling the appropriate frequency
 | |
|   // the appropriate number of times to get the frequency for the specified
 | |
|   // note.
 | |
| 
 | |
|   // if note = 16, freq = 41.2 Hz (E1 - lower limit as freq must be >40 Hz)
 | |
|   // if note = 57, freq = 440 Hz (A4 - central value of ET Scale)
 | |
|   // if note = 111, freq = 9.96 kHz (D#9 - upper limit, freq must be <10 kHz)
 | |
|   // if note = 255, freq = 1 kHz and buzzer is silent (silent note)
 | |
| 
 | |
|   // The most significant bit of freq is the "divide by 10" bit.  If set,
 | |
|   // the units for frequency are .1 Hz, not Hz, and freq must be divided
 | |
|   // by 10 to get the true frequency in Hz.  This allows for an extra digit
 | |
|   // of resolution for low frequencies without the need for using floats.
 | |
| 
 | |
|   unsigned int freq = 0;
 | |
|   unsigned char offset_note = note - 16;
 | |
| 
 | |
|   if (note == SILENT_NOTE || volume == 0)
 | |
|   {
 | |
|     freq = 1000;  // silent notes => use 1kHz freq (for cycle counter)
 | |
|     playFrequency(freq, dur, 0);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (note <= 16)
 | |
|     offset_note = 0;
 | |
|   else if (offset_note > 95)
 | |
|     offset_note = 95;
 | |
| 
 | |
|   unsigned char exponent = offset_note / 12;
 | |
| 
 | |
|   // frequency table for the lowest 12 allowed notes
 | |
|   //   frequencies are specified in tenths of a Hertz for added resolution
 | |
|   switch (offset_note - exponent * 12)  // equivalent to (offset_note % 12)
 | |
|   {
 | |
|     case 0:        // note E1 = 41.2 Hz
 | |
|       freq = 412;
 | |
|       break;
 | |
|     case 1:        // note F1 = 43.7 Hz
 | |
|       freq = 437;
 | |
|       break;
 | |
|     case 2:        // note F#1 = 46.3 Hz
 | |
|       freq = 463;
 | |
|       break;
 | |
|     case 3:        // note G1 = 49.0 Hz
 | |
|       freq = 490;
 | |
|       break;
 | |
|     case 4:        // note G#1 = 51.9 Hz
 | |
|       freq = 519;
 | |
|       break;
 | |
|     case 5:        // note A1 = 55.0 Hz
 | |
|       freq = 550;
 | |
|       break;
 | |
|     case 6:        // note A#1 = 58.3 Hz
 | |
|       freq = 583;
 | |
|       break;
 | |
|     case 7:        // note B1 = 61.7 Hz
 | |
|       freq = 617;
 | |
|       break;
 | |
|     case 8:        // note C2 = 65.4 Hz
 | |
|       freq = 654;
 | |
|       break;
 | |
|     case 9:        // note C#2 = 69.3 Hz
 | |
|       freq = 693;
 | |
|       break;
 | |
|     case 10:      // note D2 = 73.4 Hz
 | |
|       freq = 734;
 | |
|       break;
 | |
|     case 11:      // note D#2 = 77.8 Hz
 | |
|       freq = 778;
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   if (exponent < 7)
 | |
|   {
 | |
|     freq = freq << exponent;  // frequency *= 2 ^ exponent
 | |
|     if (exponent > 1)      // if the frequency is greater than 160 Hz
 | |
|       freq = (freq + 5) / 10;  //   we don't need the extra resolution
 | |
|     else
 | |
|       freq += DIV_BY_10;    // else keep the added digit of resolution
 | |
|   }
 | |
|   else
 | |
|     freq = (freq * 64 + 2) / 5;  // == freq * 2^7 / 10 without int overflow
 | |
| 
 | |
|   if (volume > 15)
 | |
|     volume = 15;
 | |
|   playFrequency(freq, dur, volume);  // set buzzer this freq/duration
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| // Returns 1 if the buzzer is currently playing, otherwise it returns 0
 | |
| unsigned char ZumoBuzzer::isPlaying()
 | |
| {
 | |
|   return !buzzerFinished || buzzerSequence != 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| // Plays the specified sequence of notes.  If the play mode is 
 | |
| // PLAY_AUTOMATIC, the sequence of notes will play with no further
 | |
| // action required by the user.  If the play mode is PLAY_CHECK,
 | |
| // the user will need to call playCheck() in the main loop to initiate
 | |
| // the playing of each new note in the sequence.  The play mode can
 | |
| // be changed while the sequence is playing.  
 | |
| // This is modeled after the PLAY commands in GW-BASIC, with just a
 | |
| // few differences.
 | |
| //
 | |
| // The notes are specified by the characters C, D, E, F, G, A, and
 | |
| // B, and they are played by default as "quarter notes" with a
 | |
| // length of 500 ms.  This corresponds to a tempo of 120
 | |
| // beats/min.  Other durations can be specified by putting a number
 | |
| // immediately after the note.  For example, C8 specifies C played as an
 | |
| // eighth note, with half the duration of a quarter note. The special
 | |
| // note R plays a rest (no sound).
 | |
| //
 | |
| // Various control characters alter the sound:
 | |
| //   '>' plays the next note one octave higher
 | |
| //
 | |
| //   '<' plays the next note one octave lower
 | |
| //
 | |
| //   '+' or '#' after a note raises any note one half-step
 | |
| //
 | |
| //   '-' after a note lowers any note one half-step
 | |
| //
 | |
| //   '.' after a note "dots" it, increasing the length by
 | |
| //       50%.  Each additional dot adds half as much as the
 | |
| //       previous dot, so that "A.." is 1.75 times the length of
 | |
| //       "A".
 | |
| //
 | |
| //   'O' followed by a number sets the octave (default: O4).
 | |
| //
 | |
| //   'T' followed by a number sets the tempo (default: T120).
 | |
| //
 | |
| //   'L' followed by a number sets the default note duration to
 | |
| //       the type specified by the number: 4 for quarter notes, 8
 | |
| //       for eighth notes, 16 for sixteenth notes, etc.
 | |
| //       (default: L4)
 | |
| //
 | |
| //   'V' followed by a number from 0-15 sets the music volume.
 | |
| //       (default: V15)
 | |
| //
 | |
| //   'MS' sets all subsequent notes to play staccato - each note is played
 | |
| //       for 1/2 of its allotted time, followed by an equal period
 | |
| //       of silence.
 | |
| //
 | |
| //   'ML' sets all subsequent notes to play legato - each note is played
 | |
| //       for its full length.  This is the default setting.
 | |
| //
 | |
| //   '!' resets all persistent settings to their defaults.
 | |
| //
 | |
| // The following plays a c major scale up and back down:
 | |
| //   play("L16 V8 cdefgab>cbagfedc");
 | |
| //
 | |
| // Here is an example from Bach:
 | |
| //   play("T240 L8 a gafaeada c+adaeafa <aa<bac#ada c#adaeaf4");
 | |
| void ZumoBuzzer::play(const char *notes)
 | |
| {
 | |
|   DISABLE_TIMER_INTERRUPT();  // prevent this from being interrupted
 | |
|   buzzerSequence = notes;
 | |
|   use_program_space = 0;
 | |
|   staccato_rest_duration = 0;
 | |
|   nextNote();          // this re-enables the timer1 interrupt
 | |
| }
 | |
| 
 | |
| void ZumoBuzzer::playFromProgramSpace(const char *notes_p)
 | |
| {
 | |
|   DISABLE_TIMER_INTERRUPT();  // prevent this from being interrupted
 | |
|   buzzerSequence = notes_p;
 | |
|   use_program_space = 1;
 | |
|   staccato_rest_duration = 0;
 | |
|   nextNote();          // this re-enables the timer1 interrupt
 | |
| }
 | |
| 
 | |
| 
 | |
| // stop all sound playback immediately
 | |
| void ZumoBuzzer::stopPlaying()
 | |
| {
 | |
|   DISABLE_TIMER_INTERRUPT();          // disable interrupts
 | |
|  
 | |
| #ifdef __AVR_ATmega32U4__
 | |
|   TCCR4B = (TCCR4B & 0xF0) | TIMER4_CLK_8;  // select IO clock
 | |
|   unsigned int top = (F_CPU/16) / 1000;     // set TOP for freq = 1 kHz: 
 | |
|   TC4H = top >> 8;                          // top 2 bits... (TC4H temporarily stores top 2 bits of 10-bit accesses)
 | |
|   OCR4C = top;                              // and bottom 8 bits
 | |
|   TC4H = 0;                                 // 0% duty cycle: top 2 bits...
 | |
|   OCR4D = 0;                                // and bottom 8 bits
 | |
| #else 
 | |
|   TCCR2B = (TCCR2B & 0xF8) | TIMER2_CLK_32; // select IO clock
 | |
|   OCR2A = (F_CPU/64) / 1000;                // set TOP for freq = 1 kHz
 | |
|   OCR2B = 0;                                // 0% duty cycle
 | |
| #endif
 | |
| 
 | |
|   buzzerFinished = 1;
 | |
|   buzzerSequence = 0;
 | |
| }
 | |
| 
 | |
| // Gets the current character, converting to lower-case and skipping
 | |
| // spaces.  For any spaces, this automatically increments sequence!
 | |
| static char currentCharacter()
 | |
| {
 | |
|   char c = 0;
 | |
|   do
 | |
|   {
 | |
|     if(use_program_space)
 | |
|       c = pgm_read_byte(buzzerSequence);
 | |
|     else
 | |
|       c = *buzzerSequence;
 | |
| 
 | |
|     if(c >= 'A' && c <= 'Z')
 | |
|       c += 'a'-'A';
 | |
|   } while(c == ' ' && (buzzerSequence ++));
 | |
| 
 | |
|   return c;
 | |
| }
 | |
| 
 | |
| // Returns the numerical argument specified at buzzerSequence[0] and
 | |
| // increments sequence to point to the character immediately after the
 | |
| // argument.
 | |
| static unsigned int getNumber()
 | |
| {
 | |
|   unsigned int arg = 0;
 | |
| 
 | |
|   // read all digits, one at a time
 | |
|   char c = currentCharacter();
 | |
|   while(c >= '0' && c <= '9')
 | |
|   {
 | |
|     arg *= 10;
 | |
|     arg += c-'0';
 | |
|     buzzerSequence ++;
 | |
|     c = currentCharacter();
 | |
|   }
 | |
| 
 | |
|   return arg;
 | |
| }
 | |
| 
 | |
| static void nextNote()
 | |
| {
 | |
|   unsigned char note = 0;
 | |
|   unsigned char rest = 0;
 | |
|   unsigned char tmp_octave = octave; // the octave for this note
 | |
|   unsigned int tmp_duration; // the duration of this note
 | |
|   unsigned int dot_add;
 | |
| 
 | |
|   char c; // temporary variable
 | |
| 
 | |
|   // if we are playing staccato, after every note we play a rest
 | |
|   if(staccato && staccato_rest_duration)
 | |
|   {
 | |
|     ZumoBuzzer::playNote(SILENT_NOTE, staccato_rest_duration, 0);
 | |
|     staccato_rest_duration = 0;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|  parse_character:
 | |
| 
 | |
|   // Get current character
 | |
|   c = currentCharacter();
 | |
|   buzzerSequence ++;
 | |
| 
 | |
|   // Interpret the character.
 | |
|   switch(c)
 | |
|   {
 | |
|   case '>':
 | |
|     // shift the octave temporarily up
 | |
|     tmp_octave ++;
 | |
|     goto parse_character;
 | |
|   case '<':
 | |
|     // shift the octave temporarily down
 | |
|     tmp_octave --;
 | |
|     goto parse_character;
 | |
|   case 'a':
 | |
|     note = NOTE_A(0);
 | |
|     break;
 | |
|   case 'b':
 | |
|     note = NOTE_B(0);
 | |
|     break;
 | |
|   case 'c':
 | |
|     note = NOTE_C(0);
 | |
|     break;
 | |
|   case 'd':
 | |
|     note = NOTE_D(0);
 | |
|     break;
 | |
|   case 'e':
 | |
|     note = NOTE_E(0);
 | |
|     break;
 | |
|   case 'f':
 | |
|     note = NOTE_F(0);
 | |
|     break;
 | |
|   case 'g':
 | |
|     note = NOTE_G(0);
 | |
|     break;
 | |
|   case 'l':
 | |
|     // set the default note duration
 | |
|     note_type = getNumber();
 | |
|     duration = whole_note_duration/note_type;
 | |
|     goto parse_character;
 | |
|   case 'm':
 | |
|     // set music staccato or legato
 | |
|     if(currentCharacter() == 'l')
 | |
|       staccato = false;
 | |
|     else
 | |
|     {
 | |
|       staccato = true;
 | |
|       staccato_rest_duration = 0;
 | |
|     }
 | |
|     buzzerSequence ++;
 | |
|     goto parse_character;
 | |
|   case 'o':
 | |
|     // set the octave permanently
 | |
|     octave = getNumber();
 | |
|     tmp_octave = octave;
 | |
|     goto parse_character;
 | |
|   case 'r':
 | |
|     // Rest - the note value doesn't matter.
 | |
|     rest = 1;
 | |
|     break;
 | |
|   case 't':
 | |
|     // set the tempo
 | |
|     whole_note_duration = 60*400/getNumber()*10;
 | |
|     duration = whole_note_duration/note_type;
 | |
|     goto parse_character;
 | |
|   case 'v':
 | |
|     // set the volume
 | |
|     volume = getNumber();
 | |
|     goto parse_character;
 | |
|   case '!':
 | |
|     // reset to defaults
 | |
|     octave = 4;
 | |
|     whole_note_duration = 2000;
 | |
|     note_type = 4;
 | |
|     duration = 500;
 | |
|     volume = 15;
 | |
|     staccato = 0;
 | |
|     // reset temp variables that depend on the defaults
 | |
|     tmp_octave = octave;
 | |
|     tmp_duration = duration;
 | |
|     goto parse_character;
 | |
|   default:
 | |
|     buzzerSequence = 0;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   note += tmp_octave*12;
 | |
| 
 | |
|   // handle sharps and flats
 | |
|   c = currentCharacter();
 | |
|   while(c == '+' || c == '#')
 | |
|   {
 | |
|     buzzerSequence ++;
 | |
|     note ++;
 | |
|     c = currentCharacter();
 | |
|   }
 | |
|   while(c == '-')
 | |
|   {
 | |
|     buzzerSequence ++;
 | |
|     note --;
 | |
|     c = currentCharacter();
 | |
|   }
 | |
| 
 | |
|   // set the duration of just this note
 | |
|   tmp_duration = duration;
 | |
| 
 | |
|   // If the input is 'c16', make it a 16th note, etc.
 | |
|   if(c > '0' && c < '9')
 | |
|     tmp_duration = whole_note_duration/getNumber();
 | |
| 
 | |
|   // Handle dotted notes - the first dot adds 50%, and each
 | |
|   // additional dot adds 50% of the previous dot.
 | |
|   dot_add = tmp_duration/2;
 | |
|   while(currentCharacter() == '.')
 | |
|   {
 | |
|     buzzerSequence ++;
 | |
|     tmp_duration += dot_add;
 | |
|     dot_add /= 2;
 | |
|   }
 | |
| 
 | |
|   if(staccato)
 | |
|   {
 | |
|     staccato_rest_duration = tmp_duration / 2;
 | |
|     tmp_duration -= staccato_rest_duration;
 | |
|   }
 | |
|   
 | |
|   // this will re-enable the timer1 overflow interrupt
 | |
|   ZumoBuzzer::playNote(rest ? SILENT_NOTE : note, tmp_duration, volume);
 | |
| }
 | |
| 
 | |
| 
 | |
| // This puts play() into a mode where instead of advancing to the
 | |
| // next note in the sequence automatically, it waits until the
 | |
| // function playCheck() is called. The idea is that you can
 | |
| // put playCheck() in your main loop and avoid potential
 | |
| // delays due to the note sequence being checked in the middle of
 | |
| // a time sensitive calculation.  It is recommended that you use
 | |
| // this function if you are doing anything that can't tolerate
 | |
| // being interrupted for more than a few microseconds.
 | |
| // Note that the play mode can be changed while a sequence is being
 | |
| // played.
 | |
| //
 | |
| // Usage: playMode(PLAY_AUTOMATIC) makes it automatic (the
 | |
| // default), playMode(PLAY_CHECK) sets it to a mode where you have
 | |
| // to call playCheck().
 | |
| void ZumoBuzzer::playMode(unsigned char mode)
 | |
| {
 | |
|   play_mode_setting = mode;
 | |
| 
 | |
|   // We want to check to make sure that we didn't miss a note if we
 | |
|   // are going out of play-check mode.
 | |
|   if(mode == PLAY_AUTOMATIC)
 | |
|     playCheck();
 | |
| }
 | |
| 
 | |
| 
 | |
| // Checks whether it is time to start another note, and starts
 | |
| // it if so.  If it is not yet time to start the next note, this method
 | |
| // returns without doing anything.  Call this as often as possible 
 | |
| // in your main loop to avoid delays between notes in the sequence.
 | |
| //
 | |
| // Returns true if it is still playing.
 | |
| unsigned char ZumoBuzzer::playCheck()
 | |
| {
 | |
|   if(buzzerFinished && buzzerSequence != 0)
 | |
|     nextNote();
 | |
|   return buzzerSequence != 0;
 | |
| }
 |