How To Reduce Time Between SPI Transactions

Alextesttech
Posts: 3
Joined: Tue Apr 07, 2026 6:41 pm

How To Reduce Time Between SPI Transactions

Postby Alextesttech » Tue Apr 07, 2026 8:13 pm

Hello I am trying to sample a ADS131M06 at 32kHz but I am struggling to get the time between SPI transactions below 30us any suggestions?

Code: Select all

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "ads131m0x.h"


static const char *TAG = "MAIN";
static QueueHandle_t adc_queue = NULL;

// ─── Sampling task ────────────────────────────────────────────────────────────
// Runs on Core 1 at highest priority — just reads the ADC and queues the data.
static void sample_task(void *arg)
{
    InitADC();
    adc_channel_data result = {0};
    toggleSYNC();
    delay_us(1);

    while (1)
    {
        if (waitForDRDYinterrupt(1000))
        {
            readData(&result);
            xQueueSend(adc_queue, &result, 0);  // non-blocking send
        }
    }
}

// ─── USB send task ────────────────────────────────────────────────────────────
// Runs on Core 0 — drains the queue and sends binary packets over USB CDC.
static void usb_task(void *arg)
{
    adc_channel_data result = {0};

    while (1)
    {
        if (xQueueReceive(adc_queue, &result, portMAX_DELAY))
        {
        }
    }
}

// ─── App entry point ──────────────────────────────────────────────────────────
void app_main(void)
{


    adc_queue = xQueueCreate(256, sizeof(adc_channel_data));  // bigger buffer at 32 kHz
    xTaskCreatePinnedToCore(sample_task, "adc_sample", 4096, NULL, configMAX_PRIORITIES - 1, NULL, 1);
    xTaskCreatePinnedToCore(usb_task,"Usb Send",4096,NULL,configMAX_PRIORITIES - 2,NULL,0);
    
}

Code: Select all

void InitSPI(void)
{
    /* NOTE: The ADS131M06 operates in SPI mode 1 (CPOL = 0, CPHA = 1). */

    spi_bus_config_t bus_cfg = {
        .mosi_io_num     = PIN_MOSI,
        .miso_io_num     = PIN_MISO,
        .sclk_io_num     = PIN_SCLK,
        .quadwp_io_num   = -1,
        .quadhd_io_num   = -1,
        .max_transfer_sz = 64,  /* Larger than the biggest frame we will ever send */
        //.isr_cpu_id = 1,
    };

    esp_err_t ret = spi_bus_initialize(SPI_HOST_DEVICE, &bus_cfg, SPI_DMA_CH_AUTO);
    if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE)
    {
        ESP_LOGE(TAG, "spi_bus_initialize failed: %s", esp_err_to_name(ret));
        return;
    }

    spi_device_interface_config_t dev_cfg = {
        .clock_speed_hz = SPI_CLOCK_HZ,
        .mode           = 1,            /* CPOL=0, CPHA=1 */
        .spics_io_num   = PIN_CS,          
        .queue_size     = 2,
        .flags          = SPI_DEVICE_NO_DUMMY,
        //.input_delay_ns = 1,
        .cs_ena_pretrans = 1,
        //.cs_ena_posttrans = 2, 
        .clock_source = SPI_CLK_SRC_XTAL,
        
        
    };
    
    ret = spi_bus_add_device(SPI_HOST_DEVICE, &dev_cfg, &s_spi);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "spi_bus_add_device failed: %s", esp_err_to_name(ret));
    }
    
}

Code: Select all

void spiSendReceiveArrays(const uint8_t dataTx[], uint8_t dataRx[], const uint8_t byteLength)
{
   
    assert(dataTx && dataRx);

    //spi_device_acquire_bus(s_spi,portMAX_DELAY);
    t.length = (size_t) byteLength * 8;
    t.tx_buffer = dataTx;
    t.rx_buffer = dataRx;
    spi_device_polling_transmit(s_spi, &t);
    //spi_device_release_bus(s_spi);
}

Code: Select all

bool readData(adc_channel_data *DataStruct)
{
    
    int i;
    uint8_t crcTx[4]                        = { 0 };
    uint8_t dataTx[24] = { 0 };
    uint8_t dataRx[24] = { 0 };
    uint8_t bytesPerWord                    = getWordByteLength();

#ifdef ENABLE_CRC_IN
    // Build CRC word (only if "RX_CRC_EN" register bit is enabled)
    uint16_t crcWordIn = calculateCRC(&DataTx[0], bytesPerWord * 2, 0xFFFF);
    crcTx[0] = upperByte(crcWordIn);
    crcTx[1] = lowerByte(crcWordIn);
#endif

    /* Set the nCS pin LOW */
    //setCS(LOW);

    // Send NULL word, receive response word
    uint16_t opcodes[7] = {0};
    opcodes[0] = OPCODE_NULL;
    uint8_t numberOfBytes = buildSPIarray(opcodes, 7, dataTx);

    // Send the opcode (and crc word, if enabled)
        spiSendReceiveArrays(dataTx,dataRx,numberOfBytes);

    DataStruct->response = combineBytes(dataRx[0], dataRx[1]);

    // (OPTIONAL) Do something with the response (STATUS) word.
    // ...Here we only use the response for calculating the CRC-OUT
    //uint16_t crcWord = calculateCRC(&dataRx[0], bytesPerWord, 0xFFFF);

    // (OPTIONAL) Ignore CRC error checking
    uint16_t crcWord = 0;

    
    DataStruct->channel0 = signExtend(&dataRx[3]);

#if (CHANNEL_COUNT > 1)

    DataStruct->channel1 = signExtend(&dataRx[6]);
    

#endif
#if (CHANNEL_COUNT > 2)

    DataStruct->channel2 = signExtend(&dataRx[9]);

#endif
#if (CHANNEL_COUNT > 3)

    DataStruct->channel3 = signExtend(&dataRx[12]);

#endif
#if (CHANNEL_COUNT > 4)

    DataStruct->channel4 = signExtend(&dataRx[15]);

#endif
#if (CHANNEL_COUNT > 5)

    DataStruct->channel5 = signExtend(&dataRx[18]);

#endif
#if (CHANNEL_COUNT > 6)

    DataStruct->channel6 = signExtend(&dataRx[21]);

#endif
#if (CHANNEL_COUNT > 7)

    DataStruct->channel7 = signExtend(&dataRx[24]);

#endif

    
    uint8_t crcOffset = (CHANNEL_COUNT + 1) * bytesPerWord;
    DataStruct->crc = combineBytes(dataRx[crcOffset], dataRx[crcOffset + 1]);

    /* NOTE: If we continue calculating the CRC with a matching CRC, the result should be zero.
     * Any non-zero result will indicate a mismatch.
     */
    //crcWord = calculateCRC(&dataRx[0], bytesPerWord, crcWord);

    /* Set the nCS pin HIGH */
    //setCS(HIGH);

    // Returns true when a CRC error occurs
    return ((bool) crcWord);
}
Attachments
SPI logic analyzer capture.jpg
SPI logic analyzer capture.jpg (305.99 KiB) Viewed 119 times

MicroController
Posts: 2672
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: How To Reduce Time Between SPI Transactions

Postby MicroController » Thu Apr 09, 2026 3:29 pm

I am trying to sample a ADS131M06 at 32kHz but I am struggling to get the time between SPI transactions below 30us
How many channels are you reading? I.e. what's your frame size/rate?

Code: Select all

    // Send NULL word, receive response word
    uint16_t opcodes[7] = {0};
    opcodes[0] = OPCODE_NULL;
    uint8_t numberOfBytes = buildSPIarray(opcodes, 7, dataTx);
Doesn't look like this should be done repeatedly before every transaction.

Handling one transaction's results can also be done concurrently with the next transaction running (double-buffering), either in another task or just by interleaving like

Code: Select all

waitForDRDYinterrupt(1000);
spi_device_polling_start(s_spi, &trans_1);

while(1) {
  spi_device_polling_end(s_spi,1);

  // <- Prepare next trans_2 here.
    
  waitForDRDYinterrupt(1000);  
  spi_device_polling_start(s_spi, &trans_2)
  
  // trans_2 is running; extract and pass on data from transaction 1...
  
  spi_device_polling_end(s_spi,1);
  
  // <- Prepare next trans_1 here.
  
  waitForDRDYinterrupt(1000);  
  spi_device_polling_start(s_spi, &trans_1)
  
  // trans_1 is running; extract and pass on data from transaction 2...  
}

Alextesttech
Posts: 3
Joined: Tue Apr 07, 2026 6:41 pm

Re: How To Reduce Time Between SPI Transactions

Postby Alextesttech » Thu Apr 09, 2026 5:48 pm

How many channels are you reading? I.e. what's your frame size/rate?
Six channels, a frame is 8 words, each word is 24bits so 192 bits at a clock speed of 20Mhz.

Code: Select all

    // Send NULL word, receive response word
    uint16_t opcodes[7] = {0};
    opcodes[0] = OPCODE_NULL;
    uint8_t numberOfBytes = buildSPIarray(opcodes, 7, dataTx);
    
Doesn't look like this should be done repeatedly before every transaction.
After looking at my code thinking about it I came to the conclusion that i needed to separate the actual transaction of data and processing, which is along the lines of what you were suggesting here
Handling one transaction's results can also be done concurrently with the next transaction running (double-buffering), either in another task or just by interleaving like

Code: Select all

waitForDRDYinterrupt(1000);
spi_device_polling_start(s_spi, &trans_1);

while(1) {
  spi_device_polling_end(s_spi,1);

  // <- Prepare next trans_2 here.
    
  waitForDRDYinterrupt(1000);  
  spi_device_polling_start(s_spi, &trans_2)
  
  // trans_2 is running; extract and pass on data from transaction 1...
  
  spi_device_polling_end(s_spi,1);
  
  // <- Prepare next trans_1 here.
  
  waitForDRDYinterrupt(1000);  
  spi_device_polling_start(s_spi, &trans_1)
  
  // trans_1 is running; extract and pass on data from transaction 2...  
}
I will rewrite and report back.

Alextesttech
Posts: 3
Joined: Tue Apr 07, 2026 6:41 pm

Re: How To Reduce Time Between SPI Transactions

Postby Alextesttech » Fri Apr 10, 2026 12:45 pm

I rewrote the read function to be simpler:

Code: Select all

bool readData(adc_channel_data *DataStruct)
{
    spiSendReceiveArrays(testtx,testrx,24);
    return true;
}
this reduced the time to ~22.4us between transactions.
I have been informed that the hardware will now require two of the ADS131M06 parts.
Would it be worth while to manually control the CS lines for each of the ADCs or just set up a new SPI device interface Config ?
Attachments
SPI logic analyzer capture.jpg
SPI logic analyzer capture.jpg (364.71 KiB) Viewed 58 times

MicroController
Posts: 2672
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: How To Reduce Time Between SPI Transactions

Postby MicroController » Mon Apr 13, 2026 3:32 pm

Would it be worth while to manually control the CS lines for each of the ADCs ... ?
I'd say no.
But the "double-buffering" approach from above may make even more sense with two ADCs.
If the ADCs don't share a common clock source, you'll also have to expect the clocks to slightly drift relative to one another; one ADC might produce more samples per second than the other, so reading them out in sync may become a bit more complicated.

Who is online

Users browsing this forum: trendictionbot and 2 guests