8

An ESP32 app using ESP-IDF (ESP32 SDK) communicates with two SPI slaves on the same SPI bus (ILI9341 TFT driver, NRF24L01+ RF transceiver). Overall, it works great. However, some of the data received from the RF transceiver is truncated, i.e. only the first few bytes are correct and the rest is garbage.

The problem is more or less reproducible and only occurs if there is SPI communication with the other slave (TFT driver) immediately before receiving the truncated data.

The problematic SPI 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 1. 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. 0x0c is 12 bytes, i.e. the 10 bytes rounded to the next word.

At transaction end, the values are length = 0x07 and size = 0x0c (length can vary slightly). So the transaction only reads 7 bytes and then somehow terminates. Or rather the DMA operations terminates.

  • Would you agree that the data indicates an early termination?
  • 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 straightforward:

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);
Codo
  • 75,595
  • 17
  • 168
  • 206
  • 1
    Apart from an electrical problem on the nCS line to the slave I know of no way to terminate a SPI transaction early - that is, the only way to terminate early is that the master stops clocking out for some reason. This hints towards either an unclean or erroneous code in the DMA finishing interrupt of the other devices SPI driver code which coincidentally clobbers some bits of the other DMA channel. BTW how does the system detect a transaction-end? – Vroomfondel Mar 23 '18 at 11:38
  • I have hooked it up to a logic analyzer and the SPI transaction is not terminated early. It rather seems the be a DMA problem or a mishandled interrupt. The ESP-IDF code can be found at https://github.com/espressif/esp-idf/blob/master/components/driver/spi_master.c. The interrupt handler starts at line 405. Unfortunately, my knowledge of ESP32 and in particular the SPI and DMA interaction is limited. – Codo Mar 23 '18 at 22:03

1 Answers1

2

It seems that the following warning – found in the SPI Slave driver documentation – also applies to a SPI master receiving data from a slave:]

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 and the problem is gone.

Let me now if you think it just works because of luck and my assumption is wrong.

Codo
  • 75,595
  • 17
  • 168
  • 206
  • 1
    The requirement for 4 byte increments isn't unfathomable, given the fact that you're dealing with DMA on a 32 bit architecture. Your flexibility in datalength is going to be largely determined by how the DMA-enabled SPI driver was implemented. From the note you found, it sounds like the driver's author didn't implement the functionality you had originally programmed. There are some similarities here to STM32F7 SPI and DMA peripherals, specifically with the 4 byte FIFO found on STM32F7 SPI peripheral - so this isn't unheard of. . . Bottom line - you found the solution. – bamos Dec 31 '19 at 13:36
  • I've also just had exactly this problem (which goes away immediately when you set `dma_chan=0` and so don't use DMA), which was fixed by telling the ESP32 to read 32 bits in a transaction where the slave only expects to be sending 16---and of course throwing away the last 16 bits. – Keeley Hoek Jun 07 '21 at 17:09