Created
August 5, 2017 07:13
-
-
Save JarrettBillingsley/13ef37fa8b546c1d9df3a7ddf405c4ed to your computer and use it in GitHub Desktop.
The tiniest synth.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; Schematic | |
; (stick a potentiometer between GND and VCC into PB2 for fun!) | |
; | |
; ATTiny85 | |
; ------------- | |
; |VCC PB0| | |
; |GND PB1| 10uF | |
; | PB2| +-------|(------ to headphone jack | |
; | PB3| 1K v + | |
; |RST PB4|-------/\/\/\---+------/\/\/\-+ | |
; ------------- | 10K(B) | | |
; 0.22uF(224) = | | |
; (film cap) | | | |
; V V | |
#include <avr/io.h> | |
#define IO_ADCH _SFR_IO_ADDR(ADCH) | |
#define IO_ADCSRA _SFR_IO_ADDR(ADCSRA) | |
#define IO_ADMUX _SFR_IO_ADDR(ADMUX) | |
#define IO_DDRB _SFR_IO_ADDR(DDRB) | |
#define IO_GTCCR _SFR_IO_ADDR(GTCCR) | |
#define IO_OCR1A _SFR_IO_ADDR(OCR1A) | |
#define IO_OCR1B _SFR_IO_ADDR(OCR1B) | |
#define IO_OCR1C _SFR_IO_ADDR(OCR1C) | |
#define IO_PLLCSR _SFR_IO_ADDR(PLLCSR) | |
#define IO_SREG _SFR_IO_ADDR(SREG) | |
#define IO_TCCR1 _SFR_IO_ADDR(TCCR1) | |
#define IO_TIMSK _SFR_IO_ADDR(TIMSK) | |
#define NUM_CHANNELS 4 | |
#define ZERO 0 | |
#define SREG_SAVE 1 | |
#define SAMPLE_READY 2 | |
#define SAMPLE_BUF 3 | |
#define MUL_HI 4 | |
; 16-bit pointer to start of channel data | |
#define CHAN_BASE 14 | |
; 24-bit rate | |
#define RATE 16 | |
; 24-bit phase | |
#define PHASE 19 | |
; top byte of phase is also the offset | |
#define OFFSET 21 | |
; remaining channel data/sample data | |
#define LEN_MASK 22 | |
#define SAMPLE 23 | |
#define START 24 | |
#define VOLUME 25 | |
; X is sample pointer | |
; Y is channel data pointer | |
; Z is used in the init_data routine and then not used as a pointer again | |
#define MUL_I 30 | |
#define I 31 | |
; ---------------------------------------------------------------- | |
; timer1 overflow ISR - likely 10 cycles, 9 on underrun | |
.global TIMER1_OVF_vect | |
TIMER1_OVF_vect: | |
in SREG_SAVE, IO_SREG | |
; any sample ready? | |
tst SAMPLE_READY | |
breq .no_sample | |
; clear the flag and output the sample buffer | |
clr SAMPLE_READY | |
out IO_OCR1B, SAMPLE_BUF | |
.no_sample: | |
out IO_SREG, SREG_SAVE | |
reti | |
; ---------------------------------------------------------------- | |
; set up ADC (for debugging) | |
adc_setup: | |
in r16, IO_ADMUX | |
ori r16, _BV(MUX0) | _BV(ADLAR) | |
out IO_ADMUX, r16 | |
in r16, IO_ADCSRA | |
ori r16, _BV(ADPS1) | _BV(ADPS0) | _BV(ADEN) | |
out IO_ADCSRA, r16 | |
ret | |
; ---------------------------------------------------------------- | |
; read from ADC | |
; !!!trashes/returns in r16 (RATE), do not call inside update loop | |
adc_read: | |
in r16, IO_ADCSRA | |
ori r16, _BV(ADSC) | |
out IO_ADCSRA, r16 | |
.adc_wait: | |
sbic IO_ADCSRA, ADSC | |
rjmp .adc_wait | |
in r16, IO_ADCH | |
ret | |
; ---------------------------------------------------------------- | |
; pwm setup (kinda important, since it generates the sound output) | |
pwm_setup: | |
; Turn on the PLL | |
in r16, IO_PLLCSR | |
ori r16, _BV(PLLE) | _BV(PCKE) | |
out IO_PLLCSR, r16 | |
; Set OCR1B compare value and OCR1C TOP value | |
out IO_OCR1A, ZERO | |
ldi r16, 128 | |
out IO_OCR1B, r16 | |
ldi r16, 255 | |
out IO_OCR1C, r16 | |
; Enable OCRB output on PB4, configure compare mode and enable PWM B | |
ldi r16, _BV(DDB4) ;| _BV(DDB1) | _BV(DDB0) | |
out IO_DDRB, r16 | |
ldi r16, _BV(PWM1B) | _BV(COM1B1) | _BV(COM1B0) | |
out IO_GTCCR, r16 | |
in r16, IO_TIMSK | |
ori r16, _BV(TOIE1) | |
out IO_TIMSK, r16 | |
; Hardware bug requires COM1A0 to be enabled. | |
; 3 = PCK/4 (64KHz sample rate) | |
; 4 = PCK/8 (32KHz sample rate) | |
ldi r16, _BV(COM1A1) | _BV(COM1A0) | 4 | |
out IO_TCCR1, r16 | |
ret | |
; ---------------------------------------------------------------- | |
; main loop | |
.global channels | |
.global sample_ram | |
.global main | |
main: | |
rcall init_data | |
rcall adc_setup | |
rcall pwm_setup | |
sei | |
clr ZERO | |
ldi XL, lo8(sample_ram) | |
ldi XH, hi8(sample_ram) | |
ldi r16, lo8(channels) | |
mov CHAN_BASE, r16 | |
ldi r16, hi8(channels) | |
mov CHAN_BASE+1, r16 | |
.wait_loop: | |
rcall adc_read | |
movw YL, CHAN_BASE | |
adiw YL, 1 | |
st Y, r16 | |
adiw YL, 9 | |
st Y, r16 | |
adiw YL, 9 | |
st Y, r16 | |
adiw YL, 9 | |
st Y, r16 | |
tst SAMPLE_READY | |
brne .wait_loop | |
; reset pointers and counters (3 cycles) | |
movw YL, CHAN_BASE | |
ldi I, NUM_CHANNELS | |
clr SAMPLE_BUF | |
; each iteration is 69 cycles for enabled, 18 for disabled. | |
; 7 cycles of constant overhead before/after loop. | |
; so it's 7+(69*NUM_CHANNELS) cycles for an update. | |
.update_loop: | |
; read rate and skip to next channel if 0 (10 cycles for enabled, 11 for disabled) | |
ld RATE, Y+ | |
ld RATE+1, Y+ | |
ld RATE+2, Y+ | |
cpi RATE, 0 | |
cpc RATE+1, 0 | |
cpc RATE+2, 0 | |
breq .chan_disabled | |
; read phase, add rate and store (15 cycles, 25 so far) | |
ld PHASE, Y | |
ldd PHASE+1, Y+1 | |
ldd PHASE+2, Y+2 | |
add PHASE, RATE | |
adc PHASE+1, RATE+1 | |
adc PHASE+2, RATE+2 | |
st Y+, PHASE | |
st Y+, PHASE+1 | |
st Y+, PHASE+2 | |
; (rate no longer needed) | |
; read len_mask, and with top byte of phase to give offset (3 cycles, 28 so far) | |
ld LEN_MASK, Y+ | |
and OFFSET, LEN_MASK | |
; (len_mask and phase no longer needed) | |
; read start, add to offset, and that's the sample address (4 cycles, 32 so far) | |
ld START, Y+ | |
add OFFSET, START | |
mov XL, OFFSET | |
; (start and offset no longer needed) | |
; read sample and volume (3 cycles, 35 so far) | |
ld VOLUME, Y+ | |
ld SAMPLE, X | |
; multiply (8-bit) sample by (4-bit) volume (30 cycles, 65 so far) | |
clr MUL_HI | |
lsr VOLUME | |
brcc .mul_zero_0 | |
add MUL_HI, SAMPLE | |
.mul_zero_0: | |
ror MUL_HI | |
ror VOLUME | |
brcc .mul_zero_1 | |
add MUL_HI, SAMPLE | |
.mul_zero_1: | |
ror MUL_HI | |
ror VOLUME | |
brcc .mul_zero_2 | |
add MUL_HI, SAMPLE | |
.mul_zero_2: | |
ror MUL_HI | |
ror VOLUME | |
brcc .mul_zero_3 | |
add MUL_HI, SAMPLE | |
.mul_zero_3: | |
ror MUL_HI | |
ror VOLUME | |
mov r11, MUL_HI | |
mov r10, VOLUME | |
lsr r11 | |
ror r10 | |
lsr r11 | |
ror r10 | |
lsr r11 | |
ror r10 | |
lsr r11 | |
ror r10 | |
add VOLUME, r10 | |
adc MUL_HI, r11 | |
; add top 8 bits of result to sample buf (1 cycle, 66 so far) | |
add SAMPLE_BUF, MUL_HI | |
.loop_next: | |
; decrement counter, loop if not 0 (3 cycles, 69 for the loop) | |
dec I | |
brne .update_loop | |
; invert sample and tell the ISR it's ready (4 cycles) | |
com SAMPLE_BUF | |
inc SAMPLE_READY | |
rjmp .wait_loop | |
.chan_disabled: | |
; skip over the 6 remaining bytes (4 cycles, total 15) | |
adiw YL, 6 | |
rjmp .loop_next | |
; --------------------------------------------------------------------------------------- | |
.global _edata | |
init_data: | |
ldi r17, hi8(_edata) | |
ldi XL, 0x60 | |
ldi XH, 0x00 | |
ldi ZL, lo8(_etext) | |
ldi ZH, hi8(_etext) | |
rjmp .cont | |
.copy_loop: | |
lpm r0, Z+ | |
st X+, r0 | |
.cont: | |
cpi XL, lo8(_edata) | |
cpc XH, r17 | |
brne .copy_loop | |
ret | |
; --------------------------------------------------------------------------------------- | |
.data | |
.type channels, @object | |
.size channels, NUM_CHANNELS * 9 | |
channels: | |
; rate------------ phase----------- len start vol | |
.byte 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x0A | |
.byte 0x40, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x40, 0x0A | |
.byte 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x0A | |
.byte 0xC0, 0x40, 0x01, 0x00, 0x00, 0x00, 0x3F, 0xC0, 0x0A | |
.org 0x100 - 0x60 | |
.type sample_ram, @object | |
.size sample_ram, 256 | |
sample_ram: | |
; sine | |
.byte 0x80, 0x8C, 0x99, 0xA5, 0xB1, 0xBC, 0xC7, 0xD1 | |
.byte 0xDA, 0xE2, 0xEA, 0xF0, 0xF5, 0xFA, 0xFD, 0xFE | |
.byte 0xFF, 0xFE, 0xFD, 0xFA, 0xF5, 0xF0, 0xEA, 0xE2 | |
.byte 0xDA, 0xD1, 0xC7, 0xBC, 0xB1, 0xA5, 0x99, 0x8C | |
.byte 0x80, 0x74, 0x67, 0x5B, 0x4F, 0x44, 0x39, 0x2F | |
.byte 0x26, 0x1E, 0x16, 0x10, 0x0B, 0x06, 0x03, 0x02 | |
.byte 0x01, 0x02, 0x03, 0x06, 0x0B, 0x10, 0x16, 0x1E | |
.byte 0x26, 0x2F, 0x39, 0x44, 0x4F, 0x5B, 0x67, 0x74 | |
; saw | |
.byte 0x00, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C | |
.byte 0x20, 0x24, 0x28, 0x2C, 0x30, 0x34, 0x38, 0x3C | |
.byte 0x40, 0x44, 0x48, 0x4C, 0x50, 0x54, 0x58, 0x5C | |
.byte 0x60, 0x64, 0x68, 0x6C, 0x70, 0x74, 0x78, 0x7C | |
.byte 0x80, 0x84, 0x88, 0x8C, 0x90, 0x94, 0x98, 0x9C | |
.byte 0xA0, 0xA4, 0xA8, 0xAC, 0xB0, 0xB4, 0xB8, 0xBC | |
.byte 0xC0, 0xC4, 0xC8, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC | |
.byte 0xE0, 0xE4, 0xE8, 0xEC, 0xF0, 0xF4, 0xF8, 0xFC | |
; tri | |
.byte 0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38 | |
.byte 0x40, 0x48, 0x50, 0x58, 0x60, 0x68, 0x70, 0x78 | |
.byte 0x80, 0x88, 0x90, 0x98, 0xA0, 0xA8, 0xB0, 0xB8 | |
.byte 0xC0, 0xC8, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8 | |
.byte 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0 | |
.byte 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0x80 | |
.byte 0x78, 0x70, 0x68, 0x60, 0x58, 0x50, 0x48, 0x40 | |
.byte 0x38, 0x30, 0x28, 0x20, 0x18, 0x10, 0x08, 0x00 | |
; square | |
.byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | |
.byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | |
.byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | |
.byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | |
.byte 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF | |
.byte 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF | |
.byte 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF | |
.byte 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment