initial commit
This commit is contained in:
commit
a4fd933189
|
@ -0,0 +1,2 @@
|
||||||
|
sketch.yaml
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace commands {
|
||||||
|
|
||||||
|
void process();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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 = ',');
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define size(V) (sizeof(V) / sizeof(V[0]))
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue