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:
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.
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).
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.
#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.
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..
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;
}
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();
}
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