Post Go back to editing

SPI Bug breaks our code? Sending with interrupt and then sending with DMA. First DMA transmission is corrupt

I'm using an ADSP-SC573. Need help, this breaks our code. Please run the supplied example code.

First it configures to send 4 bytes on the SPI using interrupt. Then it configures to send 50 bytes on SPI using DMA in a loop.

The problem is, if we send the 4 bytes using interrupt first then the first DMA transmission will be corrupt, only 2 bytes comes out (not 50). But after the first corrupt DMA transmission the following DMA transmissions are ok.

And if I don't send the 4 bytes using interrupt, then all DMA transmissions are OK.

It seems like there is some bug in the Analog devices drivers that can't handle that a SPI has been used in interrupt mode first and later in DMA mode.

Here is the code that shows this bug:

#include "adi_initialize.h"
#include <services/pwr/adi_pwr.h>
#include <drivers/spi/adi_spi.h>
#include <services/gpio/adi_gpio.h>
#include "spu.h"

#define DSTALIGN ADI_CACHE_ALIGN_MIN_4
#define SRCALIGN __attribute__((aligned(4)))
#if !defined(ADI_CACHE_LINE_LENGTH)
/* The ADI_CACHE_* macros were introduced in CCES 2.4.0 in <sys/platform.h>.
 * If using an older toolchain, define them here.
 */
#if defined(__ADSPARM__)
#define ADI_CACHE_LINE_LENGTH (32uL)
#define ADI_CACHE_ALIGN_MIN_4 __attribute__((aligned(ADI_CACHE_LINE_LENGTH)))
#elif defined(__ADSP215xx__)
#define ADI_CACHE_LINE_LENGTH (64uL)
#define ADI_CACHE_ALIGN_MIN_4 _Pragma("align 64")
#else
#error Unknown ADI_CACHE_LINE_LENGTH
#endif
#define ADI_CACHE_ROUND_UP_SIZE(size, type) \
  (((((((size) * sizeof(type)) + (ADI_CACHE_LINE_LENGTH - 1uL)) / ADI_CACHE_LINE_LENGTH) * ADI_CACHE_LINE_LENGTH) + (sizeof(type) - 1uL)) / sizeof(type))
#endif

#define SPI_DEVICE_NUMBER      1                    ///< The SPI device to use
#define SLAVE_SELECT_PIN       ADI_SPI_SSEL_ENABLE1 ///< The slave select pin to use
#define SPI_CLOCK_FREQUENCY_HZ 10000000             ///< Defines the SPI clock frequency in Hz

SRCALIGN uint8_t txBuffer2[50];///< SPI TX buffer

/// Memory required for the SPU
uint8_t spuMemory[ADI_SPU_MEMORY_SIZE];
/// SPU handle
ADI_SPU_HANDLE hSpu;

// Initializes the SPU instance.
bool setupSpu()
{
  if (adi_spu_Init(0u, spuMemory, nullptr, nullptr, &hSpu)) return false;

  // SPI1 TX DDE
  if (adi_spu_EnableMasterSecure(hSpu, 64, true) != ADI_SPU_SUCCESS) return false;

  // SPI1 TX DDE
  if (adi_spu_EnableMasterSecure(hSpu, 65, true) != ADI_SPU_SUCCESS) return false;

  // SPI1
  if (adi_spu_EnableMasterSecure(hSpu, 70, true) != ADI_SPU_SUCCESS) return false;

  return true;
}

// Send 4 bytes on SPI1 using interrupt
bool test1()
{
  uint8_t spiMemory[ADI_SPI_INT_MEMORY_SIZE]; ///< SPI memory
  ADI_SPI_HANDLE spiHandle;                   ///< SPI device handle

  if (adi_spi_Open(SPI_DEVICE_NUMBER, &spiMemory, sizeof(spiMemory), &spiHandle) != ADI_SPI_SUCCESS) return false;

  if (adi_spi_SetMaster(spiHandle, true)) return false;

  if (adi_spi_SetTransceiverMode(spiHandle, ADI_SPI_TXRX_MODE)) return false;

  if (adi_spi_EnableDmaMode(spiHandle, false)) return false;

  if (adi_spi_SetHwSlaveSelect(spiHandle, false)) return false;

  if (adi_spi_ManualSlaveSelect(spiHandle, false)) return false;

  if (adi_spi_SetTransmitUnderflow(spiHandle, true)) return false;

  if (adi_spi_SetClockPolarity(spiHandle, true)) return false;

  if (adi_spi_SetClockPhase(spiHandle, false)) return false;

  uint32_t fsysclk;
  uint32_t fsclk0; // Used for SPI0 and SPI1 (See SPI Port - Master Timing in data sheet)
  uint32_t fsclk1; // Used for SPI2

  if (adi_pwr_GetSystemFreq(0, &fsysclk, &fsclk0, &fsclk1)) return false;
  uint16_t spiClockRate = (uint16_t)(((float)fsclk0 / SPI_CLOCK_FREQUENCY_HZ) - 0.5f);

  if (adi_spi_SetClock(spiHandle, spiClockRate)) return false;

  if (adi_spi_SetSlaveSelect(spiHandle, SLAVE_SELECT_PIN)) return false;

  if (adi_spi_SetWordSize(spiHandle, ADI_SPI_TRANSFER_8BIT)) return false;

  if (adi_spi_SetTxWatermark(spiHandle, ADI_SPI_WATERMARK_50, ADI_SPI_WATERMARK_DISABLE, ADI_SPI_WATERMARK_DISABLE)) return false;

  if (adi_spi_SetRxWatermark(spiHandle, ADI_SPI_WATERMARK_50, ADI_SPI_WATERMARK_DISABLE, ADI_SPI_WATERMARK_DISABLE)) return false;

  if (adi_spi_RegisterCallback(spiHandle, 0, 0)) return false;

  uint8_t txBuffer[] = {0xff};
  uint8_t rxBuffer[3];
  ADI_SPI_TRANSCEIVER spiOrder = {txBuffer, sizeof(txBuffer), nullptr, 0, rxBuffer, sizeof(rxBuffer)};
  if (adi_spi_ReadWrite(spiHandle, &spiOrder)) return false;

  adi_spi_Close(spiHandle);

  return true;
}

volatile bool isBusySendingData = false;

void spiCallback(void *pCBParam, uint32_t Event, void *pArg)
{
  (void)pCBParam; // Unused
  (void)pArg;     // Unused
  ADI_SPI_EVENT event = (ADI_SPI_EVENT)Event;
  switch (event)
  {
    case ADI_SPI_TRANSCEIVER_PROCESSED:
      isBusySendingData = false;
      break;
    default:
      break;
  }
}

// Send 50 bytes on SPI1 using DMA
bool test2()
{
  uint8_t gpioMemory[ADI_GPIO_CALLBACK_MEM_SIZE];
  uint32_t gpioMaxCallbacks;
  if (adi_gpio_Init((void *)gpioMemory, sizeof(gpioMemory), &gpioMaxCallbacks)) return false;
  if (adi_gpio_SetDirection(ADI_GPIO_PORT_C, ADI_GPIO_PIN_14, ADI_GPIO_DIRECTION_OUTPUT)) return false;
  if (adi_gpio_Clear(ADI_GPIO_PORT_C, ADI_GPIO_PIN_14)) return false;

  uint8_t spiMemory[ADI_SPI_DMA_MEMORY_SIZE]; ///< SPI memory
  ADI_SPI_HANDLE spiHandle;                   ///< SPI device handle

  if (adi_spi_Open(SPI_DEVICE_NUMBER, &spiMemory, sizeof(spiMemory), &spiHandle) != ADI_SPI_SUCCESS) return false;

  if (adi_spi_SetMaster(spiHandle, true)) return false;

  if (adi_spi_SetTransceiverMode(spiHandle, ADI_SPI_TXRX_MODE)) return false; // Note: ADI_SPI_TX_MODE is not implemented in ADI drivers

  // Use DMA
  if (adi_spi_EnableDmaMode(spiHandle, true)) return false;

  if (adi_spi_SetHwSlaveSelect(spiHandle, false)) return false;

  if (adi_spi_ManualSlaveSelect(spiHandle, false)) return false;

  if (adi_spi_SetTransmitUnderflow(spiHandle, true)) return false;

  if (adi_spi_SetClockPhase(spiHandle, false)) return false;

  if (adi_spi_SetClockPolarity(spiHandle, true)) return false;

  // Setup SPI clock
  uint32_t fsysclk;
  uint32_t fsclk0; // Used for SPI0 and SPI1 (See SPI Port - Master Timing in data sheet)
  uint32_t fsclk1; // Used for SPI2

  if (adi_pwr_GetSystemFreq(0, &fsysclk, &fsclk0, &fsclk1)) return false;

  uint16_t spiClockRate = (uint16_t)(((float)fsclk0 / SPI_CLOCK_FREQUENCY_HZ) - 0.5f);

  if (adi_spi_SetClock(spiHandle, spiClockRate)) return false;

  if (adi_spi_SetSlaveSelect(spiHandle, SLAVE_SELECT_PIN)) return false;

  if (adi_spi_SetWordSize(spiHandle, ADI_SPI_TRANSFER_8BIT)) return false;

  if (adi_spi_SetDmaTransferSize(spiHandle, ADI_SPI_DMA_TRANSFER_8BIT)) return false;

  if (adi_spi_SetTxWatermark(spiHandle, ADI_SPI_WATERMARK_50, ADI_SPI_WATERMARK_DISABLE, ADI_SPI_WATERMARK_DISABLE)) return false;

  if (adi_spi_SetRxWatermark(spiHandle, ADI_SPI_WATERMARK_50, ADI_SPI_WATERMARK_DISABLE, ADI_SPI_WATERMARK_DISABLE)) return false;

  if (adi_spi_RegisterCallback(spiHandle, spiCallback, 0)) return false;

  ADI_SPI_TRANSCEIVER spiOrder = {txBuffer2, 50, nullptr, 0, nullptr, 0};
  isBusySendingData = true;
  adi_gpio_Set(ADI_GPIO_PORT_C, ADI_GPIO_PIN_14);
  if (adi_spi_SubmitBuffer(spiHandle, &spiOrder) != ADI_SPI_SUCCESS) return false;
  while (isBusySendingData);
  adi_gpio_Clear(ADI_GPIO_PORT_C, ADI_GPIO_PIN_14);

  adi_spi_Close(spiHandle);

  return true;

}

#define RUN_TEST1 // Define this to run test 1 (send 4 bytes witn interrupt)

int main(int argc, char *argv[])
{
  adi_initComponents();

  adi_pwr_Init(0, 25000000);

  setupSpu();
#ifdef RUN_TEST1
  test1();
#endif
  while (true)
  {
    test2();

    // A short delay
    for (volatile uint32_t i = 0; i < 1000; i++);
  }

  return 0;
}



Added the graphs again with the correct signal naming. Updated the code with SPU setup.
[edited by: masip at 7:58 AM (GMT -4) on 25 Mar 2022]
Parents
  • I don't know if this will solve the problem you are reporting, but it is potentially a bug, and a very hard one to debug.

    txBuffer2 needs to be cache-line-aligned and it also needs to be a multiple of the cache-line size.

    You have cache alignment macros and buffer sizing macros at the beginning of the code, but you don't apply them to txBuffer2.

    Something like this...

    ADI_CACHE_ALIGN_MIN_4 uint8_t txBufer2[ADI_CACHE_ROUND_UP_SIZE(50, uint8_t)];

Reply
  • I don't know if this will solve the problem you are reporting, but it is potentially a bug, and a very hard one to debug.

    txBuffer2 needs to be cache-line-aligned and it also needs to be a multiple of the cache-line size.

    You have cache alignment macros and buffer sizing macros at the beginning of the code, but you don't apply them to txBuffer2.

    Something like this...

    ADI_CACHE_ALIGN_MIN_4 uint8_t txBufer2[ADI_CACHE_ROUND_UP_SIZE(50, uint8_t)];

Children
  • Yes, I did use the correct cache alignment as you can see from my posted example. So that is not the problem here.

    I solved it by adding a row in file adi_spi_bf6xx.c function adi_spi_SubmitDmaBuffer() that clears the SPI_STAT.TF and SPI_STAT.RF flags. I have had no more issues after that. But I wonder what ADI is doing to address this issue now, they wrote that they will get back to me soon (27 days ago). I hope they will release a fix for this in the next CCES version.

  • Hi Masip,

    Apologies for the delay. We didn't forget the issue. We are checking your fix with different test scenarios. We are not facing this issue when we ran the same application in Sharc core.

    So we are checking SPI register settings between both cores when we step-in adi_spi_ReadWrite() API.

    We will update the fix version details via the same thread.

    Best Regards,
    Santhakumari.K