mirror of https://github.com/JorjBauer/aiie.git
working on speaker conversion
This commit is contained in:
parent
e17fb71d3d
commit
27e023f8ea
|
@ -1,13 +1,24 @@
|
|||
#include <Arduino.h>
|
||||
#include <softspi.h>
|
||||
#include <TeensyThreads.h>
|
||||
#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()
|
||||
|
|
|
@ -2,23 +2,25 @@
|
|||
#define __TEENSY_SPEAKER_H
|
||||
|
||||
#include "physicalspeaker.h"
|
||||
#include <MCP492X.h>
|
||||
|
||||
#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;
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue