2024-06-18 12:01 AM - edited 2024-06-18 01:57 AM
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 }
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:
How can it be that the MISO transfer contains old data?
Solved! Go to Solution.
2024-06-19 06:46 AM
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
}
}
2024-06-18 12:25 AM - edited 2024-06-18 12:31 AM
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:
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:
2024-06-18 01:05 AM
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.
2024-06-18 01:16 AM
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:
2024-06-18 01:56 AM - edited 2024-06-18 01:56 AM
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?
2024-06-18 02:44 AM - edited 2024-06-18 02:45 AM
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.
2024-06-18 03:21 AM - edited 2024-06-18 03:23 AM
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.
2024-06-18 07:08 AM - edited 2024-06-19 12:01 AM
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.
2024-06-19 12:29 AM - edited 2024-06-19 12:30 AM
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.
OBS: The two cases show how the slave should respond when the slave is busy and when it is done executing.
2024-06-19 02:30 AM
What does "OBS" stand for?