In this Blog I am giving a little bit of background on how the sampling of analog signals has been implemented in my Arduino audo-tools library. My initial approach was based on the Blog from Ivan Voras using timers and interrupts.

But there is a much better way by using the extended ESP32 I2S functionality: You can use this to sample an analog signal (e.g. from a microphone) at very high speeds and I finally used this approach in my ADC class.

Here is how you set it up using the ESP32 API:

#include "esp_a2dp_api.h"
#include "driver/i2s.h"
#include "freertos/queue.h"
#include "driver/adc.h"

// public config parameters
int sample_rate = 44100;
int dma_buf_count = 10;
int dma_buf_len = 512;
bool use_apll = false;

const i2s_port_t i2s_num = I2S_NUM_0; // Analog input only supports 0!

// select the analog pin e.g 32
adc_unit_t unit = ADC_UNIT_1;
adc1_channel_t channel = ADC1_GPIO32_CHANNEL;

// I2S config
i2s_config_t i2s_config = {
  .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
  .sample_rate = sample_rate,
  .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
  .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
  .communication_format = I2S_COMM_FORMAT_I2S_LSB,
  .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
  .dma_buf_count = dma_buf_count,
  .dma_buf_len = dma_buf_len,
  .use_apll = use_apll,
  .tx_desc_auto_clear = false,
  .fixed_mclk = 0};

// setup config
if (i2s_driver_install(i2s_num, &i2s_config, 0, NULL)!=ESP_OK){
    ESP_LOGE(ADC_TAG, "%s - %s", __func__, "i2s_driver_install");
}      

//init ADC pad
if (i2s_set_adc_mode(unit, channel)!=ESP_OK) {
    ESP_LOGE(ADC_TAG, "%s - %s", __func__, "i2s_set_adc_mode");
}

// enable the ADC
if (i2s_adc_enable(i2s_num)!=ESP_OK) {
    ESP_LOGE(ADC_TAG, "%s - %s", __func__, "i2s_adc_enable");
}

And finally you can just use i2s_read() in a loop to get the data:

size_t size_read;
i2s_read(i2s_num, dest, size_bytes, &size_read, ticks_to_wait);

Pretty simple! – but it is even simpler with my API:

ADC adc;
const int32_t max_buffer_len = 512;
int16_t buffer[max_buffer_len][2];

auto config = adc.defaultConfig());
// default pin is 34 - change it to 32 
config.setPin(32);
adc.begin(config);

// and finally read the data 
size_t len = adc.read(buffer, max_buffer_len); 


6 Comments

chess-levin · 9. March 2024 at 14:30

I build a webradio based on an ESP32 and wanted to connect BT speakers. Therefore I bought this little device https://www.tinysineaudio.com/products/tsa5001-bluetooth-5-3-audio-transmitter-board-i2s-digital-input
* Bluetooth v5.3 specification support
* I2S digital input
* Sampling Rate: 48KHz
* Bit per Sample: 16 bit, 24bit, 32bit.
* aptX, aptX Low Latency, aptX HD, SBC and AAC
This works perfect for radio stations sending streams with 48KHz, but many stations sending @ 44,1KHz or @ 24KHz. Is there a way to reencode the received audio data before sending via I2S?
Thanks in advance

Suryadeep Mandal · 18. December 2021 at 14:24

I am a beginner and do not understand the code fully. Does the i2s_read() take input from the ADC of ESP32 connected to analog mic and convert it into I2S for data transfer? And as the ADC is only 12 bit, how does 16 bit work?

    pschatzmann · 18. December 2021 at 21:22

    I haven’t found any detailed documentation for this, but I think the 12bit limitation is still valid.
    The point is, that you can use the I2S api which supports fast DMA access to sample data from your 2 ADC pins and this is more efficient then using the analog read methods.
    However you loose some function: e.g. you can not define any attenuation.

mark donners · 8. May 2021 at 22:45

How did you deal with the buffers I2S only returning data in the first half …last half of buffer is always empty

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *