Update voice to use Arcada timer

This commit is contained in:
Phillip Burgess 2019-10-04 20:25:49 -07:00
parent b65d82393e
commit 0c72e3ee9b
4 changed files with 38 additions and 56 deletions

View file

@ -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();

View file

@ -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;

View file

@ -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
View 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