AnsweredAssumed Answered

How to clear "pending" DMA request?

Question asked by waclawek.jan on Aug 16, 2012
Latest reply on Feb 1, 2017 by waclawek.jan
I came across the following problem: I have a free-running timer, and a signal being input on its TIMx_CH1 pin. I want to capture timer value for two consecutive edges on that signal, so I set up the CC1 unit for capturing, and as these events may occur relatively quickly after each other, before enabling the capture (through CC1E flag) I set up the respective DMA channel to be triggered by the capture and transfer the CCR1 register to memory. The DMA is set to be non-circular, so it disables itself after the two numbers are stored.

Now processing the numbers and several other things will occur and that might take some time. However, the input signal on TIMx_CH1 is still pulsing quickly, so several other capture events may happen until the capture gets disabled.

After some time, there is a need to repeat the process again, i.e. the DMA is set up for transferring two numbers non-circularly, and then the CC1 capture is enabled. However, the first DMA transfer occurs immediately after the DMA is enabled, before the first capture could occur!

It appears, that there is a latch at the input of the DMA arbiter, which "remembers" the triggering event. I would expect that it won't get set when the EN bit of DMA channel is at zero, but it probably is not the case. This may even be a more general, non-timer specific problem, which demonstrates itself also in the bug from 2.10.2 in the current rev.3 STM32F4xx Errata.

Below is a fragment which illustrates the problem; the capture is replaced by compare so that there is no need for external hardware to reproduce it.

Does anybody know any trick to clear this "pending" DMA request?

Thanks,

Jan Waclawek

  TIM1_CC1_DMAStream->NDTR = 1; // words to transfer
  TIM1_CC1_DMAStream->M0AR = (uint32_t)&tim1DmaBuf[0];
  TIM1_CC1_DMAStream->PAR = (uint32_t)&(TIM1->CNT);
  TIM1_CC1_DMAStream->FCR = 0;  // no FIFO

  TIM1_CC1_DMAStream->CR = 0
    OR (TIM1_CC1_DMAChannel      * DMA_SxCR_CHSEL_0   )  // channel select
    OR (DMA_SxCR_xBURST_INCR1    * DMA_SxCR_MBURST_0  )  // memory burst (only in FIFO mode)
    OR (DMA_SxCR_xBURST_INCR1    * DMA_SxCR_PBURST_0  )  // peripheral burst (only in FIFO mode)
    OR (0                        * DMA_SxCR_ACK       )  // "reserved" (says manual)
    OR (0                        * DMA_SxCR_CT        )  // current target (only in double-buffer mode)
    OR (0                        * DMA_SxCR_DBM       )  // double-buffer mode
    OR (DMA_SxCR_PL_PRIORITY_LOW * DMA_SxCR_PL_0      )  // priority level
    OR (0                        * DMA_SxCR_PINCOS    )  // peripheral increment offset size (only if peripheral address increments, FIFO mode and PBURST is 0)
    OR (DMA_SxCR_xSIZE_HALFWORD  * DMA_SxCR_MSIZE_0   )  // memory data size; in direct mode forced to the same value as PSIZE
    OR (DMA_SxCR_xSIZE_HALFWORD  * DMA_SxCR_PSIZE_0   )  // peripheral data size
    OR (1                        * DMA_SxCR_MINC      )  // memory address increments
    OR (0                        * DMA_SxCR_PINC      )  // peripheral address increments
    OR (0                        * DMA_SxCR_CIRC      )  // circular mode (forced to 1 if double-buffer mode, forced to 0 if flow control is peripheral)
    OR (DMA_SxCR_DIR_P2M         * DMA_SxCR_DIR_0     )  // data transfer direction
    OR (0                        * DMA_SxCR_PFCTRL    )  // peripheral is the flow controller (i.e. who determines end of transfer) - only for SDIO
    OR (0                        * DMA_SxCR_TCIE      )  // transfer complete interrupt enable
    OR (0                        * DMA_SxCR_HTIE      )  // half transfer interrupt enable
    OR (0                        * DMA_SxCR_TEIE      )  // transfer error interrupt enable
    OR (0                        * DMA_SxCR_DMEIE     )  // direct mode error interrupt enable
    OR (1                        * DMA_SxCR_EN        )  // stream enable
  ;

  TIM1->DIER = 0
    OR (1                       * TIM_DIER_CC1DE   )  /* Capture/Compare 1 DMA request enable */
  ;
  TIM1->ARR = 0xFFFF; // make it ineffective
  TIM1->CNT = 0;
  TIM1->CCR1 = 0x1234;  // here should the compare -> DMA transfer occur
  TIM1->CR1 = 1; // CEN = 1 -> enable timer

  while ((TIM1_CC1_DMAStream->CR AND DMA_SxCR_EN) != 0); // wait until DMA completion

  TIM1_CC1_DMAStream->LIFCR = DMA_LIFCR_CTCIF2;  // yet another undocumented (or poorly documented) "feature": DMA cannot be reenabled until the transfer completed flag in LISR is cleared


  TIM1->CR1 = 0; // CEN = 0 -> disable timer
  TIM1->SR = 0;  // clear status register, clearing the CC1IF flag

  TIM1->CCR1 = 0x2345;  // here should the next compare occur (this time, without DMA transfer, as DMA is still disabled)
  TIM1->CR1 = 1; // CEN = 1 -> enable timer

  // don't enable DMA for now and wait until the CC1 event occurs
  while ((TIM1->SR AND TIM_SR_CC1IF) == 0);
  TIM1->CR1 = 0; // CEN = 0 -> disable timer
  TIM1->SR = 0;  // clear status register, clearing the CC1IF flag


  // TIM1_CC1_DMAStream->NDTR = 1; // words to transfer
  // TIM1_CC1_DMAStream->M0AR = (uint32_t)&tim1DmaBuf[0];
  // TIM1_CC1_DMAStream->PAR = (uint32_t)&(TIM1->CNT);
  // TIM1_CC1_DMAStream->FCR = 0;  // no FIFO

  // now prepare DMA again
  TIM1_CC1_DMAStream->NDTR = 2; // words to transfer
  TIM1_CC1_DMAStream->M0AR = (uint32_t)&tim1DmaBuf[0];
  TIM1_CC1_DMAStream->PAR = (uint32_t)&(TIM1->CNT);
  TIM1_CC1_DMAStream->FCR = 0;  // no FIFO

  TIM1_CC1_DMAStream->CR = 0
    OR (TIM1_CC1_DMAChannel      * DMA_SxCR_CHSEL_0   )  // channel select
    OR (DMA_SxCR_xBURST_INCR1    * DMA_SxCR_MBURST_0  )  // memory burst (only in FIFO mode)
    OR (DMA_SxCR_xBURST_INCR1    * DMA_SxCR_PBURST_0  )  // peripheral burst (only in FIFO mode)
    OR (0                        * DMA_SxCR_ACK       )  // "reserved" (says manual)
    OR (0                        * DMA_SxCR_CT        )  // current target (only in double-buffer mode)
    OR (0                        * DMA_SxCR_DBM       )  // double-buffer mode
    OR (DMA_SxCR_PL_PRIORITY_LOW * DMA_SxCR_PL_0      )  // priority level
    OR (0                        * DMA_SxCR_PINCOS    )  // peripheral increment offset size (only if peripheral address increments, FIFO mode and PBURST is 0)
    OR (DMA_SxCR_xSIZE_HALFWORD  * DMA_SxCR_MSIZE_0   )  // memory data size; in direct mode forced to the same value as PSIZE
    OR (DMA_SxCR_xSIZE_HALFWORD  * DMA_SxCR_PSIZE_0   )  // peripheral data size
    OR (1                        * DMA_SxCR_MINC      )  // memory address increments
    OR (0                        * DMA_SxCR_PINC      )  // peripheral address increments
    OR (0                        * DMA_SxCR_CIRC      )  // circular mode (forced to 1 if double-buffer mode, forced to 0 if flow control is peripheral)
    OR (DMA_SxCR_DIR_P2M         * DMA_SxCR_DIR_0     )  // data transfer direction
    OR (0                        * DMA_SxCR_PFCTRL    )  // peripheral is the flow controller (i.e. who determines end of transfer) - only for SDIO
    OR (0                        * DMA_SxCR_TCIE      )  // transfer complete interrupt enable
    OR (0                        * DMA_SxCR_HTIE      )  // half transfer interrupt enable
    OR (0                        * DMA_SxCR_TEIE      )  // transfer error interrupt enable
    OR (0                        * DMA_SxCR_DMEIE     )  // direct mode error interrupt enable
    OR (1                        * DMA_SxCR_EN        )  // stream enable
  ;

  // This is where the problem demonstrates itself: at this point, one DMA transfer occurs, even if there was no CC1 event after the DMA was enabled
  // This demonstrates itself as:
  // - the half-transfer flag in DMA LISR register gets set beyond this point
  // - tim1DmaBuf[0] will contain the current value of (stopped) timer CNT register
  // - NDTR register is already decremented to 1

  TIM1->CCR1 = 0x3456;  // here should the next compare -> DMA transfer occur (this time, without DMA transfer, as DMA is still disabled)
  TIM1->CR1 = 1; // CEN = 1 -> enable timer

  while ((TIM1_CC1_DMAStream->CR AND DMA_SxCR_EN) != 0); // wait until DMA completion

  TIM1->CR1 = 0; // CEN = 0 -> disable timer

  __asm("nop");

  while(1);



Outcomes