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
  • An observation today. I changed the code to send 8 bytes with interrupt. After this the DMA transfer worked the first time:

    Code change:

      uint8_t txBuffer[] = {0xff};
      uint8_t rxBuffer[7];   // <---- Changed this from 3 to 7
      ADI_SPI_TRANSCEIVER spiOrder = {txBuffer, sizeof(txBuffer), nullptr, 0, rxBuffer, sizeof(rxBuffer)};

  • Hi Masip,

    For us 4bytes in interrupt mode also works fine.

    From your snippet we understand that you are not providing SPU IDs for SPI1 TX DMA channel. SPI DMA channels must be protected by system production unit. Otherwise it gives unexpected results. If you need to know about SPU registers please refer the System Production Unit chapter in HRM. .

    SPI1 TX DMA channel production id is 64 for SC573 EZKIT. You can get that id from Table 37-12: Write-Protect Register and Secure Peripheral Number(n) in HRM..

    We have referred your shared code snippet and created a project.

    Instead of closing and reconfiguring the SPI channel for every 50 bytes transfer, you can place the submit buffer API inside the infinite loop alone. Please refer the attached example code. In that project we achieved continuous 50 bytes of transfer in DMA mode after 4 bytes transferred in interrupt mode. Please connect SPI1_MISO and SPI1_MOSI pins.

    Regards,
    Santhakumari.K

    SPI_INT_DMA.zip

  • Thank you. But I must re-open the SPI bus for each device because I have a lot of different devices on the bus that needs different SPI configurations (speed, SPI mode). The example I provided is just a simplified demo to quickly  show the bug. And I also modified the code I supplied (after I posted it the first time) to include my setup of the SPU. But if there is no solution to this bug I must keep using interrupt mode in my project.

  • Hi masip,

    After added SPU APIs in your shared snippet, we did not face any issues in 50 bytes DMA mode data transfer after 4 bytes of interrupt mode data transfer. Please check the project file we already shared before. If you are still facing the issue please share us a simple project which replicate the issue in our side. This would be helpful for us to debug your issue further.

    Otherwise you can use SSL3.0 APIs for SC5xx processors. It has backward combatibility for Griffin and GriffinLite boards.

    Please follow the below steps to enable SSLDD 3.0 support for ADSP-SC58x and ADSP-SC57x processors in CCES 2.9.x
    1. Open system.svc of project.
    2. Add the required driver support add-in from "Device Drivers and System Service" add-in lists.
    3. Add the required driver support add-in from "SSLDD 3.0 support for ADSP-SC5xx" add-in lists.
    For example: Both SPI Driver for SHARC(1.0.1) from "Device Drivers and System Service" list and SPI Driver Support for SC5xx (1.0.0) should be added.
    4. Click on Finish.

    This attached sample project is for performing SPI DMA mode transfer using SSLDD 3.0 drivers. Here we are transferring 10bytes of data from SPI2 to SPI0.

    Best Regards,
    Santhakumari.K

    8032.TestSPI_Core1.zip

  • Here is a complete project that that you can run that demonstrates this bug. I'm using a ADSP-SC573 and CCES version 2.10.0.0. I can't test this on the latest CCES as my license subscription has expired.

    TEST_SPI_INT_DMA_BUG.zip

    I then tried to find the bug. I added a row in file adi_spi_bf6xx.c function adi_spi_SubmitDmaBuffer() that clears the SPI_STAT.TF and SPI_STAT.RF flags as I noticed that they were set (but they were not set if I hadn't used the interrupt driven SPI before). So I added this row to clear the flags and I think that solves the bug. But please have a look at this.

  • Hi Masip,

    Thank you for sharing the project.

    Yes we can simulate the issue in ARM core. Initially we tried in Sharc core. After completion of data transfer in interrupt mode, SPI_STAT.TF and SPI_STAT.RF bits should be cleared inside the adi_spi_ReadWrite() API. This works fine in SHARC driver API. We need to debug further. we will get back to you soon.

    Regards,
    Santhakumari.K

Reply
  • Hi Masip,

    Thank you for sharing the project.

    Yes we can simulate the issue in ARM core. Initially we tried in Sharc core. After completion of data transfer in interrupt mode, SPI_STAT.TF and SPI_STAT.RF bits should be cleared inside the adi_spi_ReadWrite() API. This works fine in SHARC driver API. We need to debug further. we will get back to you soon.

    Regards,
    Santhakumari.K

Children