update readme, remove instruction, improve port.hpp

This commit is contained in:
Jay Robson 2024-10-02 23:33:45 +10:00
parent 78965abed2
commit 9a75323691
3 changed files with 51 additions and 20 deletions

View File

@ -1,7 +1,7 @@
# Overview # 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 - Microconroller: https://git.onewaycoding.com/jay/tone-generator
- MIDI parser: https://git.onewaycoding.com/jay/midi-parser - 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 | | Pin(s) | Purpose |
|--------|----------------------------------| |--------|----------------------------------|
| 0-7 | 8-bit ADC (for audio) | | 0-7 | 8-bit DAC (for audio) |
| 8 | Next button | | 8 | Next button |
| 9-10 | LEDs | | 9-10 | LEDs |
| 11-13 | ISP Programmer, otherwise unused | | 11-13 | ISP Programmer, otherwise unused |
| A0-A3 | Serial: DTR, TXD, RXD, CTS | | A0-A3 | Serial: DTR, TXD, RXD, CTS |
| A4-A5 | I2C: SDA, SCL | | 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 |

View File

@ -6,7 +6,6 @@
namespace port { namespace port {
struct Drain { struct Drain {
uint8_t mask; uint8_t mask;
uint8_t imask;
volatile uint8_t* mode; volatile uint8_t* mode;
volatile uint8_t* in; volatile uint8_t* in;
@ -14,12 +13,11 @@ namespace port {
mask = digitalPinToBitMask(pin); mask = digitalPinToBitMask(pin);
mode = portModeRegister(digitalPinToPort(pin)); mode = portModeRegister(digitalPinToPort(pin));
in = portInputRegister(digitalPinToPort(pin)); in = portInputRegister(digitalPinToPort(pin));
imask = ~mask;
} }
inline void set(uint8_t b) AW_IN { inline void set(uint8_t b) AW_IN {
if(b) { if(b) {
*mode &= imask; *mode &= ~mask;
} else { } else {
*mode |= mask; *mode |= mask;
} }
@ -32,13 +30,11 @@ namespace port {
struct Input { struct Input {
uint8_t mask; uint8_t mask;
uint8_t imask;
volatile uint8_t* in; volatile uint8_t* in;
inline Input(unsigned pin) AW_IN { inline Input(unsigned pin) AW_IN {
mask = digitalPinToBitMask(pin); mask = digitalPinToBitMask(pin);
in = portInputRegister(digitalPinToPort(pin)); in = portInputRegister(digitalPinToPort(pin));
imask = ~mask;
} }
inline uint8_t get() AW_IN { inline uint8_t get() AW_IN {
@ -48,20 +44,18 @@ namespace port {
struct Output { struct Output {
uint8_t mask; uint8_t mask;
uint8_t imask;
volatile uint8_t* out; volatile uint8_t* out;
inline Output(unsigned pin) AW_IN { inline Output(unsigned pin) AW_IN {
mask = digitalPinToBitMask(pin); mask = digitalPinToBitMask(pin);
out = portOutputRegister(digitalPinToPort(pin)); out = portOutputRegister(digitalPinToPort(pin));
imask = ~mask;
} }
inline void set(uint8_t b) AW_IN { inline void set(uint8_t b) AW_IN {
if(b) { if(b) {
*out |= mask; *out |= mask;
} else { } else {
*out &= imask; *out &= ~mask;
} }
} }
}; };

View File

@ -79,15 +79,6 @@ static void do_next() {
scheduler::reset(); scheduler::reset();
break; 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: { case entry::Type::set: {
v >>= 4; v >>= 4;
next.type = entry::nt_tone; next.type = entry::nt_tone;