Topics

Games
Sound & Music
Watches & Clocks
Wireless
GPS
Power Supplies
Computers
Graphics
Lighting
Thermometers
Educational
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, 1, and 2-series

ATmega1608
ATmega4808
ATmega4809
ATtiny1604
ATtiny1614
ATtiny202
ATtiny3216
ATtiny3224
ATtiny3227
ATtiny402
ATtiny404
ATtiny414
ATtiny814

AVR DA/DB/DD-series

AVR128DA28
AVR128DA32
AVR128DA48
AVR128DB28
AVR128DB48
AVR64DD14
AVR32DD28

ARM

ATSAMD21
RP2040
RA4M1

RISC-V

ESP32-P4

About me

  • About me
  • Twitter
  • Mastodon

Feeds

RSS feed

Playing Notes on the ATtiny85

31st January 2018

This article describes a simple function for playing notes on the ATtiny85. I've called this function note, and it can be used to play notes on the ATtiny85 pins 1 or 4.

The note function uses Timer/Counter1 in the ATtiny85, leaving Timer/Counter0 free for delay() and millis(). It 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.

It's an improved version of my earlier article Simple Tones for ATtiny. For a similar routine for the ATmega328 or ATmega32u4 see A Lightweight Alternative to tone.

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 0 to 7 with a 1MHz clock, 0 to 10 with an 8MHz clock, and 0 to 11 with a 16MHz clock.

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.

Once you call note() the note plays until you stop it, so to get a note of a fixed length you can use delay() to time the note. for example, to play a middle C with a duration of half a second:

note(0, 4); delay(500); note(0, 0);

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);
}

The definition of note

Here's the definition of note:

const int Output = 1;                                   // Can be 1 or 4

// Cater for 16MHz, 8MHz, or 1MHz clock:
const int Clock = ((F_CPU/1000000UL) == 16) ? 4 : ((F_CPU/1000000UL) == 8) ? 3 : 0;
const uint8_t scale[] PROGMEM = {239,226,213,201,190,179,169,160,151,142,134,127};

void note (int n, int octave) {
  int prescaler = 8 + Clock - (octave + n/12);
  if (prescaler<1 || prescaler>15 || octave==0) prescaler = 0;
  DDRB = (DDRB & ~(1<<Output)) | (prescaler != 0)<<Output;
  OCR1C = pgm_read_byte(&scale[n % 12]) - 1;
  GTCCR = (Output == 4)<<COM1B0;
  TCCR1 = 1<<CTC1 | (Output == 1)<<COM1A0 | prescaler<<CS10;
}

Set the constant Output to the output pin you want to use: 1 for PB1 or 4 for PB4. The constant Clock automatically automatically adjusts the routine to give the correct notes with a clock frequency of 1MHz, 8MHz, or 16MHz.

The routine takes advantage of the fact that the prescaler used in Timer/Counter1 provides successive powers of two, 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.

Update

12th April 2018: Updated the program to leave the output in a high-impedance state when no note is playing, to avoid possible noise from the speaker.


Next: Secret Maze

Previous: Tiny Colour Watch


blog comments powered by Disqus