AnsweredAssumed Answered

Problem Using SPI1 with DMA - Changed Subject

Question asked by Myke Predko on Apr 23, 2020
Latest reply on Apr 27, 2020 by Myke Predko

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

Outcomes