Simple sound synthesis using ATtiny85 timers. master
authorPat Thoyts <patthoyts@users.sourceforge.net>
Sun, 21 Feb 2016 22:46:48 +0000 (22:46 +0000)
committerPat Thoyts <patthoyts@users.sourceforge.net>
Sun, 21 Feb 2016 22:46:48 +0000 (22:46 +0000)
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
filter.sch [new file with mode: 0644]
tiny_sound.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..c41c150
--- /dev/null
@@ -0,0 +1,3 @@
+*.hex
+*.elf
+*.o
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..c04613f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,77 @@
+# -*- Makefile -*-
+
+PROJECT := tiny_sound
+DEVICE  := attiny85
+F_CPU   := 8000000UL
+INC     := -I.
+#AVRDUDE := avrdude -c usbasp -p $(DEVICE)
+AVRDUDE := avrdude -c atmelice_isp -p $(DEVICE)
+
+CSRCS    = $(PROJECT).c
+OBJS     = $(CSRCS:.c=.o)
+
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+
+CC      := avr-gcc
+LD      := avr-gcc
+OBJCOPY := avr-objcopy
+ifeq ($(uname_S),Linux)
+RM      := rm -f
+else
+RM      := del >NUL
+endif
+CFLAGS  :=-Wall -Wcast-align -Wshadow \
+          -std=gnu99 -fshort-enums -pedantic-errors -Os -mcall-prologues \
+          -mmcu=$(DEVICE) -DF_CPU=$(F_CPU)
+LDFLAGS := -Wall -mmcu=$(DEVICE)# -Wl,-v
+LIBS    :=
+
+V := @
+Q := $(V:1=)
+QUIET_CC      = $(Q:@=@echo   CC      $@ &)$(CC)
+QUIET_LD      = $(Q:@=@echo   LD      $@ &)$(LD)
+QUIET_OBJCOPY = $(Q:@=@echo   OBJCOPY $@ &)$(OBJCOPY)
+QUIET_AVRDUDE = $(Q:@=@echo   AVRDUDE $@ &)$(AVRDUDE)
+
+all: $(PROJECT).hex
+
+%.hex: %.elf
+       $(QUIET_OBJCOPY) -j .text -j .data -O ihex $< $@
+
+%.elf: $(OBJS)
+       $(QUIET_LD) $(LDFLAGS) $^ $(LIBS) -o $@
+
+%.o: %.c
+       $(QUIET_CC) $(CFLAGS) $(INC) -c $<
+
+flash: $(PROJECT).hex
+       $(QUIET_AVRDUDE) -U flash:w:$<:i
+
+clean:
+       -@$(RM) $(addprefix $(PROJECT), .elf .hex .net .bom .cmd)
+
+.PHONY: clean
+.SECONDARY: $(addsuffix .elf, $(PROJECT)) $(OBJS)
+
+# Fuse high byte:
+# 0xdd = 1 1 0 1   1 1 0 1
+#        ^ ^ ^ ^   ^ \-+-/
+#        | | | |   |   +------ BODLEVEL 2..0 (brownout trigger level -> 2.7V)
+#        | | | |   +---------- EESAVE (preserve EEPROM on Chip Erase -> not preserved)
+#        | | | +-------------- WDTON (watchdog timer always on -> disable)
+#        | | +---------------- SPIEN (enable serial programming -> enabled)
+#        | +------------------ DWEN (debug wire enable)
+#        +-------------------- RSTDISBL (disable external reset -> enabled)
+#
+# Fuse low byte:
+# 0xe1 = 1 1 1 0   0 0 0 1
+#        ^ ^ \+/   \--+--/
+#        | |  |       +------- CKSEL 3..0 (clock selection -> HF PLL)
+#        | |  +--------------- SUT 1..0 (BOD enabled, fast rising power)
+#        | +------------------ CKOUT (clock output on CKOUT pin -> disabled)
+#        +-------------------- CKDIV8 (divide clock by 8 -> don't divide)
+# For 16.5MHz internal: -U hfuse:w:0xdd:m -U lfuse:w:0xe1:m
+# For 12MHz crystal: -U hfuse:w:0xdd:m -U lfuse:w:0b11111111:m
+# For 8MHz internal: -U hfuse:w:0xd7:m -U lfuse:w:0xe2:m
+fuse:
+       $(AVRDUDE) -e -U hfuse:w:0xd7:m -U lfuse:w:0xe2:m
diff --git a/filter.sch b/filter.sch
new file mode 100644 (file)
index 0000000..eef1ccf
--- /dev/null
@@ -0,0 +1,98 @@
+v 20140308 2
+C 55800 41700 0 0 0 title-A4-2.sym
+{
+T 63200 43000 15 30 1 1 0 4 1
+Title=Low pass audio filter
+T 62300 42000 15 16 1 1 0 4 1
+filename=filter.sch
+T 66750 43200 15 16 1 1 0 4 1
+revision=1
+T 66950 41800 15 16 1 1 0 6 1
+page=1
+T 67100 41800 15 16 1 1 0 0 1
+number_of_pages=1
+T 66750 42550 15 12 1 1 0 4 1
+date=21/02/2016
+T 65350 42150 15 16 1 1 0 4 1
+author=Pat Thoyts
+T 56000 50700 15 8 0 0 0 0 1
+symversion=1.0
+}
+C 59400 46800 1 0 0 res_horiz.sym
+{
+T 59700 49800 5 8 0 0 0 0 1
+device=resistor
+T 59700 47000 5 10 1 1 0 0 1
+refdes=R1
+T 59700 46800 5 10 1 1 0 0 1
+value=1k
+T 59700 46600 5 8 0 1 0 0 1
+footprint=0805
+T 59700 48800 5 8 0 0 0 0 1
+symversion=1.0
+}
+C 60500 46100 1 0 0 cap_vert.sym
+{
+T 60900 46500 5 10 1 1 0 0 1
+value=100nF
+T 60900 46300 5 8 0 1 0 0 1
+footprint=0805
+T 60800 48200 5 8 0 0 0 0 1
+symversion=1.0
+T 60900 46900 5 10 1 1 0 0 1
+refdes=C1
+}
+C 63400 46600 1 90 0 elko.sym
+{
+T 61200 46900 5 8 0 0 90 0 1
+device=POLARIZED_CAPACITOR
+T 63300 47100 5 8 0 1 90 0 1
+footprint=elko_RM5_D10
+T 62900 47100 5 10 1 1 0 0 1
+refdes=C2
+T 62900 46400 5 10 1 1 0 0 1
+value=10uF
+T 61800 46900 5 8 0 0 90 0 1
+symversion=2
+}
+C 61600 46300 1 0 0 potentiometer_vert.sym
+{
+T 61700 49300 5 8 0 0 0 0 1
+device=potentiometer
+T 61900 46900 5 8 0 1 0 0 1
+footprint=panel_mount_BI898
+T 61900 46900 5 10 1 1 0 0 1
+refdes=R3
+T 61700 48300 5 8 0 0 0 0 1
+symversion=1.0
+T 61900 46500 5 10 1 1 0 0 1
+value=10k
+}
+C 57900 47200 1 0 0 input-2.sym
+{
+T 57900 47400 5 10 0 0 0 0 1
+net=AUDIO:1
+T 58500 47900 5 10 0 0 0 0 1
+device=none
+T 59000 47500 5 10 1 1 0 7 1
+value=AUDIO
+}
+N 59300 47300 59600 47300 4
+N 60200 47300 61700 47300 4
+N 60800 47300 60800 47100 4
+N 61700 47300 61700 47100 4
+C 61200 45600 1 0 0 gnd-1.sym
+N 61700 46500 61700 45900 4
+N 60800 45900 61700 45900 4
+N 60800 46500 60800 45900 4
+N 62800 46800 62000 46800 4
+C 63700 46700 1 0 0 output-2.sym
+{
+T 64600 46900 5 10 0 0 0 0 1
+net=OUTPUT:1
+T 63900 47400 5 10 0 0 0 0 1
+device=none
+T 63900 47000 5 10 1 1 0 1 1
+value=OUTPUT
+}
+N 63700 46800 63400 46800 4
diff --git a/tiny_sound.c b/tiny_sound.c
new file mode 100644 (file)
index 0000000..243641d
--- /dev/null
@@ -0,0 +1,199 @@
+/* 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);
+    }
+}