2
votes

So this question is kind of a "sequel" of this one: Stm32f4: DMA + ADC Transfer pausing.

Again, I am trying to implement such an algorithm:

  1. Initialize DMA with ADC in tripple interleaved mode on one channel
  2. Wait for an external interrupt
  3. Suspend DMA transfer and the ADC
  4. Send the buffered data from memory through USART in the interrupt
  5. Resume the DMA and ADCs
  6. Exit the interrupt, goto 2.

The DMA and ADC suspends and resumes, but sometimes (in about 16% of interrupt calls) the resuming fails - DMA just writes first measurement from the ADCs and stops until next interrupt, in which DMA and ADC are restarted (since they are suspended and resumed again) and - well, everything returns back to normal until next such bug.

I've tried suspending DMA just like the Reference manual says:

In order to restart from the point where the transfer was stopped, the software has to read the DMA_SxNDTR register after disabling the stream by writing the EN bit in DMA_SxCR register (and then checking that it is at ‘0’) to know the number of data items already collected. Then:
– The peripheral and/or memory addresses have to be updated in order to adjust the address pointers
– The SxNDTR register has to be updated with the remaining number of data items to be transferred (the value read when the stream was disabled)
– The stream may then be re-enabled to restart the transfer from the point it was stopped

The only actual difference is in the written NDTR value while resuming DMA working. In my case it is buffer_size, in the RefMan case - it is the value read while pausing the DMA. In the RefMan case, DMA never starts again after pausing. In my case, as I said above, it starts, but not always.

How can I prevent this from happening?

The interrupt code looks like this currently:

void EXTI4_IRQHandler(void) {
    uint16_t temp = DMA_GetFlagStatus(DMA2_Stream0, DMA_FLAG_TEIF0);
    if(EXTI_GetITStatus(EXTI_Line4) != RESET) {
        uint16_t fPoint1 = 0;
        uint16_t fPoint2 = 0;

        //Some delay using the TIM2
        TIM_SetCounter(TIM2, 0);
        TIM_Cmd(TIM2, ENABLE);

        //Measure the first point NDTR
        fPoint1 = DMA2_Stream0->NDTR;
        while(TIM_GetITStatus(TIM2, TIM_IT_Update) != SET) {};

        //Measure the second point here.
        fPoint2 = DMA2_Stream0->NDTR;

        if(fPoint1 == fPoint2) {
            //The NDTR does not change!
            //If it does not change, it is stuck at buffer_size - 1
        }

        //Disable the timer
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
        TIM_Cmd(TIM2, DISABLE);

        DMA_Cmd(DMA2_Stream0, DISABLE);
        //Wait until the DMA will turn off
        while((DMA2_Stream0->CR & (uint32_t)DMA_SxCR_EN) != 0x00) {};

        //Turn off all ADCs
        ADC_Cmd(ADC1, DISABLE);
        ADC_Cmd(ADC2, DISABLE);
        ADC_Cmd(ADC3, DISABLE);

        //Send all the data here

        //Turn everything back on

        //Turn the DMA ON again
        DMA_SetCurrDataCounter(DMA2_Stream0, BUFFERSIZE);
        DMA_Cmd(DMA2_Stream0, ENABLE);
        while((DMA2_Stream0->CR & (uint32_t)DMA_SxCR_EN) == 0x00) {};

        //See note @ RefMan (Rev. 12), p. 410
        ADC->CCR &= ~((uint32_t)(0x000000FF));
        ADC->CCR |= ADC_TripleMode_Interl;

        ADC_Cmd(ADC1, ENABLE);
        ADC_Cmd(ADC2, ENABLE);
        ADC_Cmd(ADC3, ENABLE);
        while((ADC1->CR2 & (uint32_t)ADC_CR2_ADON) == 0) {};
        while((ADC2->CR2 & (uint32_t)ADC_CR2_ADON) == 0) {};
        while((ADC3->CR2 & (uint32_t)ADC_CR2_ADON) == 0) {};

        ADC_SoftwareStartConv(ADC1);
    }

    EXTI_ClearITPendingBit(EXTI_Line4);
}
1

1 Answers

2
votes

I've found the solution myself. I was thinking it was a DMA problem; however, it turned ot to be ADC's problem. The OVR flag in ADCx->CR register was always set when the transfer was "stuck". So, I added an interrupt to the ADC overrun situation and restarted DMA & ADC in it. The problem is solved now.