diff --git a/src/AudioLibs/R2ROutput.h b/src/AudioLibs/R2ROutput.h new file mode 100644 index 000000000..a80e74878 --- /dev/null +++ b/src/AudioLibs/R2ROutput.h @@ -0,0 +1,136 @@ +#pragma once + +#include "AudioConfig.h" +#include "AudioTimer/AudioTimer.h" +#include "AudioTools/AudioLogger.h" +#include "AudioTools/AudioOutput.h" +#include "AudioTools/Buffers.h" + +namespace audio_tools { + +/** + * @brief R2R configuration + * @author Phil Schatzmann + * @copyright GPLv3 + */ + +class R2RConfig : public AudioInfo { + public: + Vector channel1_pins{0}; + Vector channel2_pins{0}; +}; + +/** + * @brief DRAFT implementation for Output to R2R DAC + * @ingroup io + * @author Phil Schatzmann + * @copyright GPLv3 + */ + +class R2ROutput : public AudioOutput { + public: + R2RConfig defaultConfig() { + R2RConfig r; + return r; + } + + bool begin(R2RConfig c) { + cfg = c; + rcfg = c; + return begin(); + } + + bool begin() override { + if (cfg.channels == 0 || cfg.channels > 2) { + LOGE("channels is %d", cfg.channels); + return false; + } + if (rcfg.channel1_pins.size() == 0) { + LOGE("channel1_pins not defined"); + return false; + } + if (cfg.channels == 2 && + rcfg.channel2_pins.size() != rcfg.channel1_pins.size()) { + LOGE("channel2_pins not defined"); + return false; + } + setupPins(); + timer.setCallbackParameter(this); + return timer.begin(r2r_timer_callback, cfg.sample_rate, HZ); + } + + size_t write(const uint8_t *data, size_t len) override { + size_t result = buffer.writeArray(data, len); + // activate output when buffer is full + if (!is_active && buffer.isFull()) { + is_active = true; + } + return result; + } + + protected: + TimerAlarmRepeating timer; + NBuffer buffer{1024, 2}; + R2RConfig rcfg; + + void setupPins() { + for (int j = 0; j < rcfg.channel1_pins.size(); j++) { + pinMode(rcfg.channel1_pins[j], OUTPUT); + } + for (int j = 0; j < rcfg.channel2_pins.size(); j++) { + pinMode(rcfg.channel2_pins[j], OUTPUT); + } + } + + void writeValue(int channel) { + switch (cfg.bits_per_sample) { + case 8: + return writeValueT(channel); + case 16: + return writeValueT(channel); + case 24: + return writeValueT(channel); + case 32: + return writeValueT(channel); + } + } + + template + void writeValueT(int channel) { + // don't do anything if we do not have enough data + if (buffer.available()< sizeof(T)) return; + + // get next value from buffer + T value = 0; + buffer.readArray((uint8_t *)&value, sizeof(T)); + // convert to unsigned + unsigned uvalue = (int)value + NumberConverter::maxValueT() + 1; + // scale value + uvalue = uvalue >> ((sizeof(T) * 8) - rcfg.channel1_pins.size()); + // output pins + switch (channel) { + case 0: + for (int j = 0; j < rcfg.channel1_pins.size(); j++) { + digitalWrite(rcfg.channel1_pins[j], (uvalue >> j) & 1); + } + break; + case 1: + for (int j = 0; j < rcfg.channel2_pins.size(); j++) { + digitalWrite(rcfg.channel2_pins[j], (uvalue >> j) & 1); + } + break; + } + } + + static void r2r_timer_callback(void *ptr) { + R2ROutput *self = (R2ROutput *)ptr; + if (self->is_active) { + // output channel 1 + self->writeValue(0); + // output channel 2 + if (self->cfg.channels == 2) self->writeValue(1); + }; + } +}; + +} // namespace audio_tools \ No newline at end of file diff --git a/src/AudioTimer/AudioTimerSTM32.h b/src/AudioTimer/AudioTimerSTM32.h index 1747e34f5..62ed85f01 100644 --- a/src/AudioTimer/AudioTimerSTM32.h +++ b/src/AudioTimer/AudioTimerSTM32.h @@ -59,6 +59,11 @@ class TimerAlarmRepeatingDriverSTM32 : public TimerAlarmRepeatingDriverBase { case US: timer->setOverflow(time, MICROSEC_FORMAT); // 10 Hz break; + case HZ: + // convert hz to time in us + uint64_t time_us = AudioTime::toTimeUs(time); + timer->setOverflow(time_us, MICROSEC_FORMAT); // 10 Hz + break; } timer->resume(); return true;