ESP32 ADC DMA continuous read configuration

noemasson
Posts: 3
Joined: Mon May 02, 2022 9:58 am

ESP32 ADC DMA continuous read configuration

Postby noemasson » Mon May 02, 2022 1:44 pm

Hello,
I am trying to use DMA with two ADC channels in order to have two readings of a same signal. It means that the ADC writes the data of both channels into the same buffer in memory. However it seems that this buffer has only 256 slots available for both of these channels and the rest is filled with something from channel 0 (see screenshot). This happens no matter how I set the buffer length in the ADC configuration.
Screenshot from 2022-05-02 12-57-54.png
Screenshot from 2022-05-02 12-57-54.png (201.41 KiB) Viewed 4635 times
I would like to fill the buffer with only data from the two desired channels but I did not manage to find an answer in the example I base my code on and in the documentation.

Here is my ADC configuration script :

Code: Select all

#define TIMES              1024
#define SAMPLE_FREQUENCY   80000

#define GET_UNIT(x)        ((x>>3) & 0x1)

#if CONFIG_IDF_TARGET_ESP32
#define ADC_RESULT_BYTE     2
#define ADC_CONV_LIMIT_EN   1                       //For ESP32, this should always be set to 1
#define ADC_CONV_MODE       ADC_CONV_SINGLE_UNIT_1  //ESP32 only supports ADC1 DMA mode
#define ADC_OUTPUT_TYPE     ADC_DIGI_OUTPUT_FORMAT_TYPE1
#endif

#if CONFIG_IDF_TARGET_ESP32
static uint16_t adc1_chan_mask = BIT(6) | BIT(7);
static uint16_t adc2_chan_mask = 0;
static adc_channel_t channel[2] = {ADC1_CHANNEL_6, ADC1_CHANNEL_7};
#endif

static const char *TAG = "ADC DMA";

static void continuous_adc_init(uint16_t adc1_chan_mask, uint16_t adc2_chan_mask, adc_channel_t *channel, uint8_t channel_num)
{
    adc_digi_init_config_t adc_dma_config = {
        .max_store_buf_size = 4096,
        .conv_num_each_intr = TIMES,
        .adc1_chan_mask = adc1_chan_mask,
        .adc2_chan_mask = adc2_chan_mask,
    };
    ESP_ERROR_CHECK(adc_digi_initialize(&adc_dma_config));

    adc_digi_configuration_t dig_cfg = {
        .conv_limit_en = ADC_CONV_LIMIT_EN,
        .conv_limit_num = 250,
        .sample_freq_hz = SAMPLE_FREQUENCY,
        .conv_mode = ADC_CONV_MODE,
        .format = ADC_OUTPUT_TYPE,
    };

    adc_digi_pattern_config_t adc_pattern[SOC_ADC_PATT_LEN_MAX] = {0};
    dig_cfg.pattern_num = channel_num;
    for (int i = 0; i < channel_num; i++) {
        uint8_t unit = GET_UNIT(channel[i]);
        uint8_t ch = channel[i] & 0x7;
        adc_pattern[i].atten = ADC_ATTEN_DB_11;
        adc_pattern[i].channel = ch;
        adc_pattern[i].unit = unit;
        adc_pattern[i].bit_width = 12;

        ESP_LOGI(TAG, "adc_pattern[%d].atten is :%d", i, adc_pattern[i].atten);
        ESP_LOGI(TAG, "adc_pattern[%d].channel is :%d", i, adc_pattern[i].channel);
        ESP_LOGI(TAG, "adc_pattern[%d].unit is :%d", i, adc_pattern[i].unit);
    }
    dig_cfg.adc_pattern = adc_pattern;
    ESP_ERROR_CHECK(adc_digi_controller_configure(&dig_cfg));
}

esp_err_t adc_init()
{
    esp_err_t error_code;

    continuous_adc_init(adc1_chan_mask, adc2_chan_mask, channel, sizeof(channel) / sizeof(adc_channel_t));

    error_code = adc1_config_width(ADC_WIDTH_BIT_12);
    if(error_code != ESP_OK)
    {
        return error_code;
    }
    adc_digi_start();

    return ESP_OK;
}

Once I get the buffer, I split it in two according to the corresponding channels to run them through a FFT :

Code: Select all

void sort_dma_data_into_buffers(uint16_t *buff_ch0, uint16_t *buff_ch1, uint32_t ret_num, uint32_t *i0, uint32_t *i1, uint16_t *result)
{
    uint32_t n0 = 0;
    uint32_t n1 = 0;
    for (int i = 0; i < ret_num; i += ADC_RESULT_BYTE) {
        adc_digi_output_data_t *p = (void*)&result[i];
        // printf("3.1\n");
        if(p->type1.channel == ADC1_CHANNEL_6)
        {
            buff_ch0[n0] = p->type1.data;
            n0++;
        }
        else if(p->type1.channel == ADC1_CHANNEL_7)
        {
            buff_ch1[n1] = p->type1.data;
            n1++;
        }
    }
    *i0 = n0;
    *i1 = n1;
}
I find that i0+i1 = 256 and I can't get more values for the 2 channels. The idea is to get more periods of a 1.2kHz signal but I can't have more than one.
Thanks for your help !

felixcollins
Posts: 125
Joined: Fri May 24, 2019 2:02 am

Re: ESP32 ADC DMA continuous read configuration

Postby felixcollins » Tue May 31, 2022 3:15 am

Did you ever get this figured out? I'm trying to do something similar - sample two channels at 17kHz. I've fiddled with the example code but can not get it to work correctly. For starters, with two channels I would expect them to alternate, but what do we see? 6,6,7,7,7,6,7.. etc Some sort of random pattern. Also, it seems like samples are duplicated, we see several samples from one channel with identical value and then a step. Why is this?

I think that the dig adc driver is buggy and the example is broken. I had a look inside the driver and it is using a very complicated thread safe ring buffer library to store the samples. This is the architecture as far as I could understand it:
1. DMA reads ADC and writes DMA buffer using linked list config (size related to conv_num_each_intr) When the linked list is complete an interrupt is raised.
2. In the interrupt the data is copied to the ring buffer (sized by the max_store_buf_size value).
3. As soon as there is data available, adc_digi_read_bytes is unblocked and the data is available. The reason adc_digi_read_bytes may not return the number you request is that it may have a block of samples at the end of the buffer. In this case you need to call again to get the rest of your requested data from the start.

Questions:

What happens if the DMA interrupt has not been serviced before the next sample is due?
Why does the driver not simply call back with the data when the DMA is finished, instead of wasting time loading it into the ring buffer?
Why is the data from two channels not perfectly interleaved?
Why do we see duplicated values in the data for a channel (even when only sampling one channel)?

I would love to get this working properly as it seems like the hardware is capable of doing what we need. Can anyone from the ESP32 project comment on this? Can someone take a look at the driver and the example and make it work?

Who is online

Users browsing this forum: No registered users and 95 guests