cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F446 - SPI DMA

lucylectric
Associate

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.

1 ACCEPTED SOLUTION

Accepted Solutions

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.

Also, this functional code does not adhere to the recommendations found in the reference manual regarding DMA and SPI.
 
Thank you all for contributions.
 
Example of a 5-byte DMA transfer, trace 1 is chip select, trace 2 is SPI clock.
 
SPI_DMA.bmp

 

 
/***********************************************************
************************************************************
**  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  --------- */
/* ----------------------------------------------*/
 
 

View solution in original post

7 REPLIES 7
TDK
Super User

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

If you feel a post has answered your question, please click "Accept as Solution".

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

 

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

If you feel a post has answered your question, please click "Accept as Solution".
TDK
Super User

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.

If you feel a post has answered your question, please click "Accept as Solution".

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

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

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.

Also, this functional code does not adhere to the recommendations found in the reference manual regarding DMA and SPI.
 
Thank you all for contributions.
 
Example of a 5-byte DMA transfer, trace 1 is chip select, trace 2 is SPI clock.
 
SPI_DMA.bmp

 

 
/***********************************************************
************************************************************
**  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  --------- */
/* ----------------------------------------------*/