More SPI problems? Sending 4 bytes and receives 4 bytes but first byte is wrong in DMA mode. Interrupt mode works fine.

I'm using ADSP-SC573 ez-board.

I have setup SPI0 to send 4 bytes and connected MOSI to MISO so the DSP receives the sent bytes. This is a test so to find out how to send and receive SPI data at the same time (as I must later do when writing a driver for a chip).

But when I use DMA the first byte is wrong/junk and the rest of bytes is shifted on byte. If I use interrupt mode it is OK.

Image from logic analyzer using interrupt (works OK):

Image from logic analyzer using DMA at 100kHz (first byte is junk, rest of data shifted on byte):

Image from logic analyzer using DMA at 10MHz (even more crazy. It even sends data one more time (3 bytes)):

I have aligned the buffers and setup the SPU (hope those are correct):

My code (run on core 0):

#define USE_DMA // Define to use DMA
#define SPI_CLOCK_FREQUENCY 100e3 // Defines the SPI clock frequency

#include <sys/platform.h>
#include <sys/adi_core.h>
#include "adi_initialize.h"
#include "TestDrivers_Core0.h"
#include <drivers/spi/adi_spi.h>
#include <stdio.h>
#include <services/spu/adi_spu.h>
#include <sys/platform.h>
#include <stdint.h>
#include <inttypes.h>

#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

/*=============  LOCAL DEFINES   =============*/

/* When the processor's L1 and/or L2 cache is enabled the receive data buffers must */
/* be aligned on cache line boundaries. If they are not then any data access by the core may        */
/* pull a buffer into the cache while the DMA is operating on the data buffer. Both the core and    */
/* the DMA are masters on the bus. The core will be performing cache operations. The DMA engine will*/
/* not be. So it is important for the application to both align data that will be operated on by the*/
/* DMA and to separate this data from other application data.                                       */
#if defined(__ICCARM__)
#define SRCALIGN _Pragma("data_alignment=4")
#define DSTALIGN _Pragma("data_alignment=32")
#elif defined(__ADSPGCC__)
#define DSTALIGN ADI_CACHE_ALIGN_MIN_4
#define SRCALIGN __attribute__ ((aligned (4)))
#else
#define DSTALIGN ADI_CACHE_ALIGN_MIN_4
#define SRCALIGN _Pragma("align 4")
#endif

#ifdef USE_DMA
#define SPI_MEMORY_SIZE ADI_SPI_DMA_MEMORY_SIZE
#else
#define SPI_MEMORY_SIZE ADI_SPI_INT_MEMORY_SIZE
#endif

/* SPU handle */
static ADI_SPU_HANDLE hSpu;

/* Memory required for the SPU operation */
static uint8_t SpuMemory[ADI_SPU_MEMORY_SIZE];

ADI_SPI_HANDLE hSpi;
uint8_t SpiMemory[SPI_MEMORY_SIZE];

SRCALIGN uint8_t tx[4] = {1,2,3,4}; // 4 bytes to send
DSTALIGN uint8_t rx[ADI_CACHE_ROUND_UP_SIZE(4, uint8_t)]; // 4 bytes to receive


bool spuInit(void)
{
  while (1)
  {
    // Initialize SPU Service
    if (adi_spu_Init(0u, SpuMemory, nullptr, nullptr, &hSpu)) break;

    // Make SPI0 to generate secure transactions
    if ( adi_spu_EnableMasterSecure(hSpu, 69, true)) break;

    // Make SPI0 TX DMA to generate secure transactions
    if (adi_spu_EnableMasterSecure(hSpu, 62, true)) break;

    // Make SPI0 RX DMA to generate secure transactions
    if (adi_spu_EnableMasterSecure(hSpu, 63, true)) break;
    return true;
  }

  // Failure
  return false;
}

bool test(void)
{
  while (1)
  {
    if (adi_spi_Open(0, &SpiMemory, SPI_MEMORY_SIZE, &hSpi)) break;

    /* device in master of the SPI interface */
    if (adi_spi_SetMaster(hSpi, true)) break;

    if (adi_spi_SetTransceiverMode(hSpi, ADI_SPI_TXRX_MODE)) break;

#ifdef USE_DMA
    /* use DMA */
    if (adi_spi_EnableDmaMode(hSpi, true)) break;
#else
    /* use interrupt */
    if (adi_spi_EnableDmaMode(hSpi, false)) break;
#endif
    /* SPI slave select in controlled by software not hardware */
    //if (adi_spi_SetHwSlaveSelect(hSpi, false)) break;
    if (adi_spi_SetHwSlaveSelect(hSpi, false)) break;

    if (adi_spi_ManualSlaveSelect(hSpi, true)) break;

    /* send zeros if tx SPI underflows*/
    if (adi_spi_SetTransmitUnderflow(hSpi, true)) break;

    /* data transitions on falling edge of clock */
    if (adi_spi_SetClockPhase(hSpi, false)) break;

    if (adi_spi_SetClockPolarity(hSpi, false)) break;

    /* Setup SPI clock */
    uint16_t spiClockRate = (112.5e6 / SPI_CLOCK_FREQUENCY) - 0.5f; // Note it is (112.5e6 / clkFrequency) - 1.0f + 0.5f     0.5f is for rounding before truncating
    if (adi_spi_SetClock(hSpi, spiClockRate)) break;

    /* SPI slave select is on SPI slave select 6 pin */
    if (adi_spi_SetSlaveSelect(hSpi, ADI_SPI_SSEL_ENABLE6)) break;

    /* SPI data transfers are 8 bit */
    if (adi_spi_SetWordSize(hSpi, ADI_SPI_TRANSFER_8BIT)) break;

    /* generate tx data interrupt when watermark level breaches 50% level */
    /* DMA watermark levels are disabled because SPI is in interrupt mode */
    if (adi_spi_SetTxWatermark(hSpi, ADI_SPI_WATERMARK_50, ADI_SPI_WATERMARK_DISABLE, ADI_SPI_WATERMARK_DISABLE)) break;

    /* generate rx data interrupt when watermark level breaches 50% level */
    /* DMA watermark levels are disabled because SPI is in interrupt mode */
    if (adi_spi_SetRxWatermark(hSpi, ADI_SPI_WATERMARK_50, ADI_SPI_WATERMARK_DISABLE, ADI_SPI_WATERMARK_DISABLE)) break;

    if (adi_spi_RegisterCallback(hSpi, NULL, NULL)) break;

    if (adi_spi_SlaveSelect(hSpi, true)) break;

    ADI_SPI_TRANSCEIVER transceiver = { NULL, 0, tx, 4, rx, 4 };
    if (adi_spi_ReadWrite(hSpi, &transceiver)) break;

    if (adi_spi_SlaveSelect(hSpi, false)) break;

    printf("Received: ");
    for (uint8_t i = 0; i < 4; i++)
    {
      printf("%" PRIx8 "\n", rx[i]);
    }

    return true;
  }

  // Failure
  return false;
}



int main()
{
  spuInit();
  /**
   * Initialize managed drivers and/or services that have been added to
   * the project.
   * @return zero on success
   */
  adi_initComponents();

  test();

  return 0;
}

Why is this happening? What am I doing wrong?

Getting tired of these SPI-problems. Takes too much time to setup this DSP.



Added one more image showing behavioral at 10MHz with DMA.
[edited by: masip at 8:26 AM (GMT -4) on 16 Jun 2021]
  • I continued testing and found out that it works with DMA if I remove adi_spi_SlaveSelect(hSpi, true) and adi_spi_SlaveSelect(hSpi, false).

    Why is that? I thought I needed them as I have adi_spi_SetHwSlaveSelect(hSpi, false) and adi_spi_ManualSlaveSelect(hSpi, true).

    Can someone explain why the driver pulls ADI_SPI_SSEL_ENABLE6 at the start of transmission even when I have selected to control it manually by adi_spi_SetHwSlaveSelect(hSpi, false) and adi_spi_ManualSlaveSelect(hSpi, true)?

    It even says so ("is only required when adi_spi_ManualSlaveSelect is set to true"):

    /*!
     * @brief   The adi_spi_SlaveSelect API enables/disables the slave select line.
     *          The API is only required when adi_spi_ManualSlaveSelect is set to true.
     *          Otherwise the SPI driver automatically controls the assertion / de-assertion
     *          of the slave select line.
     *
     * @param[in]    hDevice      Handle of the SPI device
     * @param[in]    bEnable      Flag to manage slave selection.
     *               - true       Assert slave select.
     *               - false      De-assert slave select.
     *
     * @return      Status
     *              - #ADI_SPI_SUCCESS              The device is successfully opened for the given instance.
     *              - #ADI_SPI_FAILURE              A generalized API failure.
     *              - #ADI_SPI_INVALID_HANDLE [D]   An invalid device handle.
     *
     * @sa          adi_spi_ManualSlaveSelect().
     */
    ADI_SPI_RESULT adi_spi_SlaveSelect(ADI_SPI_HANDLE const hDevice, bool bEnable)

    But if I select to use interrupt mode instead of DMA mode then I must include adi_spi_SlaveSelect(hSpi, true) and adi_spi_SlaveSelect(hSpi, false) or else the ADI_SPI_SSEL_ENABLE6  is not set low for transmission. Why is this behavior different from DMA mode?

    It feels like Analog devices SPI driver is buggy and needs an update. Is there an update ready soon?

    This is my code that works for now:

    #define USE_DMA // Define to use DMA
    #define SPI_CLOCK_FREQUENCY_HZ 2000000 // Defines the SPI clock frequency in Hz
    
    #ifndef USE_DMA
    #if SPI_CLOCK_FREQUENCY_HZ > 1000000
    #error SPI only works to about 1MHz in interrupt mode
    #endif
    #endif
    
    #include <sys/platform.h>
    #include <sys/adi_core.h>
    #include "adi_initialize.h"
    #include "TestDrivers_Core0.h"
    #include <drivers/spi/adi_spi.h>
    #include <stdio.h>
    #include <services/spu/adi_spu.h>
    #include <sys/platform.h>
    #include <stdint.h>
    #include <inttypes.h>
    
    #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
    
    /*=============  LOCAL DEFINES   =============*/
    
    /* When the processor's L1 and/or L2 cache is enabled the receive data buffers must */
    /* be aligned on cache line boundaries. If they are not then any data access by the core may        */
    /* pull a buffer into the cache while the DMA is operating on the data buffer. Both the core and    */
    /* the DMA are masters on the bus. The core will be performing cache operations. The DMA engine will*/
    /* not be. So it is important for the application to both align data that will be operated on by the*/
    /* DMA and to separate this data from other application data.                                       */
    #if defined(__ICCARM__)
    #define SRCALIGN _Pragma("data_alignment=4")
    #define DSTALIGN _Pragma("data_alignment=32")
    #elif defined(__ADSPGCC__)
    #define DSTALIGN ADI_CACHE_ALIGN_MIN_4
    #define SRCALIGN __attribute__ ((aligned (4)))
    #else
    #define DSTALIGN ADI_CACHE_ALIGN_MIN_4
    #define SRCALIGN _Pragma("align 4")
    #endif
    
    #ifdef USE_DMA
    #define SPI_MEMORY_SIZE ADI_SPI_DMA_MEMORY_SIZE
    #else
    #define SPI_MEMORY_SIZE ADI_SPI_INT_MEMORY_SIZE
    #endif
    
    /* SPU handle */
    static ADI_SPU_HANDLE hSpu;
    
    /* Memory required for the SPU operation */
    static uint8_t SpuMemory[ADI_SPU_MEMORY_SIZE];
    
    ADI_SPI_HANDLE hSpi;
    uint8_t SpiMemory[SPI_MEMORY_SIZE];
    
    SRCALIGN uint8_t tx[4] = {1,2,3,4}; // 4 bytes to send
    DSTALIGN uint8_t rx[ADI_CACHE_ROUND_UP_SIZE(4, uint8_t)]; // 4 bytes to receive
    
    
    bool spuInit(void)
    {
      while (1)
      {
        // Initialize SPU Service
        if (adi_spu_Init(0u, SpuMemory, nullptr, nullptr, &hSpu)) break;
    
        // Make SPI0 to generate secure transactions
        if ( adi_spu_EnableMasterSecure(hSpu, 69, true)) break;
    
        // Make SPI0 TX DMA to generate secure transactions
        if (adi_spu_EnableMasterSecure(hSpu, 62, true)) break;
    
        // Make SPI0 RX DMA to generate secure transactions
        if (adi_spu_EnableMasterSecure(hSpu, 63, true)) break;
        return true;
      }
    
      // Failure
      return false;
    }
    
    bool test(void)
    {
      while (1)
      {
        if (adi_spi_Open(0, &SpiMemory, SPI_MEMORY_SIZE, &hSpi)) break;
    
        /* device in master of the SPI interface */
        if (adi_spi_SetMaster(hSpi, true)) break;
    
        if (adi_spi_SetTransceiverMode(hSpi, ADI_SPI_TXRX_MODE)) break;
    
    #ifdef USE_DMA
        /* use DMA */
        if (adi_spi_EnableDmaMode(hSpi, true)) break;
    #else
        /* use interrupt */
        if (adi_spi_EnableDmaMode(hSpi, false)) break;
    #endif
        /* SPI slave select in controlled by software not hardware */
        //if (adi_spi_SetHwSlaveSelect(hSpi, false)) break;
        if (adi_spi_SetHwSlaveSelect(hSpi, false)) break;
    
        if (adi_spi_ManualSlaveSelect(hSpi, true)) break;
    
        /* send zeros if tx SPI underflows*/
        if (adi_spi_SetTransmitUnderflow(hSpi, true)) break;
    
        /* data transitions on falling edge of clock */
        if (adi_spi_SetClockPhase(hSpi, false)) break;
    
        if (adi_spi_SetClockPolarity(hSpi, false)) break;
    
        /* Setup SPI clock */
        uint16_t spiClockRate = (112.5e6 / SPI_CLOCK_FREQUENCY_HZ) - 0.5f; // Note it is (112.5e6 / clkFrequency) - 1.0f + 0.5f     0.5f is for rounding before truncating
        if (adi_spi_SetClock(hSpi, spiClockRate)) break;
    
        /* SPI slave select is on SPI slave select 6 pin */
        if (adi_spi_SetSlaveSelect(hSpi, ADI_SPI_SSEL_ENABLE6)) break;
    
        /* SPI data transfers are 8 bit */
        if (adi_spi_SetWordSize(hSpi, ADI_SPI_TRANSFER_8BIT)) break;
    
        /* generate tx data interrupt when watermark level breaches 50% level */
        /* DMA watermark levels are disabled because SPI is in interrupt mode */
        if (adi_spi_SetTxWatermark(hSpi, ADI_SPI_WATERMARK_50, ADI_SPI_WATERMARK_DISABLE, ADI_SPI_WATERMARK_DISABLE)) break;
    
        /* generate rx data interrupt when watermark level breaches 50% level */
        /* DMA watermark levels are disabled because SPI is in interrupt mode */
        if (adi_spi_SetRxWatermark(hSpi, ADI_SPI_WATERMARK_50, ADI_SPI_WATERMARK_DISABLE, ADI_SPI_WATERMARK_DISABLE)) break;
    
        if (adi_spi_RegisterCallback(hSpi, NULL, NULL)) break;
    #ifndef USE_DMA
        // Only needed if using interrupt
        if (adi_spi_SlaveSelect(hSpi, true)) break;
    #endif
        ADI_SPI_TRANSCEIVER transceiver = { NULL, 0, tx, 4, rx, 4 };
        if (adi_spi_ReadWrite(hSpi, &transceiver)) break;
    #ifndef USE_DMA
        // Only needed if using interrupt
        if (adi_spi_SlaveSelect(hSpi, false)) break;
    #endif
        printf("Received: ");
        for (uint8_t i = 0; i < 4; i++)
        {
          printf("%" PRIx8 "\n", rx[i]);
        }
    
        return true;
      }
    
      // Failure
      return false;
    }
    
    
    
    int main()
    {
      spuInit();
      /**
       * Initialize managed drivers and/or services that have been added to
       * the project.
       * @return zero on success
       */
      adi_initComponents();
    
      test();
    
      return 0;
    }

  • 0
    •  Analog Employees 
    on Jun 16, 2021 3:23 PM

    Hello,

    We are testing our sample code using manual slave select mode. we will get back to you soon.

    Best Regards,
    Santhakumari.K

  • 0
    •  Analog Employees 
    on Jul 12, 2021 3:29 PM in reply to santha.vijay

    Hello,

    Please refer the attached test code for SC-573 in DMA mode. Here we are transferring 10bytes of data from SPI2 to SPI0. We are supporting SSLDD 3.0 in Griffin and GL processors too.

    The support for SC5xx is also available in sources when the macro SSLDD_3_SPI_SUPPORT_FOR_SC5xx is defined in the project then SSLDD 3.0 drivers are picked in adi_spi.c.

    Please follow the below procedures to include it.
    1. Open system.svc of project.
    2. Add the SPI Driver for SHARC(1.0.1) from "Device Drivers and System Service" list.
    3. Add the SPI Driver Support for SC5xx (1.0.0) from "SSLDD 3.0 support for ADSP-SC5xx" add-in lists.
    4. Click on Finish.

    Hope you can use this as a base for your development.

    TestSPI_Core1.zip