Author Topic: STM32F411CE SPI3 sends an extra byte during transmission.  (Read 2141 times)

0 Members and 2 Guests are viewing this topic.

Offline shakedownTopic starter

  • Contributor
  • Posts: 32
  • Country: gb
STM32F411CE SPI3 sends an extra byte during transmission.
« on: September 02, 2025, 10:49:27 am »
I am having a strange issue where SPI3 on a black pill (STM32F411CE) sends an extra 0xBB byte during transmission of data using DMA (stream0 for RX and stream5 for TX). This does not happen for SPI1 or SPI2 which use similar code. No matter what data I send using the SPI3_transmitReceive_DMA function it always sends 0xBB first. Example:

GPIOA->BSRR = GPIO_BSRR_BR0;

uint8_t u8_dummyData[3] = {0x01, 0x02, 0x03};
SPI3_transmitReceive_DMA(u8_dummyData, u8_rcvBuffer, 3);

GPIOA->BSRR = GPIO_BSRR_BS0;

PA0 is the CS pin and active state is low.

Result from a logic analyser is attached. I used a proper oscilloscope too and see the same result. Do you see anything obvious I have done wrong? Thank you.



Code: [Select]
uint8_t u8_getTxFinished(uint8_t u8_SPINum)
{
if (u8_SPINum == 1)
{
return s_SPI_txFinishedStates.u8_SPI1_tx_finished;
}
else if (u8_SPINum == 2)
{
return s_SPI_txFinishedStates.u8_SPI2_tx_finished;
}
else if (u8_SPINum == 3)
{
return s_SPI_txFinishedStates.u8_SPI3_tx_finished;
}
else if (u8_SPINum == 4)
{
return s_SPI_txFinishedStates.u8_SPI4_tx_finished;
}
else if (u8_SPINum == 5)
{
return s_SPI_txFinishedStates.u8_SPI5_tx_finished;
}
else
{
return 0;
}
}

void rstTxFinished(uint8_t u8_SPINum)
{
if (u8_SPINum == 1)
{
s_SPI_txFinishedStates.u8_SPI1_tx_finished = 0;
}
else if (u8_SPINum == 2)
{
s_SPI_txFinishedStates.u8_SPI2_tx_finished = 0;
}
else if (u8_SPINum == 3)
{
s_SPI_txFinishedStates.u8_SPI3_tx_finished = 0;
}
else if (u8_SPINum == 4)
{
s_SPI_txFinishedStates.u8_SPI4_tx_finished = 0;
}
else if (u8_SPINum == 5)
{
s_SPI_txFinishedStates.u8_SPI5_tx_finished = 0;
}
}

uint8_t u8_getRxFinished(uint8_t u8_SPINum)
{
if (u8_SPINum == 1)
{
return s_SPI_rxFinishedStates.u8_SPI1_rx_finished;
}
else if (u8_SPINum == 2)
{
return s_SPI_rxFinishedStates.u8_SPI2_rx_finished;
}
else if (u8_SPINum == 3)
{
return s_SPI_rxFinishedStates.u8_SPI3_rx_finished;
}
else if (u8_SPINum == 4)
{
return s_SPI_rxFinishedStates.u8_SPI4_rx_finished;
}
else if (u8_SPINum == 5)
{
return s_SPI_rxFinishedStates.u8_SPI5_rx_finished;
}
else
{
return 0;
}
}

void rstRxFinished(uint8_t u8_SPINum)
{
if (u8_SPINum == 1)
{
s_SPI_rxFinishedStates.u8_SPI1_rx_finished = 0;
}
else if (u8_SPINum == 2)
{
s_SPI_rxFinishedStates.u8_SPI2_rx_finished = 0;
}
else if (u8_SPINum == 3)
{
s_SPI_rxFinishedStates.u8_SPI3_rx_finished = 0;
}
else if (u8_SPINum == 4)
{
s_SPI_rxFinishedStates.u8_SPI4_rx_finished = 0;
}
else if (u8_SPINum == 5)
{
s_SPI_rxFinishedStates.u8_SPI5_rx_finished = 0;
}
}

void SPI3_DMA_init()
{
// Enable clock for GPIOB
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;

// Set PB3 (SCK3), PB4 (MISO3) and PB5 (MOSI3) to alternate function mode
GPIOB->MODER |= GPIO_MODER_MODE3_1|GPIO_MODER_MODE4_1|GPIO_MODER_MODE5_1;

// Set which type of alternate function it is
GPIOB->AFR[0] |= (0x06<<12)|(0x06<<16)|(0x06<<20);

// Enable clock access to SPI3
RCC->APB1ENR |= RCC_APB1ENR_SPI3EN;

// Set software slave management
SPI3->CR1 |= SPI_CR1_SSM | SPI_CR1_SSI;

// Set SPI in master mode
SPI3->CR1 |= SPI_CR1_MSTR;

// Enable SPI peripheral
SPI3->CR1 |= SPI_CR1_SPE;

// SPI3 RX DMA INIT
DMA1_STREAM0_init();

//SPI3 TX DMA INIT
DMA1_STREAM5_init();
}

static void clear_DMA1_Stream0_LowInterrupt_FCR_Flags(void)
{
uint32_t u32_clearFlags = 0;

u32_clearFlags |= 1 << 0;
u32_clearFlags |= 1 << 2;
u32_clearFlags |= 1 << 3;
u32_clearFlags |= 1 << 4;
u32_clearFlags |= 1 << 5;

/* Stream 0 so clear LO FCR instead of HI FCR */
DMA1->LIFCR |= u32_clearFlags;
}

static void clear_DMA1_Stream5_HiInterrupt_FCR_Flags(void)
{
uint32_t u32_clearFlags = 0;

u32_clearFlags |= 1 << 6; /* CFEIF5 */
u32_clearFlags |= 1 << 8; /* CDMEIF5 */
u32_clearFlags |= 1 << 9; /* CTEIF5 */
u32_clearFlags |= 1 << 10; /* CHTIF5 */
u32_clearFlags |= 1 << 11; /* CTCIF5 */

/* Stream 5 so clear HI FCR instead of LO FCR */
DMA1->HIFCR |= u32_clearFlags;
}

void SPI3_transmitReceive_DMA(uint8_t *dummy_data, uint8_t *data, uint8_t len)
{
clear_DMA1_Stream5_HiInterrupt_FCR_Flags();
clear_DMA1_Stream0_LowInterrupt_FCR_Flags();

/*RX*/
DMA1_Stream0->PAR = (uint32_t)&SPI3->DR;
DMA1_Stream0->M0AR = (uint32_t)data;
DMA1_Stream0->NDTR = len;
DMA1_Stream0->CR |= DMA_SxCR_EN;

/*TX*/
DMA1_Stream5->PAR = (uint32_t)&SPI3->DR;
DMA1_Stream5->M0AR = (uint32_t)dummy_data;
DMA1_Stream5->NDTR = len;
DMA1_Stream5->CR |= DMA_SxCR_EN;

/* Wait for DMA to finish receiving */
while (u8_getRxFinished(nSPI3) == 0) {;}
rstRxFinished(nSPI3);
}

void DMA1_STREAM0_init()
{
/*********** ************ *************/
/*********** ************ *************/
/*********** FOR SPI 3 RX *************/
/*********** ************ *************/
/*********** ************ *************/

uint32_t u32_DMARegConstructor;

/* Enable DMA1 on AHB1 clock */
RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;

/* Disable stream - Dont enable until sending data */
DMA1_Stream0->CR &= ~DMA_SxCR_EN;

/* Enable DMA clock */
SPI3->CR2 |= SPI_CR2_RXDMAEN;

u32_DMARegConstructor = 0;

/* Select Stream 0, Channel 0 for SPI3 RX*/
u32_DMARegConstructor |= 0x000 << 25;

/* Set Memory increment mode - Memory address point
* increment after each data transfer
*/
u32_DMARegConstructor |= DMA_SxCR_MINC;

/* Memory to peripheral */
u32_DMARegConstructor |= 1 << 6;

/* TCIE Interrupt enabled */
u32_DMARegConstructor |= 1 << 4;

/* Half txfr interrupt enabled */
u32_DMARegConstructor |= 1 << 3;

/* Txfr err interrupt enabled */
u32_DMARegConstructor |= 1 << 2;

/* Direct mode err interrupt enabled */
u32_DMARegConstructor |= 1 << 1;

/* Set entire register with stream 0 disabled for now */
DMA1_Stream0->CR = u32_DMARegConstructor;

/* Reset constructor to zero */
u32_DMARegConstructor = 0;

/* Disable direct mode */
u32_DMARegConstructor |= 0 << 2;

/* Set FTH to full buffer i.e. 0011*/
u32_DMARegConstructor |= 0x03; /* 0011 */

DMA1_Stream0->FCR |= u32_DMARegConstructor;

NVIC_EnableIRQ(DMA1_Stream0_IRQn);
}

void DMA1_STREAM5_init()
{
/*********** ************ *************/
/*********** ************ *************/
/*********** FOR SPI 3 TX *************/
/*********** ************ *************/
/*********** ************ *************/

uint32_t u32_DMARegConstructor;

/* Enable DMA1 on AHB1 clock */
RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;

/* Disable stream - Dont enable until sending data */
DMA1_Stream5->CR &= ~DMA_SxCR_EN;

/* Enable DMA clock */
SPI3->CR2 |= SPI_CR2_TXDMAEN;

u32_DMARegConstructor = 0;

/* Select Stream 5, Channel 0 for SPI3 TX*/
u32_DMARegConstructor |= 0x000 << 25;

/* Set Memory increment mode - Memory address point
* increment after each data transfer
*/
u32_DMARegConstructor |= DMA_SxCR_MINC;

/* Memory to peripheral */
u32_DMARegConstructor |= 1 << 6;

/* TCIE Interrupt enabled */
u32_DMARegConstructor |= 1 << 4;

/* Half txfr interrupt enabled */
u32_DMARegConstructor |= 1 << 3;

/* Txfr err interrupt enabled */
u32_DMARegConstructor |= 1 << 2;

/* Direct mode err interrupt enabled */
u32_DMARegConstructor |= 1 << 1;

/* Set entire register with stream 5 disabled for now */
DMA1_Stream5->CR = u32_DMARegConstructor;

/* Reset constructor to zero */
u32_DMARegConstructor = 0;

/* Disable direct mode */
u32_DMARegConstructor |= 0 << 2;

/* Set FTH to full buffer i.e. 0011*/
u32_DMARegConstructor |= 0x03; /* 0011 */

DMA1_Stream5->FCR |= u32_DMARegConstructor;

NVIC_EnableIRQ(DMA1_Stream5_IRQn);
}

void DMA1_Stream0_IRQHandler(void)
{
if(DMA1->LISR & (DMA_LISR_TCIF0))
{
//_rx_finished = 1;
s_SPI_rxFinishedStates.u8_SPI3_rx_finished = 1;

DMA1_Stream0->CR &= ~DMA_SxCR_EN;

DMA1->LIFCR |= DMA_LIFCR_CTCIF0;
}

/* Check and clear HTIF Stream 0 flag */
if(DMA1->LISR&(DMA_LISR_HTIF0))
  {
DMA1->LIFCR |= DMA_LIFCR_CHTIF0;
}

/* Check and clear TEIF Stream 0 flag */
if(DMA1->LISR&(DMA_LISR_TEIF0))
{
DMA1->LIFCR |= (DMA_LIFCR_CTEIF0);
}

/* Check and clear DMEIF Stream 0 flag */
if(DMA1->LISR&(DMA_LISR_DMEIF0))
{
DMA1->LIFCR |= (DMA_LIFCR_CDMEIF0);
}

/* Check and clear FEIF Stream 0 flag */
if(DMA1->LISR & (DMA_LISR_FEIF0))
{
DMA1->LIFCR |= (DMA_LIFCR_CFEIF0);
}

/* Clear DMA 1 stream 0 interrupt  */
NVIC_ClearPendingIRQ(DMA1_Stream0_IRQn);
}

void DMA1_Stream5_IRQHandler(void)
{
if(DMA1->HISR & (DMA_HISR_TCIF5))
{
//_tx_finished = 1;
s_SPI_txFinishedStates.u8_SPI3_tx_finished = 1;

DMA1_Stream5->CR &= ~DMA_SxCR_EN;

DMA1->HIFCR |= DMA_HIFCR_CTCIF5;
}

/* Check and clear HTIF Stream 5 flag */
if(DMA1->HISR & (DMA_HISR_HTIF5))
{
DMA1->HIFCR |= DMA_HIFCR_CHTIF5;
}

/* Check and clear TEIF Stream 5 flag */
if(DMA1->HISR & (DMA_HISR_TEIF5))
{
DMA1->HIFCR |= (DMA_HIFCR_CTEIF5);
}

/* Check and clear DMEIF Stream 5 flag */
if(DMA1->HISR & (DMA_HISR_DMEIF5))
{
DMA1->HIFCR |= (DMA_HIFCR_CDMEIF5);
}

/* Check and clear FEIF Stream 5 flag */
if(DMA1->HISR & (DMA_HISR_FEIF5))
{
DMA1->HIFCR |= (DMA_HIFCR_CFEIF5);
}

/* Clear DMA 1 stream 5 interrupt  */
NVIC_ClearPendingIRQ(DMA1_Stream5_IRQn);
}


Header file:
Code: [Select]
#ifndef INC_STM32F411_SPI_H_
#define INC_STM32F411_SPI_H_

#include "stm32f4xx.h"

#define nSPI1 1
#define nSPI2 2
#define nSPI3 3
#define nSPI4 4
#define nSPI5 5

typedef struct SPITxFinishedStats {
    uint8_t u8_SPI1_tx_finished;
    uint8_t u8_SPI2_tx_finished;
    uint8_t u8_SPI3_tx_finished;
    uint8_t u8_SPI4_tx_finished;
    uint8_t u8_SPI5_tx_finished;
} SPITxFinishedStats;

typedef struct SPIRxFinishedStats {
    uint8_t u8_SPI1_rx_finished;
    uint8_t u8_SPI2_rx_finished;
    uint8_t u8_SPI3_rx_finished;
    uint8_t u8_SPI4_rx_finished;
    uint8_t u8_SPI5_rx_finished;
} SPIRxFinishedStats;

void rstRxFinished(uint8_t u8_SPINum);
uint8_t u8_getRxFinished(uint8_t u8_SPINum);
void rstTxFinished(uint8_t u8_SPINum);
uint8_t u8_getTxFinished(uint8_t u8_SPINum);

/* SPI 3 */
void SPI3_DMA_init();
void SPI3_transmitReceive_DMA(uint8_t *dummy_data, uint8_t *data, uint8_t len);

void DMA1_STREAM5_init(); /* SPI 3 */
void DMA1_STREAM0_init();/* SPI 3 */

main.c which uses the library code posted before like this:

Code: [Select]
#include "main.h"
#include "STM32F411_SPI.h"
#include "stdint.h"

uint8_t u8_data[5] = {0xAA, 0xBB, 0xCC, 0xFF, 0xFE};
uint8_t u8_rcvBuff[3] = {0xDD, 0xBB, 0xCC};



void SystemClock_Config(void);
static void MX_GPIO_Init(void);

int main(void)
{

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */

  /* ***************** INIT DMA HERE *************** */
  /* *****************                        *************** */
  /* *****************                        *************** */
  SPI3_DMA_init();
  GPIOA->MODER |= GPIO_MODER_MODER0_0;
  GPIOA->BSRR = GPIO_BSRR_BS0;

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
 
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);

       
       /* ***************** TRANSMIT HERE  *************** */
       /* *****************                          *************** */
       /* *****************                         *************** */

GPIOA->BSRR = GPIO_BSRR_BR0;

uint8_t u8_dummyData[3] = {0x01, 0x02, 0x03};

SPI3_transmitReceive_DMA(u8_dummyData, u8_rcvBuff, 3);

GPIOA->BSRR = GPIO_BSRR_BS0;
  }

}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV4;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV4;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_RESET);

  /*Configure GPIO pin : PC13 */
  GPIO_InitStruct.Pin = GPIO_PIN_13;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  /*Configure GPIO pin : PA10 */
  GPIO_InitStruct.Pin = GPIO_PIN_10;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

« Last Edit: September 09, 2025, 10:01:04 pm by shakedown »
 

Offline wek

  • Frequent Contributor
  • **
  • Posts: 591
  • Country: sk
Re: STM32F411CE SPI3 sends an extra byte during transmission.
« Reply #1 on: September 02, 2025, 11:14:38 am »
There is no CS toggling at the shown waveform, and there are 4 bytes transferred rather than 3.

Post the complete code (reduced to minimal but complete compilable exhibiting the problem) together with the observed waveform for that particular code.

You may also want to place a breakpoint just before enabling the Tx DMA and read out and check/post content of SPI and DMA registers.

JW
 

Offline shakedownTopic starter

  • Contributor
  • Posts: 32
  • Country: gb
Re: STM32F411CE SPI3 sends an extra byte during transmission.
« Reply #2 on: September 02, 2025, 11:32:54 am »
Sorry the reason you can't see the CS toggling is because I did not include it to make the picture smaller, here is the full thing. The delay between CS toggling and CLK going high first time is 48us. And yes for some reason it is sending 4 bytes instead of 3 bytes. I will use a breakpoint and check for contents of SPI and DMA registers.

I have now added the complete code to the original post.
« Last Edit: September 02, 2025, 11:40:43 am by shakedown »
 

Offline Psi

  • Super Contributor
  • ***
  • Posts: 12715
  • Country: nz
Re: STM32F411CE SPI3 sends an extra byte during transmission.
« Reply #3 on: September 02, 2025, 11:54:00 am »
I think you are getting 1 junk byte in the FIFO after init, maybe something is being done in the wrong order, or not waiting, dunno.
Try force a clear before you transmit to confirm.


EDIT, actually i dont think F4 has a fifo. hm...

It does feel like an init problem though.

Code: [Select]
void SPI3_transmitReceive_DMA(uint8_t *dummy_data, uint8_t *data, uint8_t len)
{
    // 1. Wait until SPI is not busy
    while (SPI3->SR & SPI_SR_BSY) { }

    // 2. Flush RX FIFO by reading DR until RXNE is cleared
    while (SPI3->SR & SPI_SR_RXNE)
    {
        volatile uint32_t tmp = SPI3->DR;
        (void)tmp;
    }

    // 3. Clear DMA interrupt flags
    clear_DMA1_Stream5_HiInterrupt_FCR_Flags();
    clear_DMA1_Stream0_LowInterrupt_FCR_Flags();

    /* RX setup */
    DMA1_Stream0->PAR = (uint32_t)&SPI3->DR;
    DMA1_Stream0->M0AR = (uint32_t)data;
    DMA1_Stream0->NDTR = len;
    DMA1_Stream0->CR |= DMA_SxCR_EN;

    /* TX setup */
    DMA1_Stream5->PAR = (uint32_t)&SPI3->DR;
    DMA1_Stream5->M0AR = (uint32_t)dummy_data;
    DMA1_Stream5->NDTR = len;
    DMA1_Stream5->CR |= DMA_SxCR_EN;

    /* Wait for DMA to finish receiving */
    while (u8_getRxFinished(nSPI3) == 0) {;}
    rstRxFinished(nSPI3);
}
« Last Edit: September 02, 2025, 12:43:26 pm by Psi »
Greek letter 'Psi' (not Pounds per Square Inch)
 
The following users thanked this post: shakedown

Offline wek

  • Frequent Contributor
  • **
  • Posts: 591
  • Country: sk
Re: STM32F411CE SPI3 sends an extra byte during transmission.
« Reply #4 on: September 02, 2025, 04:47:26 pm »
It's hardly the complete code, I can't find u8_getRxFinished().

JW
 

Offline wek

  • Frequent Contributor
  • **
  • Posts: 591
  • Country: sk
Re: STM32F411CE SPI3 sends an extra byte during transmission.
« Reply #5 on: September 02, 2025, 05:03:11 pm »
One thing which is certainly error and IMO may lead to transmitting more than expected, although I'm not sure how exactly it works, is below, in white, so that you can reveal it when you want.
You should already have recognized the problem from observing the registers content, though.

In DMA1_STREAM0_init(), i.e. setting up DMA for Rx, you have
   /* Memory to peripheral */
   u32_DMARegConstructor |= 1 << 6;
resulting in this DMA writing *to* SPI_DR instead of reading from it.


JW
 
The following users thanked this post: shakedown

Offline Pseudobyte

  • Frequent Contributor
  • **
  • Posts: 358
  • Country: us
  • Embedded Systems Engineer / PCB Designer
Re: STM32F411CE SPI3 sends an extra byte during transmission.
« Reply #6 on: September 03, 2025, 05:11:58 pm »
What is the point in using DMA if you are just going to block on reception?

Code: [Select]

        /* Wait for DMA to finish receiving */
while (u8_getRxFinished(nSPI3) == 0) {;}
rstRxFinished(nSPI3);

wek seems to have found the most likely issue.
“They Don’t Think It Be Like It Is, But It Do”
 

Offline paulca

  • Super Contributor
  • ***
  • Posts: 6515
  • Country: gb
Re: STM32F411CE SPI3 sends an extra byte during transmission.
« Reply #7 on: September 07, 2025, 10:55:19 am »
Is this a memory alignment issue?

The SPI and DMA default to 32bit don't they?  So it can't send 24bits.
"What could possibly go wrong?"
Current Open Projects:  68000 Self Build computer + OS.
 

Offline wek

  • Frequent Contributor
  • **
  • Posts: 591
  • Country: sk
Re: STM32F411CE SPI3 sends an extra byte during transmission.
« Reply #8 on: September 08, 2025, 07:45:39 am »
> The SPI and DMA default to 32bit don't they? 

No.

The data width in 'F4 SPI can be set to be either 8-bit or 16-bit. So is the data register, 16-bit wide.

The data width handled by the dual-port DMA in 'F4 is adjustable (see DMA_SxCR.PSIZE; the default 0b00 is 8-bit width, for what it's worth).

Btw. if you read the STM32 reference manuals carefully in the chronological order, turns out, that many "common" peripherals (such as timers, UART/SPI/I2C) in older STM32 have a significant 16-bit legacy, being mostly taken over from ST's older 16-bit mcus (e.g. the ST9 family). This also spares silicon area, as the APB bus to them (and associated circuitry) needs to be only 16-bit wide. With ever shrinking technology they then underwent a partial 32-bit-ification, and since 'H7/'U5/'H5 some of the peripherals start to be replaced by completely reworked fully 32-bit versions.

JW
 
The following users thanked this post: paulca

Offline shakedownTopic starter

  • Contributor
  • Posts: 32
  • Country: gb
Re: STM32F411CE SPI3 sends an extra byte during transmission.
« Reply #9 on: September 09, 2025, 10:02:01 pm »
It's hardly the complete code, I can't find u8_getRxFinished().

JW

Apologies I have now added the declaration and definition of u8_getRxFinished, rstTxFinished, u8_getRxFinished and rstRxFinished.
 

Offline shakedownTopic starter

  • Contributor
  • Posts: 32
  • Country: gb
Re: STM32F411CE SPI3 sends an extra byte during transmission.
« Reply #10 on: September 09, 2025, 10:20:40 pm »
One thing which is certainly error and IMO may lead to transmitting more than expected, although I'm not sure how exactly it works, is below, in white, so that you can reveal it when you want.
You should already have recognized the problem from observing the registers content, though.

In DMA1_STREAM0_init(), i.e. setting up DMA for Rx, you have
   /* Memory to peripheral */
   u32_DMARegConstructor |= 1 << 6;
resulting in this DMA writing *to* SPI_DR instead of reading from it.


JW

You were right, I had the bit set incorrectly. Correctly setting it fixes the issues. If it is set from memory to peripheral (wrong) it sends out an extra byte when I debug. Although the results are a bit confusing right now, if I replicate the error again, it is sending only two bytes according to logic analyser even though there are four bytes to where M0AR is pointing.
« Last Edit: September 09, 2025, 10:26:45 pm by shakedown »
 

Offline shakedownTopic starter

  • Contributor
  • Posts: 32
  • Country: gb
Re: STM32F411CE SPI3 sends an extra byte during transmission.
« Reply #11 on: September 09, 2025, 10:21:59 pm »
What is the point in using DMA if you are just going to block on reception?

Code: [Select]

        /* Wait for DMA to finish receiving */
while (u8_getRxFinished(nSPI3) == 0) {;}
rstRxFinished(nSPI3);

wek seems to have found the most likely issue.

How to tell if the receiving or transmission is complete without the above? It uses the transfer complete flag (TCIF) to set RX finished. Code is written so that the transfer happens after the setting registers for receive because the master needs to provide a clock for the slave to send and when the data is received it is stored in the memory?
« Last Edit: September 09, 2025, 10:25:47 pm by shakedown »
 

Offline Pseudobyte

  • Frequent Contributor
  • **
  • Posts: 358
  • Country: us
  • Embedded Systems Engineer / PCB Designer
Re: STM32F411CE SPI3 sends an extra byte during transmission.
« Reply #12 on: September 10, 2025, 01:16:19 am »
How to tell if the receiving or transmission is complete without the above? It uses the transfer complete flag (TCIF) to set RX finished. Code is written so that the transfer happens after the setting registers for receive because the master needs to provide a clock for the slave to send and when the data is received it is stored in the memory?

Setting a flag when you begin transmission or reception and clearing it in the interrupt handler is the correct way of keeping track of the peripheral state.

My point is that you are blocking on the peripheral being busy which defeats the benefit of DMA (CPU offloading). Typically you configure the peripheral, setup the DMA transfer, then start the transfer. At this point you can "walk away" from the peripheral to do other tasks. You can then periodically poll the state of the of the transfer as you do, just in a non-blocking way. Instead of blocking with a while loop in SPI3_transmitReceive_DMA you can just periodically check u8_getRxFinished(nSPI3) in your main loop. Of course you should also check if the peripheral is busy before setting up a new transfer via DMA.

Now it hardly matters for sending two bytes, but consider sending/receiving significantly more data. You should also consider the CPU clock vs the data rate. For example if I have a CPU clocked at 400MHz and I am receiving data on a 1MHz SPI bus, think about how many CPU cycles you are wasting by waiting for that 1MHz transmission/reception.

“They Don’t Think It Be Like It Is, But It Do”
 

Offline shakedownTopic starter

  • Contributor
  • Posts: 32
  • Country: gb
Re: STM32F411CE SPI3 sends an extra byte during transmission.
« Reply #13 on: September 14, 2025, 02:45:27 pm »
How to tell if the receiving or transmission is complete without the above? It uses the transfer complete flag (TCIF) to set RX finished. Code is written so that the transfer happens after the setting registers for receive because the master needs to provide a clock for the slave to send and when the data is received it is stored in the memory?

Setting a flag when you begin transmission or reception and clearing it in the interrupt handler is the correct way of keeping track of the peripheral state.

My point is that you are blocking on the peripheral being busy which defeats the benefit of DMA (CPU offloading). Typically you configure the peripheral, setup the DMA transfer, then start the transfer. At this point you can "walk away" from the peripheral to do other tasks. You can then periodically poll the state of the of the transfer as you do, just in a non-blocking way. Instead of blocking with a while loop in SPI3_transmitReceive_DMA you can just periodically check u8_getRxFinished(nSPI3) in your main loop. Of course you should also check if the peripheral is busy before setting up a new transfer via DMA.

Now it hardly matters for sending two bytes, but consider sending/receiving significantly more data. You should also consider the CPU clock vs the data rate. For example if I have a CPU clocked at 400MHz and I am receiving data on a 1MHz SPI bus, think about how many CPU cycles you are wasting by waiting for that 1MHz transmission/reception.

I have been thinking about this and have a partial solution. I could give my function a new pointer argument. This way I know the memory address of the u8_buffStatus which can help me realise in the main.c file whether the transfer is complete or not:

0x00 = Nothing has happened yet.
0x0F = Transfer started.
0xFF = Transfer finished.

in main.c:main I could declare a static variable so that it holds its value between invocations. This would allow the MCU to continue doing something else and move on if DMA transfer is incomplete. The purpose of what I am making is to get data from another CPU/DEVICE_A using UART and display the values on a small screen for me to monitor, which is why I am trying to use SPI (DMA) because the screen uses SPI and I want it to be fast (I also just want to learn how to write my own library without using HAL). The problem is what do I do with the data from DEVICE_A if the DMA transfer is incomplete? I don't really want to discard the data, should I store it in another buffer (32 bit variable/4 bytes)? If I do this, I could still lose data if the DMA transfer takes long enough that the 32 bit buffer/variable is not large enough anymore.

main:
Code: [Select]
static uin8t_t u8_buffStatus;

while()
{
        u8_someData = GetDataFrom_DEVICE_A();

        if (u8_buffStatus ==0x00 || u8_buffStatus == 0xFF) //<< if transfer is complete or nothing has happened yet then send data
       {
                SPI3_transmitReceive_DMA_nonBlocking(u8_someData, u8_rcvBuff, 3, &u8_buffStatus);
        } else {// << transfer must be in progress i.e. u8_buffStatus == 0x0F;
                /* Do nothing and move on but I could lose data here unless I store it in another buffer/variable but even then I could lose data if the buffer/variable is not large enough. */
        }

        /* Do something else - add 1 + 1 - Calculate something... */
}

declare a global pointer called u8_p2Status in my lib file.

Code: [Select]
void SPI3_transmitReceive_DMA_nonBlocking(uint8_t *dummy_data, uint8_t *data, uint8_t len, uint8_t *u8_status)
{
u8_p2Status = u8_status;

clear_DMA1_Stream5_HiInterrupt_FCR_Flags();
clear_DMA1_Stream0_LowInterrupt_FCR_Flags();

/*RX*/
DMA1_Stream0->PAR = (uint32_t)&SPI3->DR;
DMA1_Stream0->M0AR = (uint32_t)data;
DMA1_Stream0->NDTR = len;
DMA1_Stream0->CR |= DMA_SxCR_EN;

/*TX*/
DMA1_Stream5->PAR = (uint32_t)&SPI3->DR;
DMA1_Stream5->M0AR = (uint32_t)dummy_data;
DMA1_Stream5->NDTR = len;
DMA1_Stream5->CR |= DMA_SxCR_EN;

        /* Transfer started */
*u8_p2Status = 0x0F;

/* Do not wait for DMA to finish anymore */
rstRxFinished(nSPI3);
}

void DMA1_Stream0_IRQHandler(void)
{
if(DMA1->LISR & (DMA_LISR_TCIF0))
{
//_rx_finished = 1;
s_SPI_rxFinishedStates.u8_SPI3_rx_finished = 1;

                /* Transfer complete */
*u8_p2Status = 0xFF;

DMA1_Stream0->CR &= ~DMA_SxCR_EN;

DMA1->LIFCR |= DMA_LIFCR_CTCIF0;
}

/* Check and clear HTIF Stream 0 flag */
if(DMA1->LISR&(DMA_LISR_HTIF0))
{
DMA1->LIFCR |= DMA_LIFCR_CHTIF0;
}

/* Check and clear TEIF Stream 0 flag */
if(DMA1->LISR&(DMA_LISR_TEIF0))
{
DMA1->LIFCR |= (DMA_LIFCR_CTEIF0);
}

/* Check and clear DMEIF Stream 0 flag */
if(DMA1->LISR&(DMA_LISR_DMEIF0))
{
DMA1->LIFCR |= (DMA_LIFCR_CDMEIF0);
}

/* Check and clear FEIF Stream 0 flag */
if(DMA1->LISR & (DMA_LISR_FEIF0))
{
DMA1->LIFCR |= (DMA_LIFCR_CFEIF0);
}

/* Clear interrupt  */
NVIC_ClearPendingIRQ(DMA1_Stream0_IRQn);
}
« Last Edit: September 14, 2025, 04:25:02 pm by shakedown »
 

Offline aae30

  • Contributor
  • Posts: 18
  • Country: fi
Re: STM32F411CE SPI3 sends an extra byte during transmission.
« Reply #14 on: September 14, 2025, 05:16:59 pm »
declare a global pointer called u8_p2Status in my lib file.

Just a random note; if the buffer status variable is used from interrupts i suggest declaring it as volatile
to guide the compiler from making possible bugs w/it.
 
The following users thanked this post: shakedown

Offline shakedownTopic starter

  • Contributor
  • Posts: 32
  • Country: gb
Re: STM32F411CE SPI3 sends an extra byte during transmission.
« Reply #15 on: September 15, 2025, 02:16:55 am »
How to tell if the receiving or transmission is complete without the above? It uses the transfer complete flag (TCIF) to set RX finished. Code is written so that the transfer happens after the setting registers for receive because the master needs to provide a clock for the slave to send and when the data is received it is stored in the memory?

Setting a flag when you begin transmission or reception and clearing it in the interrupt handler is the correct way of keeping track of the peripheral state.

My point is that you are blocking on the peripheral being busy which defeats the benefit of DMA (CPU offloading). Typically you configure the peripheral, setup the DMA transfer, then start the transfer. At this point you can "walk away" from the peripheral to do other tasks. You can then periodically poll the state of the of the transfer as you do, just in a non-blocking way. Instead of blocking with a while loop in SPI3_transmitReceive_DMA you can just periodically check u8_getRxFinished(nSPI3) in your main loop. Of course you should also check if the peripheral is busy before setting up a new transfer via DMA.

Now it hardly matters for sending two bytes, but consider sending/receiving significantly more data. You should also consider the CPU clock vs the data rate. For example if I have a CPU clocked at 400MHz and I am receiving data on a 1MHz SPI bus, think about how many CPU cycles you are wasting by waiting for that 1MHz transmission/reception.

I have been thinking about this and have a partial solution. I could give my function a new pointer argument. This way I know the memory address of the u8_buffStatus which can help me realise in the main.c file whether the transfer is complete or not:

0x00 = Nothing has happened yet.
0x0F = Transfer started.
0xFF = Transfer finished.

in main.c:main I could declare a static variable so that it holds its value between invocations. This would allow the MCU to continue doing something else and move on if DMA transfer is incomplete. The purpose of what I am making is to get data from another CPU/DEVICE_A using UART and display the values on a small screen for me to monitor, which is why I am trying to use SPI (DMA) because the screen uses SPI and I want it to be fast (I also just want to learn how to write my own library without using HAL). The problem is what do I do with the data from DEVICE_A if the DMA transfer is incomplete? I don't really want to discard the data, should I store it in another buffer (32 bit variable/4 bytes)? If I do this, I could still lose data if the DMA transfer takes long enough that the 32 bit buffer/variable is not large enough anymore.

main:
Code: [Select]
static uin8t_t u8_buffStatus;

while()
{
        u8_someData = GetDataFrom_DEVICE_A();

        if (u8_buffStatus ==0x00 || u8_buffStatus == 0xFF) //<< if transfer is complete or nothing has happened yet then send data
       {
                SPI3_transmitReceive_DMA_nonBlocking(u8_someData, u8_rcvBuff, 3, &u8_buffStatus);
        } else {// << transfer must be in progress i.e. u8_buffStatus == 0x0F;
                /* Do nothing and move on but I could lose data here unless I store it in another buffer/variable but even then I could lose data if the buffer/variable is not large enough. */
        }

        /* Do something else - add 1 + 1 - Calculate something... */
}

declare a global pointer called u8_p2Status in my lib file.

Code: [Select]
void SPI3_transmitReceive_DMA_nonBlocking(uint8_t *dummy_data, uint8_t *data, uint8_t len, uint8_t *u8_status)
{
u8_p2Status = u8_status;

clear_DMA1_Stream5_HiInterrupt_FCR_Flags();
clear_DMA1_Stream0_LowInterrupt_FCR_Flags();

/*RX*/
DMA1_Stream0->PAR = (uint32_t)&SPI3->DR;
DMA1_Stream0->M0AR = (uint32_t)data;
DMA1_Stream0->NDTR = len;
DMA1_Stream0->CR |= DMA_SxCR_EN;

/*TX*/
DMA1_Stream5->PAR = (uint32_t)&SPI3->DR;
DMA1_Stream5->M0AR = (uint32_t)dummy_data;
DMA1_Stream5->NDTR = len;
DMA1_Stream5->CR |= DMA_SxCR_EN;

        /* Transfer started */
*u8_p2Status = 0x0F;

/* Do not wait for DMA to finish anymore */
rstRxFinished(nSPI3);
}

void DMA1_Stream0_IRQHandler(void)
{
if(DMA1->LISR & (DMA_LISR_TCIF0))
{
//_rx_finished = 1;
s_SPI_rxFinishedStates.u8_SPI3_rx_finished = 1;

                /* Transfer complete */
*u8_p2Status = 0xFF;

DMA1_Stream0->CR &= ~DMA_SxCR_EN;

DMA1->LIFCR |= DMA_LIFCR_CTCIF0;
}

/* Check and clear HTIF Stream 0 flag */
if(DMA1->LISR&(DMA_LISR_HTIF0))
{
DMA1->LIFCR |= DMA_LIFCR_CHTIF0;
}

/* Check and clear TEIF Stream 0 flag */
if(DMA1->LISR&(DMA_LISR_TEIF0))
{
DMA1->LIFCR |= (DMA_LIFCR_CTEIF0);
}

/* Check and clear DMEIF Stream 0 flag */
if(DMA1->LISR&(DMA_LISR_DMEIF0))
{
DMA1->LIFCR |= (DMA_LIFCR_CDMEIF0);
}

/* Check and clear FEIF Stream 0 flag */
if(DMA1->LISR & (DMA_LISR_FEIF0))
{
DMA1->LIFCR |= (DMA_LIFCR_CFEIF0);
}

/* Clear interrupt  */
NVIC_ClearPendingIRQ(DMA1_Stream0_IRQn);
}

Think I figured out DMA transfers without blocking. Simply give chip select control to the DMA transfer function and also enable SPI interrupt at DMA transfer complete interrupt, during the SPI interrupt check if SPI TX buffer is empty then turn the chip select to high to indicate tranmission end and disable SPI interrupt. Seems to be working ok. I think for receive it will be the same except the chip select should go high during DMA transfer complete instead of SPI because SPI data is pushed to DMA (other way round this time). I realised this because during DMA transfer only, I was missing one byte and it turns out it was because I was using DMA transfer complete to indicate transmission end but the transmission doesn't end until SPI has actually finished.
« Last Edit: September 15, 2025, 03:00:18 am by shakedown »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

-->