Arduino Audio Recorder - Speed Correct but Wrong Pitch - Help Please

Afternoon all,

I’m working on an Audio Recorder project on an Adafruit Adalogger w/ SAMD21 ARM-based processor utilizing the Arduino IDE. Desired sampling frequency is 10 kHz, 12-bit resolution with analog (MEMS) microphone. I’m saving data to an SD card in binary format.

The code I’ve provided below is working almost nearly exactly as desired. My only issue comes about when playing back the recorded data on my PC. The recording speed is correct if I playback at 10kHz sampling frequency, but the pitch of the sounds recorded is much too low (exactly 0.5 times what it should be). On my PC, if I playback at double the sampling frequency (20kHz), the speed is of course 2x, but the pitch is now correct. FYI pitch and speed are related quantities, when increasing one, the other should also increase linearly (i.e. increase speed 2x, pitch should be increased 2x as well).

If I record in 8-bit resolution, I have no issues, both the playback speed and pitch are as desired. The speed/pitch issue only comes about when wanting to swap to 16-bit mode. I should note as well the Adafruit only has a 12-bit ADC, however by selecting 16-bit analog read resolution, it pads the initial 4 spots with zeros. My issue keeps coming back to the way in which I’m saving the data, I think it’s a simple solution, but just can’t seem to figure out where the mismatch is coming from. Another note is that I have to set the interrupt frequency to 20kHz to get a sampling rate of 10kHz…

#include <SPI.h>
#include <SdFat.h>
#include <stdint.h>

SdFat SD;
File myFile;
#define SD_CS_PIN 4

int mic = A0;
const int btnStart = 5;
const int btnStop = 6;
const int ledStart = 12;
const int ledStop = 13;
int recPressed = 0;
int stopPressed = 0;
int flag = 0;
uint16_t buf00[512]; // buffer array 1
uint16_t buf01[512]; // buffer array 2
unsigned int bufByteCount;
unsigned long recByteCount = 0L;
unsigned long recByteSaved = 0L;
byte bufWrite;

static void   ADCsync() {
  while (ADC->STATUS.bit.SYNCBUSY == 1); //Just wait till the ADC is free
}

void setup() {
  
// ADC Setup
// ---------------------------------------------------------------------------------------------------------
  
  // Set gain and voltage reference
  ADCsync();
  ADC->INPUTCTRL.bit.GAIN = ADC_INPUTCTRL_GAIN_1X_Val;      // Gain factor select as x1
  ADC->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_INTVCC0_Val; // Internal voltage reference used

  
  // Set sample length and averaging
  ADCsync();
  ADC->AVGCTRL.reg = 0x00; //Single conversion no averaging
  ADCsync();
  ADC->SAMPCTRL.reg = 0x0A; //sample length in 1/2 CLK_ADC cycles Default is 3F
  
  // Set prescaler, resolution, and mode
  int16_t ctrlb = 0x0400; // Control register B hibyte = prescale, lobyte is resolution and mode 
  ADCsync();
  ADC->CTRLB.reg =  ctrlb;

  analogReadResolution(16); //Set bit-resoltion of ADC (12-bit max)
// ---------------------------------------------------------------------------------------------------------

// Clock timer setup used for interrupts
// ---------------------------------------------------------------------------------------------------------
   // Set up the generic clock (GCLK4) used to clock timers
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
                    GCLK_GENDIV_ID(4);            // Select Generic Clock (GCLK) 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                     GCLK_GENCTRL_GENEN |         // Enable GCLK4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                     GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Feed GCLK4 to TC4 and TC5
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TC4 and TC5
                     GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                     GCLK_CLKCTRL_ID_TC4_TC5;     // Feed the GCLK4 to TC4 and TC5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
 
  //REG_TC4_COUNT16_CC0 = 0x0087;                 // Set the TC4 CC0 register for standard audio range (22.05kHz sampling rate)
  REG_TC4_COUNT16_CC0 = 0x0095;                   // Set the TC4 CC0 register for standard audio range (20 kHz sampling rate)
  //REG_TC4_COUNT16_CC0 = 0x012B;                 // Set the TC4 CC0 register for sleep apnea range (10 kHz sampling rate)
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization

  //NVIC_DisableIRQ(TC4_IRQn);
  //NVIC_ClearPendingIRQ(TC4_IRQn);
  NVIC_SetPriority(TC4_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
  NVIC_EnableIRQ(TC4_IRQn);         // Connect TC4 to Nested Vector Interrupt Controller (NVIC)

  REG_TC4_INTFLAG |= TC_INTFLAG_OVF;              // Clear the interrupt flags
  // REG_TC4_INTENSET = TC_INTENSET_OVF;          // Enable TC4 interrupts
  // REG_TC4_INTENCLR = TC_INTENCLR_OVF;          // Disable TC4 interrupts
 
  REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV16 |     // Set prescaler to 16, 48MHz/16 = 3MHz
                   TC_CTRLA_WAVEGEN_MFRQ |        // Put the timer TC4 into match frequency (MFRQ) mode
                   TC_CTRLA_ENABLE;               // Enable TC4
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization
// ---------------------------------------------------------------------------------------------------------
  
  pinMode(10, OUTPUT); // DON'T change this - Hardware CS pin must be left as output for SD library functions to work correctly
  pinMode(ledStart, OUTPUT);
  pinMode(ledStop, OUTPUT);
  pinMode(btnStop, INPUT_PULLUP);
  pinMode(btnStart, INPUT_PULLUP);
  
  if (SD.begin(SD_CS_PIN, SPI_FULL_SPEED)) { // initialise card on SPI to 8MHz SPI bus speed
    for (int dloop = 0; dloop < 4; dloop++) {
      digitalWrite(ledStart,!digitalRead(ledStart));
      delay(100);
    }
  } else { // if error, flash LED twice per second, until reset
    while(1) {
      digitalWrite(ledStart,!digitalRead(ledStart));
      delay(500);
    }
  }
}

void loop() {
  
  if (digitalRead(btnStart) == LOW && recPressed == 0) {
    StartRec(); // launch StartRec method
  }
  if (digitalRead(btnStop) == LOW) {
    StopRec(); // launch StopRec method
  }

  if (flag == 1) {
    recByteCount++; // non-resetting sample counter
    bufByteCount++; // resetting sample counter, counts to 512 then resets to 0
  if (bufByteCount == 512 && bufWrite == 0) { 
    bufByteCount = 0;
    bufWrite = 1;
  } else if (bufByteCount == 512 & bufWrite == 1) {
    bufByteCount = 0;
    bufWrite = 0;
  }

  if (bufWrite == 0) {
    buf00[bufByteCount] = analogRead(mic);
    }
  if (bufWrite == 1) {
    buf01[bufByteCount] = analogRead(mic);
    }
    flag = 0; // clear interrupt triggered flag
    }
    
  if (recByteCount % 1024 == 512 && recPressed == 1) { myFile.write(buf00,512); recByteSaved+= 512; } // save buf01 to SD card
  if (recByteCount % 1024 == 0 && recPressed == 1) { myFile.write(buf01,512); recByteSaved+= 512; } // save buf02 to SD card
 
}

void StartRec() { // start recording method

  digitalWrite(ledStart,HIGH);
  digitalWrite(ledStop,LOW);
  recByteCount = 0;
  recByteSaved = 0;
  recPressed = 1;
  stopPressed = 0;
  myFile = SD.open("recordingvoice.bin", FILE_WRITE);
  REG_TC4_INTENSET = TC_INTENSET_OVF;

}

void StopRec() { // stop recording method
    
  REG_TC4_INTENCLR = TC_INTENCLR_OVF;
  myFile.close();
  digitalWrite(ledStart,LOW);
  digitalWrite(ledStop,HIGH);
  recPressed = 0;
  
}

void TC4_Handler() { // interrupt method
  if (TC4->COUNT16.INTFLAG.bit.OVF && TC4->COUNT16.INTENSET.bit.OVF) {

  flag = 1; // set interrupt triggered flag
  
  REG_TC4_INTFLAG = TC_INTFLAG_OVF;         // Clear the OVF interrupt flag
  }
}