Page 1 of 1

How to use SPI of esp32 to read ADC samples periodically with fixed sample rate?

Posted: Sat Oct 21, 2017 3:30 am
by liubenyuan
Hi, I want to use two ADC (ADS8864) and esp32 to sample analog waveform and send them to PC using WIFI. The data are sampled in a fixed sample rate, for example 100ksps for ADS8864, and I want to use SPI to read them into a buffer. When the data are ready, for example, 256 samples have been collected. I memcpy them out and send them to PC.

I config a timer of 200kHz (5us), and at the interrupt of the timer, I toggle a GPIO (which is connected to the CONV port of ADC), and at every two timer interrupt, I send a semaphore to notice a task to read the ADC using the SPI peripheral of esp32.

Image

However, 1. we found that the CONV signal is not stable, which is not precisely 100khz with sharp edges. the duty cycle is approximately 0.4 and the period is changing. 2. How could I read the samples out using a SPI DMA? so that SPI can quietly sample the data and put them in a list or a cyclic buffer. 3. Can I run SPI sample in another core and notify current core when samples are ready?

Is it right in this way (for esp32) to use SPI as a ADC sampler reader and periodically (100KHz) sample an analog waveform? If not, what is the right way to do so?

Code: Select all

#define TIMER_INTR_SEL TIMER_INTR_LEVEL  /*!< Timer level interrupt */
#define TIMER_GROUP    TIMER_GROUP_0     /*!< Test on timer group 0 */
#define TIMER_DIVIDER   16               /*!< Hardware timer clock divider, 80 to get 1MHz clock to timer */
#define TIMER_SCALE    (TIMER_BASE_CLK / TIMER_DIVIDER)  /*!< used to calculate counter value */
#define TIMER_FINE_ADJ   (1.4*(TIMER_BASE_CLK / TIMER_DIVIDER)/1000000) /*!< used to compensate alarm value */
#define TIMER_INTERVAL0_SEC   (0.000005)   /*!< test interval for timer 0 */

xSemaphoreHandle semaphore;
volatile int cnt = 0;
static spi_device_handle_t spi1,spi2;

void IRAM_ATTR timer_group0_isr(void *para)
{
    int timer_idx = (int) para;
    int led_val;
    uint32_t intr_status = TIMERG0.int_st_timers.val;
    if((intr_status & BIT(timer_idx)) && timer_idx == TIMER_0) {
        TIMERG0.hw_timer[timer_idx].update = 1;
        TIMERG0.int_clr_timers.t0 = 1;

        led_val = (cnt+1)%2;
        gpio_set_level(GPIO_CONV, led_val);
        cnt++;

        if (led_val==0){
            // if CONVST falling down, tell SPI to read!
            xSemaphoreGiveFromISR(semaphore, NULL);
        }

        TIMERG0.hw_timer[timer_idx].config.alarm_en = 1;
    }

}

// config and start timer
static void tg0_timer0_init()
{
    int timer_group = TIMER_GROUP_0;
    int timer_idx = TIMER_0;
    timer_config_t config;
    config.alarm_en = 1;
    config.auto_reload = 1;
    config.counter_dir = TIMER_COUNT_UP;
    config.divider = TIMER_DIVIDER;
    config.intr_type = TIMER_INTR_SEL;
    config.counter_en = TIMER_PAUSE;
    /*Configure timer*/
    timer_init(timer_group, timer_idx, &config);
    /*Stop timer counter*/
    timer_pause(timer_group, timer_idx);
    /*Load counter value */
    timer_set_counter_value(timer_group, timer_idx, 0x00000000ULL);
    /*Set alarm value*/
    timer_set_alarm_value(timer_group, timer_idx, (TIMER_INTERVAL0_SEC * TIMER_SCALE) - TIMER_FINE_ADJ);
    /*Enable timer interrupt*/
    timer_enable_intr(timer_group, timer_idx);
    /*Set ISR handler*/
    timer_isr_register(timer_group, timer_idx, timer_group0_isr, (void*) timer_idx, ESP_INTR_FLAG_IRAM, NULL);
    /*Start timer counter*/
    timer_start(timer_group, timer_idx);
}

void timer0_task()
{
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));
    t.length= 16;
    t.flags = SPI_TRANS_USE_RXDATA;

    while(1){
        xSemaphoreTake(semaphore, portMAX_DELAY);

        spi_device_transmit(spi1, &t);
        // sample ADC1

        spi_device_transmit(spi2, &t);
        // sample ADC2
    }
    //taskdelete
}

Code: Select all

    esp_err_t ret;
    spi_bus_config_t buscfg1={
        .miso_io_num=PIN_NUM_MISO1,
        .mosi_io_num=PIN_NUM_MOSI1,
        .sclk_io_num=PIN_NUM_CLK1,
        .quadwp_io_num=-1,
        .quadhd_io_num=-1
    };
    spi_device_interface_config_t devcfg1={
        .clock_speed_hz=16*1000*1000,   //Clock out at 16MHz
        .mode=3,                        //SPI mode 3
        .queue_size=1,                  //We want to be able to queue 1 transactions at a time
    };

    spi_bus_config_t buscfg2={
        .miso_io_num=PIN_NUM_MISO2,
        .mosi_io_num=PIN_NUM_MOSI2,
        .sclk_io_num=PIN_NUM_CLK2,
        .quadwp_io_num=-1,
        .quadhd_io_num=-1
    };
    spi_device_interface_config_t devcfg2={
        .clock_speed_hz=16*1000*1000,   //Clock out at 16MHz
        .mode=3,                        //SPI mode 3
        .queue_size=1,                  //We want to be able to queue 1 transactions at a time
    };

    ret=spi_bus_initialize(HSPI_HOST, &buscfg1, 1);
    ret=spi_bus_initialize(VSPI_HOST, &buscfg2, 2);
    //assert(ret==ESP_OK);

    ret=spi_bus_add_device(HSPI_HOST, &devcfg1, &spi1);
    ret=spi_bus_add_device(VSPI_HOST, &devcfg2, &spi2);
    //assert(ret==ESP_OK);

Re: How to use SPI of esp32 to read ADC samples periodically with fixed sample rate?

Posted: Wed Oct 25, 2017 3:37 pm
by liubenyuan
:cry: :cry:

It seems like there is no one use esp32 as a ADC sampler for wireless data collection.

Is there any demo on SPI dma using esp32?

Re: How to use SPI of esp32 to read ADC samples periodically with fixed sample rate?

Posted: Fri Jan 08, 2021 9:53 am
by Jara777
Hi, I am also working on ADS8864 and ESP32 recently, but I found it difficult to find the Arduino Library for ADS8864. Would you mind giving me some information on the library?

Re: How to use SPI of esp32 to read ADC samples periodically with fixed sample rate?

Posted: Fri Jan 08, 2021 8:29 pm
by amoghjain
I also had issues with ADC but it was due to channels and wifi, specifically, adc2 cannot be used when Wifi is on.

Re: How to use SPI of esp32 to read ADC samples periodically with fixed sample rate?

Posted: Sun Jan 10, 2021 10:43 pm
by PeterR
You will find this hard to impossible with an ESP depending on exactly what other I/O etc you use.
The ESP has bus contension issues.
Now the above does depend on if you can tolerate occasional (say) 2mS drop outs.

400 KHz ->25 uS. AFAIR it takes 10 uS+ to set up a SPI DMA. You could pre-allocate the transaction but still there is a big overhead in setting the registers.
Notifying a task is one way, actually the only way with the IDF as the SPI library is task based.
So that leaves you exposed to ISRs cutting across. You need to be crystal clear with both your own ISRs and IDF framework interrupts....

& even after you are clear on all the software aspects don't forget the bus limitations which might leave you waiting even though you are the only kid on your core.

Back in the day I had an LCD DMA sometimes blocked by PC104 DMA. Really messed with V/H SYNCH. Thankfully (& many weeks) latter found that there was a secret 'supper aggressive DMA' option which saved the LCD's day.
Just saying that bus & DMA ESP arbitration details are not supper clear & few here seem that focussed on phase gain.

Re: How to use SPI of esp32 to read ADC samples periodically with fixed sample rate?

Posted: Mon Jan 11, 2021 1:33 am
by ESP_Sprite
The ESP has bus contension issues.
Source? The ESP32 buses in general are wide enough in my experience. You can however get a delay in getting the CPU to service your peripheral, as it also needs to tend to other tasks as well, some more important than peripheral servicing.

This ADC does, however, have an access pattern that makes it very difficult to use without much CPU involvement... I don't immediately have a solution for this either.