commit a4fd933189dd93c153cf7862f9ec0429bb572537 Author: Jay Robson Date: Mon Aug 19 22:43:24 2024 +1000 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..906ce73 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +sketch.yaml + diff --git a/commands.cpp b/commands.cpp new file mode 100644 index 0000000..833b274 --- /dev/null +++ b/commands.cpp @@ -0,0 +1,102 @@ + +#include +#include "util.hpp" +#include "commands.hpp" +#include "serial.hpp" +#include "tones.hpp" +#include "scheduler.hpp" + +static void help() { + Serial.println("OK help"); + Serial.println(" s: set :s,index,freq,amplitude,"); + Serial.println(" c: clear :c,index,"); + Serial.println(" g: get :g,index,"); + Serial.println(" w: write :w,[v|s|c,...]...;,"); + Serial.println(" v: volume v,level,"); + Serial.println(" s: set s,timestamp,index,freq,"); + Serial.println(" c: clear v,timestamp,index,"); + Serial.println(" p: play :p,"); + Serial.println(" r: reset :r,"); + Serial.println(" h: help :h,"); +} + +static bool get() { + int index = serial::read_int(); + if(index < 0 || index >= size(tones::all)) { + return true; + } + if(index >= tones::active) { + tones::active = index + 1; + } + Serial.print("OK "); + Serial.print(index); + Serial.print(','); + Serial.print(tones::all[index].frequency); + Serial.print(','); + Serial.print(tones::all[index].amplitude); + Serial.print(','); + Serial.println(); + return false; +} + +static bool write() { + + scheduler::clear(); + + for(;;) { + serial::read_until(); + switch(serial::buffer[0]) { + case 'v': + scheduler::add_volume(serial::read_float()); + break; + case 's': + scheduler::add_set(serial::read_int(), serial::read_int(), serial::read_int()); + break; + case 'c': + scheduler::add_clear(serial::read_int(), serial::read_int()); + break; + case ';': + return false; + default: + return true; + } + } + + scheduler::finalize(); +} + +void commands::process() { + serial::read_until(); + switch(serial::buffer[0]) { + case 'w': + if(write()) break; + Serial.println("OK"); + return; + case 's': + tones::set(serial::read_int(), serial::read_int(), serial::read_int()); + Serial.println("OK"); + return; + case 'c': + tones::clear(serial::read_int()); + Serial.println("OK"); + return; + case 'g': + if(get()) break; + return; + case 'p': + scheduler::running = false; + scheduler::do_next(); + Serial.println("OK"); + break; + case 'r': + tones::clear_all(); + Serial.println("OK"); + return; + case 'h': + help(); + return; + } + + Serial.println("ERROR"); +} + diff --git a/commands.hpp b/commands.hpp new file mode 100644 index 0000000..8c367c1 --- /dev/null +++ b/commands.hpp @@ -0,0 +1,9 @@ + +#pragma once + +namespace commands { + + void process(); + +}; + diff --git a/dac.hpp b/dac.hpp new file mode 100644 index 0000000..88042c5 --- /dev/null +++ b/dac.hpp @@ -0,0 +1,22 @@ + +#pragma once + +#include "util.hpp" + +namespace dac { + + constexpr int BITMASK_B = 0x07; + constexpr int BITMASK_D = 0xf8; + + inline void init() { + DDRB |= BITMASK_B; + DDRD |= BITMASK_D; + } + + inline void set(float v_f) { + uint8_t v = clamp((int)(v_f * -128) + 127, 0, 255); + PORTB = (PORTB & ~BITMASK_B) | (v & BITMASK_B); + PORTD = (PORTD & ~BITMASK_D) | (v & BITMASK_D); + } +}; + diff --git a/eeprom.hpp b/eeprom.hpp new file mode 100644 index 0000000..fc5232e --- /dev/null +++ b/eeprom.hpp @@ -0,0 +1,26 @@ + +#pragma once + +#include + +namespace eeprom { + + inline int address = 0; + + inline void jump(int loc = 0) { + address = 0; + } + + template + inline void read(T& type) { + EEPROM.get(address, type); + address += sizeof(type); + } + + template + inline void write(const T& type) { + EEPROM.put(address, type); + address += sizeof(type); + } +}; + diff --git a/entry.hpp b/entry.hpp new file mode 100644 index 0000000..b192579 --- /dev/null +++ b/entry.hpp @@ -0,0 +1,35 @@ + +#pragma once + +namespace entry { + + struct Set { + uint8_t index; + uint16_t frequency; + }; + + struct Clear { + uint8_t index; + }; + + struct SetTS { + uint8_t index; + uint16_t frequency; + uint32_t timestamp; + }; + + struct ClearTS { + uint8_t index; + uint32_t timestamp; + }; + + enum Type { + STOP, + VOLUME, + SET, + SET_TS, + CLEAR, + CLEAR_TS, + }; +}; + diff --git a/scheduler.cpp b/scheduler.cpp new file mode 100644 index 0000000..d0d1fbe --- /dev/null +++ b/scheduler.cpp @@ -0,0 +1,111 @@ + +#include +#include "timing.hpp" +#include "scheduler.hpp" +#include "eeprom.hpp" +#include "entry.hpp" +#include "tones.hpp" +#include "util.hpp" + +static float amplitude = 0.25; +static float ts_last = 0; +struct { + uint8_t index; + uint16_t frequency; + float amplitude; +} next; + +void scheduler::do_next() { + + if(running) { + tones::set(next.index, next.frequency, next.amplitude); + } else { + eeprom::jump(0); + running = true; + } + + entry::Type type; + eeprom::read(type); + + switch(type) { + case entry::Type::VOLUME: { + eeprom::read(amplitude); + break; + } + case entry::Type::SET: { + entry::Set e; + eeprom::read(e); + next = {e.index, e.frequency, amplitude}; + break; + } + case entry::Type::CLEAR: { + entry::Clear e; + eeprom::read(e); + next = {e.index, 0, 0}; + break; + } + case entry::Type::SET_TS: { + entry::SetTS e; + eeprom::read(e); + timestamp = e.timestamp; + next = {e.index, e.frequency, amplitude}; + break; + } + case entry::Type::CLEAR_TS: { + entry::ClearTS e; + eeprom::read(e); + timestamp = e.timestamp; + next = {e.index, 0, 0}; + break; + } + default: { + running = false; + break; + } + } +} + +void scheduler::clear() { + eeprom::jump(0); + ts_last = 0; +} + +void scheduler::add_set(uint32_t ts, uint8_t index, uint16_t freq) { + if(index >= size(tones::all)) { + return; + } + if(ts <= ts_last) { + entry::Set e {index, freq}; + eeprom::write(entry::Type::SET); + eeprom::write(e); + } else { + entry::SetTS e {index, freq, ts}; + eeprom::write(entry::Type::SET_TS); + eeprom::write(e); + } +} + +void scheduler::add_clear(uint32_t ts, uint8_t index) { + if(index >= size(tones::all)) { + return; + } + if(ts <= ts_last) { + entry::Clear e {index}; + eeprom::write(entry::Type::CLEAR); + eeprom::write(e); + } else { + entry::ClearTS e {index, ts}; + eeprom::write(entry::Type::CLEAR_TS); + eeprom::write(e); + } +} + +void scheduler::add_volume(float volume) { + eeprom::write(entry::Type::VOLUME); + eeprom::write(volume); +} + +void scheduler::finalize() { + eeprom::write(entry::Type::STOP); +} + diff --git a/scheduler.hpp b/scheduler.hpp new file mode 100644 index 0000000..6bb9573 --- /dev/null +++ b/scheduler.hpp @@ -0,0 +1,16 @@ + +#pragma once + +namespace scheduler { + + inline uint32_t timestamp = 0; + inline bool running = false; + + void do_next(); + void clear(); + void add_set(uint32_t ts, uint8_t index, uint16_t freq); + void add_clear(uint32_t ts, uint8_t index); + void add_volume(float volume); + void finalize(); +}; + diff --git a/serial.cpp b/serial.cpp new file mode 100644 index 0000000..6ded87c --- /dev/null +++ b/serial.cpp @@ -0,0 +1,24 @@ + +#include "util.hpp" +#include "serial.hpp" +#include + +void serial::init() { + Serial.begin(115200); + Serial.println("READY"); +} + +void serial::read_until(char ch) { + buffer[Serial.readBytesUntil(ch, buffer, sizeof(buffer) - 1)] = '\0'; +} + +float serial::read_float(char ch) { + read_until(ch); + return atof(buffer); +} + +int serial::read_int(char ch) { + read_until(ch); + return atoi(buffer); +} + diff --git a/serial.hpp b/serial.hpp new file mode 100644 index 0000000..6fd458e --- /dev/null +++ b/serial.hpp @@ -0,0 +1,13 @@ + +#pragma once + +namespace serial { + + inline char buffer[16]; + + void init(); + void read_until(char ch = ','); + float read_float(char ch = ','); + int read_int(char ch = ','); +}; + diff --git a/size.hpp b/size.hpp new file mode 100644 index 0000000..7b87be3 --- /dev/null +++ b/size.hpp @@ -0,0 +1,5 @@ + +#pragma once + +#define size(V) (sizeof(V) / sizeof(V[0])) + diff --git a/timing.hpp b/timing.hpp new file mode 100644 index 0000000..fdfe1d1 --- /dev/null +++ b/timing.hpp @@ -0,0 +1,16 @@ + +#pragma once + +namespace timing { + + inline uint32_t at = 0; + inline uint32_t diff = 0; + + inline void update() { + uint32_t now = micros(); + diff = now - at; + at = now; + } + +}; + diff --git a/tone-generator.ino b/tone-generator.ino new file mode 100644 index 0000000..ca5f136 --- /dev/null +++ b/tone-generator.ino @@ -0,0 +1,51 @@ + +#include "commands.hpp" +#include "tone.hpp" +#include "serial.hpp" +#include "tones.hpp" +#include "dac.hpp" +#include "timing.hpp" +#include "util.hpp" +#include "scheduler.hpp" + +unsigned long int micros_at = 0; + +inline unsigned long micros_diff() { + unsigned long now = micros(); + unsigned long diff = now - micros_at; + micros_at = now; + return diff; +} + +void setup() { + dac::init(); + serial::init(); + scheduler::do_next(); +} + +void loop() { + if(Serial.available() && Serial.read() == ':') { + commands::process(); + } + + if(scheduler::running && timing::at >= scheduler::timestamp) { + scheduler::do_next(); + } + + timing::update(); + + float value = 0; + uint32_t passed = ((uint64_t)timing::diff << (20)) / 1000000L; + + for(int i = 0; i < tones::active; i++) { + Tone& t = tones::all[i]; + if(!t.active()) { + continue; + } + t.update(passed); + value += t.get(); + } + + dac::set(value); +} + diff --git a/tone.hpp b/tone.hpp new file mode 100644 index 0000000..1233658 --- /dev/null +++ b/tone.hpp @@ -0,0 +1,45 @@ + +#pragma once + +#include +#include + +inline const float SIN[] = { + 0.000000, 0.024541, 0.049068, 0.073565, 0.098017, 0.122411, 0.146730, 0.170962, 0.195090, 0.219101, 0.242980, 0.266713, 0.290285, 0.313682, 0.336890, 0.359895, + 0.382683, 0.405241, 0.427555, 0.449611, 0.471397, 0.492898, 0.514103, 0.534998, 0.555570, 0.575808, 0.595699, 0.615232, 0.634393, 0.653173, 0.671559, 0.689541, + 0.707107, 0.724247, 0.740951, 0.757209, 0.773010, 0.788346, 0.803208, 0.817585, 0.831470, 0.844854, 0.857729, 0.870087, 0.881921, 0.893224, 0.903989, 0.914210, + 0.923880, 0.932993, 0.941544, 0.949528, 0.956940, 0.963776, 0.970031, 0.975702, 0.980785, 0.985278, 0.989177, 0.992480, 0.995185, 0.997290, 0.998795, 0.999699, + 1.000000, 0.999699, 0.998795, 0.997290, 0.995185, 0.992480, 0.989177, 0.985278, 0.980785, 0.975702, 0.970031, 0.963776, 0.956940, 0.949528, 0.941544, 0.932993, + 0.923880, 0.914210, 0.903989, 0.893224, 0.881921, 0.870087, 0.857729, 0.844854, 0.831470, 0.817585, 0.803208, 0.788346, 0.773010, 0.757209, 0.740951, 0.724247, + 0.707107, 0.689541, 0.671559, 0.653173, 0.634393, 0.615232, 0.595699, 0.575808, 0.555570, 0.534998, 0.514103, 0.492898, 0.471397, 0.449611, 0.427555, 0.405241, + 0.382683, 0.359895, 0.336890, 0.313682, 0.290285, 0.266713, 0.242980, 0.219101, 0.195090, 0.170962, 0.146730, 0.122411, 0.098017, 0.073565, 0.049068, 0.024541, + 0.000000, -0.024541, -0.049068, -0.073565, -0.098017, -0.122411, -0.146730, -0.170962, -0.195090, -0.219101, -0.242980, -0.266713, -0.290285, -0.313682, -0.336890, -0.359895, + -0.382683, -0.405241, -0.427555, -0.449611, -0.471397, -0.492898, -0.514103, -0.534998, -0.555570, -0.575808, -0.595699, -0.615232, -0.634393, -0.653173, -0.671559, -0.689541, + -0.707107, -0.724247, -0.740951, -0.757209, -0.773010, -0.788346, -0.803208, -0.817585, -0.831470, -0.844854, -0.857729, -0.870087, -0.881921, -0.893224, -0.903989, -0.914210, + -0.923880, -0.932993, -0.941544, -0.949528, -0.956940, -0.963776, -0.970031, -0.975702, -0.980785, -0.985278, -0.989177, -0.992480, -0.995185, -0.997290, -0.998795, -0.999699, + -1.000000, -0.999699, -0.998795, -0.997290, -0.995185, -0.992480, -0.989177, -0.985278, -0.980785, -0.975702, -0.970031, -0.963776, -0.956940, -0.949528, -0.941544, -0.932993, + -0.923880, -0.914210, -0.903989, -0.893224, -0.881921, -0.870087, -0.857729, -0.844854, -0.831470, -0.817585, -0.803208, -0.788346, -0.773010, -0.757209, -0.740951, -0.724247, + -0.707107, -0.689541, -0.671559, -0.653173, -0.634393, -0.615232, -0.595699, -0.575808, -0.555570, -0.534998, -0.514103, -0.492898, -0.471397, -0.449611, -0.427555, -0.405241, + -0.382683, -0.359895, -0.336890, -0.313682, -0.290285, -0.266713, -0.242980, -0.219101, -0.195090, -0.170962, -0.146730, -0.122411, -0.098017, -0.073565, -0.049068, -0.024541, +}; + +struct Tone { + uint32_t phase; + uint16_t frequency; + float amplitude; + + inline bool active() const { + return amplitude > 0; + } + + inline void update(uint32_t t) { + phase += t * frequency; + } + + inline float get() const { + uint8_t id = phase >> 12; + return SIN[id] * amplitude; + } +}; + + diff --git a/tones.cpp b/tones.cpp new file mode 100644 index 0000000..31169cd --- /dev/null +++ b/tones.cpp @@ -0,0 +1,45 @@ + +#include "util.hpp" +#include "tones.hpp" +#include "tone.hpp" + +void tones::clear_all() { + for(int i = 0; i < size(all); i++) { + all[i] = {0, 0, 0}; + } + active = 0; +} + +void tones::clear(uint8_t index) { + if(index >= size(all)) { + return; + } + all[index] = {0, 0, 0}; + + if(index == active - 1) { + prune(index); + } +} + +void tones::set(uint8_t index, uint16_t frequency, float amplitude) { + if(amplitude == 0) { + return clear(index); + } + if(index >= size(all)) { + return; + } + all[index].frequency = frequency; + all[index].amplitude = amplitude; + + if(index >= tones::active) { + tones::active = index + 1; + } +} + +void tones::prune(uint8_t index) { + int i; + for(i = index; i >= 0 && all[i].amplitude == 0; i--) { + } + active = i + 1; +} + diff --git a/tones.hpp b/tones.hpp new file mode 100644 index 0000000..6e3adc9 --- /dev/null +++ b/tones.hpp @@ -0,0 +1,16 @@ + +#pragma once + +#include "tone.hpp" + +namespace tones { + + inline Tone all[16]; + inline int active; + + void prune(uint8_t index); + void set(uint8_t index, uint16_t frequency, float amplitude); + void clear(uint8_t index); + void clear_all(); +} + diff --git a/util.hpp b/util.hpp new file mode 100644 index 0000000..1977be9 --- /dev/null +++ b/util.hpp @@ -0,0 +1,10 @@ + +#pragma once + +template +inline T clamp(T v, T a, T b) { + return min(max(v, a), b); +} + +#define size(V) (sizeof(V) / sizeof(V[0])) +