Use SPI bus concurrently from two tasks

CodeOne
Posts: 4
Joined: Sat Mar 03, 2018 9:58 am

Use SPI bus concurrently from two tasks

Postby CodeOne » Mon Mar 12, 2018 10:32 pm

My ESP32 app talks to two SPI slaves connected to the same SPI bus. Two tasks run concurrently (on the same cpu) and each one talks to one of the slaves. The program runs without problem if I use a mutex to ensure that only one task accesses the SPI bus at a time.

However, I would like to better interleave the SPI transactions of the two tasks and get rid of the mutex. The ESP-IDF documentation (http://esp-idf.readthedocs.io/en/latest ... aster.html) says:
The spi_master driver allows easy communicating with SPI slave devices, even in a multithreaded environment. It fully transparently handles DMA transfers to read and write data and automatically takes care of multiplexing between different SPI slaves on the same master.
While it might internally be capable of handling a multithreaded environment, the API doesn't seem to really support it. The problem is the function spi_device_get_trans_result: It returns the oldest queued result of any SPI transaction – without regard to who submitted it in the first place. So it mixes up the transaction results of the two tasks. There seems to be no way to restrict it to transactions started by the same task or even to a specific transaction. So in effect, you cannot wait for the result of a SPI transaction that you have started if another task is using the same SPI bus.

The function spi_device_transmit doesn't help either as it internally uses spi_device_get_trans_result as well. The documentation contains even a warning that is related to this fact:
Do not use this when there is still a transaction queued that hasn’t been finalized using spi_device_get_trans_result.
Have I missed something? Or is the SPI master really incapable of working in a multithreaded environment without additional synchronization – contrary to what the documentation suggests?

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

Re: Use SPI bus concurrently from two tasks

Postby ESP_Sprite » Tue Mar 13, 2018 2:54 am

The SPI master driver is fully multithread-compatible, given that you use a SPI slave device on only one thread, as you seem to do. The bit that you seem to have missed in the master driver is that each slave device is represented by a spi_device_handle_t that is unique to the slave on the SPI bus. So whey you do spi_device_get_trans_result, it gets a result from the queue associated with the specific SPI slave device whose handle you pass to the function; results from transactions to other devices go into the queue associated with the handles of those other devices, so they won't be returned in a call to spi_device_get_trans_result for your device.

If you have a program that behaves otherwise, I'd love to see it - either you're inadvertently doing something wrong, or there's a bug in our code.

CodeOne
Posts: 4
Joined: Sat Mar 03, 2018 9:58 am

Re: Use SPI bus concurrently from two tasks

Postby CodeOne » Tue Mar 13, 2018 10:25 pm

Thanks for pointing out the spi_device_handle_t. I indeed missed that one. That prevents the mixup between tasks. So the API design is fine and just what I need.

However, I find that data read from one of the SPI slaves is overwritten with garbage when the two tasks access the SPI bus concurrently. The first few bytes are ok but the remaining ones are garbage. Using a mutex or disabling the other SPI slaves solves the problem. So it's likely a bug - either in my code or in the SPI driver. I'll investigate further and come back with my finding.

CodeOne
Posts: 4
Joined: Sat Mar 03, 2018 9:58 am

Re: Use SPI bus concurrently from two tasks

Postby CodeOne » Fri Mar 16, 2018 9:46 pm

I have now spent quite some time digging deeper into the issue. It's still unsolved. Yet I've made an interesting observation where somebody can hopefully help me better understand what's going on.

The problem is reproducible. It only occurs if there's other activity with a different SPI slave on the same bus immediately before the problematic transaction. Otherwise the transaction works fine. The problematic transaction is a full-duplex transaction that sends a command byte and 10 dummy bytes while receiving 10 bytes. It uses the VSPI bus and DMA channel 0. If the problem occurs, only the first few bytes are correct while the last 2 to 6 bytes are invalid (0 or the value of the dummy bytes).

I dug into the SDK code (spi_master.c), added debug code and observed a surprising value in the DMA's lldesc_t struct:

At transaction start, it is initialized with length = 0x0c and size = 0x0c.
At transaction end, the values are length = 0x07 and size = 0x0c (length can vary somewhat).

This seems to indicate that SPI transaction terminated early.
  • Would you agree?
  • What could be the cause for the early termination?
  • Are there some registers that could indicate the cause of the problem?
The code is pretty straigtforward:

Code: Select all

    uint8_t* buffer = heap_caps_malloc(32, MALLOC_CAP_DMA);
    
    ...
    
    memset(buffer, CMD_NOP, len);
    spi_transaction_t trx;
    memset(&trx, 0, sizeof(spi_transaction_t));
    trx.cmd = 0x61;
    trx.tx_buffer = buffer;
    trx.length = 8 * 10;
    trx.rx_buffer = buffer;
    trx.rxlength = 8 * 10;

    esp_err_t ret = spi_device_transmit(spi_device, &trx);
    assert(ret == ESP_OK);

CodeOne
Posts: 4
Joined: Sat Mar 03, 2018 9:58 am

Re: Use SPI bus concurrently from two tasks

Postby CodeOne » Sun Mar 25, 2018 12:01 pm

I've found the following warning in the SPI Slave driver documentation (http://esp-idf.readthedocs.io/en/latest ... ave-driver):
Warning: Due to a design peculiarity in the ESP32, if the amount of bytes sent by the master or the length of the transmission queues in the slave driver, in bytes, is not both larger than eight and dividable by four, the SPI hardware can fail to write the last one to seven bytes to the receive buffer.
I've now changed the sender side to send at least 12 bytes and multiples of 4. So the master also reads a payload length of the same size – and the problem is gone.

Is it just luck that it works now, or could it be that the SPI slave design peculiarity (probably an euphemism for bug or design flaw) also applies to SPI masters receiving data?

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

Re: Use SPI bus concurrently from two tasks

Postby ESP_Sprite » Mon Mar 26, 2018 9:37 am

The 'peculiarity' indeed is an euphemism, I'm afraid. It also goes for the SPI master, however we have been able to work around the issue in the SPI-master code, or so we thought. Will investigate a bit more, maybe we can also work around this thing.

Who is online

Users browsing this forum: Google [Bot] and 103 guests