I2S over UDP from one esp32 to another esp32

Greetings,

I am trying to make a program that records mic input and sends it over UDP to another esp32(tiny pico) and outputs that data to a speaker.

Sender Specs:
tiny pico with SPH0645 i2s mic

Receiver Specs:
tiny pico with Max 98357A i2s amp

The problem, I think I am having, is RECEIVING the data and converting back to the original format(int), so the I2S.write() can output the sound.

  1. From I2S.read() the data returned is of int type
  2. I then attempt to store 256 samples in an array and send out the data
  3. Similarly, on the other pico, I attempt to retrieve that same array data using another for loop
  • I see the data and get the values from I2S.read(), but in order to use I2S.write() on the receive side, the data needs to be of int type and I am afraid that I am confusing some simple data type concepts.

Any help would be greatly appreciated!
-Twiggs

Transmit Code

/*
 *  This sketch sends random data over UDP on a ESP32 device
 *
 */
#include <WiFi.h>
#include <WiFiUdp.h>
#include <I2S.h>
// WiFi network name and password:
const char * networkName = "";
const char * networkPswd = "";
//IP address to send UDP data to:
// either use the ip address of the server or 
// a network broadcast address
const char * udpAddress = "";
const int udpPort = 5432;

//Are we currently connected?
boolean connected = false;
////////////////////////////////////////////////
////////////////////////////////////////////
const char* sound_wav[256];
int wav[256];
int bytesPacked = 0;
int TempSample = 0;
////////////////////////////////////////////////////////
////////////////////////////////////////////
//The udp library class
WiFiUDP udp;

void setup(){
  // Initilize hardware serial:
  Serial.begin(115200);
  
  //Connect to the WiFi network
  connectToWiFi(networkName, networkPswd);

  // start I2S at 8 kHz with 32-bits per sample
  if (!I2S.begin(I2S_PHILIPS_MODE, 8000, 32))
    {
    Serial.println("Failed to initialize I2S!");
    while (1); // do nothing
    }
    
  Serial.println("Set up done");
}

void loop(){


  //////////////////////////////////////////////
  // read a sample(s) into 256 byte buffer the send over UDP
  ////////////////////////////////////////

  for( int n = 0; n <= 255; n++)
  {
    TempSample = I2S.read();
    //Serial.println(TempSample);
    delay(1);
   
    wav[n] = TempSample;
    Serial.print("TempSample:::");
    Serial.println(TempSample);
    
    
    bytesPacked++;
  }
Serial.println(bytesPacked); //verify array is in bounds
    if(connected && bytesPacked >=255)// check if i2s buffer is full to then send out via UDP
    {
      Serial.println("bytes packed reset");
      bytesPacked = 0;
      for(int n2 = 0; n2 <= 256; n2++)
      {
     
      //Send a packet
      udp.beginPacket(udpAddress,udpPort);
      //udp.printf("Printing values:");
      //int len = sizeof(wav[n2]);
      Serial.print("your N2 values:");
      Serial.println(n2);
      Serial.print("your values for data:");
      Serial.println(wav[n2]);
      udp.print(wav[n2]); //data is sent correctly, but when received, I am confused on converting it back
      udp.endPacket();
      }
  
    }
   
  
  Serial.println("Done with all loops");
  delay(5000);
}

void connectToWiFi(const char * ssid, const char * pwd){
  Serial.println("Connecting to WiFi network: " + String(ssid));

  // delete old config
  WiFi.disconnect(true);
  //register event handler
  WiFi.onEvent(WiFiEvent);
  
  //Initiate connection
  WiFi.begin(ssid, pwd);

  Serial.println("Waiting for WIFI connection...");
}

//wifi event handler
void WiFiEvent(WiFiEvent_t event){
    switch(event) {
      case ARDUINO_EVENT_WIFI_STA_GOT_IP:
          //When connected set 
          Serial.print("WiFi connected! IP address: ");
          Serial.println(WiFi.localIP());  
          //initializes the UDP state
          //This initializes the transfer buffer
          udp.begin(WiFi.localIP(),udpPort);
          connected = true;
          break;
      case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
          Serial.println("WiFi lost connection");
          connected = false;
          break;
      default: break;
    }
}

Receive side pico code

/*
 *  This sketch sends random data over UDP on a ESP32 device
 *
 */
#include <WiFi.h>
#include <WiFiUdp.h>
#include <I2S.h>
// WiFi network name and password:
const char * ssid = "";
const char * password = "";

//IP address to send UDP data to:
// either use the ip address of the server or 
// a network broadcast address
const char * udpAddress = "";
const int udpPort = 5432;

//Are we currently connected?
boolean connected = false;
//////////////////////////////////////////////////////////////////////////////
//ADC buffer
char* sound_wav[255]; //one of two types of array
int bytesPacked = 0; //counter
char packetBuffer[255]; //buffer holds incoming packet
int wav[255]; //second int array
////////////////////////////////////////////////////////////////////////////////
//The udp library class
WiFiUDP udp;

void setup(){
  // Initilize hardware serial:
  Serial.begin(115200);

  
  //Connect to the WiFi network
  connectToWiFi(ssid, password);
  delay(1000);
  udp.begin(udpPort);
  delay(1000);
  //I2S.setAllPins(14, 25, 26, 26, 33); // you can change default pins; order of pins = (CLK, WS, Fs, IN, OUT)
  while (!Serial) {
     // wait for serial port to connect. Needed for native USB port only
  }

  // start I2S at 8 kHz with 32-bits per sample
  if (!I2S.begin(I2S_PHILIPS_MODE, 8000, 32)) {
    Serial.println("Failed to initialize I2S!");
    while (1); // do nothing
  }
  
  Serial.println("Set up done");
}


void loop(){
  


 if(udp.parsePacket() && connected)
  {
   
    
        for(int n= 0; n<=255; n++)
        {
          udp.read(packetBuffer,255);
          //udp.read(packetBuffer,255);
          Serial.print("Contents from packet buffer RAW: ");
          Serial.println(packetBuffer); //======>value is correct, but this is a Char*
          //wav[n] = (int)packetBuffer;// convert contents into basic int , but removes negative sign or truncates values
          //Serial.println(wav[n] );
        }
        Serial.println("DONE COUNTING");

 
  /*//waiting to get correct data type before proceeding to output via i2s
    Serial.println("starting I2S output");
      int tempSample;
      for(int n =0; n<=256; n++)
      {
        tempSample = wav[n];
        I2S.write(tempSample );
      }
      */
  }
  
  
  Serial.println("delay fives seconds and chill now");
  delay(5000);
  
}

void connectToWiFi(const char * ssid, const char * pwd){
  Serial.println("Connecting to WiFi network: " + String(ssid));

  // delete old config
  WiFi.disconnect(true);
  //register event handler
  WiFi.onEvent(WiFiEvent);
  
  //Initiate connection
  WiFi.begin(ssid, pwd);

  Serial.println("Waiting for WIFI connection...");
}

//wifi event handler
void WiFiEvent(WiFiEvent_t event){
    switch(event) {
      case ARDUINO_EVENT_WIFI_STA_GOT_IP:
          //When connected set 
          Serial.print("WiFi connected! IP address: ");
          Serial.println(WiFi.localIP());  
          //initializes the UDP state
          //This initializes the transfer buffer
          udp.begin(WiFi.localIP(),udpPort);
          connected = true;
          break;
      case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
          Serial.println("WiFi lost connection");
          connected = false;
          break;
      default: break;
    }
}

Ok, if I understand correctly, the code is reading samples into a 256-element array of int called 'wav'. On Arduino, type int is a 16-bit signed value. The entire buffer array construct is therefore 256*2 or 512 bytes long.

The counter variable called bytesPacked, might more accurately perhaps be called samplesPacked.

The code then iterates over the array and appends each 16-bit sample to a udp packet until all 256 samples been added and the packet is then sent.

Now at the other end, the program needs to receive this udp packet containing these 256 16-bit int samples. The receiving buffer therefore needs to accommodate 512 bytes:

char packetBuffer[512];

Each pair of received bytes then needs to be converted to a 16-bit int. One way to do this might be to use a union:

union sample_buffer {
  char packetBuffer[512];
  int sample[256];
} wav;

The program can then refer to the data using either the char array OR the sample array:

udp.read(wav.packetBuffer,255);
for(int n =0; n<=256; n++)
{
    I2S.write(wav.sample[n]);
}

There are other ways that the pairs of received bytes can be converted in an int. Since you are using Pico's at both ends, this might be sufficient, otherwise it might also have been necessary to consider the byte order.

rather than using UDP try esp-now-esp32 - it is direct ESP32 to ESP32 communication so should be faster than ESP32 > router > ESP32

@BitSeeker

Thanks for the quick reply! You put the B in BitSeeker for sure with your understanding of the basics about simple things that I constantly have to go back to and get held up on.

I am just going over your solution provided now. Thinking about how an int is 16 bits or 2 bytes. So when it is sent over udp, technically I will end up needing a 512 byte buffer to receive the information via udp.read(). Never seen a union til now, so thanks again for putting me onto that. I will work through this. Still not even sure if the audio will play. :upside_down_face:

in that case, I may have to side track and learn espnow via @horace recommendation.

At any rate, I appreciate the guidance/redirection from the both of you! :grinning:

Ok, after a few weeks I am circling back to the basics. I have two types of algos that accomplish sending the data. One via UDP and the other using esp_now. I will post those later when it is all said and done.

For now, I am not understanding the basic data types and methods for casting, converting, or bit wise operations to break them down. It appears that i2s.read() is actually returning a 32 bit int value and in order to send this value over UDP or through esp_now, I must break it down to an uint8_t or char; depending on which function I use.

functions prototypes of interest for your reference:

esp_now_send(const uint8_t *peer_addr, const uint8_t *data, size_t len)

void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len)//esp_now receive call back function

udp.read(unsigned char* buffer, size_t len)

udp.read(char* buffer, size_t len)

udp.write(uint8_t data)

udp.write(const uint8_t *buffer, size_t size)

So, whether I am sending data or receiving data, It appears that I will need to break down the 32 bit value into an 8 bit value and back into 32 bits. I am struggling to grasp this concept. If any one has some time to point me in the right direction, it would help a lot. If you need more info, please let me know. I have a method below that works, but seems long winded for what I need to do. Thanks in advance.

Here is an example value I am starting with: -258441216


void setup() {
  // put your setup code here, to run once:
   // Set up Serial Monitor
      Serial.begin(115200);
}

void loop() {
////////////////////////////////////////////////////////
// 32 bits to start agggain
int OG2 = -258441216;
Serial.print("reg int");
Serial.println(OG2);
////////////////////////////////////////////////////////////////
// convert from 32 bitts to multiple 8 bitt values representing 32 bit value OGG

unsigned char a = OG2 & 0xFF;
unsigned char b = (OG2 >> 8) & 0xFF;
unsigned char c = (OG2 >> 16) & 0xFF;
unsigned char d = (OG2 >> 24) & 0xFF;

Serial.println(" a vlue is " +String(a) );


Serial.println(" b vlue is " +String(b) );


Serial.println(" c vlue is " +String(c) );


Serial.println(" d vlue is " +String(d) );
//convert back and check to see if this is correct
int doo = (a & 0xFF | b << 8 | c << 16 | d << 24);

Serial.println("here is doo:" + String (doo));
////////////////////////////////////////////////////////////////


}

Careful ... 8-bit and 32-bit microcontrollers are different word sizes.

You may find these of interest:
How to send and receive struct data packet over serial - Using Arduino / Programming Questions - Arduino Forum

Can I send multiple data in one package? - Using Arduino / Programming Questions - Arduino Forum

Ok! I have been taking a different route or method in completing this task. I completed this using the arduino built in libraries and was able to easily to transfer the data over wifi with ESP_NOW libraries.

However, an external I2S circuit board( the mic) broke an now I only have an analog microphone. Therefore, I have gone down the route of using espressif libraries instead to tackle this project. It's been a heck of a learning curve, but slowly accomplishing it. So for those that run into this similar project, below is the code for the built in simplified arduino libraries; granted, if you have an I2S mic and speaker, I think it would work, although, everything broke before I was able to test it. Like, right at the time I fired up the code. LOL :rofl: :sweat_smile:

So here we go!! Let me know if it helps or does not.

Transmit

/*
  ESP-NOW I2S Transmit
*/

// Include Libraries
#include <esp_now.h>
#include <WiFi.h>
#include <I2S.h>

////////////////////////////////////////////////////////
////////////////////////////////////////////
// MAC Address of responder - edit as required
uint8_t broadcastAddress[] = {};//insert your devices mac ID here

///////////////////////////////////////////////////////////////////////

int samplesPacked = 0;
// Define a data structure
typedef struct info
{
  
unsigned char BreakDown[247];//define size of array to be packed (n-1)


}info;

// Create a structured object
 info data1;
//int array to hold temp vals to be converted
int wav[61]; //(n-1)
//////////////////////////////////////////
// Peer info
esp_now_peer_info_t peerInfo;

// Callback function called when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) 
{
  
  Serial.print("\r\nLast Packet Send Status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
  
}
////////////////
//////////////////////////
void setup() 
{
  
  // Set up Serial Monitor
  Serial.begin(115200);
 
  // Set ESP32 as a Wi-Fi Station
  WiFi.mode(WIFI_STA);

  // Initilize ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  // Register the send callback
  esp_now_register_send_cb(OnDataSent);
  
  // Register peer
  memcpy(peerInfo.peer_addr, broadcastAddress, 6);
  peerInfo.channel = 0;  
  peerInfo.encrypt = false;
  
  // Add peer        
  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    Serial.println("Failed to add peer");
    return;
  }

 // start I2S at 8 kHz with 32-bits per sample
  if (!I2S.begin(I2S_PHILIPS_MODE, 8000, 32)) {
    Serial.println("Failed to initialize I2S!");
    while (1); // do nothing
  }  
}

void loop() 
{
//////////////////////////////////////////////
// read a sample into multiple byte buffer///
////////////////////////////////////////////
Serial.println("starting I2S readings...");

if(samplesPacked == 0)//check to see if we have packed sample
{

int sample = 0;// temp sample data variable
 
          for( int n = 0; n <= 61; n++) //62 32 bit samples need a 248 element array
          {
            
           sample = I2S.read();//read 32 bits of sample data
           delay(11); //waits for buffer to fill
           
              if(sample && sample != -1 && sample != 1)
              {
                wav[n] = sample;
                //Serial.println("Sample Found:");
               // Serial.println(sample);//compare the two for clarity
               // Serial.println(data1.wav[n]);//same as stated above
                samplesPacked++;// counter
              }
          }
////////////////////////////////////////////////////
Serial.println("Packing samples into 8 bit elements...");

            for( int n = 0; n<=61; n++)//break int array into 8 bit array
            {
            data1.BreakDown[n*4] = wav[n] & 0xFF;
            //Serial.println("here is 1:" +String(data1.BreakDown[n*4]) );
            //delay(100);
            
            data1.BreakDown[n*4+1] = (wav[n] >> 8) & 0xFF;
            //Serial.println("here is 2:" +String(data1.BreakDown[n*4+1]) );
            
            data1.BreakDown[n*4+2] = (wav[n] >> 16) & 0xFF;
            //Serial.println("here is 3:" +String(data1.BreakDown[n*4+2]) );
            
            data1.BreakDown[n*4+3] = (wav[n] >> 24) & 0xFF;
              //Serial.println("here is 4:" +String(data1.BreakDown[n*4+3] ) );
                        
            }
            delay(5000);
                
}

Serial.println("done PACKING samples");


 
  // Send message via ESP-NOW
  esp_err_t result = esp_now_send(broadcastAddress, (const uint8_t *)&data1, sizeof(data1));
   
  if (result == ESP_OK) {
    Serial.println("Sending confirmed");
    samplesPacked = 0;
  }
  else {
    Serial.println("Sending error");
  }
  delay(2000);
}

Receive

/*
  ESP-NOW I2S Receive
*/

// Include Libraries
#include <esp_now.h>
#include <WiFi.h>
#include <I2S.h>

//////////////////////////////////////////////////////////
// Define a data structure to send via esp_now **no more than 250 bytes**
typedef struct info
{
  
unsigned char BreakDown[247];//define size of array to be packed (n-1)

}info;

// Create a structured object
info data2;

//int array to hold temp vals to be converted
int wav[61]; //(n-1) 
/////////////////////////////////////////

// Callback function executed when data is received
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) 
{
memcpy(&data2, (int8_t*)incomingData, sizeof(data2));//copy contents of structure to data2 struct
  
Serial.println("writing data...");

       for( int n = 0; n<=61; n++)//break int array into 8 bit array
       {

        wav[n] = (data2.BreakDown[n*4] & 0xFF)|(data2.BreakDown[n*4+1] << 8)|(data2.BreakDown[n*4+2] << 16)|(data2.BreakDown[n*4+3] << 24);

       }
Serial.println("DONE WRITING...");
delay(1000);
Serial.println("writing out data received");

      for(int i = 0; i <= 61; i++)//this loop takes data formed into an int and output audio
      {
       //Serial.println("value of [i]" + String(i) );
      //Serial.println(" values of wav[i]" + String(wav[i]) );
      I2S.write(wav[i]);
      delay(11);
      }

Serial.println("done doing stuff");
delay(5000);
}

void setup() 
{
  // Set up Serial Monitor
  Serial.begin(115200);
  
  // Set ESP32 as a Wi-Fi Station
  WiFi.mode(WIFI_STA);

  // Initilize ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }
  
  // Register callback function
  esp_now_register_recv_cb(OnDataRecv);
 I2S.setAllPins(14, 25, 26, 26, 33);
   // start I2S at 8 kHz with 32-bits per sample
  if (!I2S.begin(I2S_PHILIPS_MODE, 8000, 32)) {
    Serial.println("Failed to initialize I2S!");
    while (1); // do nothing
  }
  
}
 
void loop() 
{

}

Bonus

I would reccomend checking this dude out. Lots of info from this fella and he is accomplishing the task with full code and even examples galore. Seems very knowledgeable on i2S.

GitHub Link
Blog

Finally, when using Espressif libraries, Instead of using 50% of program storage, this is reduced to 19% and only takes up 5% of dynamic memory SO FAR. Much better IMO.

Any questions or suggestions always appreciated in helping the community grow with more info. Thanks for all those who helped me. Not sure this is even solved, but will update with the new code as soon as I get something working.

-Twigs

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.