From 27e023f8ea0d40930ada8beedc7efa12e3d3cb40 Mon Sep 17 00:00:00 2001 From: Jorj Bauer Date: Thu, 9 Jul 2020 20:31:51 -0400 Subject: [PATCH] working on speaker conversion --- teensy/teensy-speaker.cpp | 90 +++++++++++++++++++++++++++++++-------- teensy/teensy-speaker.h | 12 +++--- teensy/teensy.ino | 17 ++++++-- 3 files changed, 94 insertions(+), 25 deletions(-) diff --git a/teensy/teensy-speaker.cpp b/teensy/teensy-speaker.cpp index 6ba46d7..d4e49b8 100644 --- a/teensy/teensy-speaker.cpp +++ b/teensy/teensy-speaker.cpp @@ -1,13 +1,24 @@ #include +#include +#include #include "teensy-speaker.h" +#include "teensy-println.h" #include "globals.h" -TeensySpeaker::TeensySpeaker(uint8_t pinNum) : PhysicalSpeaker() +Threads::Mutex togmutex; +#define BUFSIZE 4096 +EXTMEM uint32_t toggleBuffer[BUFSIZE]; // cycle counts at which state toggles +uint16_t headptr, tailptr; +uint32_t lastCycleCount, toggleAtCycle; + +// How many cycles do we run the audio behind? Needs to be more than our bulk +// cycle count. +#define CYCLEDELAY 100 + +TeensySpeaker::TeensySpeaker(uint8_t sda, uint8_t scl) : PhysicalSpeaker() { toggleState = false; - speakerPin = pinNum; - pinMode(speakerPin, OUTPUT); // analog speaker output, used as digital volume control mixerValue = numMixed = 0; } @@ -15,24 +26,69 @@ TeensySpeaker::~TeensySpeaker() { } -void TeensySpeaker::toggle(uint32_t c) +void TeensySpeaker::begin() { - toggleState = !toggleState; - - mixerValue = (toggleState ? 0x1FF : 0x00); - mixerValue >>= (16-g_volume); - - // FIXME: this is one helluva hack - if (g_volume >= 8) - digitalWrite(speakerPin, toggleState ? HIGH : LOW); - //analogWrite(speakerPin, mixerValue); + lastCycleCount = g_cpu->cycles; + toggleState = false; + memset(toggleBuffer, 0, sizeof(toggleBuffer)); + headptr = tailptr = 0; } -void TeensySpeaker::maintainSpeaker(uint32_t c, uint64_t runtimeInMicros) +void TeensySpeaker::toggle(uint32_t c) { - // Nothing to do here. We can't run the speaker async, b/c not - // enough CPU time. So we run the CPU close to sync and hope that - // the direct pulsing of the speaker is reasonably close to on-time. + Threads::Scope lock(togmutex); + // Queue the speaker toggle time; maintainSpeaker will pick it up + toggleBuffer[tailptr++] = c; tailptr %= BUFSIZE; +} + +void TeensySpeaker::maintainSpeaker(uint32_t c, uint64_t microseconds) +{ + begin(); // flush! Hack. FIXME. +} + +void TeensySpeaker::maintainSpeaker() +{ + Threads::Scope lock(togmutex); + + // This is called @ SAMPLERATE (8k, as of this writing) and looks for + // any transitions that have passed before sending data to the DAC. + // The idea is that this will be called fast enough for the given number + // of cycles that we run behind, so that the buffer can't overflow. + // + // at 8kHz, that means that .000125 seconds have passed since our last + // call; where the CPU has executed about 128 instructions in the same + // time. Therefore, it can't have toggled more than 128 times. We're + // also trying to stay 100 cycles "behind", so it's possible that we have + // 228 cycles difference between an event that just fired-and-queued, + // and where we need to catch up to in this loop. + + // First, reconcile the "correct" toggleState. We pretend it's currently + // some time in the past, based on our cycle delay. In theory (as above), + // this shouldn't be more than about 228 cycles off (maybe slightly more, + // since we actually run cycles in batches). + uint32_t curTime = g_cpu->cycles - CYCLEDELAY; + // And then find any events that should have happened, accounting for them: + while (headptr != tailptr) { + if (curTime >= toggleBuffer[headptr]) { + toggleState = !toggleState; + headptr++; headptr %= BUFSIZE; + } else { + // The time to deal with this one has not come yet, so we're done for now + break; + } + } + + // Now we can safely update the DAC based on the current toggleState + uint16_t v = (toggleState ? 0x1FF : 0x0); + uint8_t configbits = + (0 << 3) | // channel (A/B; A=0) + (0 << 2) | // buffered (no) + (1 << 1) | // gain (1 = 1x; 0 = 2x) + 1; // keep channel active + uint8_t b = ((configbits << 4) | (v & 0xF00)) >> 8; + uint8_t b2 = (v & 0xFF); + spi_send(b); + spi_send(b2); } void TeensySpeaker::beginMixing() diff --git a/teensy/teensy-speaker.h b/teensy/teensy-speaker.h index 3a9a669..58b83e3 100644 --- a/teensy/teensy-speaker.h +++ b/teensy/teensy-speaker.h @@ -2,23 +2,25 @@ #define __TEENSY_SPEAKER_H #include "physicalspeaker.h" +#include + +#define SAMPLERATE 8000 class TeensySpeaker : public PhysicalSpeaker { public: - TeensySpeaker(uint8_t pinNum); + TeensySpeaker(uint8_t sda, uint8_t scl); virtual ~TeensySpeaker(); - virtual void begin() {}; + virtual void begin(); virtual void toggle(uint32_t c); - virtual void maintainSpeaker(uint32_t c, uint64_t runtimeInMicros); + virtual void maintainSpeaker(); + virtual void maintainSpeaker(uint32_t c, uint64_t microseconds); virtual void beginMixing(); virtual void mixOutput(uint8_t v); private: - uint8_t speakerPin; - bool toggleState; uint32_t mixerValue; diff --git a/teensy/teensy.ino b/teensy/teensy.ino index 75031d1..405ba0d 100644 --- a/teensy/teensy.ino +++ b/teensy/teensy.ino @@ -38,6 +38,7 @@ TeensyUSB usb; int cpuThreadId; int displayThreadId; int maintenanceThreadId; +int speakerThreadId; int biosThreadId = -1; Bounce resetButtonDebouncer = Bounce(); @@ -109,7 +110,7 @@ void setup() pinMode(SPEAKERPIN, OUTPUT); // analog speaker output, used as digital volume control println("creating virtual hardware"); - g_speaker = new TeensySpeaker(SPEAKERPIN); + g_speaker = new TeensySpeaker(18, 19); // FIXME abstract constants println(" fm"); // First create the filemanager - the interface to the host file system. @@ -176,12 +177,14 @@ void setup() cpuThreadId = threads.addThread(runCPU); displayThreadId = threads.addThread(runDisplay); maintenanceThreadId = threads.addThread(runMaintenance); + speakerThreadId = threads.addThread(runSpeaker); // Set the relative priorities of the threads by defining how long a "slice" // is for each (in 100uS "ticks") // At a ratio of 50:10:1, we get about 30FPS and 100% CPU speed. threads.setTimeSlice(displayThreadId, 100); threads.setTimeSlice(cpuThreadId, 20); threads.setTimeSlice(maintenanceThreadId, 1); + threads.setTimeSlice(speakerThreadId, 3); // guessing at a good value } // FIXME: move these memory-related functions elsewhere... @@ -257,8 +260,16 @@ void biosInterrupt() g_keyboard->maintainKeyboard(); } -//bool debugState = false; -//bool debugLCDState = false; +void runSpeaker() +{ + uint32_t nextRuntime = 0; + while (1) { + if (micros() > nextRuntime) { + nextRuntime = micros() + ((float)1000000/(float)SAMPLERATE); // 125 uS per cycle @ 8k sample rate + ((TeensySpeaker *)g_speaker)->maintainSpeaker(); + } + } +} // FIXME: how often does this really need to run? We can threads.yield() when we're running too quickly void runMaintenance()