How to refer to the built in microphone?

I am pretty new to Arduino and have this question:

I try to make an audio recorder with this library on an Arduino Nano 33 BLE Sense: https://github.com/TMRh20/AutoAnalogAudio

Based on the example sketch called "SdAudioRecording", how can I make this work to use the built in microphone instead of expecting an external microphone on pin A0?

Current serial monitor output:
After starting a recording:

Start Recording: /test.wav

After stopping a recording:

File contains no data, exiting
Recording Stopped

I suggest to check whether Arduino LLC has provided example code and a library to access the built-in microphone on the Nano 33 BLE Sense, and start from there.

Thanks for your reply!
I think that is this library: https://docs.arduino.cc/learn/built-in-libraries/pdm/

This library looks very spartan without the possibility to save audio on SD. That's why I looked for other libraries...

The library shows you how to access the microphone.

You will have to add the code to write to SD.

Ok I am going to try that path, thanks again.

Keep in mind that writing to SD is slow, and may not be able to keep up with the microphone.

It is very difficult to optimize code to achieve continuous, gap free recording of audio signals at a high rate. Double buffering is required and the microphone buffer size must be exact multiples of the SD block size (usually 512 bytes).

In my case, I only want to take samples from max a few seconds so that could maybe help.

My thinking:

  1. Create array with samples during recording
  2. Convert to wav. eg: c++ - WAV file from captured PCM sample data - Stack Overflow
  3. Write wav to SD. Will try to use some of this code: Simple Arduino Voice Recorder for Spy Bug Voice Recording

Hmm, I am struggling with how to get an array with recorded audio. The PDM documentation is very briefly, but apparently PDM.read() dumps recorded data in a buffer array. By testing I find that it doesn't add it to the buffer, but dumps it every time to the start of the array.

I am trying to get an array audioSample[160000], that contains 10 seconds of audio. (the board has 256k RAM so I hope this is possible). At the moment I am first testing the principle with a smaller array.

This is the relevant piece of code:

void recordAudio(){
  Serial.println("try to record audio");

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

  // initialize PDM
  if (!PDM.begin(1, sampleRate)) {
    Serial.println("Failed to start PDM!");
    while (1);
  }
  delay(1000);
  PDM.end();
  processAudio();
  audioSamplesRead = 0;
}

void onPDMdata() {
  // query the number of bytes available
  int bytesAvailable = PDM.available();
  
  // read into the sample buffer
  PDM.read(audioSample, bytesAvailable);

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

Due to the linear process in C++ It looks like when the audio recording function is running, I can't run another function to collect the audio buffer to the audio sample if needed. In the above code I removed the buffer step, ultimately PDM.read should write to a buffer.

Any ideas how to approach this? While the project evolved, I now prefer not to use an SD-card but process the audio directly and remove it.

Please post an entire sketch that demonstrates what you're doing. Too much context is missing from the snippet you posted to make sense of it.

What does this mean, exactly?

What kind of processing needs to take place? And what does 'directly' mean - after collecting, say, 10 seconds of audio, or in real time?

Thanks @rsmls

This is the entire sketch until now:

#include <PDM.h>
#include <Arduino_HS300x.h>

int sampleRate = 16000;

volatile int audioSamplesRead;
int audioSample[32000];


float temperature;
float humidity;

void setup() {
  Serial.begin(9600);
  while (!Serial);
  PDM.setBufferSize(1024);

  while (!Serial);

  if (!HS300x.begin()) {
    Serial.println("Failed to initialize humidity temperature sensor!");
    while (1);
  }
}

void loop() {
  Serial.println("run loop");
  temperature = HS300x.readTemperature();
  humidity    = HS300x.readHumidity();
  recordAudio();
  delay(99999999);
}

void recordAudio(){
  Serial.println("try to record audio");

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

  // initialize PDM
  if (!PDM.begin(1, sampleRate)) {
    Serial.println("Failed to start PDM!");
    while (1);
  }
  delay(1000);
  PDM.end();
  processAudio();
  audioSamplesRead = 0;
}

void onPDMdata() {
  // query the number of bytes available
  int bytesAvailable = PDM.available();
  
  // read into the sample buffer
  PDM.read(audioSample, bytesAvailable);

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

void processAudio(){
  sendValues();
}

void sendValues(){
  Serial.print("Temperature: ");
  Serial.println(temperature);
  Serial.print("Humidity: ");
  Serial.println(humidity);
  Serial.println("Audio: ");
  for (int i = 0; i < audioSamplesRead;i++){
    Serial.print("Iteration ");
    Serial.print(i);
    Serial.print(": ");
    Serial.println(audioSample[i]);
  }
  Serial.println();
}

About the lineair process: when you run this program, you will only see "Loop One" in the Serial Monitor.

loopOne();
loopTwo();

int loopOne(){
  Serial.println("Loop One");
  loopOne();
}

int loopTwo(){
  Serial.println("Loop Two");
  loopTwo();
}

The audio processes that will take place is extracting the features to make a compact summary of the audio to send it. This can take place after the recording is finished.

Oh I see that I made a mistake in sendValues().

This code does already more:

void sendValues(){
  Serial.print("Temperature: ");
  Serial.println(temperature);
  Serial.print("Humidity: ");
  Serial.println(humidity);
  Serial.println("Audio: ");
  for (int i = 0; i < sizeof(audioSample);i++){
    Serial.print("Iteration ");
    Serial.print(i);
    Serial.print(": ");
    Serial.println(audioSample[i]);
  }
  Serial.println();
}

I will get back, one moment.

The problem is that PDM stands for Pulse Density Modulation, it is nothing like a normal audio sample, which is why your code is failing.

Here is an example of how to visualise the signal on the serial monitor. Please use Arduino OS 1.8.19 as the OS 2.x has a rubbishly small size display on the serial plotter.

/*
  This example reads audio data from the on-board PDM microphones, and prints
  out the samples to the Serial console. The Serial Plotter built into the
  Arduino IDE can be used to plot the audio data (Tools -> Serial Plotter)

  Circuit:
  - Arduino Nano 33 BLE board, or
  - Arduino Nano RP2040 Connect, or
  - Arduino Portenta H7 board plus Portenta Vision Shield

  This example code is in the public domain.
  hacked by Mike Cook to show blank when input is below 300
*/

#include <PDM.h>

// default number of output channels
static const char channels = 1;

// default PCM output frequency
static const int frequency = 16000;

// Buffer to read samples into, each sample is 16-bits
short sampleBuffer[512];

// Number of audio samples read
volatile int samplesRead;

void setup() {
  Serial.begin(9600);
  while (!Serial);

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

  // Optionally set the gain
  // Defaults to 20 on the BLE Sense and 24 on the Portenta Vision Shield
  // PDM.setGain(30);

  // Initialize PDM with:
  // - one channel (mono mode)
  // - a 16 kHz sample rate for the Arduino Nano 33 BLE Sense
  // - a 32 kHz or 64 kHz sample rate for the Arduino Portenta Vision Shield
  if (!PDM.begin(channels, frequency)) {
    Serial.println("Failed to start PDM!");
    while (1);
  }
}

void loop() {
  // Wait for samples to be read
  if (samplesRead) {

    // Print samples to the serial monitor or plotter
    if(sampleBuffer[0] > 300) {
    for (int i = 0; i < samplesRead; i++) {
      if(channels == 2) {
        Serial.print("L:");
        Serial.print(sampleBuffer[i]);
        Serial.print(" R:");
        i++;
      }
      Serial.println(sampleBuffer[i]);
     }
    }
    // Clear the read count
    samplesRead = 0;
  }
}

/**
 * Callback function to process the data from the PDM microphone.
 * NOTE: This callback is executed as part of an ISR.
 * Therefore using `Serial` to print messages inside this function isn't supported.
 * */
void onPDMdata() {
  // Query the number of available bytes
  int bytesAvailable = PDM.available();

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

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

@Grumpy_Mike I saw that example indeed, thanks. But what if I want to save the data in an array?
interesting comment about it being PDM. So that sounds like a hassle to convert that to wav data? Sounds like I am not on the right track with this PDM module..

Here is an example of the output, don't know yet wat to do with that :thinking:

Iteration 12081: 0
Iteration 12082: 0
Iteration 12083: 0
Iteration 12084: 0
Iteration 12085: 7624
Iteration 12086: 0
Iteration 12087: 0
Iteration 12088: 0
Iteration 12089: 0
Iteration 12090: 0
Iteration 12091: 0
Iteration 12092: 0
Iteration 12093: 0
Iteration 12094: 0
Iteration 12095: 7624
Iteration 12096: 7624
Iteration 12097: 0
Iteration 12098: 0
Iteration 12099: 536956244
Iteration 12100: 0
Iteration 12101: -497406299
Iteration 12102: -858993460
Iteration 12103: -858993460
Iteration 12104: -858993460
Iteration 12105: -858993460

Unsurprisingly:

The data already are in an array, internal to the PDM microphone library. You just need to copy them elsewhere.

Here is test code I wrote for the Adafruit Clue, which saves a short audio recording in SPI flash memory (acting like an SD card).

//working 4/6/2024
// upped gain to 42 (default was 20, too quiet)
/*
  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>

Adafruit_Arcada arcada;
#include <PDM.h>
#define SAMPLES 1024

// buffer for audio samples, each sample is 16-bits
// setting a larger buffer avoids dropping audio samples
// writes to flash are slow!

int16_t sampleBuffer[SAMPLES];

// number of samples read
volatile int samplesRead;

File file;

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

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


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

  file = arcada.open("/audio.dat", O_CREAT | O_WRITE);
  if (!file) {
    Serial.println("\r output file open failure");
    while (1) yield();
  }
  PDM.setBufferSize(2048);  //bytes!
  // initialize PDM with:
  // - one channel (mono mode)
  // - a 16 kHz sample rate
  if (!PDM.begin(1, 16000)) {
    Serial.println("Failed to start PDM!");
    while (1) yield();
  }
  // optionally set the gain, defaults to 20
  PDM.setGain(42);
  Serial.println("recording");
}
int nframes = 250;

void loop() {
  // wait for samples to be read
  if (samplesRead) {
    int bytes_written = file.write((char *)sampleBuffer, 2*SAMPLES);
    //    Serial.println(nframes);
    nframes--;
    if (nframes == 0 || bytes_written < 2*SAMPLES) { //done, or out of space on filesys
      file.close();
      Serial.println("stopped");
      arcada.filesysListFiles();
      Serial.flush();
      while (1) yield();
    }
    samplesRead = 0;
  }
}

void onPDMdata() { //callback
  // 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;
}

Thanks for sharing, I am working on it now with this as a start, and will post any outcome.

That is another Sketch, I meant the fact that you can't run two looping functions the same time. I think that is solved by checking the recorded time in a while loop, something like this:

void recordAudio(){

  // initialize PDM
  if (!PDM.begin(1, sampleRate)) {
    Serial.println("Failed to start PDM!");
    while (1);
  }
  delay(50); // To make shure the buffer is filled when the while loop starts

  while(audioSampleRead < 16000){
    if (samplesRead) {
      // Do stuff
    }
  }
  PDM.end();
  audioSamplesRead = 0;
  processAudio();
}

Check out Atomic14 video on youtube. He has code for the project on github. He uses an esp32 though. Record and Playback Audio

Yeah, something like that. Not sure if that particular approach you posted is such a great idea, but it might work. In general terms, you might look into the concept of a 'state machine'. There's a particularly useful (IMO) example here: State Machines and Arduino Implementation – Norwegian Creations