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
flag
should be declared volatile.