+/* Copyright (C) 2016 Pat Thoyts <patthoyts@users.sourceforge.net>
+ *
+ * Demonstration of simple sound output using PWM on an ATtiny85.
+ *
+ * Based on the example at:
+ *   http://www.technoblogy.com/show?QVN
+ * Modified to play a tune.
+ *
+ * The sound output is on PB4 and should be passed through a low
+ * pass filter.
+ *
+ */
+
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/pgmspace.h>
+#include <avr/power.h>
+#include <avr/sleep.h>
+#include <avr/wdt.h>
+#include <util/delay.h>
+
+/*
+ * counter values to generate specific notes
+ *   tone_count = (freq * 65536) / 20000
+ */
+unsigned int Tones[] = {
+    /*    freq   */
+    /* A3 220.00 */ 721,
+    /* B3 246.94 */ 809,
+    /* C4 261.63 */ 857,
+    /* D4 293.00 */ 960,
+    /* E4 329.63 */ 1080,
+    /* F4 349.23 */ 1144,
+    /* G4 392.00 */ 1285,
+    /* A4 440.00 */ 1442,
+};
+
+volatile unsigned int acc;
+static unsigned int note = 857; /* middle C */
+static int tune_index = 0;
+static char tune[] = "CCGGAAG FFEEDDC GGFFEED GGFFEED CCGGAAG FFEEDDC   ";
+
+#define SQUARE_WAVE
+//#define SAWTOOTH_WAVE
+ISR(TIMER0_COMPA_vect)
+{
+    acc += note;
+
+#ifdef SQUARE_WAVE
+    OCR1B = (acc >> 8) & 0x80;
+#elif defined(SAWTOOTH_WAVE)
+    OCR1B = acc >> 8;
+#else
+    signed char temp, mask;
+    temp = acc >> 8;
+    mask = temp >> 7;
+    OCR1B = temp ^ mask;
+#endif
+}
+
+ISR(TIMER0_OVF_vect)
+{
+    PORTB ^= _BV(PB1);
+}
+
+ISR(TIMER1_OVF_vect)
+{
+    PORTB ^= _BV(PB0);
+}
+
+/* Section 12.3.9: enable the 64Mhz PLL as asynchronous clock source
+ * for timer 1 allowing the PLL to settle.
+ */
+static void enable_pll()
+{
+    PLLCSR = _BV(PLLE);
+    _delay_us(100);
+    while (!(PLLCSR & _BV(PLOCK)))
+        ;
+    PLLCSR |= _BV(PCKE);
+}
+
+/* use timer 0 and PWM on OC1B for sound generation */
+static void init_sound()
+{
+    cli();
+
+    DDRB |= _BV(DDB4);
+    PORTB &= ~_BV(PB4);
+
+    enable_pll();
+
+    /*
+     * Configure timer 0 to call the COMPA interrupts at 20 kHz
+     * In the compare match interrupt handler, OCR1B gets set to
+     * a value defined by the current note
+
+     * WGM  = 0b111: Fast PWM (0->OCRA)
+     * CS   = 0b010: CLK / 8
+     * OCR0A = 49 causes a further /50 so 8e6/8/50 == 20 kHz
+     */
+    TCCR0A = _BV(WGM01) | _BV(WGM00);
+    TCCR0B = _BV(WGM02) | _BV(CS01);
+    OCR0A = 49;
+
+    /*
+     * CTC1  = 0: continuous counter (does not restart on compare match)
+     * COM1A = 0b00: OC1A disconnected
+     * COM1B = 0b01: OC1B clear on compare match, set at BOTTOM, OC1B# enabled
+     * PWM1A = 0: disabled modulator A (attached to PB0 and PB1)
+     * PWM1B = 1: enable PWM mode for OCR1B and 0 when match OCR1C
+     * CS    = 0b0001: PCK/1
+     *
+     * timer 1 has PWM enabled for B and toggles the output line on
+     * a match with OCR1B which is set by the note.
+     * OCR1B sets the duty cycle (when to switch the line low) and
+     * OCR1C (defaults to 0xFF) sets the TOP value and controls the frequency
+     * so in this case, 64e6/1/256 == 250kHz
+     *
+     * The tone is created in the OCR1B interrupt handler by setting
+     * the 16bit accumulator and using the top bit to control the PWM
+     */
+    TCCR1 = _BV(CS10);
+    GTCCR = _BV(PWM1B) | /*_BV(COM1B1) |*/ _BV(COM1B0);
+
+    /* Enable the compare match interrupt for A and add a overflow
+     * interrupt for testing the clock frequency with the oscilloscope
+     * (could use this as a 20kHz counter instead of _delay_ms later)
+     *
+     * For some reason, setting TOIE1 causes continual resets.
+     */
+    TIMSK = _BV(OCIE0A) | _BV(TOIE0); /* | _BV(TOIE1); */
+
+    sei();
+}
+
+/* flash the hearbeat led (PB2) on startup */
+static void indicate_startup()
+{
+    DDRB |= _BV(PB2);
+    PORTB &= ~(PB2);
+
+    for (int n = 0; n < 20; ++n)
+    {
+        PORTB ^= _BV(PB2);
+        _delay_ms(50);
+    }
+}
+
+/* emit silence for ms milliseconds by disabling the output line */
+#define PAUSE(x) \
+    DDRB &= ~_BV(DDB4); \
+    _delay_ms((x));   \
+    DDRB |= _BV(DDB4)
+
+int
+main(void)
+{
+    const int tempo = 400;
+
+    wdt_enable(WDTO_1S);
+
+    power_adc_disable();
+    power_usi_disable();
+
+    indicate_startup();
+    init_sound();
+
+    /* some test outputs for oscilloscope probing */
+    DDRB |= _BV(DDB1) | _BV(DDB0);
+    PORTB &= ~(_BV(PB1) | _BV(PB0));
+
+    for(;;)
+    {
+        wdt_reset();
+
+        int x = (int)tune[tune_index++];
+        if (x == 0)
+        {
+            tune_index = 0;
+            continue;
+        }
+        if (x == 32) /* space means silence for 1 note */
+        {
+            PAUSE(tempo);
+            continue;
+        }
+
+        x = x - (int)'A';
+        if (x < 2)
+            x = x + 7;
+        note = Tones[x];
+
+        PAUSE(50); /* provide a break after each note */
+
+        _delay_ms(tempo); /* play each note for tempo milliseconds */
+        PORTB ^= _BV(PB2);
+    }
+}