Skip to content

Commit 0c72e3e

Browse files
Update voice to use Arcada timer
1 parent b65d823 commit 0c72e3e

4 files changed

Lines changed: 38 additions & 56 deletions

File tree

M4_Eyes/M4_Eyes.ino

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ static inline uint16_t readBoop(void) {
118118

119119
// Crude error handler. Prints message to Serial Monitor, blinks LED.
120120
void fatal(const char *message, uint16_t blinkDelay) {
121+
Serial.begin(9600);
121122
Serial.println(message);
122123
for(bool ledState = HIGH;; ledState = !ledState) {
123124
digitalWrite(LED_BUILTIN, ledState);
@@ -135,13 +136,9 @@ uint32_t availableRAM(void) {
135136
// SETUP FUNCTION - CALLED ONCE AT PROGRAM START ---------------------------
136137

137138
void setup() {
138-
if (!arcada.arcadaBegin()) {
139-
while (1);
140-
}
139+
if(!arcada.arcadaBegin()) fatal("Arcada init fail!", 100);
141140

142-
if (!arcada.filesysBeginMSD()) {
143-
fatal("No filesystem found!", 100);
144-
}
141+
if(!arcada.filesysBeginMSD()) fatal("No filesystem found!", 250);
145142

146143
arcada.displayBegin();
147144

M4_Eyes/pdmvoice.cpp

Lines changed: 14 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,15 @@
33

44
#if defined(ADAFRUIT_MONSTER_M4SK_EXPRESS)
55

6+
#include "globals.h"
67
#include <SPI.h>
78

89
#define MIN_PITCH_HZ 65
910
#define MAX_PITCH_HZ 1600
1011
#define TYP_PITCH_HZ 175
1112

12-
// Playback timer stuff - use TC3 on MONSTER M4SK (no TC4 on this board)
13-
#define TIMER TC3
14-
#define TIMER_IRQN TC3_IRQn
15-
#define TIMER_IRQ_HANDLER TC3_Handler
16-
#define TIMER_GCLK_ID TC3_GCLK_ID
17-
#define TIMER_GCM_ID GCM_TC2_TC3
13+
static void voiceOutCallback(void);
14+
static float actualPlaybackRate;
1815

1916
// PDM mic allows 1.0 to 3.25 MHz max clock (2.4 typical).
2017
// SPI native max is is 24 MHz, so available speeds are 12, 6, 3 MHz.
@@ -150,38 +147,8 @@ bool voiceSetup(bool modEnable) {
150147

151148
analogWriteResolution(12);
152149

153-
// Feed TIMER off GCLK1 (already set to 48 MHz by Arduino core)
154-
GCLK->PCHCTRL[TIMER_GCLK_ID].bit.CHEN = 0; // Disable channel
155-
while(GCLK->PCHCTRL[TIMER_GCLK_ID].bit.CHEN); // Wait for disable
156-
GCLK_PCHCTRL_Type pchctrl;
157-
pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK1_Val;
158-
pchctrl.bit.CHEN = 1;
159-
GCLK->PCHCTRL[TIMER_GCLK_ID].reg = pchctrl.reg;
160-
while(!GCLK->PCHCTRL[TIMER_GCLK_ID].bit.CHEN); // Wait for enable
161-
162-
// Disable timer before configuring it
163-
TIMER->COUNT16.CTRLA.bit.ENABLE = 0;
164-
while(TIMER->COUNT16.SYNCBUSY.bit.ENABLE);
165-
166-
// 16-bit counter mode, 1:1 prescale, match-frequency generation mode
167-
TIMER->COUNT16.CTRLA.bit.MODE = TC_CTRLA_MODE_COUNT16;
168-
TIMER->COUNT16.CTRLA.bit.PRESCALER = TC_CTRLA_PRESCALER_DIV1_Val;
169-
TIMER->COUNT16.WAVE.bit.WAVEGEN = TC_WAVE_WAVEGEN_MFRQ_Val;
170-
171-
TIMER->COUNT16.CTRLBCLR.reg = TC_CTRLBCLR_DIR; // Count up
172-
while(TIMER->COUNT16.SYNCBUSY.bit.CTRLB);
173-
174150
voicePitch(1.0); // Set timer interval
175151

176-
TIMER->COUNT16.INTENSET.reg = TC_INTENSET_OVF; // Overflow interrupt
177-
NVIC_DisableIRQ(TIMER_IRQN);
178-
NVIC_ClearPendingIRQ(TIMER_IRQN);
179-
NVIC_SetPriority(TIMER_IRQN, 0); // Top priority
180-
NVIC_EnableIRQ(TIMER_IRQN);
181-
182-
TIMER->COUNT16.CTRLA.bit.ENABLE = 1; // Enable timer
183-
while(TIMER->COUNT16.SYNCBUSY.bit.ENABLE); // Wait for it
184-
185152
return true; // Success
186153
}
187154

@@ -196,12 +163,13 @@ bool voiceSetup(bool modEnable) {
196163
// adjustment (after appying constraints) will be returned.
197164
float voicePitch(float p) {
198165
float desiredPlaybackRate = sampleRate * p;
199-
int32_t period = (int32_t)(48000000.0 / desiredPlaybackRate + 0.5);
200-
if(period > 2500) period = 2500; // Hard limit is 65536, 2.5K is a practical limit
201-
else if(period < 250) period = 250; // Leave some cycles for IRQ handler
202-
TIMER->COUNT16.CC[0].reg = period - 1;
203-
while(TIMER->COUNT16.SYNCBUSY.bit.CC0);
204-
float actualPlaybackRate = 48000000.0 / (float)period;
166+
// Clip to sensible range
167+
if(desiredPlaybackRate < 19200) desiredPlaybackRate = 19200; // ~0.41X
168+
else if(desiredPlaybackRate > 192000) desiredPlaybackRate = 192000; // ~4.1X
169+
arcada.timerCallback(desiredPlaybackRate, voiceOutCallback);
170+
// Making this assumption here knowing Arcada will use 1:1 prescale:
171+
int32_t period = (int32_t)(48000000.0 / desiredPlaybackRate);
172+
actualPlaybackRate = 48000000.0 / (float)period;
205173
p = (actualPlaybackRate / sampleRate); // New pitch
206174
jumpThreshold = (int)(jump * p + 0.5);
207175
return p;
@@ -223,10 +191,8 @@ void voiceGain(float g) {
223191
void voiceMod(uint32_t freq, uint8_t waveform) {
224192
if(modBuf) { // Ignore if no modulation buffer allocated
225193
if(freq < MOD_MIN) freq = MOD_MIN;
226-
uint16_t period = TIMER->COUNT16.CC[0].reg + 1; // Audio out timer ticks
227-
float playbackRate = 48000000.0 / (float)period; // Audio out samples/sec
228-
modLen = (int)(playbackRate / freq + 0.5);
229-
if(modLen < 2) modLen = 2;
194+
modLen = (int)(actualPlaybackRate / freq + 0.5);
195+
if(modLen < 2) modLen = 2;
230196
if(waveform > 4) waveform = 4;
231197
modWave = waveform;
232198
yield();
@@ -397,9 +363,7 @@ void PDM_SERCOM_HANDLER(void) {
397363
evenWord ^= 1;
398364
}
399365

400-
// Playback timer interrupt
401-
void TIMER_IRQ_HANDLER(void) {
402-
TIMER->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF;
366+
static void voiceOutCallback(void) {
403367

404368
// Modulation is done on the output (rather than the input) because
405369
// pitch-shifting modulated input would cause weird waveform
@@ -443,7 +407,7 @@ void TIMER_IRQ_HANDLER(void) {
443407
} else { // Slowed down
444408
// Playback may underflow recording, need to advance periodically
445409
int16_t dist = (playbackIndex >= recIndex) ?
446-
(playbackIndex - recIndex) : (recBufSize - (recIndex - playbackIndex));
410+
(playbackIndex - recIndex) : (recBufSize - 1 - (recIndex - playbackIndex));
447411
if(dist <= jumpThreshold) {
448412
playbackIndexJumped = (playbackIndex + jump) % recBufSize;
449413
jumping = true;

M4_Eyes/user.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#if 0 // Change to 1 to enable this code (enable only ONE user*.cpp!)
2+
13
// This file provides a crude way to "drop in" user code to the eyes,
24
// allowing concurrent operations without having to maintain a bunch of
35
// special derivatives of the eye code (which is still undergoing a lot
@@ -63,3 +65,5 @@ void user_loop(void) {
6365
}
6466
*/
6567
}
68+
69+
#endif // 0

M4_Eyes/user_servo.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#if 0 // Change to 1 to enable this code (enable only ONE user*.cpp!)
2+
3+
#include <Arduino.h>
4+
#include <Servo.h>
5+
6+
Servo myservo;
7+
8+
void user_setup(void) {
9+
myservo.attach(3);
10+
}
11+
12+
void user_loop(void) {
13+
int pos = map(millis() % 2000, 0, 2000, 0, 180);
14+
myservo.write(pos);
15+
}
16+
17+
#endif // 0

0 commit comments

Comments
 (0)