1

For my final project on the university as a Systems Engineer, I'm working on a "electronic stethoscope", which is giving me a lot of headaches.

I bought a Adafruit Feather ESP32 Huzzah (https://learn.adafruit.com/adafruit-huzzah32-esp32-feather/overview) which has a dual core processor that works around 240 MHz of speed, it has support of wireless communication with Bluetooth classic (which is the one I'm using) and also works with the I2S protocol.

That is the reason why I bought an I2S MEMS Microphone SPH0645 (https://www.adafruit.com/product/3421), which according with its documentation works under 50Hz-15KHz.

As a first approach, I thought that it would be enough, but I'm getting a very slow sample rate that is around 800 samples per second. With these values, I can't reproduce any sound with the expected quality.

On the other hand, since the ESP32 has notado a high level library to interact with I2S such as ArduinoSound I had to create my own code based on the specification (https://esp-idf.readthedocs.io/en/v2.0/api/peripherals/i2s.html).

My code on the Arduino IDE looks like this:

#include "libraries/SmappioSound/SmappioSound.cpp"
#include "BluetoothSerial.h"

int *buffer;
BluetoothSerial serialBT; 
int value = 0;
int framesRead = 0;

SmappioSound smappioSound(6835); //value for stabilization

void setup() {
  // put your setup code here, to run once:

  pinMode(DATA_PIN, INPUT); // Supuestamente necesario para que no haya ruido
  serialBT.begin("smappio_PCM");
  Serial.begin(115200);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  smappioSound.begin(buffer);
}

void loop() {

  //gets the amount of bytes readed from the buffer. 
  framesRead = smappioSound.read() / I2S_BITS_PER_SAMPLE_32BIT;

  for(int index = 0; index <= framesRead; index++)
  {    

      value = smappioSound.getSampleValue(index);
      if(index % 2 == 0)
      {          
          serialBT.print(String(value) + ",");
          Serial.println(value);          
      }          
  }
}

The SmappioSound.h library is where the interaction with I2S occurs. The configuration for i2S is the following:

#pragma region // Sección de Configuración
  static const print_mode_t PRINT_MODE = BYTES;                                   // Tipo de impresión: BITS | BYTES | INTEGER | FULL_DETEAILED
  static const bool PRINT_BOTH_CHANNELS = false;                                    // Indica si imprime solo el "Canal 0" ó el "Canal 0" y el "Canal 1"
  static const int FRAMES_REQUESTED = 1;                                           // Parece relacionarse con la cantidad de buffers
  static const int TICKS_TO_WAIT = 0;                                            // Investigar
  static const i2s_bits_per_sample_t BITS_PER_SAMPLE = I2S_BITS_PER_SAMPLE_32BIT;   // Datasheet: 24. Ejemplo: 32.

  static const i2s_config_t SPH_CONFIG = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),                                            // El controlador es el Maestro y Recibe, el microfono es Esclavo y Transmite
    .sample_rate = 32000,                                                                           // Entre 32KHz y 64KHz (Datasheet)
    .bits_per_sample = BITS_PER_SAMPLE,                                                             
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,                                                   // El formato que usa el micrófono. No se encontró el Datasheet
    .communication_format = (i2s_comm_format_t) I2S_COMM_FORMAT_PCM,    //(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB) // Investigar
    //MSB: most significant byte -> BIG ENDIAN
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,                                                       // High interrupt priority (investigar)
    .dma_buf_count = 2,                                                                            // Cantidad de buffers, 128 max.
    .dma_buf_len = 32 * 2                                                                           // Tamaño de cada buffer
  };

#pragma endregion Sección de Configuración

As you can see, I'm trying to get 32KHz as sample rate with 32 bit-depth (not working for 16, and 24 bits), and I'm setting 115200 baud-rate in the Arduino IDE.

On the other hand, I created a .NET Winforms application that is receiving the generated PCM values over bluetooth 4.0. After 1 second is giving me only 693 samples which is very few.

enter image description here

Can I reach at least 20KHz as sample rate with these hardware components? Do you see something wrong in my code?

  • 1
    Serial is slow. Very slow. You can't do audio over it like that. – Majenko May 28 '18 at 18:32
  • Thanks Majenko. What hardware component can you recommend for streaming audio with high quality? – Mauro Bilotti May 28 '18 at 18:42
  • Usb with isochronous transfers. – Majenko May 28 '18 at 18:43
  • I don't get it. Are you using Bluetooth or USB-UART serial as a transmission medium? Because you transmit the values on both channels. – Maximilian Gerhardt May 28 '18 at 22:50
  • I want to transfer only 1 channel. I just saw that I can use channel_format = I2S_CHANNEL_FMT_ONLY_LEFT to send only mono. I'm using Bluetooth as a transmission medium. – Mauro Bilotti May 28 '18 at 23:05
2

Here's a few tips on optimization.

  for(int index = 0; index <= framesRead; index++)
  {    

      value = smappioSound.getSampleValue(index);
      if(index % 2 == 0)
      {          
          serialBT.print(String(value) + ",");
          Serial.println(value);          
      }          
  }

Doing

          serialBT.print(String(value) + ",");
          Serial.println(value);          

is very wasteful. First of all you're debug-outputting every value as an ASCII string to Serial, second you're doing the same thing over serialBT with an additional ,.

It's wasteful because:

  • your baud rate is extremely low (115200 on Serial)
  • converting every value to ASCII will induce an overhead

Your int value is 32 bits / 4 bytes which may take arbitrary values. Say the value is at its positive max at 0x7fffffff = 214748364. Then transmitting the ASCII string will take 9 bytes (10 bytes for largest negative value), sending the raw binary value will always take 4 bytes. Thus you should always send your samples in raw binary format, not converted to an ASCII String.

At a wanted sampling rate of 32KHz with 32-bit values, you'd need an effective bit rate of 32000*32 = 1,024,000 Bits/s. Your Serial on which you are also printing every 2nd value, is on 115,200 bit/s (and you'd still have to account for the UART overhead, aka start and stop bits for every 8 data bits!).

Depending on the quality of the USB-UART converter on your board (FTDI, CP2102: good, CH340: bad), you can actually get to over 1 megabaud. The ESP32's UART periphal will allow you to set whatever clock divider you want to divide the 80 MHz APB clock (source, chapter 13.3.2). ESP-IDF has the UART_BITRATE_MAX defined to 5,000,000 (source), and since Arduino-ESP32 derives from ESP-IDF, that should be possible there, too.

As I said, the USB-UART adapter between the ESP32 and your PC is critical. It has to support such a high speeds with a sufficiently large TX/RX buffer. The max baud rate of a CP2101 is 921600 (source), an FT232RL (source) up to 3 megabaud.

In general you don't want to give your sample values to the peripheral value-wise. Both your Serial and your BluetoothSerial have the possibility to enqueue a whole buffer at once (source). I don't know the real underlying implementation where the final samples will be stored (and held stable for a while) but you might just be able to do

framesRead = smappioSound.read() / I2S_BITS_PER_SAMPLE_32BIT;
serialBT.write((uint8_t*) buffer, framesRead * sizeof(int32_t));

to send the whole buffer at once. You'd also have to check how to handle or set the transmission speed on the bluetooth peripheral. I don't know much about the ESP32's BT / BLE radio to answer that.

Speed on a UART adapter could further be advanced to a non-blocking DMA backed variant. Since both I2S and the UART periphal support DMA transfers. Thus, the maximum transfer speed would be reachable.

Another posisbility would be to change the transission medium. You could have your ESP32 join a WiFi network and transmit its audio buffers via UDP or TCP to some server listening on the PC. I think however Bluetooth and UART (with the right tweaks) would also be possible.

  • Thanks a lot Maximilian, I will check all the details of your response and I'll let you know how it goes. – Mauro Bilotti May 28 '18 at 23:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.