SPI slave: multiple TRANS_DONE interrupts

PavelA
Posts: 4
Joined: Mon Jul 23, 2018 8:28 am

SPI slave: multiple TRANS_DONE interrupts

Postby PavelA » Mon Jul 23, 2018 12:12 pm

Hello.

We use ESP-WROOM-32 module (ESP32 rev 0) and ESP-IDF v3.1/
We use SPI in slave mode.

We have encountered the following issue.
We continuously launch SPI transfers from master
(at least 300 times per second).
We have tested two different transfer sizes
(64 bytes with and without DMA, 1536 bytes with DMA).
When necessay amount of data is clocked into ESP32,
interrupt SPI_TRANS_DONE_INT occurs more than once:
a) at least twice after full packet is transferred but before CS signal rises.
b) and at least once after CS signal rises.
So there are at least two extra interrupts.

Image

We consider it is not normal.

And maybe that wouldn't bother us,
but we have to fill transfer queue continuously with outgoing packets.
Because of above-mentioned extra interrupts
at least two transfer descriptors are lost by the SPI driver on each actual SPI transfer
(on each interrupt new transfer descriptor is taken from the queue,
but only the last transfer really occurs).

We tried to find a workaround.
And we processed only one interrupt of the group and ignored all others.
But in this case after some (random) time of operation
SPI slave starst failing to receive packets from master
(only first byte of the transfer is received).

What can be done to fix it?

Best regards,
Pavel
Last edited by PavelA on Tue Jul 24, 2018 8:09 am, edited 1 time in total.

ESP_Sprite
Posts: 9764
Joined: Thu Nov 26, 2015 4:08 am

Re: SPI slave: multile TRANS_DONE interrupts

Postby ESP_Sprite » Tue Jul 24, 2018 1:49 am

Do you have some source code you can show us? If this happened on a hardware level, I'd think our SPI driver also wouldn't work, but I haven't seen issues with that yet, so I imagine you're doing something odd in your sources.

PavelA
Posts: 4
Joined: Mon Jul 23, 2018 8:28 am

Re: SPI slave: multiple TRANS_DONE interrupts

Postby PavelA » Tue Jul 24, 2018 10:56 am

Here is our example.
In it we do not queue multiple transfers.
Just initialize SPI and launch transfers one by one.
We used it to investigate issue with multiple SPI interrupts on each transfer.

Code: Select all

#define SPI_HOST        VSPI_HOST
#define GPIO_MOSI       23
#define GPIO_MISO       19
#define GPIO_SCLK       18
#define GPIO_CS         5

#define INTERNAL_QSIZE  60
#define SPI_BUF_SIZE    1536
#define SPI_PKT_SOF     0x55AA

DRAM_ATTR uint8_t spi_rx_static_buf[SPI_BUF_SIZE];
DRAM_ATTR uint8_t spi_tx_static_buf[SPI_BUF_SIZE];

TaskHandle_t xHandleSlaveTask = NULL;

static int pkt_cnt = 0;
static int fail_cnt = 0;

struct spi_pkt {
    uint16_t sof;
    uint16_t ver;
    uint16_t cmd;
    uint16_t length;
    uint16_t state;
    uint8_t free_txbuf;
    uint8_t free_rxbuf;
    uint8_t reserved[4];
};

/**
 * @brief Main spi task.
 * 
 * @param pvParameters 
 */
void spi_slave_task( void * pvParameters)
{
    int cnt = 0;
    int ret = 0;
    spi_slave_transaction_t dt;

    ESP_LOGI("spi_slave_task","");

    dt.length = SPI_BUF_SIZE * 8;
    
    dt.rx_buffer = spi_rx_static_buf;
    dt.tx_buffer = spi_tx_static_buf;

    while(1) 
    {
        cnt++;

        ret = spi_slave_queue_trans(SPI_HOST, &dt, portMAX_DELAY);
    
        if (ret != ESP_OK)
        {
            ESP_LOGI("spi_slave_queue_trans", "%d ", ret);
        } 
        
        spi_slave_get_trans_result(SPI_HOST, &ret_tr, portMAX_DELAY);
        
        if ( dt.rx_buffer ) {
            struct spi_pkt* pkt = (struct spi_pkt*) dt.rx_buffer;
            
            if (pkt->sof == SPI_PKT_SOF){
                pkt_cnt++;
            } else {
                fail_cnt++;
            }
        } 
    }
}

void spi_slave_drv_init()
{
    esp_err_t ret;

    //Configuration for the SPI bus
    spi_bus_config_t buscfg={
        .mosi_io_num=GPIO_MOSI,
        .miso_io_num=GPIO_MISO,
        .sclk_io_num=GPIO_SCLK
    };

    //Configuration for the SPI slave interface
    spi_slave_interface_config_t slvcfg={
        .mode = 0,
        .spics_io_num = GPIO_CS,
        .queue_size = INTERNAL_QSIZE,
        .flags = 0,
    };


    //Configure handshake line as output
    //Enable pull-ups on SPI lines so we don't detect rogue pulses when no master is connected.
    gpio_set_pull_mode(GPIO_MOSI, GPIO_PULLUP_ONLY);
    gpio_set_pull_mode(GPIO_SCLK, GPIO_PULLUP_ONLY);
    gpio_set_pull_mode(GPIO_CS, GPIO_PULLUP_ONLY);

    //Initialize SPI slave interface
    ret = spi_slave_initialize(SPI_HOST, &buscfg, &slvcfg, 1);
    assert(ret==ESP_OK);

    ESP_LOGI("SPI slave init","");

    BaseType_t xReturned;

    xReturned = xTaskCreate(&spi_slave_task, "spi_slave_task", 5 * configMINIMAL_STACK_SIZE, NULL, 5, &xHandleSlaveTask);
    if( xReturned != pdPASS )
    {
        ESP_LOGE ("xTaskCreate", "spi_slave_task: %d", xReturned);
    }
}

//Main application
void app_main()
{
    spi_slave_drv_init();
}

ESP_Sprite
Posts: 9764
Joined: Thu Nov 26, 2015 4:08 am

Re: SPI slave: multiple TRANS_DONE interrupts

Postby ESP_Sprite » Wed Jul 25, 2018 2:27 am

...Your example doesn't match with what you say, I see just you using the SPI slave driver as normal. I can see no place where you hook the TRANS_DONE interrupt. Does this example already exhibit what you state, and if so, how?

PavelA
Posts: 4
Joined: Mon Jul 23, 2018 8:28 am

Re: SPI slave: multiple TRANS_DONE interrupts

Postby PavelA » Wed Jul 25, 2018 7:47 am

Yes, in this example we use SPI driver as normal, but even with it we already see multiple TRANS_DONE interrupts.
We also added gpio_set_level() calls on a dedicated GPIO to spi_intr() in spi_slave.c (set "1" after TRANS_DONE bit check and "0" on exit of spi_intr() procedure) to see when interrupts are processed.

ESP_Sprite
Posts: 9764
Joined: Thu Nov 26, 2015 4:08 am

Re: SPI slave: multiple TRANS_DONE interrupts

Postby ESP_Sprite » Thu Jul 26, 2018 4:44 am

Yes, this is because the driver is optimized to handle multiple queued transactions: it will normally queue the next transaction in the queue in the interrupt handler. However, in your case, you only have one transfer queued and you wait for that transfer to complete before queueing the next one. When the SPI hardware is done with this transfer, the interrupt handler will trigger and see that there's nothing more in the queue, and instead of acknowledging the interrupt to make it go away (as in spi_slave.c:395), it will simply disable the interrupts; the flag is still set but won't generate an interrupt. As soon as you queue a new transfer, the queueing code won't duplicate the logic to handle the new transfer, but will simply re-enable interrupts (spi_slave.c:266), which makes the interrupt handler trigger a second time. This time, it notices there's a new transfer and happily handles it. In other words: the multiple interrupts here are an artifact of the implementation of the driver when used this way and are intentional, and not a hardware feature/flaw.

PavelA
Posts: 4
Joined: Mon Jul 23, 2018 8:28 am

Re: SPI slave: multiple TRANS_DONE interrupts

Postby PavelA » Thu Jul 26, 2018 7:25 am

In the example I provided we queue transfers one by one.

But in our real application we have to fill transfer queue continuously.
There are usually more than one queued transfers at each particular moment in time.
And several of them a read from the queue
while several interrupts occur after one transfer is done.
But only one new transfer (which is read from the queue last) really follows.
So we just loose a number of queued descriptors.

ESP_Sprite
Posts: 9764
Joined: Thu Nov 26, 2015 4:08 am

Re: SPI slave: multiple TRANS_DONE interrupts

Postby ESP_Sprite » Thu Jul 26, 2018 1:33 pm

Again, do you have code we can look at?

Who is online

Users browsing this forum: No registered users and 32 guests