How to clear ''pending'' DMA request?
Posted on August 16, 2012 at 21:45
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);
