From a4fd933189dd93c153cf7862f9ec0429bb572537 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Mon, 19 Aug 2024 22:43:24 +1000 Subject: [PATCH] initial commit --- .gitignore | 2 + commands.cpp | 102 +++++++++++++++++++++++++++++++++++++++++ commands.hpp | 9 ++++ dac.hpp | 22 +++++++++ eeprom.hpp | 26 +++++++++++ entry.hpp | 35 ++++++++++++++ scheduler.cpp | 111 +++++++++++++++++++++++++++++++++++++++++++++ scheduler.hpp | 16 +++++++ serial.cpp | 24 ++++++++++ serial.hpp | 13 ++++++ size.hpp | 5 ++ timing.hpp | 16 +++++++ tone-generator.ino | 51 +++++++++++++++++++++ tone.hpp | 45 ++++++++++++++++++ tones.cpp | 45 ++++++++++++++++++ tones.hpp | 16 +++++++ util.hpp | 10 ++++ 17 files changed, 548 insertions(+) create mode 100644 .gitignore create mode 100644 commands.cpp create mode 100644 commands.hpp create mode 100644 dac.hpp create mode 100644 eeprom.hpp create mode 100644 entry.hpp create mode 100644 scheduler.cpp create mode 100644 scheduler.hpp create mode 100644 serial.cpp create mode 100644 serial.hpp create mode 100644 size.hpp create mode 100644 timing.hpp create mode 100644 tone-generator.ino create mode 100644 tone.hpp create mode 100644 tones.cpp create mode 100644 tones.hpp create mode 100644 util.hpp 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])) +