Skip to content

Instantly share code, notes, and snippets.

@JarrettBillingsley
Created August 5, 2017 07:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JarrettBillingsley/13ef37fa8b546c1d9df3a7ddf405c4ed to your computer and use it in GitHub Desktop.
Save JarrettBillingsley/13ef37fa8b546c1d9df3a7ddf405c4ed to your computer and use it in GitHub Desktop.
The tiniest synth.
; 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