cancel
Showing results for 
Search instead for 
Did you mean: 

DMA Double Buffer IRQ

markgilson9
Associate II
Posted on December 14, 2011 at 20:35

I currently have code which runs on the STM32F4-Discovery board and DMA's data from the ADC to a buffer.  I am using double buffering, so I can operate on one buffer while the other is being filled.  In short, my code works; both buffers are correctly filled with data.  The problem I'm having is that I'm interrupted at the end of filling both, when I really want to be interrupted when the first has been filled and the other one just starts getting filled by the DMA.  Some of my code is below:

DMA_InitStructure.DMA_Channel = DMA_Channel_2;  

  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC3_DR_ADDRESS;

  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&ADCBuf[0];

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;

  DMA_InitStructure.DMA_BufferSize = 800;

  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;

  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

  DMA_InitStructure.DMA_Priority = DMA_Priority_High;

  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         

  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;

  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;

  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

  DMA_Init(DMA2_Stream0, &DMA_InitStructure);

  DMA_DoubleBufferModeConfig(DMA2_Stream0, (uint32_t)&ADCBuf[1], DMA_Memory_0);

  DMA_DoubleBufferModeCmd(DMA2_Stream0, ENABLE);

  

  DMA_ITConfig(DMA2_Stream0, DMA_IT_HT | DMA_IT_TC | DMA_IT_TE, ENABLE);

  DMA_Cmd(DMA2_Stream0, ENABLE);

I then enable the interrupt:

void NVIC_Configuration(void)

{

  NVIC_InitTypeDef NVIC_InitStructure;

  /* Configure and enable DMA interrupt */

  NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;

  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

  NVIC_Init(&NVIC_InitStructure);

}

And then finally I have the IRQHandler reset the interrupt flags:

void DMA2_Stream0_IRQHandler()

{

DMA_ClearFlag(DMA2_Stream0, DMA_IT_HT | DMA_IT_TC | DMA_IT_TE);

}

Any ideas as to how to be able to interrupt immediately following the filling of the first buffer?  I'm wondering if I have to use 2 separate streams?  Thanks in advance.
15 REPLIES 15
tomas239955_stm1_st
Associate II
Posted on December 14, 2011 at 20:45

Hi,

If i know right than 

DMA_FIFOThreshold

works only if  

DMA_FIFOMode

is enabled.

Or not?

Regards,

Tomas Kamenicky

markgilson9
Associate II
Posted on December 14, 2011 at 20:53

Ah yes, nice catch.  Not really related to my question, but you're right, the 

DMA_FIFOThreshold_HalfFull

is not necessary.

infoinfo989
Associate III
Posted on December 14, 2011 at 21:03

The DMA will give you an interrupt under the following conditions:

1) When the double-buffer ''rolls over''. When the double-buffer DMA rolls over it will set the TC bit, which will generate an interrupt (assuming you have it enabled, which in your case you do). So, let's be clear here, every time the DMA rolls over, you get an interrupt.

2) When the DMA is turned off. If you turn off the DMA ''manually'', by writing a 0 to its config register, that will cause the TC bit to be set, again generating an interrupt.

You are doing the right thing by clearing the TC bit in your interrupt handler. Just be careful that because your interrupt handler is so short, there is a risk you might get the interrupt twice, because the interrupt clear might not have propagated through to the NVIC by the time your handler exits. Clearing the flags twice is generally enough to allow that extra time for the interrupt to clear out (assuming there's nothing else in your interrupt handler).

If you only want to be interrupted at the end of filling buffer 0 (for example) and not at the end of filling buffer 1, then you can clear the interrupt enable bit in the DMA config register in your interrupt handler. That way you'll only get the interrupt once. Of course, how you then turn that interrupt back on again is a whole other question.

Alternatively, in your interrupt handler, you can simply look at the CT bit in the DMA config register. The CT bit tells you which buffer (0 or 1) is currently being filled, and you can then respond accordingly. When the DMA has finished filling buffer 0, it will flip the CT bit to indicate it's now filling buffer 1, and set the TC bit which will trigger the interrupt. In your handler you can check the CT bit, and if it shows that buffer 1 is currently being filled, you know buffer 0 has just been finished.

http://blog.frankvh.com/2011/08/18/stm32f2xx-dma-controllers/

markgilson9
Associate II
Posted on December 14, 2011 at 21:13

Interesting, so if I understand you correctly, you're saying that the very first interrupt I should get will be when the first buffer is filled, but the second is not.  What I'm trying to say is that when I breakpoint in the DMA2_Stream0_IRQHandler(), I see both buffers filled with data.  Is this due to the DMA continuing to run even after I breakpoint, or is there a setting I'm missing?  Here's essentially what I'd like to happen:

1.) Buffer 0 fills

2.) interrupt occurs (I service Buffer 0)

3.) Buffer 1 fills

4.) interrupt occurs (I service Buffer 1)

5.) repeat 1 through 4

infoinfo989
Associate III
Posted on December 14, 2011 at 22:54

What you want to do is entirely normal and reasonable - it certainly should be possible. So yes, you should receive an interrupt each time the DMA finishes filling a buffer.

Normally the NVIC is configured and its interrupt enabled before the DMA is setup. You appear to be doing the NVIC setup after you've setup the DMA.

Exactly what problem (symptom) are you seeing? Are you getting any interrupts at all?

markgilson9
Associate II
Posted on December 15, 2011 at 14:31

Alright, I think I cleared some issues up by changing what I do in my IRQHandler:

        DMA_ClearFlag(DMA2_Stream0, DMA_IT_TC | DMA_IT_TE);

DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TC | DMA_IT_TE);

/* The check below finds out what buffer we're currently writing to.

  If the DMA is currently writing to buffer 0, then we know we should

  be grabbing data from buffer 1, because it's the one that just finished   */

if (DMA_GetCurrentMemoryTarget(DMA2_Stream0) == 0)

memcpy(&TotalBuf[cnt], ADCBuf[1], 1600);

else

memcpy(&TotalBuf[cnt], ADCBuf[0], 1600);

cnt += 800;

I do see one final issue, and that being that I believe I'm dropping one or two samples at the ''borders'' of the buffers.  In other words, when switching from buffer0 to buffer1 or vice versa, I see that the ADC samples (I'm injecting a sine wave, so I can see it) don't quite align.  So my question is, when I'm in this IRQHandler, does sampling still occur, or is everything ''stopped''?  If sampling is stopped while in this IRQHandler, how can I tell the ADC to continue sampling?

markgilson9
Associate II
Posted on December 15, 2011 at 16:06

Hmmm, interesting...  It seems that when I change my NVIC_IRQChannelPreemptionPriority to 4, the problem disappears.  So I must be pulling the processor off the ADC & DMA tasks while in the IRQHandler().

infoinfo989
Associate III
Posted on December 16, 2011 at 01:56

The ADC should keep sampling while the processor is in the interrupt handler, and similarly the DMA should keep servicing the ADC DMA requests to collect that ADC data and put it into a buffer.

markgilson9
Associate II
Posted on January 24, 2012 at 17:20

Upon further investigation I've found that my IRQ_Handler() function is constantly being called.  I've set my ADC sampling rate to 8000Hz, and created 2 double buffers of size 8000, which should provide 1 second between interrupts.  I've watched the ''current buffer'' bit and notice that it switches every second as it should, but even though I clear my TC and TE bits, I never leave the interrupt handler...it is constantly called.  What am I doing wrong?

DMA_ClearFlag(DMA2_Stream0, DMA_IT_TC | DMA_IT_TE);

DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TC | DMA_IT_TE);