2

I'm working on an audio recorder project on an Adafruit Adalogger with SAMD21 ARM-based processor using the Arduino IDE. The desired sampling frequency is 10 kHz, 12-bit resolution with an 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 10 kHz sampling frequency, but the pitch of the sound recorded is much too low (exactly 0.5 times what it should be). On my PC, if I playback at 20 kHz, 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 saving in 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 four 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.

#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++; // increment sample counter
    bufByteCount++;
    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
  }
}

I'm using a MATLAB script I've written to playback the data, notably v.2012 since it provides the "specgramdemo" function which I'm utilizing to visualize the spectrogram and also, but less importantly, provides playback functionality of the recording.

close all
clear all
clc

[inStr, pathStr] = uigetfile('*.bin','input files','D:');
A2 = inStr;
fid = fopen([pathStr, inStr], 'r');
% [filename, permission, machineformat] = fopen(fid)
sixteen_bit_pad = fread(fid, inf, 'uint16', 'ieee-le');
fclose(fid);
sixteen_bit_pad = sixteen_bit_pad';

% prompt = {'Enter sampling frequency for recording in Hz:'};
% dlg_title = 'Sampling Frequency';
% num_lines = 1;
% defaultans = {'10000'};

% Fs = str2double(inputdlg(prompt,dlg_title,num_lines,defaultans));
Fs = 20000;

twelve_bit = zeros(size(sixteen_bit_pad));
twelve_bit(1:1:end) = bitshift(sixteen_bit_pad(1:1:end),-4);

time = linspace(0, length(twelve_bit)/Fs, length(twelve_bit));

figure, plot(time, twelve_bit)
xlabel('Time (seconds)'); ylabel('Amplitude (12-bit Value)');
specgramdemo(twelve_bit, Fs)

If you happen to have these tools at your disposal and want to hear first-hand the mismatch, I've included a .bin file that has some voice/breathing sounds recorded. File here: Binary File

5
  • How many bytes/second are in the recorded file? eg. if you record for 10 seconds what is the file size / 10? flag should be declared volatile. Commented Apr 10, 2017 at 4:39
  • 1
    What are you using to play back the audio on the PC? Are you remembering to tell it about the difference between files containing 8-bit samples and files containing 16-bit samples? It sounds like you're trying to play back the 16-bit files with software that is expecting 8-bit samples. Are you also getting a lot of distortion on loud sounds?
    – Dave Tweed
    Commented Apr 10, 2017 at 11:17
  • @BruceAbbott Thanks Bruce, noted and will update my code to reflect such! By no means am I a programmer, still learning as I go and appreciate the quick tip. For a duration of 10.2016 seconds, I've recorded 204,032 samples. With each sample being 2 bytes length, total of 408,064 bytes. 408,064/10.2016 gives 40,000 bytes/second.
    – vq35dett01
    Commented Apr 10, 2017 at 15:20
  • @DaveTweed, I'm using a MATLAB script I've written to playback the data, notably v.2012 since it provides the "specgramdemo" function which I'm utilizing to visualize the spectrogram and also, but less importantly, provides playback functionality of the recording. I've updated my original post to include the script. I do get some distortion yes, some "clicking" sounds in the recording and saturation of the signal with high amplitude sounds.
    – vq35dett01
    Commented Apr 10, 2017 at 15:22
  • 1
    "For a duration of 10.2016 seconds, I've recorded 204,032 samples" - you have twice as many samples as you should have, which suggests that you are recording 2 samples per interrupt - which would mean the flag is not working as expected. Commented Apr 10, 2017 at 20:31

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.