Page 1 of 1

I2S ADC Read - erroneous values for some input voltage levels

Posted: Thu Jun 23, 2022 1:07 pm
by gwentech
Hello. I have an application where an ESP32-WROOM-32D is reading ADC values via I2S at 10kHz continuously. It works great throughout the ADC input range except for a few regions where it seems to read the same value regardless of the input voltage.

It looks like the ADC is reading correctly, but the I2S values are incorrect. Sample values of one such region below. I've tried many different I2S handling schemes including this example (https://github.com/espressif/arduino-esp32/pull/2302), but same results. This happens on many devices and the problem regions are around 0x600, 0x4FF, 0x87F, and 0x8FF. Any ideas?

Example problem region:

InputV -> I2S val [analogRead()]
----------------------------------------------
1.34V -> 1438 [1438] <---I2S val is same as atomic read from analogRead(), good
1.35V -> 1455 [1455] <---I2S val is same as atomic read from analogRead(), good
1.36V -> 1536 [1462] <---I2S val is incorrect! Fills buffer with 0x600. Note, analogRead() value makes sense.
1.37V -> 1536 [1474] <---I2S val is incorrect! Fills buffer with 0x600. Note, analogRead() value makes sense.
1.38V -> 1536 [1487] <---I2S val is incorrect! Fills buffer with 0x600. Note, analogRead() value makes sense.
1.39V -> 1536 [1498] <---I2S val is incorrect! Fills buffer with 0x600. Note, analogRead() value makes sense.
1.40V -> 1536 [1509] <---I2S val is incorrect! Fills buffer with 0x600. Note, analogRead() value makes sense.
1.41V -> 1536 [1521] <---I2S val is incorrect! Fills buffer with 0x600. Note, analogRead() value makes sense.
1.42V -> 1540 [1535] <---I2S val is nearly the same as atomic read from analogRead(), probably good

1.43V -> 1551 [1551] <---I2S val is same as atomic read from analogRead(), good
1.44V -> 1566 [1566] <---I2S val is same as atomic read from analogRead(), good

Code: Select all

void adc_task(void *pvParameters)
{
    int bytes_read,bytes_read_sum;
    esp_err_t rc[ITERATIONS];
    esp_err_t composite_rc;
    int i;
 
    rawADCValue=0;  

    // Give our application task time to make sure the relay is open and that our nonvolatile memory is read.
    vTaskDelay(1000);

    i=0;
    do
    {
    InitADC();
                        
    // Overriding this until we can get back to the ADC part.                    
    adc_ready_for_app=true;

    while(adc_init_ok)
    {            
        // Get each bank of current readings
        bytes_read_sum=0;
        for(i=0;i<ITERATIONS;i++)
        {
           rc[i]=i2s_read(I2S_NUM_0, (char*)&i2s_read_buff[i*(SIZEOF_BUF>>1)], SIZEOF_BUF , (size_t*)&bytes_read, portMAX_DELAY);
           bytes_read_sum+=bytes_read;
        }
    
        // See if all bank reads are ok.  composite_rc will end up with the last non-ok value.
        composite_rc=ESP_OK;
        for(i=0;i<ITERATIONS;i++)
            if(rc[i]!=ESP_OK)
                composite_rc=rc[i];

        // If all bank readings were good, crunch the numbers.
        if(composite_rc==ESP_OK)
        {
            i2s_stop(I2S_NUM_0);
            i2s_adc_disable(I2S_NUM_0);
           
            // send bytes_read to crunchNumbers()
            crunchNumbers(bytes_read_sum);
            memset((void*)i2s_read_buff,0,SIZEOF_BUF*ITERATIONS);

            i2s_zero_dma_buffer(I2S_NUM_0);
            i2s_start(I2S_NUM_0);
            while(i2s_adc_enable(I2S_NUM_0) != ESP_OK)
            {
                Serial.printf("\n\rADC: i2c enable FAILED");
                vTaskDelay(100);               
            }
 
        }
        vTaskDelay(1);

    }

    Serial.printf("\n\rADC: TASK ENDED!!");
    vTaskDelete(NULL);

}

static void InitADC(void)
{
    currentReading=0;
    adc_init_ok=0;
    adc_ready_for_app=false;
    zeroCurrentVal=0;

    // Configure I2S to do periodic ADC reads and store them in the DMA buffer. 
    // The benefit is that we can do ADC reads and store results without using
    // the cpu core.
    i2s_config.mode                 = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN);
    i2s_config.sample_rate          = I2S_SAMPLE_RATE;              // The format of the signal using ADC_BUILT_IN
    i2s_config.bits_per_sample      = I2S_BITS_PER_SAMPLE_16BIT;    // is fixed at 12bit, stereo, MSB
    i2s_config.channel_format       = I2S_CHANNEL_FMT_ALL_LEFT;
    i2s_config.communication_format = I2S_COMM_FORMAT_I2S_MSB;
    i2s_config.intr_alloc_flags     = ESP_INTR_FLAG_LEVEL1;
    i2s_config.dma_buf_count        = 8;
    i2s_config.dma_buf_len          = SIZEOF_BUF;
    i2s_config.use_apll             = false;
    i2s_config.tx_desc_auto_clear   = false;
    i2s_config.fixed_mclk           = 0;

    if (adc_gpio_init(ADC_UNIT_1, ADC_CHANNEL_0) == ESP_OK)
    {
        // config adc atten and width.
        // 12-bits (4095) is 3.3V
        adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_11db);
        adc1_config_width(ADC_WIDTH_12Bit);

        esp_adc_cal_characterize(ADC_UNIT_1,ADC_ATTEN_DB_11,ADC_WIDTH_12Bit,DEF_VREF, &adc1_cal);

        adc_power_acquire();

        // ALl operations must success
        if (i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL) == ESP_OK)
        {
            Serial.printf("\n\rADC: driver installed successfully");
            // I2S_CHANNEL_MONO has storing a single sample
            if (i2s_set_clk(I2S_NUM_0, I2S_SAMPLE_RATE, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO) == ESP_OK)
            {
                Serial.printf("\n\rADC: i2s clock set successfully");
                // Setup I2S driver in ADC conversion mode
                if (i2s_set_adc_mode(ADC_UNIT_1, ADC1_CHANNEL_0) == ESP_OK)
                {
                    Serial.printf("\n\rADC: mode set successfully");

                    //? ADC_SAMPLE_START();
                    i2s_zero_dma_buffer(I2S_NUM_0);
                    if (i2s_adc_enable(I2S_NUM_0) == ESP_OK)
                    {
                        Serial.printf("\n\rADC: enabled successfully");
                        adc_init_ok=true;
                    }
                    else
                        Serial.printf("\n\rADC: i2c enable FAILED");

                }
                else
                    Serial.printf("\n\rADC: mode set FAILED");

            }
            else
                Serial.printf("\n\rADC: i2s clock FAILED");
        }
        else
            Serial.printf("\n\rADC: driver install FAILED");      
            
    }
    else
        Serial.printf("\n\rADC: gpio init FAILED");
   
 }


static void crunchNumbers(int bytes_read)
{
    int i;
    float tempf1;
    float acc;
    int samples;
    double long accl;
    uint32_t reading;
    int tempint;
    int allZerosFlag=1;

    // Nothing to do. No big deal.
    if(bytes_read==0)
    {
        //Serial.printf("\n\rADC: no bytes");
        return;
    }

    samples=bytes_read>>1;

    accl=0;
    for(i=0;i<samples;i++)
    {
        // Take reading
        reading=(int)i2s_read_buff[i];
        accl+=(reading*reading);
        if(reading)
            allZerosFlag=0;
    }

    accl/=samples;

    tempf1=accl;
    tempf1=sqrtf(accl);

    // round it
    tempint=tempf1;
    if((tempf1-tempint)>0.5f)
        tempint++;

    // Stored for reference in calibration.
    rawADCValue=tempint;        


}