cancel
Showing results for 
Search instead for 
Did you mean: 

memcpy vs split SPI transfer

MLang.7
Associate III

Hello,

I came across this question while writing a driver and I would like to ask what is your best practice in this regard. So lets say you want to interface with an chip via SPI. To perform any action, a command byte or a sequence of commands is required followed by some application data. The sequence looks like that:

| command bytes | data |

In order the perform the data transmission, I could do something like this:

/* 'pTxBuf', 'pRxBuf' are pointers to the data buffer. 'Size' is the number of data bytes to be transferred */
 
uint8_t txCmd[2] = { COMMAND_BYTE_1,  COMMAND_BYTE_2 };
uint8_t rxCmd[2] = { 0 };
 
/* Start transmission by taking CS low */
HAL_GPIO_WritePin(CS_GPIO_Port CS_GPIO_Pin, GPIO_PIN_RESET);
 
/* Command transmission */
HAL_SPI_TransmitReceive(&hspi1, &txCmd, &rxCmd, 2, HAL_MAX_DELAY);
 
/* Data transmission */
HAL_SPI_TransmitReceive(&hspi1, pTxBuf, pRxBuf, Size, HAL_MAX_DELAY);
 
/* Stop transmission by taking CS high */
HAL_GPIO_WritePin(CS_GPIO_Port CS_GPIO_Pin, GPIO_PIN_SET);
 

While this works perfectly, I somehow don't feel comfortable with splitting the transfer. In another approach memcpy is engaged to copy the command sequence and data to one buffer:

/* 'pTxBuf', 'pRxBuf' are pointers to the data buffer. 'Size' is the number of data bytes to be transferred */
 
/* Calculate buffer size */
uint8_t tmpBufferSize = 2 + Size;
 
/* Create temporary buffers for holding the command sequence and data */
uint8_t tmpTxBuffer[tmpBufferSize] = { 0 };
uint8_t tmpRxBuffer[tmpBufferSize] = { 0 };
 
/* Set both command bytes */ 
txCmd[0] = COMMAND_BYTE_1;
txCmd[1] = COMMAND_BYTE_2;
 
/* Copy application data to temporary buffer */
memcpy(tmpTxBuffer + 2, pTxBuf, Size)
 
/* Start transmission by taking CS low */
HAL_GPIO_WritePin(CS_GPIO_Port CS_GPIO_Pin, GPIO_PIN_RESET);
 
/* Transmit entire temporary buffer */
HAL_SPI_TransmitReceive(&hspi1, &tmtTxBuffer, &tmpRxBuffer, tmpBufferSize, HAL_MAX_DELAY);
 
/* Stop transmission by taking CS high */
HAL_GPIO_WritePin(CS_GPIO_Port CS_GPIO_Pin, GPIO_PIN_SET);

With the second method only one HAL access is needed. I think this could be helpful for later abstraction or when using DMA. On the other hand the use memcpy carries the risk of memory leakage.

What is your best practice for transferring command sequences and data via SPI?

1 REPLY 1
S.Ma
Principal

To me it's very dependent on your HW configuration. Maybe ask yourself:

  • How long is the biggest SPI transaction?
  • If using blocking scheme, does it impact your application?
  • Is the SPI datarate very high and max bandwidth is going to be optimized at some point?
  • Are there going to be multiple slaves with same or different protocol?
  • If you want to later make SPI work as background task with interrupts, DMA will reduce the interrupt frequency and improve overall core performance from pipeline breaks (interrupts)
  • Then maybe you'll want to have a queue of SPI transactions with callbacks/flags...
  • Do you want your SPI driver generic and reusable? For these slaves? For any type of slaves devices ?
  • Maybe use SW emulation of the DMA with same APIs so you can switch between the two when possible?
  • Even though configuring a DMA maybe wasted to transfer a single byte, the code structure maybe easier to maintain despite the optimization penalty.
  • Think WCET (worst case execution time / latency)