Problem Using SPI1 with DMA - Changed Subject

cancel
Showing results for 
Search instead for 
Did you mean: 

Problem Using SPI1 with DMA - Changed Subject

Jump to solution
527 Views
myke_predko
Senior Contributor III

Hi,

I'm trying to write to an OLED display using SPI with DMA.  I can write successfully with straight SPI calls but I'm getting mixed results and I could use some suggestions as well as answer a couple of questions.  

I want to use DMA transfers for the following reasons:

1. Speed.  The OLED requires 8kBytes for a full screen read/write which takes 57ms when in single byte mode at 120Mhz and 93ms at 60Mhz (see my previous question regarding clocking) and to avoid visible flashing, I need to get below 20ms for the full 8k transfer of the screen image. 

2. Multi-tasking.  Rather than send data to the OLED, I woudl like the processor executing some data processing tasks. 

I have based the DMA code on Kyle Manna's GitHub code here - dspi_edma_transfer.c without the Slave operations.  This code seems to be used by other people as well.  I've ported it to the FRDM-K22F (SDK 2.7.0)  running at 60MHz, but I have also tested the code at 120MHz.  

The OLED is connected to the FRDM-K22F using the SPI1 poirt,  One of the major deviations in the code is that I need to send single bytes to the OLED for initialization and setting up data writes.  I have discovered that the DMA SPI port does NOT handle single byte transfers, so I am using the same routines as when I was testing out the SPI Port/OLED display.  Note that I am running this under FreeRTOS - I don't think that should be an issue, but I want to be complete with the information. 

The code works fine for the OLED initialization and, after that, one block transfer takes place but the "DSPI_MasterEDMACallback" method doesn't seem to be called (and "spiTransferCompleted" is not set true).  Just to be clearer, the sequnce of events are:

  • Initialize the OLED with multiple single byte sends to the OLED.  These work with the appropriate Callback executing.  
  • Setup a data write to the OLED using multiple single byte writes to the OLED.  These work with the appropriate Callback executing.  
  • Do a DMA write to the OLED of 256 bytes (for now - see below, I don't want to exceed the 511 byte limit).  This appears to work correctly but the appropriate Callback NEVER Executes.  
  • Attempt to do more single byte writes to the OLED - these do not execute because the "spiTransferCompleted" flag is not set from the previous DMA write.  

 

SPI Defines/Constants:

enum { SPI_TRANSFER_BAUDRATE = 1000000U
     };


#define OLED_DSPI_MASTER_DMA_BASE DMA_BASE
#define OLED_DSPI_MASTER_DMA_BASEADDR ((DMA_Type *)(OLED_DSPI_MASTER_DMA_BASE))
#define OLED_DSPI_MASTER_DMA_MUX_BASE DMAMUX_BASE
#define OLED_DSPI_MASTER_DMA_MUX_BASEADDR ((DMAMUX_Type *) \     

                                          (OLED_DSPI_MASTER_DMA_MUX_BASE))

#define OLED_DSPI_MASTER_DMA_TX_REQUEST_SOURCE kDmaRequestMux0SPI1
#define OLED_DSPI_MASTER_DMA_RX_REQUEST_SOURCE kDmaRequestMux0SPI1

#define OLED_DSPI_MASTER_BASE (SPI1_BASE)
#define OLED_DSPI_MASTER_BASEADDR ((SPI_Type *)OLED_DSPI_MASTER_BASE)
#define OLED_DSPI_MASTER_IRQN (SPI1_IRQn)

#define OLED_DSPI_MASTER_CLK_SRC (DSPI1_CLK_SRC)
#define OLED_DSPI_MASTER_CLK_FREQ CLOCK_GetFreq(DSPI1_CLK_SRC)

SPI Global Variables:

const dspi_master_config_t oledSPIConfig =
{ .whichCtar = kDSPI_Ctar0
, .ctarConfig.baudRate = SPI_TRANSFER_BAUDRATE
, .ctarConfig.bitsPerFrame = 8
, .ctarConfig.cpol = kDSPI_ClockPolarityActiveHigh
, .ctarConfig.cpha = kDSPI_ClockPhaseFirstEdge
, .ctarConfig.direction = kDSPI_MsbFirst
, .ctarConfig.pcsToSckDelayInNanoSec = 1000000000U / SPI_TRANSFER_BAUDRATE
, .ctarConfig.lastSckToPcsDelayInNanoSec = 1000000000U / SPI_TRANSFER_BAUDRATE
, .ctarConfig.betweenTransferDelayInNanoSec = 500000000U / SPI_TRANSFER_BAUDRATE

, .whichPcs = kDSPI_Pcs0
, .pcsActiveHighOrLow = kDSPI_PcsActiveLow

, .enableContinuousSCK = FALSE
, .enableRxFifoOverWrite = FALSE
, .enableModifiedTimingFormat = FALSE
, .samplePoint = kDSPI_SckToSin0Clock
};

uint8_t spiCmdBuffer[16];
uint8_t spiTXBuffer[SPIBUFFERSIZE];
uint8_t spiRXBuffer[SPIBUFFERSIZE];
dspi_master_handle_t oledSPIHandle;
dspi_transfer_t masterXfer = { .rxData = spiRXBuffer
                             , .configFlags = kDSPI_MasterCtar0 |
                                              kDSPI_MasterPcs0 |
                                              kDSPI_MasterPcsContinuous
                             };
edma_handle_t dspiEdmaMasterRxRegToRxDataHandle;
edma_handle_t dspiEdmaMasterTxDataToIntermediaryHandle;
edma_handle_t dspiEdmaMasterIntermediaryToTxRegHandle;
dspi_master_edma_handle_t g_dspi_edma_m_handle;
volatile uint32_t spiTransferCompleted = TRUE;

SPI with Single Byte &  DMA/EDMA initialization:

/* DMA MUX init */
  DMAMUX_Init(OLED_DSPI_MASTER_DMA_MUX_BASEADDR);
  DMAMUX_SetSource(OLED_DSPI_MASTER_DMA_MUX_BASEADDR
                 , masterRxChannel
                 , OLED_DSPI_MASTER_DMA_RX_REQUEST_SOURCE);
  DMAMUX_EnableChannel(OLED_DSPI_MASTER_DMA_MUX_BASEADDR
                     , masterRxChannel);

#if (defined OLED_DSPI_MASTER_DMA_TX_REQUEST_SOURCE)
  DMAMUX_SetSource(OLED_DSPI_MASTER_DMA_MUX_BASEADDR
                 , masterTxChannel
                 , OLED_DSPI_MASTER_DMA_TX_REQUEST_SOURCE);
  DMAMUX_EnableChannel(OLED_DSPI_MASTER_DMA_MUX_BASEADDR
                     , masterTxChannel);
#endif

  EDMA_GetDefaultConfig(&userConfig);
  EDMA_Init(OLED_DSPI_MASTER_DMA_BASEADDR
          , &userConfig);

  DSPI_MasterInit(OLED_DSPI_MASTER_BASEADDR
                , &oledSPIConfig
                , SPI_TRANSFER_BAUDRATE);

//  DSPI_MaasterTransferCreateHandle Call Added for Single Byte Transfers

  DSPI_MasterTransferCreateHandle(OLED_DSPI_MASTER_BASEADDR
                                , &oledSPIHandle
                                , DSPI_MasterCallback
                                , NULL);

/* Set up dspi master */
  memset(&dspiEdmaMasterRxRegToRxDataHandle
       , 0
       , sizeof(dspiEdmaMasterRxRegToRxDataHandle));
  memset(&dspiEdmaMasterTxDataToIntermediaryHandle
       , 0
       , sizeof(dspiEdmaMasterTxDataToIntermediaryHandle));
  memset(&dspiEdmaMasterIntermediaryToTxRegHandle
       , 0
       , sizeof(dspiEdmaMasterIntermediaryToTxRegHandle));

  EDMA_CreateHandle(&dspiEdmaMasterRxRegToRxDataHandle
                  , OLED_DSPI_MASTER_DMA_BASEADDR
                  , masterRxChannel);
  EDMA_CreateHandle(&dspiEdmaMasterTxDataToIntermediaryHandle
                  , OLED_DSPI_MASTER_DMA_BASEADDR
                  , masterIntermediaryChannel);
  EDMA_CreateHandle(&dspiEdmaMasterIntermediaryToTxRegHandle
                  , OLED_DSPI_MASTER_DMA_BASEADDR
                  , masterTxChannel);

  DSPI_MasterTransferCreateHandleEDMA(OLED_DSPI_MASTER_BASEADDR
                                    , &g_dspi_edma_m_handle
                                    , DSPI_MasterEDMACallback
                                    , NULL
                                    , &dspiEdmaMasterRxRegToRxDataHandle
                                    , &dspiEdmaMasterTxDataToIntermediaryHandle
                                    , &dspiEdmaMasterIntermediaryToTxRegHandle);

SPI with DMA/EDMA Callback and DMA Transfer Initiation code:

void DSPI_MasterEDMACallback(SPI_Type *base
                           , dspi_master_edma_handle_t *handle
                           , status_t status
                           , void *userData) {

  if (status == kStatus_Success) {
    __NOP();
  }

  spiTransferCompleted  =  TRUE;

}


void SPI_BulkTransfer() {
int32_t status;

  for (; !spiTransferCompleted;) { }
  spiTransferCompleted = FALSE;
  masterXfer.dataSize = 256;  //  Put in for testing
  if (kStatus_Success != (status =   

      DSPI_MasterTransferEDMA(OLED_DSPI_MASTER_BASEADDR
                            , &g_dspi_edma_m_handle
                            , &masterXfer))) {
    PRINTF("\nDSPI_MasterTranserEDMA Error: %i"
         , status);
    vTaskSuspend(NULL);
  }
}

Single Byte SPI Callback and Transfer Initiation code:

void DSPI_MasterCallback(SPI_Type *base
                       , dspi_master_handle_t *handle
                       , status_t status
                       , void *userData) {

  if (status == kStatus_Success) {
    __NOP();
  }

  spiTransferCompleted = TRUE;
}

void DSPI_ByteSend() {

int32_t status;

  for (; !spiTransferCompleted;) { }
  spiTransferCompleted = FALSE;
  if (kStatus_Success != (status =

      DSPI_MasterTransferNonBlocking(OLED_DSPI_MASTER_BASEADDR
                                   , &oledSPIHandle
                                   , &masterXfer))) {
    PRINTF("DSPI Command Send Failed - %i"
         , status);
    vTaskSuspend(NULL);
  }

}

Any idea why the first block write (or the single byte write) Callback is NOT executing after the first DMA operation.  As I noted, there are a number of single byte operations before the block write that execute correctly.  

Questions:

1.  Is there a way of sending single bytes using SPI DMA?  Block Data writes to the OLED seem to be limited to two bytes or more.  There is no "NOP" bytes for the OLED that could be teamed up with the commands.  

  1.1. Can the single byte SPI writes co-exist with the multi-byte SPI writes?  

2.  Can I use a single callback for the single byte and DMA transfers (Assuming that the solution to the problems allows both approaches to be used)?  This would require type casting one or the other paremeters in the Callback methods.  

3. Once I have the code working, how do I remove the "spiRXBuffer" and associated read?  The SPI port to the OLED is only one way, so the buffer is SRAM that I would like to find a better use for.  

  3.1.  Is it an issue that I'm doing bi-directional send/receives on the single "kDmaRequestMux0SPI1" channel?  Ideally, I just want to do transmits.  

4.  Looking at the EDMA code, it looks like the maximum size that can be transferred is 511 bytes - the OLED requires 8,192 bytes.  When I attempt to send more than 511 bytes, I get an error 603 ("kStatus_DSPI_OutOfRange") from DSPI_MasterTransferEDMA.  After sending a block of (say 256) bytes, can I call the DSPI_MasterTransferEDMA from within the EDMA callback?  

Thanx,

myke

1 Solution
408 Views
myke_predko
Senior Contributor III

Okay, I have figured out how to get DMA working with SPI1 - the primary reason why I was having with getting SPI DMA transfers to work was that I was using SPI1 (which only has one DMA trigger) when all the examples out there use SPI0 (which has two triggers).  Along with that, I figured out how to use the SPI DMAs for single byte commands to the OLED controller that I am using.  

Thank you to Gerardo Rodriguez who put the answer in here: SPI1 with DMA on K22 - BUG - it just took me a while to figure out enough to understand what was being explained.  The issue is that SPI1 only has one trigger while SPI0 has two.  

Here is the SPI code that I am now using - the basis is from the "dspi_edma_b2b_transfer_master" example project with a few modifcations the primary reasons for:

  1. Having code that will work for both SPI0 as well as SPI1
  2. Handling datablocks larger than the "limited_size" value outlined in "fsl_dspi_edma.c"
  3. Handling only data writes - if you want to do data reads as well as writes or use half-duplex mode, you will have to do some work on your own.  

The SPI Defines/Constants are identical to what I was using above.  I'm including them here so somebody researching (ie copying the code) doesn't have to search for the values.  

enum { SPI_TRANSFER_BAUDRATE = 10000000U
     };


#define OLED_DSPI_MASTER_DMA_BASE DMA_BASE
#define OLED_DSPI_MASTER_DMA_BASEADDR ((DMA_Type *)(OLED_DSPI_MASTER_DMA_BASE))
#define OLED_DSPI_MASTER_DMA_MUX_BASE DMAMUX_BASE
#define OLED_DSPI_MASTER_DMA_MUX_BASEADDR ((DMAMUX_Type *) \         

                                            (OLED_DSPI_MASTER_DMA_MUX_BASE))

#define OLED_DSPI_MASTER_DMA_TX_REQUEST_SOURCE kDmaRequestMux0SPI1
#define OLED_DSPI_MASTER_DMA_RX_REQUEST_SOURCE kDmaRequestMux0SPI1

#define OLED_DSPI_MASTER_BASE (SPI1_BASE)
#define OLED_DSPI_MASTER_BASEADDR ((SPI_Type *)OLED_DSPI_MASTER_BASE)
#define OLED_DSPI_MASTER_IRQN (SPI1_IRQn)

#define OLED_DSPI_MASTER_CLK_SRC (DSPI1_CLK_SRC)
#define OLED_DSPI_MASTER_CLK_FREQ CLOCK_GetFreq(DSPI1_CLK_SRC)

The Global Variables have been updated to include a second dspi_transfer_t object which is actually used for the EDMA APIs as well as variables to keep track of how much data has been sent.  

const dspi_master_config_t oledSPIConfig =
  { .whichCtar = kDSPI_Ctar0
  , .ctarConfig.baudRate = SPI_TRANSFER_BAUDRATE
  , .ctarConfig.bitsPerFrame = 8
  , .ctarConfig.cpol = kDSPI_ClockPolarityActiveHigh
  , .ctarConfig.cpha = kDSPI_ClockPhaseFirstEdge
  , .ctarConfig.direction = kDSPI_MsbFirst
  , .ctarConfig.pcsToSckDelayInNanoSec = 1000000000U / SPI_TRANSFER_BAUDRATE
  , .ctarConfig.lastSckToPcsDelayInNanoSec = 1000000000U / SPI_TRANSFER_BAUDRATE
  , .ctarConfig.betweenTransferDelayInNanoSec = 500000000U / SPI_TRANSFER_BAUDRATE

  , .whichPcs = kDSPI_Pcs0
  , .pcsActiveHighOrLow = kDSPI_PcsActiveLow

  , .enableContinuousSCK = FALSE
  , .enableRxFifoOverWrite = FALSE
  , .enableModifiedTimingFormat = FALSE
  , .samplePoint = kDSPI_SckToSin0Clock
  };

enum { SPIBUFFERSIZE = 8192
     };
QueueHandle_t spiQUEUE_Handle;
QueueHandle_t spiRXQUEUE_Handle;
uint8_t spiCmdBuffer[16];
uint8_t spiTXBuffer[SPIBUFFERSIZE];
dspi_master_handle_t oledSPIHandle;
dspi_transfer_t spiXfer       = { .rxData = NULL
                                , .configFlags = kDSPI_MasterCtar0 |
                                                 kDSPI_MasterPcs0 |
                                                 kDSPI_MasterPcsContinuous
                                };
dspi_transfer_t actualSPIXfer = { .rxData = NULL
                                , .configFlags = kDSPI_MasterCtar0 |
                                                 kDSPI_MasterPcs0 |
                                                 kDSPI_MasterPcsContinuous
                                };
edma_handle_t dspiEdmaMasterRxRegToRxDataHandle;
edma_handle_t dspiEdmaMasterTxDataToIntermediaryHandle;
edma_handle_t dspiEdmaMasterIntermediaryToTxRegHandle;
dspi_master_edma_handle_t g_dspi_edma_m_handle;
volatile uint32_t spiTransferCompleted = TRUE;
uint32_t spiBlockSize = 510; // Should be 511, but keeping it even
uint32_t spiSentCurrently;

DMA/SPI initialization.  Note that I check to see whether or not the SPI has one or two trigger sources - this was the issue with the orginal code.  SPI0 has two trigger sources while SPI1 only has one (and has to use the RX trigger).  I put in the check used by "fsl_dspi_edma.c" to only set the TX source if it is appropriate as well as change the size of the maximum DMA block size that can be sent for this case.

DSPI_MasterInit(OLED_DSPI_MASTER_BASEADDR
              , &oledSPIConfig
              , CLOCK_GetFreq(DSPI1_CLK_SRC));

/* DMA MUX init */
DMAMUX_Init(OLED_DSPI_MASTER_DMA_MUX_BASEADDR);
DMAMUX_SetSource(OLED_DSPI_MASTER_DMA_MUX_BASEADDR
               , masterRxChannel
               , OLED_DSPI_MASTER_DMA_RX_REQUEST_SOURCE);
DMAMUX_EnableChannel(OLED_DSPI_MASTER_DMA_MUX_BASEADDR
               , masterRxChannel);

/* If DMA has two triggers, add the "TX" one */
if (1 == FSL_FEATURE_DSPI_HAS_SEPARATE_DMA_RX_TX_REQn(OLED_DSPI_MASTER_BASEADDR)) {
  DMAMUX_SetSource(OLED_DSPI_MASTER_DMA_MUX_BASEADDR
                 , masterTxChannel
                 , OLED_DSPI_MASTER_DMA_TX_REQUEST_SOURCE);
  DMAMUX_EnableChannel(OLED_DSPI_MASTER_DMA_MUX_BASEADDR
                     , masterTxChannel);
  spiBlockSize = 32766; // Should be 32,767, but keeping it even
}


EDMA_GetDefaultConfig(&userConfig);
EDMA_Init(OLED_DSPI_MASTER_DMA_BASEADDR
        , &userConfig);

/* Set up dspi master */
memset(&dspiEdmaMasterRxRegToRxDataHandle
     , 0
     , sizeof(dspiEdmaMasterRxRegToRxDataHandle));
memset(&dspiEdmaMasterTxDataToIntermediaryHandle
     , 0
     , sizeof(dspiEdmaMasterTxDataToIntermediaryHandle));
memset(&dspiEdmaMasterIntermediaryToTxRegHandle
     , 0
     , sizeof(dspiEdmaMasterIntermediaryToTxRegHandle));

EDMA_CreateHandle(&dspiEdmaMasterRxRegToRxDataHandle
                , OLED_DSPI_MASTER_DMA_BASEADDR
                , masterRxChannel);
EDMA_CreateHandle(&dspiEdmaMasterTxDataToIntermediaryHandle
                , OLED_DSPI_MASTER_DMA_BASEADDR
                , masterIntermediaryChannel);
EDMA_CreateHandle(&dspiEdmaMasterIntermediaryToTxRegHandle
                , OLED_DSPI_MASTER_DMA_BASEADDR
                , masterTxChannel);

DSPI_MasterTransferCreateHandleEDMA(OLED_DSPI_MASTER_BASEADDR
                                  , &g_dspi_edma_m_handle
                                  , DSPI_MasterEDMACallback
                                  , NULL
                                  , &dspiEdmaMasterRxRegToRxDataHandle
                                  , &dspiEdmaMasterTxDataToIntermediaryHandle
                                 , &dspiEdmaMasterIntermediaryToTxRegHandle);

Before I send data, I check the status of "spiTransferCompleted" and reset it.  In my case, I have to do this because I may have to change the value of a Command/Data line, but it's always good practice to to this as this way you don't waste processor cycles after starting the DMA cycle waiting for the transfer to complete - instead you're allowing basic programming to take place while the DMA is executing and hopefully it will complete before you need it again.  I'm mentioning it because in the examples, I see the following code after the call to "DSPI_MasterTransferEDMA".

for (; !spiTransferCompleted;) { }
spiTransferCompleted = FALSE;

In my application, I have to send up to 8,192 bytes but if you look at the code for "DSPI_MasterTransferEDMA" you'll see that the transfer limit for devices which only have one trigger (ie SPI1) are limited to a data size of 511 bytes.  To handle this situation, I break up the DMA initiation calls to the size of the block the DMA can send with larger blocks being at least 256 bytes so the call to "DSPI_MasterTransferEDMA" that is located within the Callback can complete before the DMA interrupt is requested again and you end up in a situation where you get nested Callbacks with data that has not yet been sent being overwritten.  If the user is sending less than a full block, the DMA exeutes normally.  

void SPI_BulkTransfer() {
#if (2 != SDK_DEBUGCONSOLE)
int32_t status;
#endif


  spiSentCurrently = 0;
  actualSPIXfer.txData = spiXfer.txData;
  if ((spiBlockSize + 0) >= spiXfer.dataSize) {
    actualSPIXfer.dataSize = spiXfer.dataSize;
  }
  else if ((spiBlockSize + 256) > spiXfer.dataSize) {
    actualSPIXfer.dataSize = 256;
  }
  else {
    actualSPIXfer.dataSize = spiBlockSize;
  }

  DSPI_MasterTransferEDMA(OLED_DSPI_MASTER_BASEADDR
                        , &g_dspi_edma_m_handle
                        , &actualSPIXfer);
}

As noted above, the SPI DMA Callback will request additional transfers if they are required with the minimum size of additional transfers being 256 bytes.  

void DSPI_MasterEDMACallback(SPI_Type *base
                           , dspi_master_edma_handle_t *handle
                           , status_t status
                           , void *userData) {


  if (status == kStatus_Success) {
    spiSentCurrently += actualSPIXfer.dataSize;
    actualSPIXfer.txData = &spiXfer.txData[spiSentCurrently];
    if ((spiXfer.dataSize + 0) == spiSentCurrently) {
      spiTransferCompleted = TRUE;
    }
    else {
      if (256 >= (spiXfer.dataSize - spiSentCurrently)) {
        actualSPIXfer.dataSize = spiXfer.dataSize - spiSentCurrently;
      }
      else if ((spiBlockSize + 0) > ((spiXfer.dataSize - spiSentCurrently))) {
        actualSPIXfer.dataSize = (spiXfer.dataSize - spiSentCurrently) - 256;
      }
      else {
        actualSPIXfer.dataSize = spiBlockSize;
      }
      DSPI_MasterTransferEDMA(OLED_DSPI_MASTER_BASEADDR
                            , &g_dspi_edma_m_handle
                            , &actualSPIXfer);
    }
  }

}

 

In answer to the questions in the original post:

1.  Is there a way of sending single bytes using SPI DMA?  Block Data writes to the OLED seem to be limited to two bytes or more.  There is no "NOP" bytes for the OLED that could be teamed up with the commands.  

[MAP] I discovered that if I sent an invalid command bye to the OLED before the actual one, the OLED ignored it and everything was okay.  Similarly, with a single data byte as part of the command, if I sent a single 0x00 byte after it, it was ignored.  

  1.1. Can the single byte SPI writes co-exist with the multi-byte SPI writes?  

[MAP] As I have everything working with a DMA, I haven't pursued this.  I do have the question out to the OLED controller manufacturer (Solomon Systech) and I'm waiting for a response.  

2.  Can I use a single callback for the single byte and DMA transfers (Assuming that the solution to the problems allows both approaches to be used)?  This would require type casting one or the other paremeters in the Callback methods.  

[MAP] This is no longer relevant due to the use of only DMA.  

3. Once I have the code working, how do I remove the "spiRXBuffer" and associated read?  The SPI port to the OLED is only one way, so the buffer is SRAM that I would like to find a better use for.  

[MAP] In going through the SPI/EDMA code I discovered that if you load rxData or txData of the dspi_transfer_t structure passed to the DMA request then it is ignored.  

  3.1.  Is it an issue that I'm doing bi-directional send/receives on the single "kDmaRequestMux0SPI1" channel?  Ideally, I just want to do transmits.  

[MAP] This is basically the issue that caused the problems with getting DMA operations to work correctly and is addressed in the code above.  

4.  Looking at the EDMA code, it looks like the maximum size that can be transferred is 511 bytes - the OLED requires 8,192 bytes.  When I attempt to send more than 511 bytes, I get an error 603 ("kStatus_DSPI_OutOfRange") from DSPI_MasterTransferEDMA.  After sending a block of (say 256) bytes, can I call the DSPI_MasterTransferEDMA from within the EDMA callback?  

[MAP] This is addressed above.

So, was all this worth it?  In terms of performance, I see a pretty substantial increase in full OLED write speed changing from single byte writes to DMA:

- At 60MHz, Single Byte OLED write is 93ms and with DMA it dropped down to 17ms

- At 120MHz, Single Byte OLED write is 57ms and with DMA it dropped down to 12ms

At 17ms or 12ms, the screen update appears to be instantaneous but at the original speeds, the update was easily observed.  

This was an investigation that took two weeks of work (including finding the right wiring and initialization/image load code for hte OLED) but the results are quite impressive.  

View solution in original post

1 Reply
409 Views
myke_predko
Senior Contributor III

Okay, I have figured out how to get DMA working with SPI1 - the primary reason why I was having with getting SPI DMA transfers to work was that I was using SPI1 (which only has one DMA trigger) when all the examples out there use SPI0 (which has two triggers).  Along with that, I figured out how to use the SPI DMAs for single byte commands to the OLED controller that I am using.  

Thank you to Gerardo Rodriguez who put the answer in here: SPI1 with DMA on K22 - BUG - it just took me a while to figure out enough to understand what was being explained.  The issue is that SPI1 only has one trigger while SPI0 has two.  

Here is the SPI code that I am now using - the basis is from the "dspi_edma_b2b_transfer_master" example project with a few modifcations the primary reasons for:

  1. Having code that will work for both SPI0 as well as SPI1
  2. Handling datablocks larger than the "limited_size" value outlined in "fsl_dspi_edma.c"
  3. Handling only data writes - if you want to do data reads as well as writes or use half-duplex mode, you will have to do some work on your own.  

The SPI Defines/Constants are identical to what I was using above.  I'm including them here so somebody researching (ie copying the code) doesn't have to search for the values.  

enum { SPI_TRANSFER_BAUDRATE = 10000000U
     };


#define OLED_DSPI_MASTER_DMA_BASE DMA_BASE
#define OLED_DSPI_MASTER_DMA_BASEADDR ((DMA_Type *)(OLED_DSPI_MASTER_DMA_BASE))
#define OLED_DSPI_MASTER_DMA_MUX_BASE DMAMUX_BASE
#define OLED_DSPI_MASTER_DMA_MUX_BASEADDR ((DMAMUX_Type *) \         

                                            (OLED_DSPI_MASTER_DMA_MUX_BASE))

#define OLED_DSPI_MASTER_DMA_TX_REQUEST_SOURCE kDmaRequestMux0SPI1
#define OLED_DSPI_MASTER_DMA_RX_REQUEST_SOURCE kDmaRequestMux0SPI1

#define OLED_DSPI_MASTER_BASE (SPI1_BASE)
#define OLED_DSPI_MASTER_BASEADDR ((SPI_Type *)OLED_DSPI_MASTER_BASE)
#define OLED_DSPI_MASTER_IRQN (SPI1_IRQn)

#define OLED_DSPI_MASTER_CLK_SRC (DSPI1_CLK_SRC)
#define OLED_DSPI_MASTER_CLK_FREQ CLOCK_GetFreq(DSPI1_CLK_SRC)

The Global Variables have been updated to include a second dspi_transfer_t object which is actually used for the EDMA APIs as well as variables to keep track of how much data has been sent.  

const dspi_master_config_t oledSPIConfig =
  { .whichCtar = kDSPI_Ctar0
  , .ctarConfig.baudRate = SPI_TRANSFER_BAUDRATE
  , .ctarConfig.bitsPerFrame = 8
  , .ctarConfig.cpol = kDSPI_ClockPolarityActiveHigh
  , .ctarConfig.cpha = kDSPI_ClockPhaseFirstEdge
  , .ctarConfig.direction = kDSPI_MsbFirst
  , .ctarConfig.pcsToSckDelayInNanoSec = 1000000000U / SPI_TRANSFER_BAUDRATE
  , .ctarConfig.lastSckToPcsDelayInNanoSec = 1000000000U / SPI_TRANSFER_BAUDRATE
  , .ctarConfig.betweenTransferDelayInNanoSec = 500000000U / SPI_TRANSFER_BAUDRATE

  , .whichPcs = kDSPI_Pcs0
  , .pcsActiveHighOrLow = kDSPI_PcsActiveLow

  , .enableContinuousSCK = FALSE
  , .enableRxFifoOverWrite = FALSE
  , .enableModifiedTimingFormat = FALSE
  , .samplePoint = kDSPI_SckToSin0Clock
  };

enum { SPIBUFFERSIZE = 8192
     };
QueueHandle_t spiQUEUE_Handle;
QueueHandle_t spiRXQUEUE_Handle;
uint8_t spiCmdBuffer[16];
uint8_t spiTXBuffer[SPIBUFFERSIZE];
dspi_master_handle_t oledSPIHandle;
dspi_transfer_t spiXfer       = { .rxData = NULL
                                , .configFlags = kDSPI_MasterCtar0 |
                                                 kDSPI_MasterPcs0 |
                                                 kDSPI_MasterPcsContinuous
                                };
dspi_transfer_t actualSPIXfer = { .rxData = NULL
                                , .configFlags = kDSPI_MasterCtar0 |
                                                 kDSPI_MasterPcs0 |
                                                 kDSPI_MasterPcsContinuous
                                };
edma_handle_t dspiEdmaMasterRxRegToRxDataHandle;
edma_handle_t dspiEdmaMasterTxDataToIntermediaryHandle;
edma_handle_t dspiEdmaMasterIntermediaryToTxRegHandle;
dspi_master_edma_handle_t g_dspi_edma_m_handle;
volatile uint32_t spiTransferCompleted = TRUE;
uint32_t spiBlockSize = 510; // Should be 511, but keeping it even
uint32_t spiSentCurrently;

DMA/SPI initialization.  Note that I check to see whether or not the SPI has one or two trigger sources - this was the issue with the orginal code.  SPI0 has two trigger sources while SPI1 only has one (and has to use the RX trigger).  I put in the check used by "fsl_dspi_edma.c" to only set the TX source if it is appropriate as well as change the size of the maximum DMA block size that can be sent for this case.

DSPI_MasterInit(OLED_DSPI_MASTER_BASEADDR
              , &oledSPIConfig
              , CLOCK_GetFreq(DSPI1_CLK_SRC));

/* DMA MUX init */
DMAMUX_Init(OLED_DSPI_MASTER_DMA_MUX_BASEADDR);
DMAMUX_SetSource(OLED_DSPI_MASTER_DMA_MUX_BASEADDR
               , masterRxChannel
               , OLED_DSPI_MASTER_DMA_RX_REQUEST_SOURCE);
DMAMUX_EnableChannel(OLED_DSPI_MASTER_DMA_MUX_BASEADDR
               , masterRxChannel);

/* If DMA has two triggers, add the "TX" one */
if (1 == FSL_FEATURE_DSPI_HAS_SEPARATE_DMA_RX_TX_REQn(OLED_DSPI_MASTER_BASEADDR)) {
  DMAMUX_SetSource(OLED_DSPI_MASTER_DMA_MUX_BASEADDR
                 , masterTxChannel
                 , OLED_DSPI_MASTER_DMA_TX_REQUEST_SOURCE);
  DMAMUX_EnableChannel(OLED_DSPI_MASTER_DMA_MUX_BASEADDR
                     , masterTxChannel);
  spiBlockSize = 32766; // Should be 32,767, but keeping it even
}


EDMA_GetDefaultConfig(&userConfig);
EDMA_Init(OLED_DSPI_MASTER_DMA_BASEADDR
        , &userConfig);

/* Set up dspi master */
memset(&dspiEdmaMasterRxRegToRxDataHandle
     , 0
     , sizeof(dspiEdmaMasterRxRegToRxDataHandle));
memset(&dspiEdmaMasterTxDataToIntermediaryHandle
     , 0
     , sizeof(dspiEdmaMasterTxDataToIntermediaryHandle));
memset(&dspiEdmaMasterIntermediaryToTxRegHandle
     , 0
     , sizeof(dspiEdmaMasterIntermediaryToTxRegHandle));

EDMA_CreateHandle(&dspiEdmaMasterRxRegToRxDataHandle
                , OLED_DSPI_MASTER_DMA_BASEADDR
                , masterRxChannel);
EDMA_CreateHandle(&dspiEdmaMasterTxDataToIntermediaryHandle
                , OLED_DSPI_MASTER_DMA_BASEADDR
                , masterIntermediaryChannel);
EDMA_CreateHandle(&dspiEdmaMasterIntermediaryToTxRegHandle
                , OLED_DSPI_MASTER_DMA_BASEADDR
                , masterTxChannel);

DSPI_MasterTransferCreateHandleEDMA(OLED_DSPI_MASTER_BASEADDR
                                  , &g_dspi_edma_m_handle
                                  , DSPI_MasterEDMACallback
                                  , NULL
                                  , &dspiEdmaMasterRxRegToRxDataHandle
                                  , &dspiEdmaMasterTxDataToIntermediaryHandle
                                 , &dspiEdmaMasterIntermediaryToTxRegHandle);

Before I send data, I check the status of "spiTransferCompleted" and reset it.  In my case, I have to do this because I may have to change the value of a Command/Data line, but it's always good practice to to this as this way you don't waste processor cycles after starting the DMA cycle waiting for the transfer to complete - instead you're allowing basic programming to take place while the DMA is executing and hopefully it will complete before you need it again.  I'm mentioning it because in the examples, I see the following code after the call to "DSPI_MasterTransferEDMA".

for (; !spiTransferCompleted;) { }
spiTransferCompleted = FALSE;

In my application, I have to send up to 8,192 bytes but if you look at the code for "DSPI_MasterTransferEDMA" you'll see that the transfer limit for devices which only have one trigger (ie SPI1) are limited to a data size of 511 bytes.  To handle this situation, I break up the DMA initiation calls to the size of the block the DMA can send with larger blocks being at least 256 bytes so the call to "DSPI_MasterTransferEDMA" that is located within the Callback can complete before the DMA interrupt is requested again and you end up in a situation where you get nested Callbacks with data that has not yet been sent being overwritten.  If the user is sending less than a full block, the DMA exeutes normally.  

void SPI_BulkTransfer() {
#if (2 != SDK_DEBUGCONSOLE)
int32_t status;
#endif


  spiSentCurrently = 0;
  actualSPIXfer.txData = spiXfer.txData;
  if ((spiBlockSize + 0) >= spiXfer.dataSize) {
    actualSPIXfer.dataSize = spiXfer.dataSize;
  }
  else if ((spiBlockSize + 256) > spiXfer.dataSize) {
    actualSPIXfer.dataSize = 256;
  }
  else {
    actualSPIXfer.dataSize = spiBlockSize;
  }

  DSPI_MasterTransferEDMA(OLED_DSPI_MASTER_BASEADDR
                        , &g_dspi_edma_m_handle
                        , &actualSPIXfer);
}

As noted above, the SPI DMA Callback will request additional transfers if they are required with the minimum size of additional transfers being 256 bytes.  

void DSPI_MasterEDMACallback(SPI_Type *base
                           , dspi_master_edma_handle_t *handle
                           , status_t status
                           , void *userData) {


  if (status == kStatus_Success) {
    spiSentCurrently += actualSPIXfer.dataSize;
    actualSPIXfer.txData = &spiXfer.txData[spiSentCurrently];
    if ((spiXfer.dataSize + 0) == spiSentCurrently) {
      spiTransferCompleted = TRUE;
    }
    else {
      if (256 >= (spiXfer.dataSize - spiSentCurrently)) {
        actualSPIXfer.dataSize = spiXfer.dataSize - spiSentCurrently;
      }
      else if ((spiBlockSize + 0) > ((spiXfer.dataSize - spiSentCurrently))) {
        actualSPIXfer.dataSize = (spiXfer.dataSize - spiSentCurrently) - 256;
      }
      else {
        actualSPIXfer.dataSize = spiBlockSize;
      }
      DSPI_MasterTransferEDMA(OLED_DSPI_MASTER_BASEADDR
                            , &g_dspi_edma_m_handle
                            , &actualSPIXfer);
    }
  }

}

 

In answer to the questions in the original post:

1.  Is there a way of sending single bytes using SPI DMA?  Block Data writes to the OLED seem to be limited to two bytes or more.  There is no "NOP" bytes for the OLED that could be teamed up with the commands.  

[MAP] I discovered that if I sent an invalid command bye to the OLED before the actual one, the OLED ignored it and everything was okay.  Similarly, with a single data byte as part of the command, if I sent a single 0x00 byte after it, it was ignored.  

  1.1. Can the single byte SPI writes co-exist with the multi-byte SPI writes?  

[MAP] As I have everything working with a DMA, I haven't pursued this.  I do have the question out to the OLED controller manufacturer (Solomon Systech) and I'm waiting for a response.  

2.  Can I use a single callback for the single byte and DMA transfers (Assuming that the solution to the problems allows both approaches to be used)?  This would require type casting one or the other paremeters in the Callback methods.  

[MAP] This is no longer relevant due to the use of only DMA.  

3. Once I have the code working, how do I remove the "spiRXBuffer" and associated read?  The SPI port to the OLED is only one way, so the buffer is SRAM that I would like to find a better use for.  

[MAP] In going through the SPI/EDMA code I discovered that if you load rxData or txData of the dspi_transfer_t structure passed to the DMA request then it is ignored.  

  3.1.  Is it an issue that I'm doing bi-directional send/receives on the single "kDmaRequestMux0SPI1" channel?  Ideally, I just want to do transmits.  

[MAP] This is basically the issue that caused the problems with getting DMA operations to work correctly and is addressed in the code above.  

4.  Looking at the EDMA code, it looks like the maximum size that can be transferred is 511 bytes - the OLED requires 8,192 bytes.  When I attempt to send more than 511 bytes, I get an error 603 ("kStatus_DSPI_OutOfRange") from DSPI_MasterTransferEDMA.  After sending a block of (say 256) bytes, can I call the DSPI_MasterTransferEDMA from within the EDMA callback?  

[MAP] This is addressed above.

So, was all this worth it?  In terms of performance, I see a pretty substantial increase in full OLED write speed changing from single byte writes to DMA:

- At 60MHz, Single Byte OLED write is 93ms and with DMA it dropped down to 17ms

- At 120MHz, Single Byte OLED write is 57ms and with DMA it dropped down to 12ms

At 17ms or 12ms, the screen update appears to be instantaneous but at the original speeds, the update was easily observed.  

This was an investigation that took two weeks of work (including finding the right wiring and initialization/image load code for hte OLED) but the results are quite impressive.  

View solution in original post