Skip to main content
Graduate
June 18, 2024
Solved

SPI MISO shifted

  • June 18, 2024
  • 18 replies
  • 6437 views

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?
 

    This topic has been closed for replies.
    Best answer by PPopo.1

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

     

    18 replies

    Graduate
    June 19, 2024

    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.

    Graduate
    June 19, 2024

    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?

    PPopo.1Answer
    Graduate
    June 19, 2024

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

     

    Graduate II
    June 19, 2024

    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).

     

    Graduate
    June 20, 2024

    Thank you for your replies. 

    @BarryWhit I do want to understand why the current setup isn't working. I am well aware that if I only call the HAL_SPI_TransmitReceive_DMA in the main loop (or in the ISR) it would work as expected.
    I have setup this code example to simplify my problem as much as possible. Really I am working on a motor control project with two chips a dedicated motor controller and a main chip that handles bluetooth and so on. The main chip should always be able to ping the motor controller, even if it has requested it to carry out a command. Secondly the motor controller should respons the main controller when it has executed a command and is ready for a new. If the main chip cannot ping the motor controller it should kill the motor controller (turn off the power).

    The diagram depicts the current functionality and the desired functionality. 

    Nikolaj_TL_5-1718865825829.png


    Currently the transfer is handled using synchronization flags, this introduces a delay. Although the slave has finished its task and has a result ready it transfers 'Busy_State', because a transfer only can be setup in the ISR. 
    Double Buffering has the same problem it still has a response delay. 

    Graduate
    June 20, 2024

    The only purpose of the DEBUG pin is to show that there is plenty of time between the TxBuffer is updated in main until a new transfer starts. 

    Nikolaj_TL_1-1718864795497.png


    I know that I should monitor whether a SPI transfer is ongoing before I update the TxBuffer. I will handle this after I have resolved my main problem, which is that I cannot update the TxBuffer after I have initiated a SPI transfer. 

    I see the same behaviour as in the Screenshot if I only update the TxBuffer in the main loop and omits calling HAL_SPI_TransmitReceive_DMA

    int main() {
     ...
    
     if (HAL_SPI_TransmitReceive_DMA(&hspi1, currentTxBuffer, RxBuffer, BUFFERSIZE) != HAL_OK) {
     Error_Handler(); // Handle error
     }
    
     while (1)
     {
    	if(msgReceived == 1) {
    		memset(TxBuffer, SPI_Counter, BUFFERSIZE);
    		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, BUFFERSIZE); // setup a new transfer
    	HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_1);		// DEBUG
    }
    Graduate II
    June 20, 2024

    > @BarryWhit I do want to understand why the current setup isn't working. I am well aware that if I only call

    > the HAL_SPI_TransmitReceive_DMA in the main loop (or in the ISR) it would work as expected.

     

    So, you do understand you can't start a DMA while one is ongoing, but you don't understand why your code, which does just that, is corrupting data? I don't follow.

     

    >> I know that I should monitor whether a SPI transfer is ongoing before I update the TxBuffer. I will

    >> handle this after I have resolved my main problem, which is that I cannot update the TxBuffer

    >> after I have initiated a SPI transfer. 

     

    Your original post asked why you're seeing data corruption, and you got an answer.

    You also cannot touch the buffer while a transfer is ongoing (not in code, not by restarting DMA). You're not going to "resolve" that.

     

    > Currently the transfer is handled using synchronization flags, this introduces a delay.

     

    are you trying to say that you need a way to interrupt an ongoing transfer? because that's completely unrelated to the issue we've been trying to help you with. If you've resolved the data corruption issue, I suggest you accept the solution  @PPopo.1 provided and start a new thread.

     

    Graduate
    June 20, 2024

    Sorry for the unclear communication and the inconvenience it has caused. I tried communicated the desired functionality through the sequence diagrams. 
    I will accept @PPopo.1 s soultion and start a new thread. 

    Graduate II
    June 20, 2024

    So the data corruption issue has been resolved. That's progress!