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 02:37 AM
OBS is short for 'Observation' used to draw attention to something.
2024-06-19 02:39 AM
'OBS' used in this context is short for 'observe', used to draw attention to something.
2024-06-19 02:40 AM
ah. I use "NOTE:" - it's less acronymical.
2024-06-19 02:57 AM - edited 2024-06-19 03:01 AM
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.
2024-06-19 03:05 AM
I personaly would do it with freeRTOS and queues.
2024-06-19 04:00 AM
If he already knows FreeRTOS, sure. that would work. If not, it's a large detour.
2024-06-19 04:58 AM
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.
2024-06-19 05:56 AM
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?
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.
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?
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-19 10:29 AM - edited 2024-06-19 12:53 PM
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).