Skip to main content
JSmit.11
Associate II
March 4, 2021
Question

SPI with DMA: Add delay between bytes

  • March 4, 2021
  • 9 replies
  • 5213 views

Hi all,

I am working with a peripheral using SPI at 25MHz.

I would like to use DMA to send a continuous stream of data but the problem is that peripheral requires a 125ns delay between bytes.

Is there any way to achieve that with DMA?

I guess if i use DMA there will be no delays between bytes and thus it is not suitable for me.

Thanks

    This topic has been closed for replies.

    9 replies

    Uwe Bonnes
    Chief
    March 4, 2021

    Perhaps try to set up DMA, enable SPI and immediate disable it. With the RXNE interrupt set, reenable SPI in the interrupt after the delay has happended.

    waclawek.jan
    Super User
    March 4, 2021

    Which STM32?

    Trigger the DMA from a timer, not from SPI itself.

    JW

    JSmit.11
    JSmit.11Author
    Associate II
    March 5, 2021

    Hi, thanks for reply.

    I am using two cpu's.

    STM32L4S7ZIT6 and

    STM32L462RET6

    You mean that each byte transmission should be triggered by the timer? And and delay should be added between transmissions?

    waclawek.jan
    Super User
    March 5, 2021

    > You mean that each byte transmission should be triggered by the timer?

    Yes.

    > And and delay should be added between transmissions?

    You set the timer so that timing of triggers of individual transfers is equal to duration of the SPI frame (byte) plus the required delay, plus some reserve for DMA jitter caused by conflicts within DMA and on buses, resynchronizations, etc. - this depends on the particularities of your aplication. Try.

    JW

    JSmit.11
    JSmit.11Author
    Associate II
    June 16, 2021

    Hello,

    After a long break i have returned to that issue.

    I have some problems with DMAMUX overrun interrupt.

    I decided to use LPTIM1 timer OUT event as a synchronization event. I have the following settings for DMAMUX:

    NBREQ = 0 (1 spi transfer)

    SE=1 (synchronization enable)

    EGE=0 (event disable)

    I see that DMA transfer occur. And I can regulate the intervals between SPI transfers changing the LPTIM1 counter value.

    The problem is that i have DMAMUX overrun interrupt. That causes problems with detection of SPI completion. (HAL_SPI_TxCpltCallback is not called)

    I see that overrun happens via SOFx register. I have read the manual which says that it can happen when synchronization. Here is the quote:

    [FROM RM042]

    If a new DMA request trigger event occurs before the DMAMUX request generator counter underrun (the internal counter programmed via the GNBREQ field of the DMAMUX_RGxCR register), and if the request generator channel x was enabled via GE, then the request trigger event overrun flag bit OFx is asserted by the hardware in the status DMAMUX_RGSR register

    [FROM RM END]

    So as I understand if a request from LPTIM will happen while there is DMA transaction the ovverun interrupt will happen.

    But i my case the LPTIM overflow time is much larger than SPI transfer. I have checked that. So it might not an issue here.

    Thre is another quote from RM0432:

    [FROM RM042]

    The request generator channel x must be disabled (DMAMUX_RGxCR.GE = 0) at the completion of the usage of the related channel of the DMA controller. Else, upon a new detected trigger event, there is a trigger overrun due to the absence of an acknowledge (that is, no served request) received from the DMA.

    [FROM RM042 END]

    So let say I send via SPI 5 bytes. After 5th byte is send if the channel is not disabled the next trigger from timer will cause overrun interrupt.

    This is how i understand above note. I see that overrun happens only after all spi transfers complete.

    Am i correct? Do i need to disable the channel manually.

    I am using HAL. After I get a overrun interrupt, my breakpoint triggers. I see through peripherals view that channel is enabled..

    Maybe there is a bug in a HAL library?

    Thanks

    waclawek.jan
    Super User
    June 16, 2021

    > I see that overrun happens via SOFx register.

    So it's a Synchronization overrun, yet you're quoiting from the Trigger overrun (indicated by OFx) subchapter.

    0693W00000BbA88QAF.pngI don't use Cube.

    JW

    JSmit.11
    JSmit.11Author
    Associate II
    June 16, 2021

    Sorry. I have mixed all up.

    I have Synchronization overrun.

    But in RM there is the same quote about it.

    [QUOTE]

    Synchronization overrun and interrupt If a new synchronization event occurs before the request counter underrun (the internal request counter programmed via the NBREQ field of the DMAMUX_CxCR register), the synchronization overrun flag bit SOFx is set in the DMAMUX_CSR status register. Note: The request multiplexer channel x synchronization must be disabled (DMAMUX_CxCR.SE = 0) at the completion of the use of the related channel of the DMA controller. Else, upon a new detected synchronization event, there is a synchronization overrun due to the absence of a DMA acknowledge (that is, no served request) received from the DMA controller.

    [END]

    So maybe i need to disable the channel after DMA transfer completed?

    waclawek.jan
    Super User
    June 16, 2021

    Or just ignore the interrupt (don't enable it).

    JW

    JSmit.11
    JSmit.11Author
    Associate II
    July 6, 2021

    Thanks. I have implemented that successfully using DMAMUX.

    Although it is harder to implement on a more simple CPU which has no dmamux channel and so dma can't be synchronized.

    My idea is to use timer interrupt and on interrupt program dma to transfer one byte.

    Hope that will take less that 125 ns.

    Piranha
    Principal III
    July 17, 2021

    > My idea is to use timer interrupt and on interrupt program dma to transfer one byte.

    For a single byte you don't need DMA at all. Just write the data to the peripheral with CPU and it will be even faster.

    JSmit.11
    JSmit.11Author
    Associate II
    June 16, 2021

    I will try.

    But do i understand correctly that when DMA transfer is finished the next trigger from TIM will cause overrun event? Because transfer counter NBREQ  is over.

    That is a bit strange design. Maybe it is done for purpose.

    MasterT
    Lead II
    July 18, 2021

    I had similar problem with interfacing Analog Device ADC to STM32F7. (uCPU should support Combined PWM feature.)

    ADC sampling rate is 1 MSPS, 16-bits, and it requires 500-700 nsec to complete analog-digital conversion. Whatever time left is for data clocking out. Idea I come up with, is using two timers. One is a master, defines frame rate, CS pulse. Another timer is gated, and provides SPI clock. SPI itself on stm32F configured in slave mode, so spi-CS connected to adc-CS and to Timer-CS driver pin. Same with spi-clock - Timer as a driver connected to spi-CLK & adc-CLK. To make it all to run synchronously, start with clock (25 MHz or whatever) than calculated required PWM for master though gated timer outputs exactly necessary number of pulses.

    void sampl_rate(uint16_t ksps)
    {
     uint16_t tim2_clok;
     uint16_t tim2_pwm1;
     uint16_t tim2_pwm2;
     uint16_t tim3_clok;
     
     if(ksps > 1500) ksps = 1500;
     
     tim3_clok = 2; 
     
     tim2_clok = roundf(108000.0 /(tim3_clok *ksps));
     tim2_clok *= tim3_clok; 
     
     tim2_pwm1 = 16 * tim3_clok +dly_trans;
     tim2_pwm2 = dly_trans; // shift right 25 nsec
     
     TIM2->CR1 &= ~(TIM_CR1_CEN);
     TIM3->CR1 &= ~(TIM_CR1_CEN);
     
     TIM2->ARR = tim2_clok -1;
     TIM3->ARR = tim3_clok -1;
     
     TIM2->CCR1 = tim2_pwm1;
     TIM2->CCR2 = tim2_pwm2;
     TIM2->CCR3 = (tim2_pwm1 *5) /6;
     //Charge pump:
     TIM2->CCR4 = tim2_clok /2;
     TIM3->CCR2 = tim3_clok /2;
     
     TIM2->CNT = 0;
     TIM3->CNT = 0; 
     // p. 41
     // 5. Reset TIM3 by writing ‘1 in UG bit (TIM3_EGR register).
     // 6. Reset TIM2 by writing ‘1 in UG bit (TIM2_EGR register).
     TIM2->EGR = (TIM_EGR_UG | TIM_EGR_CC2G | TIM_EGR_CC1G);
     TIM3->EGR = (TIM_EGR_UG | TIM_EGR_CC2G);
     
     TIM2->CR1 |= (TIM_CR1_CEN);
     TIM3->CR1 |= (TIM_CR1_CEN);
     
     resolutin = 108000.0f /(tim2_clok *32768.0f);
    }
     
    void SPI4_Init(void)
    {
     hspi4.Instance = SPI4;
     hspi4.Init.Mode = SPI_MODE_SLAVE;
     hspi4.Init.Direction = SPI_DIRECTION_2LINES_RXONLY;
     hspi4.Init.DataSize = SPI_DATASIZE_16BIT;
     hspi4.Init.NSS = SPI_NSS_HARD_INPUT;
     hspi4.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
     hspi4.Init.TIMode = SPI_TIMODE_ENABLE;
     hspi4.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
     hspi4.Init.CRCPolynomial = 7;
     hspi4.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
     hspi4.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
     
     if (HAL_SPI_Init(&hspi4) != HAL_OK)
     {
     Error_Handler();
     }
    }
     
    static void TIM2_Config(void)
    {
     TIM_ClockConfigTypeDef sClockSourceConfig = { 0};
     TIM_MasterConfigTypeDef sMasterConfig = { 0};
     TIM_OC_InitTypeDef sConfigOC = { 0};
     
     __HAL_RCC_TIM2_CLK_ENABLE();
     
     htim2.Instance = TIM2;
     htim2.Init.Prescaler = 0;
     htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
     htim2.Init.Period = 0x07CF;
     htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
     htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
     
     if (HAL_TIM_Base_Init(&htim2) != HAL_OK) {
     Error_Handler();
     }
     
     sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
     
     if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) {
     Error_Handler();
     }
     
     sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC2REF;
     sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
     
     if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) {
     Error_Handler();
     }
     
     sConfigOC.OCMode = TIM_OCMODE_PWM1;
     sConfigOC.Pulse = 0x03E8;
     sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
     sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
     
     if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) {
     Error_Handler();
     }
     
     sConfigOC.OCMode = TIM_OCMODE_COMBINED_PWM2;//TIM_OCMODE_PWM1;
     sConfigOC.Pulse = 0x03B6;
     sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
     sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
     
     if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) {
     Error_Handler();
     }
     
     sConfigOC.OCMode = TIM_OCMODE_PWM1;
     sConfigOC.Pulse = 0x3E8;
     sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
     sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
     
     if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_3) != HAL_OK) {
     Error_Handler();
     }
     
     if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
     {
     Error_Handler();
     }
     
     HAL_TIM_Base_Start(&htim2);
     
     if (HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1) != HAL_OK) {
     Error_Handler();
     }
     
     if (HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2) != HAL_OK) {
     Error_Handler();
     }
     
     if (HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3) != HAL_OK) {
     Error_Handler();
     }
     
     if (HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4) != HAL_OK) {
     Error_Handler();
     }
    }
     
    static void TIM3_Config(void)
    {
     TIM_ClockConfigTypeDef sClockSourceConfig = { 0};
     TIM_SlaveConfigTypeDef sSlaveConfig = { 0};
     TIM_MasterConfigTypeDef sMasterConfig = { 0};
     TIM_OC_InitTypeDef sConfigOC = { 0};
     
     __HAL_RCC_TIM3_CLK_ENABLE();
     
     htim3.Instance = TIM3;
     htim3.Init.Prescaler = 0;
     htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
     htim3.Init.Period = 0x31;
     htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
     htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
     
     if (HAL_TIM_Base_Init(&htim3) != HAL_OK) {
     Error_Handler();
     }
     
     sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
     
     if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK) {
     Error_Handler();
     }
     
     sSlaveConfig.SlaveMode = TIM_SLAVEMODE_GATED;
     sSlaveConfig.InputTrigger = TIM_TS_ITR1;
     
     if (HAL_TIM_SlaveConfigSynchro(&htim3, &sSlaveConfig) != HAL_OK) {
     Error_Handler();
     }
     
     sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
     sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
     
     if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK) {
     Error_Handler();
     }
     
     sConfigOC.OCMode = TIM_OCMODE_PWM1;
     sConfigOC.Pulse = 0x19;
     sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW; 
     sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
     
     if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) {
     Error_Handler();
     }
     
     HAL_TIM_Base_Start(&htim3);
     
     if (HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2) != HAL_OK) {
     Error_Handler();
     }
    }