cancel
Showing results for 
Search instead for 
Did you mean: 

SPI MISO shifted

Nikolaj_TL
Associate III

Hello ST Community, 

I have setup a SPI master and a SPI slave, both use DMA in Normal mode. The word size is 8-bits and the data size is 8-bytes. The problem is that the slave transmits messages which contains old data. 

When it should send:
MISO: { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }
it sends:
MISO: { 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01 }

When it should send:
MISO: { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 }
it sends:
MISO: { 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02 }

Nikolaj_TL_0-1718698066796.png

OBS: The debug gpio is toggled when the ISR finishes and when theTxBuffer is edited in main.



This pattern continues for every transfer it contains 3 old bytes.

Implementation (slave):
The slave updates the response in the main function.

 

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_SPI1_Init();

  HAL_SPI_TransmitReceive_DMA(&hspi1, TxBuffer, RxBuffer, 8);

  while (1)
  {
	if(msgReceived == 1) {
		memset(TxBuffer, SPI_Counter, BUFFERSIZE);
		HAL_SPI_TransmitReceive_DMA(&hspi1, TxBuffer, RxBuffer, BUFFERSIZE); // update SPI transfer
		
		msgReceived = 0;
		HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_1);		// DEBUG
	}
  }
}

 

 

void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
	msgReceived = 1;
	SPI_Counter++;
	HAL_SPI_TransmitReceive_DMA(&hspi1, TxBuffer, RxBuffer, 8); // setup a new transfer
	HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_1);		// DEBUG
}

 


Setup:
SetupSetup

How can it be that the MISO transfer contains old data?
 

1 ACCEPTED SOLUTION

Accepted Solutions

If the TxBuffer is modified while a DMA transfer is still ongoing, you may end up with partial or stale data being sent. This often happens if the buffer update timing overlaps with DMA operations.

You are starting new DMA transfers from both the main loop and the HAL_SPI_TxRxCpltCallback. This can cause overlapping DMA requests, resulting in inconsistent buffer states.

 

You start the first DMA with a length of 8 but subsequently use BUFFERSIZE for the next transactions. Ensure consistency in buffer sizes to avoid partial data transmissions.

 

Updating TxBuffer while DMA might be using it can cause race conditions, leading to old or corrupted data being sent.

 

Something like this might help you with double buffering:

 

#include "stm32f4xx_hal.h"

#define BUFFERSIZE 8

uint8_t TxBuffer1[BUFFERSIZE];
uint8_t TxBuffer2[BUFFERSIZE];
uint8_t RxBuffer[BUFFERSIZE];
uint8_t *currentTxBuffer;
uint8_t *nextTxBuffer;
volatile uint8_t msgReceived = 0;
volatile uint8_t bufferReady = 0;
volatile uint8_t SPI_Counter = 0;
SPI_HandleTypeDef hspi1;

void SystemClock_Config(void);
void MX_GPIO_Init(void);
void MX_DMA_Init(void);
void MX_SPI1_Init(void);
void Error_Handler(void);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_SPI1_Init();

    // Initialize buffers
    currentTxBuffer = TxBuffer1;
    nextTxBuffer = TxBuffer2;
    memset(currentTxBuffer, 0x00, BUFFERSIZE); // Initial data pattern

    // Start the first DMA transfer
    if (HAL_SPI_TransmitReceive_DMA(&hspi1, currentTxBuffer, RxBuffer, BUFFERSIZE) != HAL_OK)
    {
        Error_Handler(); // Handle error
    }

    while (1)
    {
        if (msgReceived == 1)
        {
            // Prepare the next buffer
            memset(nextTxBuffer, SPI_Counter, BUFFERSIZE);

            // Indicate that the next buffer is ready for transmission
            bufferReady = 1;

            // Clear the flag to avoid reprocessing
            msgReceived = 0;

            HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_1); // DEBUG
        }
    }
}

void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
    if (hspi->Instance == SPI1)
    {
        SPI_Counter++; // Update the counter for the next data pattern

        // Swap buffers if the new buffer is ready
        if (bufferReady)
        {
            uint8_t *temp = currentTxBuffer;
            currentTxBuffer = nextTxBuffer;
            nextTxBuffer = temp;
            bufferReady = 0; // Clear buffer ready flag
        }

        // Start the next DMA transfer with the updated buffer
        if (HAL_SPI_TransmitReceive_DMA(&hspi1, currentTxBuffer, RxBuffer, BUFFERSIZE) != HAL_OK)
        {
            Error_Handler(); // Handle error
        }

        // Indicate transfer completion
        msgReceived = 1;

        HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_1); // DEBUG
    }
}

 

View solution in original post

24 REPLIES 24
PPopo.1
Senior

The problem with your SPI communication, where old data is being partially transmitted, likely arises due to the timing and synchronization between your data preparation and the SPI transmit/receive operations. Here’s a detailed analysis and how you might fix the issue:

Issue Explanation

When using DMA for SPI communication in a non-blocking mode, data preparation and transfer initiation must be carefully synchronized. Your main loop and the HAL_SPI_TxRxCpltCallback are both modifying the TxBuffer and restarting the DMA transfer. If the buffer is updated while a DMA transfer is in progress, it may lead to partial transmission of the old data. Specifically, the issue is:

  1. Buffer Update Timing: Your TxBuffer is updated in the main loop and also in the callback function, potentially leading to race conditions. The buffer might be modified before the previous transfer is completed, resulting in mixed or partial data being transmitted.

The flag msgReceived should avoid the buffer being changed in the main function if not a new message is received. 

In the following I have set a debug gpio when the ISR finishes and reset the same gpio when the main function has processed the TxBuffer. From this it is clear that the new Txbuffer ready when a new transaction begins. 
DEBUGDEBUG

Issue with partial transmission indicates there might still be a timing problem or insufficient synchronization between buffer updates and DMA transfers.

Even with msgReceived flag, there can be a problem if:

  • The buffer is being updated too soon after starting a DMA transfer.
  • The DMA transfer is re-triggered before the buffer update is fully complete.
Nikolaj_TL
Associate III

Adding a flag (answerPending) resolves the problem, but introduces an additional response delay.

Msg1: Master sends request
Msg2: ... (TxBuffer not updated jet)
Msg3: Slave response to master

 

int main(void) {
  ...

  while (1)
  {
	if(msgReceived == 1) {
		memset(tempTxBuffer, SPI_Counter, BUFFERSIZE);
		answerPending = 1;

		msgReceived = 0;
		HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_1);		// DEBUG
	}
  }
}
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
	msgReceived = 1;

	if( answerPending == 1 ) {
		memcpy(TxBuffer, tempTxBuffer, BUFFERSIZE);
		answerPending = 0;
	}

	SPI_Counter++;

	HAL_SPI_TransmitReceive_DMA(&hspi1, TxBuffer, RxBuffer, BUFFERSIZE);
	HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_1);		// DEBUG
}

 

 
How could the synchronization be implemented without introducing an additional delay?

BarryWhit
Senior

This probably won't fix your problem, but it's logically wrong to set msgReceived=0 *after* calling HAL_SPI_TransmitReceive_DMA from main.

As soon as you call HAL_SPI_TransmitReceive_DMA, write "ownership" of msgReceived passes to the callback. 

- If you feel a post has answered your question, please click "Accept as Solution".
- Once you've solved your issue, please consider posting a summary with any additional details you've learned. Your new knowledge may help others in the future.

 

  • Double Buffering: Use two buffers to alternate between reading and writing. This ensures that one buffer is always available for DMA while the other is being prepared.In double buffering, you maintain two separate buffers. While the DMA is transmitting from one buffer, the other can be updated. After the DMA transfer completes, you switch the buffers.

  • Atomic Operations: Ensure buffer updates and DMA re-triggering are atomic, avoiding conflicts between the main loop and the DMA callback.

 

BarryWhit
Senior

Since they are modified by an interrupt handler, you should mark these synchronization variables `volatile`.  If you don't, the compiler might mistakenly optimize away code you need and ruin your day.

- If you feel a post has answered your question, please click "Accept as Solution".
- Once you've solved your issue, please consider posting a summary with any additional details you've learned. Your new knowledge may help others in the future.
Nikolaj_TL
Associate III

Thank you for the response @PPopo.1  and @BarryWhit

I have been working on implementing the double buffering setup, but haven't been able to achieve the desired functionality. How would you implement the double buffering or the atomic operations?

I have sketched a sequence diagram which illustrates the desired functionality, which I hope to achieve from solving the problem which I have stated. 
Sequence DiagramSequence Diagram

OBS: The two cases show how the slave should respond when the slave is busy and when it is done executing. 

BarryWhit
Senior

What does "OBS" stand for?

- If you feel a post has answered your question, please click "Accept as Solution".
- Once you've solved your issue, please consider posting a summary with any additional details you've learned. Your new knowledge may help others in the future.