Topics

► Games

► Sound & Music

► Watches & Clocks

► GPS

► Power Supplies

► Computers

► Graphics

► Thermometers

► Wearables

► Test Equipment

► Tutorials

► Libraries

► PCB-Based Projects

By processor

AVR ATtiny

► ATtiny10

► ATtiny2313

► ATtiny84

► ATtiny841

► ATtiny85

► ATtiny861

► ATtiny88

AVR ATmega

► ATmega328

► ATmega1284

AVR 0-series and 1-series

► ATmega4809

► ATtiny1604

► ATtiny1614

► ATtiny3216

► ATtiny3227

► ATtiny402

► ATtiny404

► ATtiny414

► ATtiny814

AVR DA/DB-series

► AVR128DA28

► AVR128DA32

► AVR128DA48

► AVR128DB28

ARM

► ATSAMD21

► RP2040

► RA4M1

About me

  • About me
  • Twitter
  • Mastodon

Feeds

RSS feed

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:

Note.jpg

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