cancel
Showing results for 
Search instead for 
Did you mean: 

How to use SPI + DMA in Full Duplex Mode

JHeni
Associate II

Hey guys,

I'm currently struggling to setup my SPI to work in Full Duplex Mode together with an DMA.

I'm using 2 STM32F4 to communicate via SPI + DMA

The Initialization for both MCUs was created using STM32Cube.

This is the generated Init Code for the master

/* SPI2 init function */
void MX_SPI2_Init(void)
{
 
  hspi2.Instance = SPI2;
  hspi2.Init.Mode = SPI_MODE_MASTER;
  hspi2.Init.Direction = SPI_DIRECTION_2LINES;
  hspi2.Init.DataSize = SPI_DATASIZE_16BIT;
  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi2.Init.NSS = SPI_NSS_SOFT;
  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_ENABLE;
  hspi2.Init.CRCPolynomial = 11;
  if (HAL_SPI_Init(&hspi2) != HAL_OK)
  {
    Error_Handler();
  }
 
}
void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{
 
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(spiHandle->Instance==SPI2)
  {
  /* USER CODE BEGIN SPI2_MspInit 0 */
 
  /* USER CODE END SPI2_MspInit 0 */
    /* SPI2 clock enable */
    __HAL_RCC_SPI2_CLK_ENABLE();
  
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**SPI2 GPIO Configuration    
    PB13     ------> SPI2_SCK
    PB14     ------> SPI2_MISO
    PB15     ------> SPI2_MOSI 
    */
    GPIO_InitStruct.Pin = UCE_UCN_SCLK_Pin|UCE_UCN_MOSI_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
 
    GPIO_InitStruct.Pin = UCE_UCN_MISO_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(UCE_UCN_MISO_GPIO_Port, &GPIO_InitStruct);
 
    /* SPI2 DMA Init */
    /* SPI2_RX Init */
    hdma_spi2_rx.Instance = DMA1_Stream3;
    hdma_spi2_rx.Init.Channel = DMA_CHANNEL_0;
    hdma_spi2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_spi2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi2_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_spi2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_spi2_rx.Init.Mode = DMA_NORMAL;
    hdma_spi2_rx.Init.Priority = DMA_PRIORITY_LOW;
    hdma_spi2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_spi2_rx) != HAL_OK)
    {
      Error_Handler();
    }
 
    __HAL_LINKDMA(spiHandle,hdmarx,hdma_spi2_rx);
 
    /* SPI2_TX Init */
    hdma_spi2_tx.Instance = DMA1_Stream4;
    hdma_spi2_tx.Init.Channel = DMA_CHANNEL_0;
    hdma_spi2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_spi2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi2_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_spi2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_spi2_tx.Init.Mode = DMA_NORMAL;
    hdma_spi2_tx.Init.Priority = DMA_PRIORITY_LOW;
    hdma_spi2_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_spi2_tx) != HAL_OK)
    {
      Error_Handler();
    }
 
    __HAL_LINKDMA(spiHandle,hdmatx,hdma_spi2_tx);
 
    /* SPI2 interrupt Init */
    HAL_NVIC_SetPriority(SPI2_IRQn, 6, 0);
    HAL_NVIC_EnableIRQ(SPI2_IRQn);
  /* USER CODE BEGIN SPI2_MspInit 1 */
 
  /* USER CODE END SPI2_MspInit 1 */
  }
 
}

There's no magic there =)

Now if i'm using

if(HAL_OK != HAL_SPI_TransmitReceive_DMA(&hspi2,(uint8_t*)(&dummy_tx_packet), (uint8_t*)(&dummy_rx_packet), sizeof(UCUCCom_Single_Packet_T)))
{
	hal_errors++;
}

If I record the Clk using an oscilloscope I'm seeing a nice signal.

=> So there is no hardware problem

However I'm using a complex slave selection mechanism which is not supported by the HAL drivers. So I've to start the transmit and receive myself. The problem is that no data are transmitted.

The problem with my Code: There is no transmission.

The the master does not generate a Clk Signal.

The manual states (see attachment) states on page 951 in Chapter 29.3.11 Communication using DMA.

When starting communication using DMA, to prevent DMA channel management raising error events, these steps must be followed in order:

1.Enable DMA Rx buffer in the RXDMAEN bit in the SPI_CR2 register, if DMA Rx is used.

2. Enable DMA streams for Tx and Rx in DMA registers, if the streams are used.

3. Enable DMA Tx buffer in the TXDMAEN bit in the SPI_CR2 register, if DMA Tx is used.

4. Enable the SPI by setting the SPE bit

I believe i'm following this order.

static void Enable_SPI(const uint8_t enable, const SPI_HandleTypeDef* const pSPI)
{
	if ((NULL == pSPI) || (NULL == pSPI->Instance))
	{
		return;
	}
 
	if ((NULL == pSPI->hdmarx) || (NULL == pSPI->hdmarx->Instance))
	{
		return;
	}
	if ((NULL == pSPI->hdmatx) || (NULL == pSPI->hdmatx->Instance))
	{
		return;
	}
 
	if (1 == enable)
	{
		//enable RX DMA Buffer
		SET_BIT(pSPI->Instance->CR2, SPI_CR2_RXDMAEN);
 
		//setup DMA RX stream
		WRITE_REG(pSPI->hdmarx->Instance->NDTR, (sizeof(UCUCCom_Single_Packet_T)) / 2);   // 16 bit Mode
		WRITE_REG(pSPI->hdmarx->Instance->PAR, (uint32_t ) &(pSPI->Instance->DR));   // from SPI DR ...
		WRITE_REG(pSPI->hdmarx->Instance->M0AR, (uint32_t ) &ucuccom_rx_buffer);   // ... to  RX Memory
 
		//enable DMA RX Stream
		SET_BIT(pSPI->hdmarx->Instance->CR, (DMA_SxCR_TCIE |   // Transfer Complete Interrupt
				DMA_SxCR_TEIE |// Transfer Error Interrupt
				DMA_SxCR_EN));// Enable
 
		//setup DMA TX Stream
		WRITE_REG(pSPI->hdmatx->Instance->NDTR, (sizeof(UCUCCom_Single_Packet_T)) / 2);   // 16 bit mode
		WRITE_REG(pSPI->hdmatx->Instance->M0AR, (uint32_t ) pucuccom_tx_buffer);   // from TX memory ...
		WRITE_REG(pSPI->hdmatx->Instance->PAR, (uint32_t ) &(pSPI->Instance->DR));   // ... to SPI DR.
 
		//enable DMA TX Stream
		SET_BIT(pSPI->hdmatx->Instance->CR, ( DMA_SxCR_TEIE |   // Transfer Error Interrupt
				DMA_SxCR_EN));// Enable
 
		//enable DMA TX Buffer
		SET_BIT(pSPI->Instance->CR2, SPI_CR2_TXDMAEN);
 
 
		//SPI enable
		SET_BIT(pSPI->Instance->CR1, SPI_CR1_SPE);
	}
	else
	{
		//close DMA RX Stream
		CLEAR_BIT(pSPI->hdmarx->Instance->CR, (DMA_SxCR_TCIE |   // Transfer Complete Interrupt
				DMA_SxCR_TEIE |// Transfer Error Interrupt
				DMA_SxCR_EN));// Enable
 
		//close DMA TX Stream
		CLEAR_BIT(pSPI->hdmatx->Instance->CR, (DMA_SxCR_TEIE |   // Transfer Error Interrupt
				DMA_SxCR_EN));// Enable
 
		//disable SPI
		//Wait for transmission complete
		//Timeout 1ms
		const uint32_t ms_timeout = 1;
		const uint32_t ticks_at_start = HAL_GetTick();
		uint32_t current_ticks = 0;
		uint32_t timediff = 0;
		while (timediff <= ms_timeout)
		{
 
			current_ticks = HAL_GetTick();
			timediff = current_ticks - ticks_at_start;
 
			const uint32_t register_value = READ_BIT(pSPI->Instance->SR, (SPI_SR_RXNE | SPI_SR_TXE));
 
			const uint32_t rxne = (SPI_SR_RXNE & register_value);
			const uint32_t txe = (SPI_SR_TXE & register_value);
 
			if ((SPI_SR_RXNE == rxne) && (SPI_SR_TXE == txe))
			{
				//no data left => disable SPI now
				break;
			}
 
			//short break
			osDelay(1);
 
		}
 
		/*
		 * according to the Reference Manual one should wait for BSY = 0
		 * however the STM32F4 errata Sheet mentions that this flag is unreliable
		 * therefore we ignore this flag
		 */
 
		//Read Data (there should be no data left to read => RX data was allready read in tome other code part)
		READ_REG(pSPI->Instance->DR);   // Read DR to clear flags
 
		//disable DMA RX und TX Buffer
		SET_BIT(pSPI->Instance->CR2, ( SPI_CR2_RXDMAEN |SPI_CR2_TXDMAEN));
 
		SPI_RESET_CRC(pSPI);   // reset CRC
		CLEAR_BIT(pSPI->Instance->SR, SPI_SR_CRCERR);   // clear Flag
 
		CLEAR_BIT(pSPI->Instance->CR1, SPI_CR1_SPE);   // SPI disable
 
	}
 
}

this code is run in a seperate task which basically does nothing else than

void Some_Task()
{
 
	for (;;)
	{
		
 
		//  reset SPI
		Enable_SPI(0, &hspi2);
		
		//Clear old flags
		xEventGroupClearBits(UCCOM_internalEventBits, UCCOM_ALL_ISR_EVENTS); 
 
		//Transfer on
		Enable_SPI(1, &hspi2);
 
		
		// wait for transfer complete
		EventBits_t flagsAtReturn = xEventGroupWaitBits(UCCOM_internalEventBits,
		UCCOM_EVENT_CRC_COMPLETE | UCCOM_EVENT_DMARX_ERROR | UCCOM_EVENT_DMATX_ERROR,
		pdFALSE,
		pdFALSE, WAIT_TIME); //3 ms Waittime
 
		//SPI RXNEIE is set in RX DMA TC Interrupt
		
 
		//do some stuff to use the RX Data....
		
 
 
		// 7. Disable Transfer Hardware
		Enable_SPI(0, &hspi2);
 
	}
	
}

3 REPLIES 3
S.Ma
Principal

If using interrupt, use DMA RX interrupt for the master side. DMA TX will define the number of bytes transiting on the bus.

As for the slave, their DMA must be setup prior to communication (NSS falling edge EXTI ?) and better use DMA in cyclic mode instead of normal mode.

In this case, end of transmission is NSS rise edge (EXTI) for the slave.

As for the HAL, make sure to read all the header files for both SPI and DMA.

When SPI+DMA is used, all callbacks should come from SPI, none from DMA.

Read out and check content of relevant DMA and SPI registers.

JW

JHeni
Associate II

Hey guys,

thx for your answer.

After doing some debugging i found out that for some reason my task was faster than expected. So the SPI got diabled before the transmission/reception was complete.

So moving the SPI disable into the SPI interrupt solved my problem :):grinning_face:

thx for your help