How to use SPI of esp32 to read ADC samples periodically with fixed sample rate?
Posted: Sat Oct 21, 2017 3:30 am
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.
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?
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.
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);