cancel
Showing results for 
Search instead for 
Did you mean: 

SPI transfer size using DMA peripheral flow control mode

PRobe
Associate II

I need to transfer data between two micro controllers - both short commands and large blocks of data.

The STM will receive and so will be the SPI slave. I intend to use DMA.

The SPI master will transmit either short or long packets and so need a way to receive variable lengths using DMA - I could not find an example & wanted a quick start.

My guess is that if I should use hdma_tx.Init.Mode=DMA_PFCTRL & hope that means that the slave transaction will complete once master completes brings CS high? Anyone tried this? Other than adding a length field to the packet, how may I get the number of bytes transferred?

Perhaps a easier route is to capture the SPI complete event?

In either case I wonder what the meaning of 'SIze' in the HAL_SPI_*** calls becomes.

8 REPLIES 8

You didn't care to tell us the STM32 model you are using, but read the Flow controller subchapter of the DMA chapter in the RM, you might find something like this there:

 The peripheral indicates by hardware to the DMA controller

when the last data are being transferred. This feature is only supported for peripherals

that are able to signal the end of the transfer, that is: SDIO.

If you have a CS line between master and slave, then the easiest thing to do is to interrupt upon CS change (use it as an EXTI) and then you know the number of bytes transferred from the DMA's NDTR.

JW

S.Ma
Principal

Are there any other SPI slaves on the bus?

PRobe
Associate II

Indeed. I was just answering the first response. Sharing the bus is the tricky part...

Thanks. STM32F401CB

My RM is not as explicit and states:

'The last external burst or single request has been generated from the peripheral...'

re: Using CS as EXTI. So I just call HAL_SPI_DMAStop() (which then chains through the completion handlers)?

Can I use CS as an EXTI when that pin is being used as slave hardware NSS?

My slave will be full duplex and return status information lodged in the minimum packet size.

I would therefore need to enable/disable my MISO depending on CS or upset the other slaves.

On a shared SPI bus using software NSS (if I am to use EXTI) then this would have too much latency.

Do back to DMA events I think.

I did not find a CS state change INT.

S.Ma
Principal

EXTI is independent of chosen alternate function, however you'll need to workaround the HAL as it has yet to enable this scheme.

Personally, used NSS SW+EXTI on both edges, with SPI with DMA in cyclic mode for slaves (no DMA interrupt). First transaction exchange the desired block transfer size (in and out bound on 4 wire), then second transaction is the DMA block transfer which size if the biggest block size.

Allow minimal pause when NSS toggles for slaves to get ready for the transaction or process the incoming data message.

PRobe
Associate II

Thanks,

Could you reference how to use EXTI in AF mode to trigger on CS plz?

I can only see TX/RX/ERROR SPI events, not the needed CS change, in the RM.

The rest is difficult to follow as the HAL/RM is so ambiguous.

Circular mode reloads length after each transfer. Fine. But when is transfer end? If you are SW NSS then transfer end is when Size has been reached(?). So seems to me that Circular is only for regular (e.g. ADC) outputs as described and not for variable packet lengths which I need

I have an ESP on the other end & ESP latency is crippling.

S.Ma
Principal

ok, got EXTI on SCK, you can probably adapt to NSS (HW) pin:

void SPIP_EnableSCK_EXTI(SPIP_t* pSPIP) { // STM32L4R5
// PD1 = SCK
  SYSCFG->EXTICR[0] &= ~(0xF<<4);
  SYSCFG->EXTICR[0] |= (3<<4);
  EXTI->IMR1 |= GPIO_PIN_1; // interrupt enable
  EXTI->RTSR1 |= GPIO_PIN_1; // rising sense
  EXTI->FTSR1 |= GPIO_PIN_1; // falling sense
// Above is the simplest way to have EXTI on AF pin.
  __HAL_GPIO_EXTI_CLEAR_IT(pSPIP->pSCK->Init.Pin);
  __HAL_GPIO_EXTI_CLEAR_FLAG(pSPIP->pSCK->Init.Pin);
  //EXTI->IMR1 |= (1<<1);
  HAL_NVIC_SetPriority(EXTI1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(EXTI1_IRQn);
}
 
void SPIP_DisableSCK_EXTI(SPIP_t* pSPIP) {
  HAL_NVIC_DisableIRQ(EXTI1_IRQn);  
}
 
void EXTI1_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI0_IRQn 0 */
  /* USER CODE END EXTI0_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);
  /* USER CODE BEGIN EXTI0_IRQn 1 */
  /* USER CODE END EXTI0_IRQn 1 */
}

DMA needs to know the block size going to be transffered:

  • You send the information in a previous transaction
  • You have a max incoming RAM buffer size and loop through it. The received data contains the valid block size

Of course, if planning to exchange data full duplex MISO and MOSI, there are two directions, two blocks, two sizes sharing the same SCK clock pulses...

One implemented solution if there are only MCU on the SPI bus is to send data at each edge of NSS, rising edge, I send a small "header" which contains the coming next data size and simultaneously, I received the slave intended block size to receive. Then on NSS falling edge, the DMA do a pure block transfer uninterrupted. In this particular case, NSS is SW controlled and MISO/MOSI at slave are dynamically configured from the NSS dual edge EXTI interrupt. All is handled in a ISR based state machine to hide the details and minimize core load.

  1. There's probably no peripheral in the 'F401 which could act as peripheral controller for DMA. The RM is obviously derived from other models which do have some of those, and their list has been simply removed, leaving for confusion.
  2. KIC was so kind to provide you code for the EXTI, adopt it for NSS
  3. Configure DMA in Circular mode and leave it running. You don't need to stop it, ever. It's your Rx circular buffer - you surely are familiar with circular buffers, with head to push and tail to pop, both wrapping around? The given stream's NDTR is the buffer's head (except that it decrements from buffer size to 1, so index is buffer_size-NDTR). You provide tail yourself, and pop when the EXTI indicates NSS going low.
  4. You don't need to fiddle with MOSI, in slave mode it's threestated by hardware driven by NSS, https://community.st.com/s/question/0D50X00009XkaBbSAJ/does-spi-in-slave-mode-threestate-miso-on-inactive-nss-or-not . This is a fairly standard feature across SPI modules, allowing in multi-slave to tie together all MISOs and all MOSIs. While the designers of SPI in STM32 left many omissions and rough edges, this one they got right.

I don't Cube.

JW

PRobe
Associate II

Thanks all. Fairly straight forwards once you ditch the HAL straight jacket.

Shame they messed up SPI SLAVE, stop/start DMA on NSS - how hard is that?

I am going to play & double check. If I set SPI transfer to 65535 then I can terminate SPI on NSS EXTI.

I would still have to enforce bus gap between frames but better (on ESP) than programming SPI length following first frame.