PDM microphone sampling errors (audio recorder)

For CircuitPython issues, ask in the Adafruit CircuitPython forum.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Post Reply
User avatar
sj_remington
 
Posts: 879
Joined: Mon Jul 27, 2020 4:51 pm

PDM microphone sampling errors (audio recorder)

Post by sj_remington »

I am experimenting with code to create an audio recorder for short clips, using the Clue PDM mic and the on board QSPI flash file system, and have run into two problems that appear to be associated with the NRF52840 PDM library. The code (Arduino IDE 1.8.19) is posted below, and works pretty well for voice recordings (64 seconds maximum, given the small QSPI flash size).

1. The microphone gain setting does not seem to do anything, so recorded sound levels are at the default gain, and very low. Unfortunately, using sample rate 16 kHz, the Clue processor clock rate appears to be too slow to permit scaling up each individual sample buffer, while also sampling and writing to the file.

2. Occasional sampling errors (1 or 2 audio samples dropped) cause audible glitches when recording a pure tone, which is very annoying. The error is periodic, but rare enough that I have not been able to determine the frequency. I suspect that this is a beat phenomenon with the audio sample rate and the microphone clock.

I'm wondering if anyone has encountered this glitch with other nrf52840 PDM microphone setups, as I have no other to test.

Raw, recorded data across the glitch at sample 34048
>34042, -1975
>34043, -1685
>34044, -1340
>34045, -965
>34046, -565
>34047, -140
>34048, 1915 <
>34049, 2155
Screen shot from Audacity showing recorded 440 Hz signal:
Capture.PNG
Capture.PNG (45.08 KiB) Viewed 225 times
Recorder code. This code writes a raw data file, which I convert to .wav on the PC using a standalone program.

Code: Select all

//working 11/6/2023, except gain too low, glitches when recording 440 Hz tone

/*
  This example reads audio data from the on-board PDM microphone
  and saves to a QSPI flash file audio.dat
  // 2Mb flash = 2097152 bytes, 4096 512 byte blocks
*/

#include <Adafruit_Arcada.h>
uint32_t buttons, last_buttons;

Adafruit_Arcada arcada;
#include <PDM.h>

// buffer for samples, each sample is 16-bits
int16_t sampleBuffer[256];
// number of samples read
volatile int samplesRead;

File file;
char outputFile[15] = {0};

void setup() {
  //  Serial.begin(115200);
  //  while (!Serial) yield();

  // configure the data receive callback
  PDM.onReceive(onPDMdata);

  // optionally set the gain, defaults to 20 (increasing gain makes no difference, sjr)
  // PDM.setGain(100);  // tried 30, 50, 100

  if (!arcada.arcadaBegin()) {
    while (1) yield();
  }
  //Arcada_FilesystemType
  arcada.filesysBegin(ARCADA_FILESYS_QSPI);

  // Start TFT and fill black
  arcada.displayBegin();

  // Turn on backlight
  arcada.setBacklight(255);
  arcada.display->setTextWrap(false);
  arcada.display->fillScreen(ARCADA_BLACK);
  arcada.display->setTextColor(ARCADA_GREEN);
  arcada.display->setTextSize(2);
  arcada.display->println("Audio Recorder");

  // initialize PDM with:
  // - one channel (mono mode)
  // - a 16 kHz sample rate
  if (!PDM.begin(1, 16000)) {
    arcada.display->println("PDM failure");
    while (1) yield();
  }

  arcada.display->println("A start/stop B end");
  delay(300); //wait for microphone to settle
}
int nframes = 0; //frame count, max 4000 on QSPI flash
int filenum = 0; //file number
int recording = 0; //run/stop mode
void loop() {

  buttons = arcada.variantReadButtons();
  if (buttons != last_buttons) {
    last_buttons = buttons;
    
    if (buttons & ARCADA_BUTTONMASK_B) recording = -1;
    
    if (buttons & ARCADA_BUTTONMASK_A) {
      recording = 1;
      snprintf(outputFile, sizeof(outputFile), "/audio%02d.dat", filenum); //generate a name
      file = arcada.open(outputFile, O_CREAT | O_WRITE);
      if (!file) {
        arcada.display->println("output file open failure");
        while (1) yield();
      }
      else { //display output file name
        arcada.display->println(outputFile);
        delay(250); //skip button clicks
      }
    } //button A pressed
  } //buttons

  //      int x, avg = 0;
  while (recording > 0) {
    // wait for samples to be read
    if (samplesRead) {
      //for(int i=0; i<samplesRead; i++) sampleBuffer[i] <<=3;  //tried scaling, abandoned because blocks are skipped
      file.write((char *)sampleBuffer, 512);
      nframes++;
      samplesRead = 0;
    }//samples read

    if (nframes > 4000) recording = -1; //out of space on QSPI flash

    // check buttons for stop
    buttons = arcada.variantReadButtons();
    if (buttons != last_buttons) {
      last_buttons = buttons;
      if (buttons & ARCADA_BUTTONMASK_A) { //stop and close file
        recording = 0;
        file.close();
        arcada.display->println(nframes);
        filenum++; //next file name
      }
    } //buttons changed
  } // while recording > 0

  if (recording < 0) { //end session and expose QSPI flash to host
    arcada.display->println("stopped");
    delay(10);
    arcada.filesysBeginMSD(ARCADA_FILESYS_QSPI); //expose QSPI flash as drive
    while (1) yield();
  }
} //loop

void onPDMdata() {
  // query the number of bytes available
  int bytesAvailable = PDM.available();

  // read into the sample buffer
  PDM.read(sampleBuffer, bytesAvailable);

  // 16-bit, 2 bytes per sample
  samplesRead = bytesAvailable / 2;
}

User avatar
sj_remington
 
Posts: 879
Joined: Mon Jul 27, 2020 4:51 pm

Re: PDM microphone sampling errors (audio recorder)

Post by sj_remington »

I forgot to add that the noticeable glitches are often, if not always at a sample buffer boundary.

Three successive examples occur at sample numbers 34048, 36096 and 38144 in the audio data shown above, or at ends of data blocks 133, 141 and 149 (a separation of 8x256 samples in each case).

It seems possible that at least some of the missing samples are lost during the double buffer swap in PDM.cpp, possibly as a result of an interrupt from some other event, or a short delay during writing to the QSPI flash file.

Suggestions appreciated.

User avatar
sj_remington
 
Posts: 879
Joined: Mon Jul 27, 2020 4:51 pm

Re: PDM microphone sampling errors (audio recorder)

Post by sj_remington »

I modified the code to access the microphone buffer address, and write the data directly to the QSPI file (skipping the copy to local buffer).

No change, glitches are still there. The problem is definitely in the library code.

Revised code (note the new object "PDMDoubleBuffer db")

Code: Select all

//working 11/6/2023, except gain too low, glitches when recording 440 Hz tone

/*
  This example reads audio data from the on-board PDM microphone
  and saves to a QSPI flash file audio.dat
  // 2Mb flash = 2097152 bytes, 4096 512 byte blocks
*/

#include <Adafruit_Arcada.h>
uint32_t buttons, last_buttons;

Adafruit_Arcada arcada;
#include <PDM.h>

#include "utility/PDMDoubleBuffer.h"
PDMDoubleBuffer db;

// buffer for samples, each sample is 16-bits
int16_t sampleBuffer[256];
// number of samples read
volatile int samplesRead;

File file;
char outputFile[15] = {0};

void setup() {
  //  Serial.begin(115200);
  //  while (!Serial) yield();

  // configure the data receive callback
  PDM.onReceive(onPDMdata);

  // optionally set the gain, defaults to 20 (increasing gain makes no difference, sjr)
  // PDM.setGain(100);  // tried 30, 50, 100

  if (!arcada.arcadaBegin()) {
    while (1) yield();
  }
  //Arcada_FilesystemType
  arcada.filesysBegin(ARCADA_FILESYS_QSPI);

  // Start TFT and fill black
  arcada.displayBegin();

  // Turn on backlight
  arcada.setBacklight(255);
  arcada.display->setTextWrap(false);
  arcada.display->fillScreen(ARCADA_BLACK);
  arcada.display->setTextColor(ARCADA_GREEN);
  arcada.display->setTextSize(2);
  arcada.display->println("Audio Recorder");

  // initialize PDM with:
  // - one channel (mono mode)
  // - a 16 kHz sample rate
  if (!PDM.begin(1, 16000)) {
    arcada.display->println("PDM failure");
    while (1) yield();
  }

  arcada.display->println("A start/stop B end");
  delay(300); //wait for microphone to settle
}
int nframes = 0; //frame count, max 4000 on QSPI flash
int filenum = 0; //file number
int recording = 0; //run/stop mode
void loop() {

  buttons = arcada.variantReadButtons();
  if (buttons != last_buttons) {
    last_buttons = buttons;

    if (buttons & ARCADA_BUTTONMASK_B) recording = -1;

    if (buttons & ARCADA_BUTTONMASK_A) {
      recording = 1;
      snprintf(outputFile, sizeof(outputFile), "/audio%02d.dat", filenum); //generate a name
      file = arcada.open(outputFile, O_CREAT | O_WRITE);
      if (!file) {
        arcada.display->println("output file open failure");
        while (1) yield();
      }
      else { //display output file name
        arcada.display->println(outputFile);
        delay(250); //skip button clicks
      }
    } //button A pressed
  } //buttons

  //      int x, avg = 0;
  while (recording > 0) {
    // wait for samples to be read
    if (samplesRead) {
      //file.write((char *)sampleBuffer, 512);  //<<<skip this, buffer written in callback
      nframes++;
      samplesRead = 0;
    }//samples read

    if (nframes > 4000) recording = -1; //out of space on QSPI flash

    // check buttons for stop
    buttons = arcada.variantReadButtons();
    if (buttons != last_buttons) {
      last_buttons = buttons;
      if (buttons & ARCADA_BUTTONMASK_A) { //stop and close file
        recording = 0;
        file.close();
        arcada.display->println(nframes);
        filenum++; //next file name
      }
    } //buttons changed
  } // while recording > 0

  if (recording < 0) { //end session and expose QSPI flash to host
    arcada.display->println("stopped");
    delay(10);
    arcada.filesysBeginMSD(ARCADA_FILESYS_QSPI); //expose QSPI flash as drive
    while (1) yield();
  }
} //loop

void onPDMdata() {
  // query the number of bytes available
  int bytesAvailable = PDM.available();

  // read into the sample buffer
  // PDM.read(sampleBuffer, bytesAvailable);
  file.write((char *)db.data(), bytesAvailable);  //directly access the microphone buffer, write to QSPI
  // 16-bit, 2 bytes per sample
  samplesRead = bytesAvailable / 2;
}

User avatar
sj_remington
 
Posts: 879
Joined: Mon Jul 27, 2020 4:51 pm

Re: PDM microphone sampling errors (audio recorder)

Post by sj_remington »

I now see that Adafruit adapted the Arduino PDM library. So I've posted an issue here: https://github.com/victorromeo/ArduinoPDM

Interestingly, the Arduino repository has a note explaining why setting the gain doesn't seem to work: the gain setting has to be performed AFTER calling the PDM.begin() method.

Unfortunately, when trying to use the original Arduino library (which has more options), I run into a series of compile errors like the following, which suggests that a data type is being confused with a pointer.

If someone knows how to fix those, I would appreciate input. Otherwise, I'll carry on.
In file included from C:\Users\Jim\Desktop\Clue\PDM_fileB2\src\PDM.cpp:24:

C:\Users\Jim\AppData\Local\Arduino15\packages\adafruit\hardware\nrf52\1.5.0\cores\nRF5/nordic/nrfx/hal/nrf_pdm.h:521:57: note: initializing argument 1 of 'void nrf_pdm_clock_set(NRF_PDM_Type*, nrf_pdm_freq_t)'

521 | NRF_STATIC_INLINE void nrf_pdm_clock_set(NRF_PDM_Type * p_reg, nrf_pdm_freq_t pdm_freq)

| ~~~~~~~~~~~~~~~^~~~~

C:\Users\Jim\Desktop\Clue\PDM_fileB2\src\PDM.cpp:29:29: error: cannot convert 'nrf_pdm_freq_t' to 'NRF_PDM_Type*'

Post Reply
Please be positive and constructive with your questions and comments.

Return to “CLUE Board”