I2S conflict with Cyberon 'voice recognition' model

Hi, I am trying to play audio from my Arduino nano rp2040 running on mbed_os architecture. I found the library https://github.com/pschatzmann/arduino-audio-tools/tree/main

I found the .h file named "src/AudioI2S/I2SRP2040-MBED.h"

I thought that maybe this would be compatible with the board. I am compiling in arduino IDE. I get the following error:

fatal error: FreeRTOS.h: No such file or directory, include "FreeRTOS.h"

I did at one point download the FreeRTOS. But I could see it was only supporting AVR architecture, as mentioned this is mbed_os. I was compiling the simple star wars example in your readme file. Is there another way or a different type of RTOS I should be using for my board?

Try this one, it works with RP2040

1 Like

HI, I am trying to achieve the PWM or I2S in the mbed architecture. As that is also where the voice recognition module lies. Within supported architecture here, in library properties:

https://github.com/earlephilhower/ESP8266Audio/blob/master/library.properties

It only shows RP2040 which is not the same as mbed_os. This wont be compatible for what I am looking for. It kind of explains my predicament.

Thanks

Consider picking another MCU with an I2S interface for this particular project. SAMD and ESP32 are cheap and popular, with plenty of open source code.

1 Like

Hey, thanks for reply. I am starting to understand there are better choices in MCU. However this nano rp2040 presents an interesting prospect with offline voice recognition capabilities. Hence the purchase.

I know I could use esp32 microphone and then post process maybe in the cloud or something using tensorflow. I was also constrained as this need to be in a really small package.

Also im in a country that takes like 2 weeks to get parts. Not to go on a long rant of why I chose lol. But yea im trying to utilise what I have which I think is enough.

I've thought through trying to use the rpi sdk as the audio I2S and this side works. But the voice recognition works on mbed_os so its super frustrating.

I just found this lib. And it could be the answer to having I2S work on the mbed_os.

https://github.com/pschatzmann/rp2040-i2s

Thanks,

Hi I now have the I2S playing audio from an SD card and to the speakers through a MAX98357a DAC/amp. This is using MBED_OS as opposed to the raspberry pi development environment. using the following lib:

https://github.com/pschatzmann/rp2040-i2s

My issue now is that the I2S.begin does not intialise. Even when I take out the while loop and put an else print 'failed'. It seems to get stuck. the Arduino nano rp2040 just remains flashing orange. And it blocks the cyberon model from going to active listening state.

My thoughts are that there may be a resource conflict between Cyberon model using PCM on the mic and the I2S being used for audio output. Can any one shed any light on this?

#include <Arduino.h>
#include <DSpotterSDK_MakerHL.h>
#include <LED_Control.h>

#include <I2S.h>
#include <SD.h>
#include <SPI.h>

// The DSpotter License Data.
#include "CybLicense.h"
#define DSPOTTER_LICENSE g_lpdwLicense

// The DSpotter Keyword Model Data.
#if defined(TARGET_ARDUINO_NANO33BLE) || defined(TARGET_PORTENTA_H7) || defined(TARGET_NICLA_VISION)
// For ARDUINO_NANO33BLE and PORTENTA_H7
#include "Model_L1.h"             // The packed level one model file.
// For NANO_RP2040_CONNECT
#elif defined(TARGET_NANO_RP2040_CONNECT)
#include "Model_L0.h"             // The packed level zero model file.
#endif
#define DSPOTTER_MODEL g_lpdwModel

// The VR engine object. Only can exist one, otherwise not worked.
static DSpotterSDKHL g_oDSpotterSDKHL;

const int chipSelect = 29; // SD card chip select pin
File wavFile;

// Callback function for VR engine
void VRCallback(int nFlag, int nID, int nScore, int nSG, int nEnergy)
{
  if (nFlag==DSpotterSDKHL::InitSuccess)
  {
      //ToDo
  }
  else if (nFlag==DSpotterSDKHL::GetResult)
  {
      /*
      When getting an recognition result,
      the following index and scores are also return to the VRCallback function:
          nID        The result command id
          nScore     nScore is used to evaluate how good or bad the result is.
                     The higher the score, the more similar the voice and the result command are.
          nSG        nSG is the gap between the voice and non-command (Silence/Garbage) models.
                     The higher the score, the less similar the voice and non-command (Silence/Garbage) models are.
          nEnergy    nEnergy is the voice energy level.
                     The higher the score, the louder the voice.
      */
      //ToDo
  }
  else if (nFlag==DSpotterSDKHL::ChangeStage)
  {
      switch(nID)
      {
          case DSpotterSDKHL::TriggerStage:
            LED_RGB_Off();
            LED_BUILTIN_Off();
            break;
          case DSpotterSDKHL::CommandStage:
            LED_BUILTIN_On();
            break;
          default:
            break;
      }
  }
  else if (nFlag==DSpotterSDKHL::GetError)
  {
      if (nID == DSpotterSDKHL::LicenseFailed)
      {
          //Serial.print("DSpotter license failed! The serial number of your device is ");
          //Serial.println(DSpotterSDKHL::GetSerialNumber());
      }
      g_oDSpotterSDKHL.Release();
      while(1);//hang loop
  }
  else if (nFlag == DSpotterSDKHL::LostRecordFrame)
  {
      //ToDo
  }
}

void setup()
{
  // Init LED control
  LED_Init_All();

  // Init Serial output for show debug info
  Serial.begin(9600);
  while(!Serial);
  DSpotterSDKHL::ShowDebugInfo(true);

  // Init VR engine & Audio
  if (g_oDSpotterSDKHL.Init(DSPOTTER_LICENSE, sizeof(DSPOTTER_LICENSE), DSPOTTER_MODEL, VRCallback) != DSpotterSDKHL::Success)
    return;
  
  // Initialize SD card
  if (!SD.begin(chipSelect)) {
    Serial.println("Failed to initialize SD card!");
  } else {
    Serial.println("Initialised SD card");
  }

    // Open the WAV file
  wavFile = SD.open("lords.wav");
  if (!wavFile) {
    Serial.println("Failed to open WAV file!");
  } else {
    Serial.println("Opened WAV file");
  }

  // Skip WAV header (44 bytes for standard WAV files)
  wavFile.seek(44);

  // Start I2S with a sample rate of 44100 Hz (CD quality), 16 bits per sample
  if (!I2S.begin(22500)) {
    Serial.println("Failed to initialize I2S!");
    while (1);
  }
}

void loop()
{
  // Do VR
  g_oDSpotterSDKHL.DoVR();
}

@embeddedexplore ,

Your two or more topics on the same or similar subject have been merged.

Please do not duplicate your questions as doing so wastes the time and effort of the volunteers trying to help you as they are then answering the same thing in different places.

Please create one topic only for your question and choose the forum category carefully. If you have multiple questions about the same project then please ask your questions in the one topic as the answers to one question provide useful context for the others, and also you won’t have to keep explaining your project repeatedly.

Repeated duplicate posting could result in a temporary or permanent ban from the forum.

Could you take a few moments to Learn How To Use The Forum

It will help you get the best out of the forum in the future.

Thank you.

I will take my slap on the wrist for that one :slight_smile: Thanks for sharing that and I am now bettered educated for it.

1 Like

When starting out with a new MCU and peripheral, look for the simplest library example (I2S in this case) and get it running first.

Why the comment/code mismatch below, and what are the valid sample rates?

  // Start I2S with a sample rate of 44100 Hz (CD quality), 16 bits per sample
  if (!I2S.begin(22500)) {

Hi stand alone. This code works. which is I2S_rp2040 library mentioned above.

The reason for using 22500 opposed to standard 44khz is because it was running at double speed although i changed mp3 to WAV at 44khz. The reason for which I am not clear on just yet. I also tried 44khz with the Cyberon model integration. And it did not make a difference to the 'I2S.begin' hang. Just to confirm the script below at selected sample rate works. Its when I use with the model the begin gets stuck in a hang state.

#include <I2S.h>
#include <SD.h>
#include <SPI.h>

const int chipSelect = 29; // SD card chip select pin
File wavFile;

void setup() {
  Serial.begin(115200);

  // Initialize SD card
  if (!SD.begin(chipSelect)) {
    Serial.println("Failed to initialize SD card!");
    while (1);
  }

  // Open the WAV file
  wavFile = SD.open("lords.wav");
  if (!wavFile) {
    Serial.println("Failed to open WAV file!");
    while (1);
  }

  // Skip WAV header (44 bytes for standard WAV files)
  wavFile.seek(44);

  // Start I2S with a sample rate of 44100 Hz (CD quality), 16 bits per sample
  if (!I2S.begin(22500)) {
    Serial.println("Failed to initialize I2S!");
    while (1);
  }
}

void loop() {
  // Read and send samples to I2S
  while (wavFile.available()) {
    int16_t sample = wavFile.read();           // Read low byte
    sample |= wavFile.read() << 8;             // Read high byte

    I2S.write(sample);                         // Send sample to I2S (left channel)
    I2S.write(sample);                         // Send same sample to I2S (right channel)
  }

  // Close file when done
  wavFile.close();
  while (1);
}

Sounds like a pin, memory or other conflict with the model library.

It’s a great point, I thought maybe the Arduino.h would conflict with the i2s.h and the way the pins are defined. So i commented Arduino.h out compiled and flashed.

Still wouldn’t work. As side note not sure why they put Arduino.h in the sketch. Maybe if I used platformio or something like this.

Do you have any advice of how I can dig deeper into the libraries which are used for cyberon and the pin mapping they are using. To see if I have a conflict?

First check if you are running out of memory. I2S begin() probably allocates a large RAM buffer and, I assume, so does the model library. The order in which you initialize the objects may make a difference, and the return values of .Init and .begin may be informative.

There are various free memory functions, so you can check memory status at any place in the code, but I'm not familiar with what is available for the RP2040.

Then go through the library source code and note all pin assignments.

Edit: check whether the voice model library takes over the I2S port.

I tried checking the memory using the memoryFree library here which seems to cater for the M0 cortex.

https://github.com/mpflaga/Arduino-MemoryFree

However the results seem not to indicate the free space.

I also found this thread here talking about memory allocation on the rp2040 hardware.

https://forum.arduino.cc/t/stack-memory-issue/938879/4

Do you have a way I can check the RAM before and after the initilisation?

#include <MemoryFree.h>
#include <pgmStrToRAM.h>

#include "I2S.h"

void setup() {
  Serial.begin(115200);
  while (!Serial) {}  // Wait for serial connection.

  Serial.print("Free memory before I2S init: ");
  Serial.println(freeMemory());

  // Initialize I2S
  if (!I2S.begin(44100)) {
    Serial.println("Failed to initialize I2S!");
  } else {
    Serial.print("Free memory after I2S init: ");
    Serial.println(freeMemory());
  }
}

void loop() {}

Results

13:22:19.768 -> Free memory before I2S init: -8033
13:22:19.768 -> Free memory after I2S init: -16225

Those would be unsigned positive integers, misinterpreted by Serial.print as signed, and are changing in the expected direction for 8 kB buffer allocation.

Hey that makes sense now. I found a lib that had a little script in it. And it seems to be giving me realistic values now. I tried a simple I2S begin and then reading before and after the memory. I got the following results:

15:49:42.042 -> Free memory before I2S init: 88780
15:49:42.299 -> Free memory after I2S init: 86030

I then put this memory read back into the main voice recognition model. I got the following reuslts:

19:39:35.297 -> Free memory start of init: 30480
19:39:35.378 -> Free memory before I2S init: 30480
19:39:36.233 -> Free memory before Cyberon model init: 30350

Note ! I actually commented out the I2S init because it got stuck in hang and crashed the Arduino.

But Its strange immediately after setting serial that the avail memory is 50k bytes less than what it was in the basic script, could this be my issue or am I making things up?

Basic script:

#include "I2S.h"

#define FREEMEM_CELL 10

struct elem {
    struct elem *next;
    char dummy[FREEMEM_CELL-2];
};

int getFreeMemory(void) {
    int counter;
    struct elem *head, *current, *nextone;

    current = head = (struct elem*) malloc(sizeof(struct elem));

    if (head == NULL) {
        return 0;
    }

    counter = 0;

    do {
        counter++;
        current->next = (struct elem*) malloc(sizeof(struct elem));
        current = current->next;
    } while (current != NULL);

    current = head;

    do {
        nextone = current->next;
        free(current);
        current = nextone;
    } while (nextone != NULL);

    return counter * FREEMEM_CELL;
}

void setup() {
    Serial.begin(115200);
    while (!Serial) {}  // Wait for serial connection.

    // Print free memory before I2S initialization
    Serial.print("Free memory before I2S init: ");
    Serial.println(getFreeMemory());

    // Initialize I2S
    if (!I2S.begin(44100)) {
        Serial.println("Failed to initialize I2S!");
    } else {
        Serial.print("Free memory after I2S init: ");
        Serial.println(getFreeMemory());
    }
}

void loop() {
    // Print free memory periodically
    Serial.print("> free memory: ");
    Serial.println(getFreeMemory());

    delay(1000);  // Delay for 1000 milliseconds (1 second)
}

Voice model

//#include <Arduino.h>
#include <DSpotterSDK_MakerHL.h>
#include <LED_Control.h>

#include <I2S.h>
#include <SD.h>
#include <SPI.h>

// The DSpotter License Data.
#include "CybLicense.h"
#define DSPOTTER_LICENSE g_lpdwLicense

// The DSpotter Keyword Model Data.
#if defined(TARGET_ARDUINO_NANO33BLE) || defined(TARGET_PORTENTA_H7) || defined(TARGET_NICLA_VISION)
// For ARDUINO_NANO33BLE and PORTENTA_H7
#include "Model_L1.h"             // The packed level one model file.
// For NANO_RP2040_CONNECT
#elif defined(TARGET_NANO_RP2040_CONNECT)
#include "Model_L0.h"             // The packed level zero model file.
#endif
#define DSPOTTER_MODEL g_lpdwModel

#define FREEMEM_CELL 10

// The VR engine object. Only can exist one, otherwise not worked.
static DSpotterSDKHL g_oDSpotterSDKHL;

const int chipSelect = 29; // SD card chip select pin
File wavFile;

struct elem {
    struct elem *next;
    char dummy[FREEMEM_CELL-2];
};

int getFreeMemory(void) {
    int counter;
    struct elem *head, *current, *nextone;

    current = head = (struct elem*) malloc(sizeof(struct elem));

    if (head == NULL) {
        return 0;
    }

    counter = 0;

    do {
        counter++;
        current->next = (struct elem*) malloc(sizeof(struct elem));
        current = current->next;
    } while (current != NULL);

    current = head;

    do {
        nextone = current->next;
        free(current);
        current = nextone;
    } while (nextone != NULL);

    return counter * FREEMEM_CELL;
}

// Callback function for VR engine
void VRCallback(int nFlag, int nID, int nScore, int nSG, int nEnergy)
{
  if (nFlag==DSpotterSDKHL::InitSuccess)
  {
      //ToDo
  }
  else if (nFlag==DSpotterSDKHL::GetResult)
  {
      /*
      When getting an recognition result,
      the following index and scores are also return to the VRCallback function:
          nID        The result command id
          nScore     nScore is used to evaluate how good or bad the result is.
                     The higher the score, the more similar the voice and the result command are.
          nSG        nSG is the gap between the voice and non-command (Silence/Garbage) models.
                     The higher the score, the less similar the voice and non-command (Silence/Garbage) models are.
          nEnergy    nEnergy is the voice energy level.
                     The higher the score, the louder the voice.
      */
      //ToDo
          if (nID == 10000) {
        // Read and send samples to I2S
        while (wavFile.available()) {
          int16_t sample = wavFile.read();           // Read low byte
          sample |= wavFile.read() << 8;             // Read high byte

          I2S.write(sample);                         // Send sample to I2S (left channel)
          I2S.write(sample);                         // Send same sample to I2S (right channel)
        }

        // Close file when done
        wavFile.close();
        while (1);
    }
  }
  else if (nFlag==DSpotterSDKHL::ChangeStage)
  {
      switch(nID)
      {
          case DSpotterSDKHL::TriggerStage:
            LED_RGB_Off();
            LED_BUILTIN_Off();
            break;
          case DSpotterSDKHL::CommandStage:
            LED_BUILTIN_On();
            break;
          default:
            break;
      }
  }
  else if (nFlag==DSpotterSDKHL::GetError)
  {
      if (nID == DSpotterSDKHL::LicenseFailed)
      {
          //Serial.print("DSpotter license failed! The serial number of your device is ");
          //Serial.println(DSpotterSDKHL::GetSerialNumber());
      }
      g_oDSpotterSDKHL.Release();
      while(1);//hang loop
  }
  else if (nFlag == DSpotterSDKHL::LostRecordFrame)
  {
      //ToDo
  }
}

void setup()
{
  // Init Serial output for debug info
  Serial.begin(115200);
  delay(1000);

  // Print free memory before Cyberon model
  Serial.print("Free memory start of init: ");
  Serial.println(getFreeMemory());

  // Print free memory before I2S initialization
  Serial.print("Free memory before I2S init: ");
  Serial.println(getFreeMemory());
  // // Start I2S with a sample rate of 22500 Hz (change to 44100 Hz if needed)
  // if (!I2S.begin(22500)) {
  //   Serial.println("Failed to initialize I2S!");
  // } else{
  //   Serial.print("Free memory after I2S init: ");
  //   Serial.println(getFreeMemory());
  // }

  //while (!Serial); // Wait for Serial to be ready
  DSpotterSDKHL::ShowDebugInfo(true);

  // Init LED control
  LED_Init_All();

  // Initialize SD card
  if (!SD.begin(chipSelect)) {
    Serial.println("Failed to initialize SD card!");
  }

  // Open the WAV file
  wavFile = SD.open("lords.wav");
  if (!wavFile) {
    Serial.println("Failed to open WAV file!");
  }

  // Skip WAV header (44 bytes for standard WAV files)
  wavFile.seek(44);

  // Print free memory before Cyberon model
  Serial.print("Free memory before Cyberon model init: ");
  Serial.println(getFreeMemory());
  // Init VR engine & Audio
  if (g_oDSpotterSDKHL.Init(DSPOTTER_LICENSE, sizeof(DSPOTTER_LICENSE), DSPOTTER_MODEL, VRCallback) != DSpotterSDKHL::Success) {
    Serial.println("Failed to initialize DSpotter SDK!");
  }
}


void loop()
{
  // Do VR
  g_oDSpotterSDKHL.DoVR();
}


Sorry, I can't make any sense out of your post.

The Nano RP2040 is supposed to have 264 kB of RAM. To the following minimal Arduino program add Serial and the print free memory code, and try to understand what the output is telling you.

void setup(){}
void loop(){}

Hey, what I was trying to say is that I tried a script that just ran I2S.begin. This had 80,000 bytes of available memory. When I put into voice recognition model it had 30,000bytes. These were both after initialising the serial.begin in the start up. So nothing had happened before.

After a search, it would seem that I would need roughly 4kb - 8kb memory space to run the I2S. Therefore I don't think memory is causing the hang. And will now look into PIN out's in libraries. Like suggested :slight_smile:

Hi,

I could not check inside DSpotterSDK_MakerHL.h as it seems private. I commented everything out of the code apart from the #include "I2S.h" and the dspottersdk. This is when the I2S initiation causes a hang.

So It's a conflict between I2S and the cyberon model. I know that the RP2040 does not have a I2S so it uses the PIO. My next thought is there could be a PIO (I2S) and a PCM conflict?

Really appreciate everyone's help.

A glance at DSpotterSDKMakerHL.cpp clearly indicates that the library uses the PDM microphone.

/**
   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.
**/