espressif / arduino-esp32

Arduino core for the ESP32
GNU Lesser General Public License v2.1
13.28k stars 7.35k forks source link

I2S ADC sample rates are off by a factor of 0.82 #9811

Open qschibler opened 3 months ago

qschibler commented 3 months ago

Board

Adafruit Huzzah 32

Device Description

Nothing else than a microphone + Huzzah board

Hardware Configuration

The board is attached to an analog amplified microphone. The output of the microphone is connected to channel 6 of ADC 1.

Version

v3.0.1

IDE Name

Arduino IDE

Operating System

Windows 10

Flash frequency

80MHz

PSRAM enabled

yes

Upload speed

921600

Description

When I sample the analog signal coming from the microphone using I2S ADC_BUILT_IN mode, the real sampling rate = 0.82*i2s_config.sample_rate. I've tested that the ratio is constant accross the following sampling rate value : 30000, 40000, 44100, 50000, 60000.

Sketch

QueueHandle_t queue;

uint16_t buf[1024];

int start;
int sample_rate = 30000;
void setup() {
  Serial.begin(921600);

  i2s_config_t i2s_config = 
  {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
    .sample_rate = sample_rate,              // The format of the signal using ADC_BUILT_IN
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // is fixed at 12bit, stereo, MSB
    .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
    .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 4,                 
    .dma_buf_len = sizeof(buf)/sizeof(*buf),
    .use_apll=0,
    .tx_desc_auto_clear=1,
    .fixed_mclk = 0,
  };

  i2s_driver_install(I2S_NUM_0, &i2s_config, 1, &queue);
  i2s_set_adc_mode(ADC_UNIT_1, ADC1_CHANNEL_6);
  i2s_adc_enable(I2S_NUM_0);

  start = millis();
}
i2s_event_t event;
int samples = 0;

void loop() {
  // put your main code here, to run repeatedly:
  xQueueReceive(queue, &event, portMAX_DELAY);
  if(event.type == I2S_EVENT_RX_DONE)
  {
    size_t bytes_read;
    i2s_read(I2S_NUM_0, &buf, sizeof(buf), &bytes_read, 0);

    samples += bytes_read / 2;
    float rate = samples*1000/(millis()-start);
    float ratio = rate / (float)sample_rate;
    Serial.println(ratio);
  }
}

Debug Message

Nothing to show here

Other Steps to Reproduce

No response

I have checked existing issues, online documentation and the Troubleshooting Guide

me-no-dev commented 2 months ago

I2S has changed in IDF 5.1. I guess you did get a deprecation notice when you compiled. Look into ADC continuous mode.

BTW, the code you presented is not Arduino API code, but rather direct ESP-IDF code. We have our own abstractions in the form of I2S library (that does not handle ADC/DAC) and ADC continuous mode: https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-adc.h#L80-L127

qschibler commented 2 months ago

Thanks, I didn't get the deprecation notice when I compiled (at least through the Arduino IDE). I looked at the arduino API ADC continuous mode, I don't really understand how it works. You can ask for a number of adc reads, and get the mean value of those ? If so, the sampling frequency is the real one or the sampling frequency of the mean values ? Also if you get notified everytime there is a value to be read, it isn't using the DMA is it ?

lbernstone commented 2 months ago

The continuous driver uses a ringbuffer. When a conversion frame is full, it sends a notification- not with every sample. https://docs.espressif.com/projects/esp-idf/en/release-v5.1/esp32s3/api-reference/peripherals/adc_continuous.html#register-event-callbacks

qschibler commented 2 months ago

Yes that is what I gathered from the ESP-IDF doc, but the arduino API uses analogContinuousRead, which -from the arduino example code- seems to only return a single value via the buffer argument. EDIT: read the source code, and indeed the arduino API average over the conversion frame value

qschibler commented 2 months ago

I ported the code to the ESP-IDF continuous adc API, and I get the same result. I will edit this post with the code once I have time to make a minimal working example

softhack007 commented 2 months ago

@qschibler your example seems to measure the time for receiving the I2S_EVENT_RX_DONE event, which is indirect as the time when you receive the event may not be exactly the point in time when I2S has completed to fill its buffer.

Did you also do a more direct test, like feeding a 2Khz test signal into the microphone, then checking if you actually have a 2Khz signal in the data delivered by i2s_read() ?