A Lightweight Alternative to tone
1st May 2016
This article describes a simpler alternative to the Arduino built-in function tone for ATmega328-based Arduino boards and projects. I've called this function note, and it can be used to play notes in the four-octave range C3 to B6 on the Arduino pin D3. It you just want to add some sound to your application it consumes fewer resources than the full tone function:
The note function doesn't use interrupts, so the sound output is unaffected by other interrupt-driven processes, and it includes a lookup table for the well-tempered scale divisors, so you don't need to remember frequency values.
I've also included a version for ATmega32u4-based boards, like the LilyPad USB and Arduino Micro. This version has a nine-octave range, C1 to B9, on Arduino pin D10.
Using note
The note function takes two parameters:
- A note number, from 0 to 11, representing the note in the well-tempered scale, where 0 represents C, 1 represents C#, and so on up to 11 for B.
- The octave, which can be from 3 to 6 on the ATmega328 or 1 to 9 on the ATmega32u4.
So to play middle C, C4, use:
note(0, 4);
To stop the note playing call note with two zero arguments:
note(0, 0);
Invalid octaves will have the same effect.
The note number can be greater than 11 as an alternative way of representing notes in higher octaves, so you could play C5 either with:
note(0, 5);
or with:
note(12, 4);
For example, the following program plays the scale of C:
void loop() { for (int n=0; n<=12; n++) { note(n, 4); if (n!=4 && n!=11) n++; delay(500); } note(0, 0); delay(10000); }
To hear the result connect a piezo speaker between pin D3 and ground (or D10 on ATmega32u4-based Arduinos). I recommend getting a piezo speaker at least 20mm across for better quality sound.
ATmega328 version
Like the Arduino tone function, note uses Timer/Counter2 in the ATmega328, so pins D3 and D11 cannot be used for analogue PWM output at the same time.
Here's the definition of note:
const uint8_t scale[] PROGMEM = {239,226,213,201,190,179,169,160,151,142,134,127}; void note (int n, int octave) { DDRD = DDRD | 1<<DDD3; // PD3 (Arduino D3) as output TCCR2A = 0<<COM2A0 | 1<<COM2B0 | 2<<WGM20; // Toggle OC2B on match int prescaler = 9 - (octave + n/12); if (prescaler<3 || prescaler>6) prescaler = 0; OCR2A = pgm_read_byte(&scale[n % 12]) - 1; TCCR2B = 0<<WGM22 | prescaler<<CS20; }
It takes advantage of the fact that the prescaler used in Timer/Counter2 provides four successive powers of two: 32, 64, 128, and 256, so these can be used by the note function to set the octave directly. The table of divisors for one octave is given by the array scale[], which is stored in PROGMEM to avoid using up RAM.
ATmega32u4 version
On the ATmega32u4 note uses Timer/Counter4, so D6 and D13 cannot be used for analogue PWM output at the same time.
const uint8_t scale[] PROGMEM = {239,226,213,201,190,179,169,160,151,142,134,127}; void note (int n, int octave) { DDRB = DDRB | 1<<DDB6; // PB6 (Arduino D10) as output TCCR4A = 0<<COM4A0 | 1<<COM4B0; // Toggle OC4B on match int prescaler = 10 - (octave + n/12); if (prescaler<1 || prescaler>9) prescaler = 0; OCR4C = pgm_read_byte(&scale[n % 12]) - 1; TCCR4B = prescaler<<CS40; }
Timer/Counter4 on the ATmega32u4 provides a full range of prescaler divisors, allowing this version of note to have a wider range of octaves.
Comparison with tone
I used the following equivalent programs to compare the memory requirements of tone and note on the Arduino Uno:
void loop() { tone(3, NOTE_C4, 1000); }
and
void loop() { note(0, 4); delay(1000); }
The following table shows the memory usage according to the Arduino IDE:
program memory | dynamic memory | |
tone | 2402 bytes | 32 bytes |
note | 826 bytes | 9 bytes |
If you want something similar for the ATtiny85 see my earlier article Simple Tones for ATtiny.
Update
13th January 2017: I've altered the divisors slightly to give a better approximation to the well-tempered scale.
blog comments powered by Disqus