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?
 

24 REPLIES 24

OBS is short for 'Observation' used to draw attention to something.

Nikolaj_TL
Associate III

'OBS' used in this context is short for 'observe', used to draw attention to something.

BarryWhit
Senior III

ah. I use "NOTE:" - it's less acronymical.

- If a post has answered your question, please acknowledge the help you received by clicking "Accept as Solution".
- Once you've solved your issue, please consider posting a summary of any additional details you've learned. Your new knowledge may help others in the future.
BarryWhit
Senior III

The first question you need to decide is whether your application can do I/O and computation sequentially (simpler, slower) or whether it needs to handle both concurrently (a bit more complex, faster).

 

Sequentially - "get some data, process it, get some more data, process it"

concurrently - " get some data and immediately start the next transfer, process the already received data while

the next transfer is ongoing".

 

If "sequential" operation is good enough for you, there's no need for double-buffering, nor an extra synchronization variable. All you need to do is to be disciplined about which code block "owns" the buffers at any given time. Only the code block that owns the buffers is allowed to access them (for read OR write), and ownership transfers back and forth between them, using the access-synchronizing variable. With the DMA start/complete locations in the code serving as the logical transition points.

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

I personaly would do it with freeRTOS and queues. 

BarryWhit
Senior III

If he already knows FreeRTOS, sure. that would work. If not, it's a large detour.

- If a post has answered your question, please acknowledge the help you received by clicking "Accept as Solution".
- Once you've solved your issue, please consider posting a summary of any additional details you've learned. Your new knowledge may help others in the future.
PPopo.1
Senior

So looking at your code a bit better, I have few questions. 

HAL_SPI_TransmitReceive_DMA(&hspi1, TxBuffer, RxBuffer, BUFFERSIZE); // update SPI transfer

You are using TransmitReceive function 2 times. First in the interrupt routine and after interrupt routine you use it again in the main. What is the purpose of that? 


The next issue I see is that you toggle pin right after using this function.

DMA runs in the background, and if you toggle the pin before the message is sent completely, it will interrupt the process. Make sure that the data is sent or received completely before toggeling.

Nikolaj_TL
Associate III

Ideally I would implement sequential first and then concurrently. 
The first screenshot i posted from the logic analyser it showed that the TxBuffer was processed way before the next transation began. How can it be that I still see old data in the firste 3-bytes of the transfer?Logic AnalyserLogic Analyser

The reference manual (for STM32F031, rm0091) states that the SPI Tx Buffer can contain 3-bytes. When DMA is initiated it bumps the DMA_CNDTRx register from 8-bytes to 5-bytes likely because it transfers the first three bytes to the SPI TxBuffer which is illustrated in the block diagram below. 
{C35C8B80-FE37-4AA1-BE1D-EB000ABEE1B2}.png
When I try to update the TxBuffer by setting up a new transfer I think that it is this FIFO that doesn't get cleared properly from the previous transfer. I am having a hard time debugging this as I haven't found any register or flash address for the TxFIFO & RxFIFO. 


I am working on a setup with tight timing requirements, why I cannot use FreeRTOS. Can you point me to any ressources with double buffering in embedded systems, or do you have suggestions any insights on the SPI TxBuffer, that might not be cleared properly?

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
    }
}

 

BarryWhit
Senior III

Update: PPopo.1  just told you what the issue is. You should not have calls to HAL_SPI_TransmitReceive_DMA in both your callback *and* your main loop. 

 

Can you point me to any resources with double buffering in embedded systems, 

 

IMHO you should not try implementing a more complex design until you fully understand why your current one isn't working. Instead of adding complexity (adding more sync variables, more conditionals, switching to double-buffering), you should be trying to simplify as much as possible until the source of the problem becomes clear (like draining a pond to catch a fish).

 

> When I try to update the TxBuffer by setting up a new transfer I think that it is this FIFO that doesn't get cleared properly from the previous transfer. 

  do you have suggestions any insights on the SPI TxBuffer, that might not be cleared properly?

 

You can certainly check the silicon errata for you chip, but if there isn't something there assume this is not the issue.

 

Also look at  http://efton.sk/STM32/gotcha/g66.html (for general knowledge).

 

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