+/* 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);
+ }
+}