► Games

► Sound & Music

► Watches & Clocks


► Power Supplies

► Tools

► Tutorials

By processor

► ATtiny10

► ATtiny85

► ATtiny84

► ATtiny841

► ATtiny2313

► ATtiny861

► ATmega328

► ATmega1284

About me

  • About me



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 ot 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 millis() to time the note. for example, to play a middle C with a duration of half a second:

note(0, 4); millis(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++;
  note(0, 0);

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) {
  DDRB = DDRB | 1<<Output;                         // Define output
  int prescaler = 8 + Clock - (octave + n/12);
  if (prescaler<1 || prescaler>15 || octave==0) prescaler = 0;
  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.

blog comments powered by Disqus