cancel
Showing results for 
Search instead for 
Did you mean: 

STM32L0 SPI communication with DMA, Slow CS

Dude
Associate III

STM32L0 SPI communication with DMA. The SPI CS line is controlled with software. It appears to stay low for about 72uS when the communication only takes about 4uS. 

Right before setting the DMA to roll, I set the CS to 0. Nothing else is pending for the DMA. When the DMA RX full interrupt triggers, I set the CS to 1.

Why does the CS take as long as it does? How can I make this time shorter? I would like to do communications more often, if possible.

13 REPLIES 13
S.Ma
Principal

Core frequency?

HAL or LL/direct register writes?

Only DMA RX interrupt for SPI?

Repeatable?

No other higher prio interrupts running?

Dude
Associate III

To answer your questions:

  1. Internal clock: 16MHZ
  2. SPI DMA via LL register writes (but I have a UART utilizing the HAL libraries. I will try switching this over to LL and see if there is any improvement)
  3. DMA RX and TX interrupts.
  4. Repeatable (happens every time)
  5. No other interrupts running (well, except for the fundamental built in interrupts, like NMI, Hardfault, SVC, PENDSV and SysTick)
Dude
Associate III
 
 
void restart_DMA(void)
 
{
 
LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
 
LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_3);
 
 
 
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, Rx_buffer_size);
 
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_3, Tx_buffer_size);
 
 
 
/* (5) Enable DMA interrupts complete/error */
 
// Rx receive channel interrupts
 
LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_2);
 
LL_DMA_EnableIT_TC (DMA1, LL_DMA_CHANNEL_2);
 
LL_DMA_EnableIT_TE (DMA1, LL_DMA_CHANNEL_2);
 
 
 
// Tx transmit channel interrupts
 
LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_3);
 
LL_DMA_EnableIT_TC (DMA1, LL_DMA_CHANNEL_3);
 
LL_DMA_EnableIT_TE (DMA1, LL_DMA_CHANNEL_3);
 
 
 
//Enable DMA interrupts
 
NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0);
 
NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
 
}
 
 
 
/**
 
 * @brief This function configures the DMA Channels for SPI1
 
 * @note This function is used to :
 
 *    -1- Enable DMA1 clock
 
 *    -2- Configure NVIC for DMA1 transfer complete/error interrupts
 
 *    -3- Configure the DMA1_Channel2 functional parameters
 
 *    -4- Configure the DMA1_Channel3 functional parameters
 
 *    -5- Enable DMA1 interrupts complete/error
 
 * @param  None
 
 * @retval None
 
 */
 
void Configure_DMA(void)
 
{
 
/* DMA1 used for SPI1 Transmission
 
* DMA1 used for SPI1 Reception
 
*/
 
 
 
//LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
 
//LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_3);
 
 
 
 
 
//uint8_t temp = 0;
 
/* (1) Enable the clock of DMA1 and DMA1 */
 
LL_SPI_Disable(SPI1);
 
 
 
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
 
 
 
/* (2) Configure NVIC for DMA transfer complete/error interrupts */
 
NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0);
 
NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
 
NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0); // NVIC_SetPriority(DMA1_Channel3_IRQn, 0);
 
NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
 
 
 
/* (3) Configure the DMA1_Channel2 functional parameters, the Receive Channel */
 
LL_DMA_ConfigTransfer(DMA1, LL_DMA_CHANNEL_2, LL_DMA_DIRECTION_PERIPH_TO_MEMORY | LL_DMA_PRIORITY_HIGH
 
| LL_DMA_MODE_NORMAL        | LL_DMA_PERIPH_NOINCREMENT
 
| LL_DMA_MEMORY_INCREMENT      | LL_DMA_PDATAALIGN_BYTE
 
| LL_DMA_MDATAALIGN_BYTE);
 
 
 
LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_2, &(SPI1 -> DR), (uint32_t)&mdrcvBuffer,
 
LL_DMA_DIRECTION_PERIPH_TO_MEMORY); //LL_SPI_DMA_GetRegAddr(SPI1)
 
 
 
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, Rx_buffer_size);
 
 
 
//LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_2, LL_DMA_REQUEST_0); ////////////////
 
 
 
/* (4) Configure the DMA1_Channel3 functional parameters, the Transmit Channel */
 
LL_DMA_ConfigTransfer(DMA1, LL_DMA_CHANNEL_3, LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_PRIORITY_HIGH
 
| LL_DMA_MODE_NORMAL        | LL_DMA_PERIPH_NOINCREMENT
 
| LL_DMA_MEMORY_INCREMENT      | LL_DMA_PDATAALIGN_BYTE
 
| LL_DMA_MDATAALIGN_BYTE);
 
 
 
LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_3, (uint32_t)&mdsendBuffer, &(SPI1 -> DR),
 
LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
 
 
 
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_3, Tx_buffer_size);
 
 
 
//LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_3, LL_DMA_REQUEST_0); /////////////////
 
 
 
/* (5) Enable DMA interrupts complete/error */
 
// Rx receive channel interrupts
 
LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_2);
 
LL_DMA_EnableIT_TC (DMA1, LL_DMA_CHANNEL_2);
 
LL_DMA_EnableIT_TE (DMA1, LL_DMA_CHANNEL_2);
 
 
 
// Tx transmit channel interrupts
 
LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_3);
 
LL_DMA_EnableIT_TC (DMA1, LL_DMA_CHANNEL_3);
 
LL_DMA_EnableIT_TE (DMA1, LL_DMA_CHANNEL_3);
 
 
 
/* Check what interrupts are or are not enabled -> everything checked out fine, leaving for debugging in the future */
 
//temp = LL_DMA_IsEnabledIT_TC(DMA1, LL_DMA_CHANNEL_2); // Channel 2,   TC or Transfer Complete: 1 (interrupt enabled)
 
//temp = LL_DMA_IsEnabledIT_HT(DMA1, LL_DMA_CHANNEL_2); // Channel 2, HT or Half Transfer Complete: 0 (interrupt not enabled)
 
//temp = LL_DMA_IsEnabledIT_TE(DMA1, LL_DMA_CHANNEL_2); // Channel 2,     TE or Transfer Error: 1 (interrupt enabled)
 
 
 
//temp = LL_DMA_IsEnabledIT_TC(DMA1, LL_DMA_CHANNEL_3); // Channel 3,   TC or Transfer Complete: 1 (interrupt enabled)
 
//temp = LL_DMA_IsEnabledIT_HT(DMA1, LL_DMA_CHANNEL_3); // Channel 3, HT or Half Transfer Complete: 0 (interrupt not enabled)
 
//temp = LL_DMA_IsEnabledIT_TE(DMA1, LL_DMA_CHANNEL_3); // Channel 3,     TE or Transfer Error: 1 (interrupt enabled)
 
 
 
}
 
 
 
/**
 
 * @brief This function configures SPI1.
 
 * @note This function is used to :
 
 *    -1- Enables GPIO clock and configures the SPI1 pins.
 
 *    -2- Configure SPI1 functional parameters.
 
 * @note  Peripheral configuration is minimal configuration from reset values.
 
 *     Thus, some useless LL unitary functions calls below are provided as
 
 *     commented examples - setting is default configuration from reset.
 
 * @param None
 
 * @retval None
 
 */
 
 
 
/**
 
 * @brief This function Activate SPI1
 
 * @param None
 
 * @retval None
 
 */
 
void Activate_SPI(void)
 
{
 
 
 
/*
 
* 1. SPI -> Enable DMA Rx with RXDMAEN bit
 
* 2. DMA -> Enable RX and TX DMA streams or channels
 
* 3. SPI -> Enable DMA Tx with TXDMAEN bit
 
* 4. SPI -> Enable SPI with SPE bit
 
*
 
*/
 
 
 
/* Enable DMA RX / TX Interrupt */
 
LL_SPI_DisableDMAReq_RX(SPI1);
 
LL_SPI_DisableDMAReq_TX(SPI1);
 
 
 
//Enable DMA interrupts
 
NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0);
 
NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
 
// 1. SPI -> Enable DMA Rx with RXDMAEN bit
 
LL_SPI_EnableDMAReq_RX(SPI1);
 
 
 
// 2. DMA -> Enable RX and TX DMA streams or channels
 
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);
 
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_3);
 
 
 
// 3. SPI -> Enable DMA Tx with TXDMAEN bit
 
LL_SPI_EnableDMAReq_TX(SPI1);
 
 
 
//4. SPI -> Enable SPI with SPE bit
 
LL_SPI_Enable(SPI1);
 
}

Dude
Associate III

-----------------------------------------------------------------------------

------------------------------------------------------------------------------

----- stm32L0xx_it.c

------------------------------------------------------------------------------

------------------------------------------------------------------------------

void DMA1_Channel2_3_IRQHandler(void)
 
{
 
 /* USER CODE BEGIN DMA1_Channel2_3_IRQn 0 */
 
 
 
	// ---- External Variable Declaration
 
	extern volatile uint32_t _Data[num_];
 
	extern volatile uint8_t Index;
 
	extern volatile __IO uint8_t ubTransmissionComplete;
 
	extern volatile __IO uint8_t ubReceptionComplete;
 
	//extern volatile uint8_t mdrcvBuffer [Rx_buffer_size];         // SPI Rx buffer, 4-bytes long
 
 
 
	// ---- Internal Variable Declaration
 
	uint8_t i = 0;
 
 
 
 
 
	// ---- Channel 2 interrupts, the Rx channel
 
	if( LL_DMA_IsActiveFlag_GI2(DMA1))   // An interrupt of some kind on DMA channel 2 has occurred
 
	{
 
 if(   LL_DMA_IsActiveFlag_TE2(DMA1) & LL_DMA_IsEnabledIT_TE(DMA1, LL_DMA_CHANNEL_2))  //Transfer Error Interrupt?
 
 {
 
 /* Disable DMA1 Rx Channel */
 
 LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
 
 
 
 /* Disable DMA1 Tx Channel */
 
 LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_3);
 
 }
 
 else if ( LL_DMA_IsActiveFlag_HT2(DMA1) & LL_DMA_IsEnabledIT_HT(DMA1, LL_DMA_CHANNEL_2) ) // Half complete interrupt?
 
 {
 
 LL_DMA_ClearFlag_HT2(DMA1);
 
 }
 
 else if( LL_DMA_IsActiveFlag_TC2(DMA1) & LL_DMA_IsEnabledIT_TC(DMA1, LL_DMA_CHANNEL_2)) // Fully Complete Interrupt?
 
 {
 
 LL_DMA_ClearFlag_TC2(DMA1);           // Clear Rx interrupt
 
 
 
 
 
 // ---- Transmission Complete, turn off respective Chip Select
 
 if(Index == CHIPA){	CS_MSA_OFF;	}
 
 if(Index == CHIPB){	CS_MSB_OFF;	}
 
 
 
 
 
 // ---- Prepare the Send Buffer for the next go around
 
 if(Index == CHIPA)
 
 {
 
 for(i = 0; i < Tx_buffer_size; i++)
 
 {
 
 mdsendBuffer[i] = B_send[i];
 
 A_send[i] = 0x00;
 
 }
 
 }
 
 
 
 
 
 // ---- Transmission Complete, Obtain respective sensor data
 
 if(Index != num_)
 
 {
 
 _Data[Index ]   = 0;
 
 _Data[Index]   |= mdrcvBuffer[0];
 
 
 
 for ( i = 1; i < 4; i++ )
 
 {
 
 _Data[Index] = _Data[Index] << 8;
 
 _Data[Index] |= mdrcvBuffer[i];
 
 }
 
 }
 
 
 
 // ---- Increment Index for the next phase, ie the next sensor
 
 Index += 1;
 
 
 
 // ---- If you have reached the end, reset everything
 
 if(Index == num_)
 
 {
 
 Index = 0;
 
 }
 
 // ---- If you haven't reached the end, setup the DMA for the next phase, ie the next sensor
 
 else
 
 {
 
 restart_DMA();               // Get the DMA engine going again
 
                      // Asking for data from the second sensor now
 
 if(Index == CHIPB) { data_channel_DMA(CHIPB, sensor_axis); }    // Send out the request, should the activate SPI operation be here on in the TMAG function?
 
 }
 
 }
 
	}
 
 
 
	// ---- Channel 3 interrupts, the Tx channel
 
	if( LL_DMA_IsActiveFlag_GI3(DMA1))   // An interrupt of some kind on DMA channel 3 has occurred
 
	{
 
 if( LL_DMA_IsActiveFlag_TE3(DMA1) & LL_DMA_IsEnabledIT_TE(DMA1, LL_DMA_CHANNEL_3) )  //Transfer Error Interrupt?
 
 {
 
 /* Disable DMA1 Rx Channel */
 
 LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
 
 
 
 /* Disable DMA1 Tx Channel */
 
 LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_3);
 
 
 
 }
 
 else if ( LL_DMA_IsActiveFlag_HT3(DMA1) & LL_DMA_IsEnabledIT_HT(DMA1, LL_DMA_CHANNEL_3) ) // Half complete interrupt?
 
 {
 
 LL_DMA_ClearFlag_HT3(DMA1);
 
 }
 
 else if( LL_DMA_IsActiveFlag_TC3(DMA1) & LL_DMA_IsEnabledIT_TC(DMA1, LL_DMA_CHANNEL_3) ) // Fully Complete Interrupt?
 
 {
 
 ubTransmissionComplete  = 1;
 
 LL_DMA_ClearFlag_TC3(DMA1);       //This seems to be required, otherwise the interrupt seems to continually trigger
 
 }
 
	}

Dude
Associate III

It seems to take 100uS to transmit and 100uS between transmits (multiple external devices)0693W00000WLLjHQAX.png 

Dude
Associate III

Goals:

  1. Tighten the time window for a single SPI communication operation, even just to half it to 50uS
  2. Tighten the time window between SPI communications, as much as possible.

I haven't yet found anything to suggest why the communication seems to be blocked or delayed like this. I sense that the DMA might run in blocked time intervals of ~100uS though. Perhaps I can look into its timing to see if that is the case.

In the future please use the code posting tool </> icon, as in-lining the code removes all formatting/indentation.

Sorry not clear on how the two transfers occur or interact. Don't see any of the code related to this.

I guess one would want to do the least possible reconfiguration of the entire SPI/DMA.

The bus is symmetrical, same amount of data going out as coming in.

Watch for FIFO's and thresholds.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

>>The bus is symmetrical, same amount of data going out as coming in

Point here being, not clear if you check for this, and really no point in doing TWO interrupts to service this. TX will be leading, RX will be trailing, and the better point to address things, as the last bit has gone over the wire. You can drop CS, and pivot directly into the next transfer.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

Okay fair enough. I had been looking in the past at the bare bones of what I would need to reset to get the DMA rolling again. I should look at this again. (after I got it working, I stopped looking further on optimizing everything)