initial commit

This commit is contained in:
Jay Robson 2024-08-19 22:43:24 +10:00
commit a4fd933189
17 changed files with 548 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
sketch.yaml

102
commands.cpp Normal file
View File

@ -0,0 +1,102 @@
#include <Arduino.h>
#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");
}

9
commands.hpp Normal file
View File

@ -0,0 +1,9 @@
#pragma once
namespace commands {
void process();
};

22
dac.hpp Normal file
View File

@ -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);
}
};

26
eeprom.hpp Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <EEPROM.h>
namespace eeprom {
inline int address = 0;
inline void jump(int loc = 0) {
address = 0;
}
template <typename T>
inline void read(T& type) {
EEPROM.get(address, type);
address += sizeof(type);
}
template <typename T>
inline void write(const T& type) {
EEPROM.put(address, type);
address += sizeof(type);
}
};

35
entry.hpp Normal file
View File

@ -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,
};
};

111
scheduler.cpp Normal file
View File

@ -0,0 +1,111 @@
#include <Arduino.h>
#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);
}

16
scheduler.hpp Normal file
View File

@ -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();
};

24
serial.cpp Normal file
View File

@ -0,0 +1,24 @@
#include "util.hpp"
#include "serial.hpp"
#include <Arduino.h>
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);
}

13
serial.hpp Normal file
View File

@ -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 = ',');
};

5
size.hpp Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#define size(V) (sizeof(V) / sizeof(V[0]))

16
timing.hpp Normal file
View File

@ -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;
}
};

51
tone-generator.ino Normal file
View File

@ -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);
}

45
tone.hpp Normal file
View File

@ -0,0 +1,45 @@
#pragma once
#include <inttypes.h>
#include <avr/pgmspace.h>
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;
}
};

45
tones.cpp Normal file
View File

@ -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;
}

16
tones.hpp Normal file
View File

@ -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();
}

10
util.hpp Normal file
View File

@ -0,0 +1,10 @@
#pragma once
template <typename T>
inline T clamp(T v, T a, T b) {
return min(max(v, a), b);
}
#define size(V) (sizeof(V) / sizeof(V[0]))