diff --git a/README.md b/README.md index 9ff813a..df887cb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Overview -This uses an AVR microcontroller (ATMega328P-PU, which is in the Uno R3). +This uses an AVR microcontroller (ATMega328P-PU). - Microconroller: https://git.onewaycoding.com/jay/tone-generator - MIDI parser: https://git.onewaycoding.com/jay/midi-parser @@ -11,10 +11,56 @@ This uses an AVR microcontroller (ATMega328P-PU, which is in the Uno R3). | Pin(s) | Purpose | |--------|----------------------------------| -| 0-7 | 8-bit ADC (for audio) | +| 0-7 | 8-bit DAC (for audio) | | 8 | Next button | | 9-10 | LEDs | | 11-13 | ISP Programmer, otherwise unused | | A0-A3 | Serial: DTR, TXD, RXD, CTS | | A4-A5 | I2C: SDA, SCL | +# Pin Description + +| Pin(s) | Purpose | +|--------|----------------------------------| +| 0-7 | 8-bit DAC (for audio) | +| 8 | Next button | +| 9-10 | LEDs | +| 11-13 | ISP Programmer, otherwise unused | +| A0-A3 | Serial: DTR, TXD, RXD, CTS | +| A4-A5 | I2C: SDA, SCL | + +# 8 bit DAC + +this is a resistor ladder. one major advantage i get for using all of pins 0-7, is i can set all of them very efficiently, and simultaneously if i write to them directly (setting to `PORTD` instead of using `digitalWrite` in a loop). unfortunately this means i have to use software serial, but the performance compromise was worth it, because setting pins via registers directly is extremely fast in the right circumstances. + +# I2C + +this is to communicate with EEPROM. there is 256 KB installed. songs are read from here. i am actually using my own implementation here of software i2c, because i needed to write entire 128 byte pages to EEPROM in a single go. my implementation is master only, and it doesn't require any buffers or interrupts. it is not limited in the amount of bytes a single transaction or response may have. + +# Serial + +via serial, EEPROM can be flashed, or songs can be streamed directly. + +# Tone generating, mixing, and playback + +this is the most performance critical part, and is done entirely in software. there are 32 channels for tones to play in, meaning up to 32 tones can play at one time. all of this is done using integer math. + +# Amplifiers + +there are 2 dual opamps, using 3 opamps in total. 1 is to drive some transistors to create a raised ground at 2.5V. this is the speaker ground. downside of this is that in order for this to connect to an audio system (such as a speaker, headphones, or mic input), both systems must be isolated from each other. + +one opamp follows the signal from the 8 bit DAC, but with gain from the potentiometer to prevent clipping. the output of this is followed by another opamp, which drives some transistors to increase the current. + +# Instruction Set + +for streaming from eeprom and serial, i need a representation that is as compact as possible. there are a total of 7 instructions (but 6 in use here). + +| ID | Name | Size (bytes) | Provides | +|-|-|-|-| +| 0 | stop | 1 | halts execution | +| 1 | setup | 8 | provides important information for the song, such as volume, microseconds per tick, and where to jump on song skip | +| 2 | set | 3 | assigns a frequency to a channel | +| 3 | set-ts | 5 | same as set, but waits until given ticks have passed | +| 4 | clear | 1 | stops a channel from playing | +| 5 | clear-ts | 4 | same as clear, but waits until given ticks have passed | + diff --git a/port.hpp b/port.hpp index bd67cbf..1ee2a1c 100644 --- a/port.hpp +++ b/port.hpp @@ -6,7 +6,6 @@ namespace port { struct Drain { uint8_t mask; - uint8_t imask; volatile uint8_t* mode; volatile uint8_t* in; @@ -14,12 +13,11 @@ namespace port { mask = digitalPinToBitMask(pin); mode = portModeRegister(digitalPinToPort(pin)); in = portInputRegister(digitalPinToPort(pin)); - imask = ~mask; } - + inline void set(uint8_t b) AW_IN { if(b) { - *mode &= imask; + *mode &= ~mask; } else { *mode |= mask; } @@ -32,13 +30,11 @@ namespace port { struct Input { uint8_t mask; - uint8_t imask; volatile uint8_t* in; inline Input(unsigned pin) AW_IN { mask = digitalPinToBitMask(pin); in = portInputRegister(digitalPinToPort(pin)); - imask = ~mask; } inline uint8_t get() AW_IN { @@ -48,20 +44,18 @@ namespace port { struct Output { uint8_t mask; - uint8_t imask; volatile uint8_t* out; inline Output(unsigned pin) AW_IN { mask = digitalPinToBitMask(pin); out = portOutputRegister(digitalPinToPort(pin)); - imask = ~mask; } inline void set(uint8_t b) AW_IN { if(b) { *out |= mask; } else { - *out &= imask; + *out &= ~mask; } } }; diff --git a/scheduler.cpp b/scheduler.cpp index 19d11d3..f6e6d12 100644 --- a/scheduler.cpp +++ b/scheduler.cpp @@ -79,15 +79,6 @@ static void do_next() { scheduler::reset(); break; } - case entry::Type::tempo: { - next.type = entry::nt_tempo; - v >>= 4; - next.tempo.ticks = (v & bm(20)) - tick_offset; - v >>= 20; - next.tempo.us_per_tick = v & bm(29); - timestamp = next.tempo.ticks * config.us_per_tick + ts_init; - break; - } case entry::Type::set: { v >>= 4; next.type = entry::nt_tone;