2025-10-22 12:52 PM - last edited on 2025-10-23 2:01 AM by Andrew Neil
What's wrong with my code? Every time I start a DMA transfer only 2 Bytes get transferred although I specified 16 bytes.
Also, I get an FIFO Error after transmit, whereas Transmit Complete is ok.
/* FUNCTION ************************************/
void SPI1_Init (void)
/************************************************/
{
LL_SPI_Enable (SPI1);
LL_DMA_EnableIT_HT (DMA2, LL_DMA_STREAM_3);
LL_DMA_EnableIT_TE (DMA2, LL_DMA_STREAM_3);
LL_DMA_EnableIT_TC (DMA2, LL_DMA_STREAM_3);
LL_DMA_EnableIT_DME (DMA2, LL_DMA_STREAM_3);
LL_DMA_EnableIT_FE (DMA2, LL_DMA_STREAM_3);
} /* --- SPI1_Init --- */
/* FUNCTION ************************************/
void SPI1_Write_DMA (uint8_t *buff, uint16_t count)
/*
* Write a complete buffer to SPI with DMA
***********************************************/
{
uint32_t reg_addr;
reg_addr = LL_SPI_DMA_GetRegAddr(SPI1);
LL_DMA_ConfigAddresses (DMA2, LL_DMA_STREAM_3, (uint32_t)buff,
reg_addr, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
LL_DMA_SetDataLength (DMA2, LL_DMA_STREAM_3, count);
LL_SPI_EnableDMAReq_TX (SPI1);
LL_DMA_EnableStream (DMA2, LL_DMA_STREAM_3); // Mem to Periph
} /* --- SPI1_Write_DMA --- */
/* FUNCTION ************************************/
void spi_output_DMA (void)
/************************************************/
{
SPI1_Init ();
dma_SPI_write_complete = 0; // IRQ
W5500_CS_LOW;
SPI1_Write_DMA (spi_databuff, 16);
while (dma_SPI_write_complete == 0)
{
// wait for DMA finished
}
W5500_CS_HIGH;
delay_ms(20);
} /* --- spi_output_DMA --- */
Edited to apply source code formatting - please see How to insert source code for future reference.
Solved! Go to Solution.
2025-10-23 3:49 AM
OK, I found a solution that works for me. See listing below. All flags but <transfer complete> and <transfer error> don't need to be enabled and should be ignored. Starting point would be void <spi_output_DMA (....)>.
Noteworthy the FIFO error flag is always active although not enabled. While debugging this code I found that clearing this flag leads to stopping the DMA engine while more bytes need to be transferred.
/***********************************************************
************************************************************
** MODULE spi_dma.c **
** PROJECT **
** COMPILER **
** LANGUAGE C **
** DATE 21.10.2025 **
** AUTHOR lucylectric **
** ABSTRACT **
** 21.10.2025 / Creation **
************************************************************
***********************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "main.h"
#include "stm32f4xx_ll_spi.h"
#include "stm32f4xx_ll_dma.h"
#include "stm32f4xx_ll_gpio.h"
#include "spi_dma.h"
/***********************************************/
#define W5500_CS_LOW LL_GPIO_ResetOutputPin (W5500_CS_GPIO_Port, W5500_CS_Pin);
#define W5500_CS_HIGH LL_GPIO_SetOutputPin (W5500_CS_GPIO_Port, W5500_CS_Pin);
#define W5500_RESET_LOW LL_GPIO_ResetOutputPin (W5500_RESET_GPIO_Port, W5500_RESET_Pin);
#define W5500_RESET_HIGH LL_GPIO_SetOutputPin (W5500_RESET_GPIO_Port, W5500_RESET_Pin);
static volatile uint16_t dma_SPI_write_complete;
static volatile uint16_t dma_SPI_read_complete;
static uint8_t spi_databuff[200] =
{
0x55, 0xAA, 0x81, 0x42, 0x55, 0xAA, 0x81, 0x42,
0x55, 0xAA, 0x81, 0x42, 0x55, 0xAA, 0x81, 0x42,
0x55, 0xAA, 0x81, 0x42, 0x55, 0xAA, 0x81, 0x42,
0x55, 0xAA, 0x81, 0x42, 0x55, 0xAA, 0x81, 0x42
};
/* FUNCTION ************************************/
void SPI1_Init (void)
/* prepare SPI1 & DMA flags
***********************************************/
{
W5500_CS_HIGH;
//LL_SPI_Disable (SPI1); // not required
//LL_DMA_EnableIT_HT (DMA2, LL_DMA_STREAM_3); // half transfer flag
LL_DMA_EnableIT_TE (DMA2, LL_DMA_STREAM_3); // transfer error flag
LL_DMA_EnableIT_TC (DMA2, LL_DMA_STREAM_3); // transfer complete flag
//LL_DMA_EnableIT_DME (DMA2, LL_DMA_STREAM_3); // direct mode error flag
// don't enable FIFO error flag
//LL_DMA_EnableIT_FE (DMA2, LL_DMA_STREAM_3); // FIFO error flag
} /* --- SPI1_Init --- */
/* FUNCTION ************************************/
void DMA2_Stream0_IRQHandler_cont (void)
/* DMA SPI RX - Receive
***********************************************/
{
// LL_DMA_ClearFlag_TC0 transfer complete flag
// LL_DMA_ClearFlag_HT0 half transfer flag
// LL_DMA_ClearFlag_TE0 transfer error flag
// LL_DMA_ClearFlag_DME0 direct mode error flag
// LL_DMA_ClearFlag_FE0 FIFO error flag
if (LL_DMA_IsActiveFlag_TC3 (DMA2))
{
LL_DMA_ClearFlag_TC3 (DMA2); // Transfer complete
}
if (LL_DMA_IsActiveFlag_TE3 (DMA2))
{
LL_DMA_ClearFlag_TE3 (DMA2); // transfer error flag
}
dma_SPI_read_complete = 1; // set ready flag
} /* --- DMA2_Stream0_IRQHandler_cont --- */
/* FUNCTION ************************************/
void DMA2_Stream3_IRQHandler_cont (void)
/* DMA SPI TX - Transmit
***********************************************/
{
// LL_DMA_ClearFlag_TC3 transfer complete flag
// LL_DMA_ClearFlag_HT3 half transfer flag
// LL_DMA_ClearFlag_TE3 transfer error flag
// LL_DMA_ClearFlag_DME3 direct mode error flag
// LL_DMA_ClearFlag_FE3 FIFO error flag
if (LL_DMA_IsActiveFlag_TC3 (DMA2))
{
LL_DMA_ClearFlag_TC3 (DMA2); // Transfer complete
}
if (LL_DMA_IsActiveFlag_TE3 (DMA2))
{
LL_DMA_ClearFlag_TE3 (DMA2); // transfer error flag
}
#if 0 // do not handle these flags & don't clear them
if (LL_DMA_IsActiveFlag_HT3 (DMA2))
{
LL_DMA_ClearFlag_HT3 (DMA2); // half transfer flag
}
if (LL_DMA_IsActiveFlag_DME3 (DMA2))
{
LL_DMA_ClearFlag_DME3 (DMA2); // direct mode error flag
}
if (LL_DMA_IsActiveFlag_FE3 (DMA2))
{
LL_DMA_ClearFlag_FE3 (DMA2); // FIFO error flag
}
#endif
dma_SPI_write_complete = 1; // set ready flag
} /* --- DMA2_Stream3_IRQHandler_cont --- */
/* FUNCTION ************************************/
void SPI1_Write_DMA (uint8_t *buff, uint16_t count)
/*
* Write a complete buffer to SPI with DMA
***********************************************/
{
uint32_t reg_addr;
LL_SPI_Enable (SPI1); // enable SPI engine
// everything else is done in the CubeMX init
// set source and dest address
reg_addr = LL_SPI_DMA_GetRegAddr(SPI1);
LL_DMA_ConfigAddresses (DMA2, LL_DMA_STREAM_3, (uint32_t)buff,
reg_addr, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
LL_DMA_SetDataLength (DMA2, LL_DMA_STREAM_3, count);
LL_SPI_EnableDMAReq_TX (SPI1); // enable SPI for DMA operation
LL_DMA_EnableStream (DMA2, LL_DMA_STREAM_3); // start DMA mem to periph
} /* --- SPI1_Write_DMA --- */
/* FUNCTION ************************************/
void spi_output_DMA (uint16_t byte_count)
/* SPI data transfer
* - output buffer is static local (test version)
* - prepare DMA flags
* - handle chip select
* - start DMA engine
* - check & wait for complete flags (DMA & SPI)
***********************************************/
{
SPI1_Init ();
dma_SPI_write_complete = 0; // clear DMA finished flag
W5500_CS_LOW; // enable chip select
SPI1_Write_DMA (spi_databuff, byte_count); // start SPI & DMA
while (dma_SPI_write_complete == 0)
{
// wait for DMA finished
}
LL_SPI_DisableDMAReq_TX (SPI1);
while (LL_SPI_IsActiveFlag_BSY(SPI1) != 0)
{
// wait for SPI finished
}
W5500_CS_HIGH; // chip select off
} /* --- spi_output_DMA --- */
/* ----------------------------------------------*/
/* ----------- End of file spi_dma.c --------- */
/* ----------------------------------------------*/2025-10-22 1:01 PM
> Every time I start a DMA transfer only 2 Bytes get transferred although I specified 16 bytes.
How do you know this?
Probably the issue is in a part of the code we haven't been shown, or how you're interpreting the result.
Does dma_SPI_write_complete get set to 1? How/where? Make sure it's done after the SPI is complete, not when the DMA is complete.
2025-10-22 1:19 PM
> How do you know this?
I see it on my oscilloscope. The chip select goes inactive after 2 bytes.
> Probably the issue is in a part of the code we haven't been shown
Perhaps ....
> or how you're interpreting the result.
I see it on my oscilloscope.
> Does dma_SPI_write_complete get set to 1? How/where?
Yes, it's done in the DMA2_Stream3_IRQHandler, which is called when the DMA transfer has been executed.
> Make sure it's done after the SPI is complete, not when the DMA is complete.
I cannot imagine to check SPI status bits if the DMA controller controls the SPI machine. How would have to be done this?
2025-10-22 2:02 PM
> Yes, it's done in the DMA2_Stream3_IRQHandler, which is called when the DMA transfer has been executed.
That's a problem. When DMA is done transferring its data to the SPI, the SPI still has to send it out on the line. If you raise CS at this point, you miss part the transaction.
Poll the SPI BSY flag for the end of the transaction. Other chip families have better ways of doing this. The reference manual discusses this in "28.3.9 SPI communication using DMA (direct memory addressing)"
Probably something else going on as well, as you shouldn't be getting TC flag with only 2/16 bytes being sent. Unless you have FIFO enabled, which it doesn't sound like.
2025-10-22 2:07 PM
A better (less CPU) way of doing this is to set it up as two-way transfer (even if MISO is not initialized) and de-assert CS when all bytes have been received, since that is necessarily after all bytes have been sent. This can be done in the receiving DMA stream interrupt.
2025-10-22 2:23 PM
> That's a problem. When DMA is done transferring its data to the SPI,
> the SPI still has to send it out on the line. If you raise CS at this point, you miss part
> the transaction. Poll the SPI BSY flag for the end of the transaction.
Ok, I will try this, but I'm not convinced that this is the solution. Just because the CS works independently from the SPI and the DMA machine (just done by port I/O). The SPI stream could run on for the full 16 bytes even if the CS has gone high, but it doesn't.
> Probably something else going on as well, as you shouldn't be getting
> TC flag with only 2/16 bytes being sent.
This is the point! I get a TC and a FIFO error. But I can't figure out what's the FIFO problem.
> Unless you have FIFO enabled, which it doesn't sound like.
No, the FIFO is not enabled. Everything is configured by CubeMX, so there shouldn't be any issue .... I can post the init code if desired.
2025-10-22 10:00 PM - edited 2025-10-22 10:02 PM
If you don't use DMA FIFO, don't enable the FIFO error interrupt.
It occurs because of the particular sequencing of DMA stream enable and DMA enable in SPI, but can be safely ignored if FIFO is not used.
This is is mentioned in the dual-port DMA appnote. This has also been discussed in this forum several times in the past.
JW
2025-10-23 3:49 AM
OK, I found a solution that works for me. See listing below. All flags but <transfer complete> and <transfer error> don't need to be enabled and should be ignored. Starting point would be void <spi_output_DMA (....)>.
Noteworthy the FIFO error flag is always active although not enabled. While debugging this code I found that clearing this flag leads to stopping the DMA engine while more bytes need to be transferred.
/***********************************************************
************************************************************
** MODULE spi_dma.c **
** PROJECT **
** COMPILER **
** LANGUAGE C **
** DATE 21.10.2025 **
** AUTHOR lucylectric **
** ABSTRACT **
** 21.10.2025 / Creation **
************************************************************
***********************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "main.h"
#include "stm32f4xx_ll_spi.h"
#include "stm32f4xx_ll_dma.h"
#include "stm32f4xx_ll_gpio.h"
#include "spi_dma.h"
/***********************************************/
#define W5500_CS_LOW LL_GPIO_ResetOutputPin (W5500_CS_GPIO_Port, W5500_CS_Pin);
#define W5500_CS_HIGH LL_GPIO_SetOutputPin (W5500_CS_GPIO_Port, W5500_CS_Pin);
#define W5500_RESET_LOW LL_GPIO_ResetOutputPin (W5500_RESET_GPIO_Port, W5500_RESET_Pin);
#define W5500_RESET_HIGH LL_GPIO_SetOutputPin (W5500_RESET_GPIO_Port, W5500_RESET_Pin);
static volatile uint16_t dma_SPI_write_complete;
static volatile uint16_t dma_SPI_read_complete;
static uint8_t spi_databuff[200] =
{
0x55, 0xAA, 0x81, 0x42, 0x55, 0xAA, 0x81, 0x42,
0x55, 0xAA, 0x81, 0x42, 0x55, 0xAA, 0x81, 0x42,
0x55, 0xAA, 0x81, 0x42, 0x55, 0xAA, 0x81, 0x42,
0x55, 0xAA, 0x81, 0x42, 0x55, 0xAA, 0x81, 0x42
};
/* FUNCTION ************************************/
void SPI1_Init (void)
/* prepare SPI1 & DMA flags
***********************************************/
{
W5500_CS_HIGH;
//LL_SPI_Disable (SPI1); // not required
//LL_DMA_EnableIT_HT (DMA2, LL_DMA_STREAM_3); // half transfer flag
LL_DMA_EnableIT_TE (DMA2, LL_DMA_STREAM_3); // transfer error flag
LL_DMA_EnableIT_TC (DMA2, LL_DMA_STREAM_3); // transfer complete flag
//LL_DMA_EnableIT_DME (DMA2, LL_DMA_STREAM_3); // direct mode error flag
// don't enable FIFO error flag
//LL_DMA_EnableIT_FE (DMA2, LL_DMA_STREAM_3); // FIFO error flag
} /* --- SPI1_Init --- */
/* FUNCTION ************************************/
void DMA2_Stream0_IRQHandler_cont (void)
/* DMA SPI RX - Receive
***********************************************/
{
// LL_DMA_ClearFlag_TC0 transfer complete flag
// LL_DMA_ClearFlag_HT0 half transfer flag
// LL_DMA_ClearFlag_TE0 transfer error flag
// LL_DMA_ClearFlag_DME0 direct mode error flag
// LL_DMA_ClearFlag_FE0 FIFO error flag
if (LL_DMA_IsActiveFlag_TC3 (DMA2))
{
LL_DMA_ClearFlag_TC3 (DMA2); // Transfer complete
}
if (LL_DMA_IsActiveFlag_TE3 (DMA2))
{
LL_DMA_ClearFlag_TE3 (DMA2); // transfer error flag
}
dma_SPI_read_complete = 1; // set ready flag
} /* --- DMA2_Stream0_IRQHandler_cont --- */
/* FUNCTION ************************************/
void DMA2_Stream3_IRQHandler_cont (void)
/* DMA SPI TX - Transmit
***********************************************/
{
// LL_DMA_ClearFlag_TC3 transfer complete flag
// LL_DMA_ClearFlag_HT3 half transfer flag
// LL_DMA_ClearFlag_TE3 transfer error flag
// LL_DMA_ClearFlag_DME3 direct mode error flag
// LL_DMA_ClearFlag_FE3 FIFO error flag
if (LL_DMA_IsActiveFlag_TC3 (DMA2))
{
LL_DMA_ClearFlag_TC3 (DMA2); // Transfer complete
}
if (LL_DMA_IsActiveFlag_TE3 (DMA2))
{
LL_DMA_ClearFlag_TE3 (DMA2); // transfer error flag
}
#if 0 // do not handle these flags & don't clear them
if (LL_DMA_IsActiveFlag_HT3 (DMA2))
{
LL_DMA_ClearFlag_HT3 (DMA2); // half transfer flag
}
if (LL_DMA_IsActiveFlag_DME3 (DMA2))
{
LL_DMA_ClearFlag_DME3 (DMA2); // direct mode error flag
}
if (LL_DMA_IsActiveFlag_FE3 (DMA2))
{
LL_DMA_ClearFlag_FE3 (DMA2); // FIFO error flag
}
#endif
dma_SPI_write_complete = 1; // set ready flag
} /* --- DMA2_Stream3_IRQHandler_cont --- */
/* FUNCTION ************************************/
void SPI1_Write_DMA (uint8_t *buff, uint16_t count)
/*
* Write a complete buffer to SPI with DMA
***********************************************/
{
uint32_t reg_addr;
LL_SPI_Enable (SPI1); // enable SPI engine
// everything else is done in the CubeMX init
// set source and dest address
reg_addr = LL_SPI_DMA_GetRegAddr(SPI1);
LL_DMA_ConfigAddresses (DMA2, LL_DMA_STREAM_3, (uint32_t)buff,
reg_addr, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
LL_DMA_SetDataLength (DMA2, LL_DMA_STREAM_3, count);
LL_SPI_EnableDMAReq_TX (SPI1); // enable SPI for DMA operation
LL_DMA_EnableStream (DMA2, LL_DMA_STREAM_3); // start DMA mem to periph
} /* --- SPI1_Write_DMA --- */
/* FUNCTION ************************************/
void spi_output_DMA (uint16_t byte_count)
/* SPI data transfer
* - output buffer is static local (test version)
* - prepare DMA flags
* - handle chip select
* - start DMA engine
* - check & wait for complete flags (DMA & SPI)
***********************************************/
{
SPI1_Init ();
dma_SPI_write_complete = 0; // clear DMA finished flag
W5500_CS_LOW; // enable chip select
SPI1_Write_DMA (spi_databuff, byte_count); // start SPI & DMA
while (dma_SPI_write_complete == 0)
{
// wait for DMA finished
}
LL_SPI_DisableDMAReq_TX (SPI1);
while (LL_SPI_IsActiveFlag_BSY(SPI1) != 0)
{
// wait for SPI finished
}
W5500_CS_HIGH; // chip select off
} /* --- spi_output_DMA --- */
/* ----------------------------------------------*/
/* ----------- End of file spi_dma.c --------- */
/* ----------------------------------------------*/