Update voice to use Arcada timer
This commit is contained in:
parent
b65d82393e
commit
0c72e3ee9b
4 changed files with 38 additions and 56 deletions
|
|
@ -118,6 +118,7 @@ static inline uint16_t readBoop(void) {
|
|||
|
||||
// Crude error handler. Prints message to Serial Monitor, blinks LED.
|
||||
void fatal(const char *message, uint16_t blinkDelay) {
|
||||
Serial.begin(9600);
|
||||
Serial.println(message);
|
||||
for(bool ledState = HIGH;; ledState = !ledState) {
|
||||
digitalWrite(LED_BUILTIN, ledState);
|
||||
|
|
@ -135,13 +136,9 @@ uint32_t availableRAM(void) {
|
|||
// SETUP FUNCTION - CALLED ONCE AT PROGRAM START ---------------------------
|
||||
|
||||
void setup() {
|
||||
if (!arcada.arcadaBegin()) {
|
||||
while (1);
|
||||
}
|
||||
if(!arcada.arcadaBegin()) fatal("Arcada init fail!", 100);
|
||||
|
||||
if (!arcada.filesysBeginMSD()) {
|
||||
fatal("No filesystem found!", 100);
|
||||
}
|
||||
if(!arcada.filesysBeginMSD()) fatal("No filesystem found!", 250);
|
||||
|
||||
arcada.displayBegin();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,18 +3,15 @@
|
|||
|
||||
#if defined(ADAFRUIT_MONSTER_M4SK_EXPRESS)
|
||||
|
||||
#include "globals.h"
|
||||
#include <SPI.h>
|
||||
|
||||
#define MIN_PITCH_HZ 65
|
||||
#define MAX_PITCH_HZ 1600
|
||||
#define TYP_PITCH_HZ 175
|
||||
|
||||
// Playback timer stuff - use TC3 on MONSTER M4SK (no TC4 on this board)
|
||||
#define TIMER TC3
|
||||
#define TIMER_IRQN TC3_IRQn
|
||||
#define TIMER_IRQ_HANDLER TC3_Handler
|
||||
#define TIMER_GCLK_ID TC3_GCLK_ID
|
||||
#define TIMER_GCM_ID GCM_TC2_TC3
|
||||
static void voiceOutCallback(void);
|
||||
static float actualPlaybackRate;
|
||||
|
||||
// PDM mic allows 1.0 to 3.25 MHz max clock (2.4 typical).
|
||||
// SPI native max is is 24 MHz, so available speeds are 12, 6, 3 MHz.
|
||||
|
|
@ -150,38 +147,8 @@ bool voiceSetup(bool modEnable) {
|
|||
|
||||
analogWriteResolution(12);
|
||||
|
||||
// Feed TIMER off GCLK1 (already set to 48 MHz by Arduino core)
|
||||
GCLK->PCHCTRL[TIMER_GCLK_ID].bit.CHEN = 0; // Disable channel
|
||||
while(GCLK->PCHCTRL[TIMER_GCLK_ID].bit.CHEN); // Wait for disable
|
||||
GCLK_PCHCTRL_Type pchctrl;
|
||||
pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK1_Val;
|
||||
pchctrl.bit.CHEN = 1;
|
||||
GCLK->PCHCTRL[TIMER_GCLK_ID].reg = pchctrl.reg;
|
||||
while(!GCLK->PCHCTRL[TIMER_GCLK_ID].bit.CHEN); // Wait for enable
|
||||
|
||||
// Disable timer before configuring it
|
||||
TIMER->COUNT16.CTRLA.bit.ENABLE = 0;
|
||||
while(TIMER->COUNT16.SYNCBUSY.bit.ENABLE);
|
||||
|
||||
// 16-bit counter mode, 1:1 prescale, match-frequency generation mode
|
||||
TIMER->COUNT16.CTRLA.bit.MODE = TC_CTRLA_MODE_COUNT16;
|
||||
TIMER->COUNT16.CTRLA.bit.PRESCALER = TC_CTRLA_PRESCALER_DIV1_Val;
|
||||
TIMER->COUNT16.WAVE.bit.WAVEGEN = TC_WAVE_WAVEGEN_MFRQ_Val;
|
||||
|
||||
TIMER->COUNT16.CTRLBCLR.reg = TC_CTRLBCLR_DIR; // Count up
|
||||
while(TIMER->COUNT16.SYNCBUSY.bit.CTRLB);
|
||||
|
||||
voicePitch(1.0); // Set timer interval
|
||||
|
||||
TIMER->COUNT16.INTENSET.reg = TC_INTENSET_OVF; // Overflow interrupt
|
||||
NVIC_DisableIRQ(TIMER_IRQN);
|
||||
NVIC_ClearPendingIRQ(TIMER_IRQN);
|
||||
NVIC_SetPriority(TIMER_IRQN, 0); // Top priority
|
||||
NVIC_EnableIRQ(TIMER_IRQN);
|
||||
|
||||
TIMER->COUNT16.CTRLA.bit.ENABLE = 1; // Enable timer
|
||||
while(TIMER->COUNT16.SYNCBUSY.bit.ENABLE); // Wait for it
|
||||
|
||||
return true; // Success
|
||||
}
|
||||
|
||||
|
|
@ -196,12 +163,13 @@ bool voiceSetup(bool modEnable) {
|
|||
// adjustment (after appying constraints) will be returned.
|
||||
float voicePitch(float p) {
|
||||
float desiredPlaybackRate = sampleRate * p;
|
||||
int32_t period = (int32_t)(48000000.0 / desiredPlaybackRate + 0.5);
|
||||
if(period > 2500) period = 2500; // Hard limit is 65536, 2.5K is a practical limit
|
||||
else if(period < 250) period = 250; // Leave some cycles for IRQ handler
|
||||
TIMER->COUNT16.CC[0].reg = period - 1;
|
||||
while(TIMER->COUNT16.SYNCBUSY.bit.CC0);
|
||||
float actualPlaybackRate = 48000000.0 / (float)period;
|
||||
// Clip to sensible range
|
||||
if(desiredPlaybackRate < 19200) desiredPlaybackRate = 19200; // ~0.41X
|
||||
else if(desiredPlaybackRate > 192000) desiredPlaybackRate = 192000; // ~4.1X
|
||||
arcada.timerCallback(desiredPlaybackRate, voiceOutCallback);
|
||||
// Making this assumption here knowing Arcada will use 1:1 prescale:
|
||||
int32_t period = (int32_t)(48000000.0 / desiredPlaybackRate);
|
||||
actualPlaybackRate = 48000000.0 / (float)period;
|
||||
p = (actualPlaybackRate / sampleRate); // New pitch
|
||||
jumpThreshold = (int)(jump * p + 0.5);
|
||||
return p;
|
||||
|
|
@ -223,10 +191,8 @@ void voiceGain(float g) {
|
|||
void voiceMod(uint32_t freq, uint8_t waveform) {
|
||||
if(modBuf) { // Ignore if no modulation buffer allocated
|
||||
if(freq < MOD_MIN) freq = MOD_MIN;
|
||||
uint16_t period = TIMER->COUNT16.CC[0].reg + 1; // Audio out timer ticks
|
||||
float playbackRate = 48000000.0 / (float)period; // Audio out samples/sec
|
||||
modLen = (int)(playbackRate / freq + 0.5);
|
||||
if(modLen < 2) modLen = 2;
|
||||
modLen = (int)(actualPlaybackRate / freq + 0.5);
|
||||
if(modLen < 2) modLen = 2;
|
||||
if(waveform > 4) waveform = 4;
|
||||
modWave = waveform;
|
||||
yield();
|
||||
|
|
@ -397,9 +363,7 @@ void PDM_SERCOM_HANDLER(void) {
|
|||
evenWord ^= 1;
|
||||
}
|
||||
|
||||
// Playback timer interrupt
|
||||
void TIMER_IRQ_HANDLER(void) {
|
||||
TIMER->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF;
|
||||
static void voiceOutCallback(void) {
|
||||
|
||||
// Modulation is done on the output (rather than the input) because
|
||||
// pitch-shifting modulated input would cause weird waveform
|
||||
|
|
@ -443,7 +407,7 @@ void TIMER_IRQ_HANDLER(void) {
|
|||
} else { // Slowed down
|
||||
// Playback may underflow recording, need to advance periodically
|
||||
int16_t dist = (playbackIndex >= recIndex) ?
|
||||
(playbackIndex - recIndex) : (recBufSize - (recIndex - playbackIndex));
|
||||
(playbackIndex - recIndex) : (recBufSize - 1 - (recIndex - playbackIndex));
|
||||
if(dist <= jumpThreshold) {
|
||||
playbackIndexJumped = (playbackIndex + jump) % recBufSize;
|
||||
jumping = true;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#if 0 // Change to 1 to enable this code (enable only ONE user*.cpp!)
|
||||
|
||||
// This file provides a crude way to "drop in" user code to the eyes,
|
||||
// allowing concurrent operations without having to maintain a bunch of
|
||||
// special derivatives of the eye code (which is still undergoing a lot
|
||||
|
|
@ -63,3 +65,5 @@ void user_loop(void) {
|
|||
}
|
||||
*/
|
||||
}
|
||||
|
||||
#endif // 0
|
||||
|
|
|
|||
17
M4_Eyes/user_servo.cpp
Normal file
17
M4_Eyes/user_servo.cpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#if 0 // Change to 1 to enable this code (enable only ONE user*.cpp!)
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Servo.h>
|
||||
|
||||
Servo myservo;
|
||||
|
||||
void user_setup(void) {
|
||||
myservo.attach(3);
|
||||
}
|
||||
|
||||
void user_loop(void) {
|
||||
int pos = map(millis() % 2000, 0, 2000, 0, 180);
|
||||
myservo.write(pos);
|
||||
}
|
||||
|
||||
#endif // 0
|
||||
Loading…
Reference in a new issue