你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32F2xx DMA Controllers

[复制链接]
STMCU小助手 发布时间:2021-12-2 14:47

If you’re moving a bunch of data around, the DMA controllers are going to be your best friends. However they have their quirks and undocumented features. This post will describe what I’ve learned about them, and hopefully others can contribute if they can shed light as well.

Note there is also a Part 2 to this post.

FIFOs

The DMA controller contains a bunch of small FIFOs, one for each stream. You don’t need to use them (well, sometimes you do, but usually it’s optional) however you’d be a bit crazy not to. The FIFOs allow the DMA to function without losing data if something else with a higher priority is using the bus. But the FIFOs do introduce complexities, as we shall soon see.

DMA Configuration Register

A number of bits in the configuration register (eg channel selection) are marked: “These bits are protected and can be written only if EN is ‘0’ “. What does that mean? The register defaults to zero. If I want to set these bits and enable the DMA controller, do I need to perform two writes: one to set these bits while keeping EN = 0. Then a second write which only sets EN = 1?

The short answer is “yes”, you should do two writes to the configuration register to enable the DMA. My experiments seem to indicate that performing a single write the configuration register, setting those bits AND setting EN = 1 in one hit, appears to work. But that’s not guaranteed, and ST tech support recommends performing two writes.

Start Me Up

Configuring the various STM32F2xx DMA registers and setting EN = 1 should cause the DMA to start. Imagine my surprise then, when the DMA worked the first time, but not the second.

From performing some experiments I discovered, and ST subsequently confirmed, that the DMA stream will NOT start up if its interrupt status bits are set. This is not described in the current documentation. Even if you’re not using interrupts, you MUST clear the appropriate bits in the DMA Interrupt Flag Clear register. I don’t know which bit in particular the hardware cares about, so I just clear them all for that stream.

Reading DMA Registers

This wasn’t obvious to me from the documentation: the address registers (M0AR and M1AR) do not change during the course of a DMA transfer. For example, in the documentation, for the configuration register MINC bit, it states:

Memory address pointer is incremented after each data transfer (increment is done according to MSIZE)) W( g* ~3 r4 b+ ]9 e

Certainly sounds like the address register would increment, doesn’t it? In fact they do not. What actually happens is when the DMA starts up (or restarts in the case of circular or double-buffer mode) the address register(s) are copied to internal shadow registers that we lowly programmers cannot see. It’s the shadow address registers which increment during a DMA; not the visible M0AR / M1AR registers. M0AR and M1AR remain unaltered during the DMA.

This is an important point. If you think you can simply read the value of M0AR at the completion of a DMA to determine where the DMA stopped writing, for example, that won’t work. M0AR will still be pointing to the beginning of your data buffer.

NTDR does decrement though – you can read this register (at any time) to see where the DMA controller is.

Circular Mode

The documentation for circular mode is pretty skimpy. It says:

When the circular mode is activated, the number of data items to be transferred is automatically reloaded with the initial value programmed during the stream configuration phase, and the DMA requests continue to be served.
. M9 F8 _+ V9 j, |

This tells us the NDTR register is reloaded once it falls to zero, in the same manner as the double-buffer mode. What about everything else? I asked ST tech support a bunch of questions and here’s what I learned.

M0AR is the address register used for circular mode (M1AR is only used for double-buffer mode and nothing else). When circular mode is enabled, once NDTR counts down to zero, the following takes place:

  • The TCIF flag is set (which can generate an interrupt if enabled)
  • The “M0AR shadow register” is reloaded to its initial (M0AR) value, so the DMA will continue writing at the start of the buffer once again
  • NDTR is reloaded to its initial value
    4 T+ u, L/ Z6 p2 u" B/ m& t8 s

In short, the DMA will keep writing to the same buffer, over and over again, triggering an interrupt each time it fills the buffer, and it’ll do this forever until its disabled (usually by software writing to the DMA configuration register).

As noted before, you’ll never see the value of the M0AR register change so don’t bother trying, however you will see the NDTR register decrement as the DMA proceeds to write the buffer.

Double-Buffer Mode

This is simply a variant of Circular Mode, where two address pointers M0AR and M1AR are used instead of one, and the SMT32F2xx DMA reloads them alternately, each time NDTR counts down to zero. So the DMA fills buffer 0, then buffer 1, then buffer 0, then buffer 1, etc, for example. The really nice thing about this mode is you’re permitted to update the not-currently-in-use address register, on the fly. Which means you can extend this mode to as many buffers as you wish, by changing the inactive register. The CT bit in the DMA configuration register tells you which address register is currently in use; you’re free to update the other register to point to a new buffer.

Burst Mode

It states this in the documentation, but just to re-iterate: If you’re using the DMA in burst mode (for example allow the FIFO to fill with 16 bytes / 4 words, then write the whole FIFO to memory in one burst) which is the most efficient method, be aware that your buffer needs to be aligned on a 1kB address boundary. If its not, if the burst straddles a 1kB boundary, it’ll generate an AHB bus error.

Flow Controller

The documentation is very clear about this, still I’ve seen it pop up on the forums. In almost all cases the DMA will itself be the DMA flow controller. The only possible exception is the STM32F2xx SDIO port (for which you have the option).

STM32F2xx FIFO Flush

I found this topic to be very confusing, primarily because the documentation mentions it very briefly, and that very briefness (is “briefness” even a word?) led to a number of misconceptions which took some time to iron out with ST tech support. Hopefully the following section will help clarify things.

In an “ideal” DMA operation, you would specify the amount of data to be transferred in the NDTR register, specify things like burst mode (or not), FIFO fullness, etc, and start the DMA running. Ideally everything would be a neat multiple of everything else; for example you set a FIFO threshold of “FIFO full” (16 bytes) and your data quantity is a multiple of 16 bytes. The DMA would run to completion, in this case for an integer number of FIFO fills and empties, and life’s good.

As we all know, real life isn’t always this neat and tidy. What happens if you (via software) turn off the DMA controller partway through? In this kind of example the DMA can “terminate” with data remaining in its FIFO. This is the purpose of the FIFO flush.

For starters, it’s worth noting that the DMA will not accept any more data than what the NDTR register specifies for it. For example, if your data source is the DCMI port, which can happily throw data at the DMA all day long, and you specify “18 bytes” for the NDTR register along with a “FIFO full” threshold and a burst mode, the DMA will not take 32 bytes from the DCMI. The DMA will only accept 18 bytes from the DCMI, resulting in one and a quarter FIFO loads (so to speak). When reading out the FIFO, the DMA will do the first 16 bytes as a burst as you specified, and the remaining two bytes as single accesses. Hence the DMA will complete normally, even though the amount of DMA data wasn’t a nice multiple of the FIFO or burst size.

This is an important point because it means the DMA can never overrun the end of your destination data buffer. Even in the event of a FIFO flush, provided your NDTR register is never programmed with a value larger than your buffer size, this specifies the maximum amount of data the DMA will accept from the data source, and hence the maximum amount of data that can be written into the destination buffer.

If software turns off the DMA transfer partway through, there may be data sitting in the DMA FIFO at the moment the DMA is disabled. This is the scenario for a FIFO flush. In this case the DMA will continue emptying the FIFO, writing that data to memory, until the FIFO is empty.

I do not know the value of the NDTR register in this scenario. Is NDTR the value at the moment the DMA stream was turned off, or is NDTR the value after the flush is completed, IE including the amount of data that was written out as a result of the flush? I suspect the latter, but I don’t know for certain.

In any case, it’s important to note that the TCIF flag will be set. If you turn off the DMA and want to know if the flush is complete, all you need do is wait on the TCIF flag becoming set, and at that point you know the DMA stream is finished and idle. Even though you might think the DMA transfer wasn’t complete because you halted it partway through, the DMA controller thinks it was completed and will set its TCIF flag (potentially generating an interrupt if you have that enabled). If you plan on restarting the DMA, to have it continue on from where you stopped it, remember you need to update the M0AR (and/or possibly the M1AR) register, because the address register will still be pointing to the start of your buffer, and not to where the DMA just finished writing to.

That’s it for this post. Those are the things I’ve learnt so far about the SMT32F2xx DMA controllers, as well as any still-open questions. Next post will talk about the STM32F2xx DCMI (Digital Camera Interface) peripheral.

The discussion around the STM32F2xx and STM32F4xx DMA controllers is becoming a bit too large for a single posting, so here we are with Part 2!

Clearing a DMA Interrupt

In theory this is very simple – in practice not so much. The documentation for the “DMA low interrupt flag clear register” (DMA_LIFCR) and the “DMA high interrupt flag clear register” (DMA_HIFCR) shows bits, such as CTCIFx: Stream x clear transfer complete interrupt flag (x = 3..0). The documentation says to write a “1″ to the bit to clear the interrupt flag. That is correct.

However, I was finding that in certain circumstances the interrupt kept on reocurring, immediately, even though I was clearing out the interrupt.

DMA Interrupt Re-entering Reason #1

There is a well-known reason for this. The Cortex-M3 / M4 is a pipelined processor. If you clear the interrupt at the very bottom of your interrupt service routine code, immediately before you exit your interrupt routine, the clearing of the interrupt might not have propagated through the processor before your interrupt routine exited, meaning that the interrupt will be immediately triggered again.

It’s important to ensure the interrupt clear has actually happened before you exit your interrupt code. The easy way to do this is to clear the interrupt at the very top of your interrupt handler, before you do anything else. Then the time taken by executing your interrupt handler code allows lots of time for the interrupt clear to propagate. If this isn’t possible, or if your interrupt handler is extremely short, write to the interrupt clear register, then do a while() loop on the interrupt flag register to wait until the clear happens. Note that if you do this, make sure you put a timeout of some description in your while() loop to prevent a hangup. The interrupt clear should normally take effect very quickly.

DMA Interrupt Re-entering Reason #2

My interrupt re-entering was not caused by #1 above, and I spent a frustrating day hunting down the reason, finally discovering reason #2.

My interrupt handler code looked generally like this:

void DMA2_Stream1_IRQHandler (void){  DMA2->LIFCR = (uint32_t)0x00000F40;      // clear DMA IRQ flags   ... do a bunch of stuff ....   // setup the DMA for a new transfer - DMA currently on so turn it off first  DMA2->S1CR = (uint32_t)0x02025410;      // need to turn off DMA first to change settings  while (DMA2->S1CR & DMA_CR_EN);         // wait until DMA is actually off   DMA2->S1M0AR = ...       DMA2->S1NDTR = ...       ...  DMA2->S1CR = (uint32_t)0x02065510;      // everything setup but not enabled       DMA2->LIFCR = (uint32_t)0x00000F40;     // clear any pending (old) DMA2 Stream 1 interrupts  DMA2->S1CR = (uint32_t)0x02065511;      // everything setup and enabled   }
  g' T; Q8 S6 z, q. Q7 J& a8 a

You can see two interrupt clears taking place. One at the top of the interrupt handler (as it should be), and a second clear shortly before I re-enable the DMA controller, to ensure nothing is pending when the DMA is started up again.

Remember too, that the STM32F2xx / STM32F4xx DMA won’t even start if it has a pending interrupt  so clearing the interrupt flags is not optional – it must be done (even if you’re not using interrupts).

This second clear is not necessarily essential – it’s just for my own peace of mind, to be sure the DMA will start up correctly.

The reason the interrupt immediately re-enters is here:

setup the DMA for a new transfer – DMA currently on so turn it off firstDMA2->S1CR = (uint32_t)0×02025410; // need to turn off DMA first to change settings/ F9 A* k; B2 ~. n

In this example the DMA is currently enabled, due to it being in a double-buffer / circular mode. I turn off the DMA. This results in the DMA TCIF flag being set. Again, so to speak – the TCIF was set earlier which triggered this interrupt, I cleared the flag at the top of the interrupt handler, and now that I’m turning off the DMA the TCIF flag has become set a second time. In the DMA configuration register bit 4, “TCIE: Transfer complete interrupt enable” is set, meaning that when the TCIF flag becomes set, it’s passed on to the NVIC (the Cortex-M3 / M4 Nested Vectored Interrupt Controller).

Hence at this point, even though the STM32F2xx / STM32F4xx DMA has been turned off, the simple act of turning it off has caused a new DMA interrupt to be pending in the NVIC. Because this particular code is in an interrupt service routine, this pending interrupt hasn’t executed yet, but it will the moment we exit our interrupt handler. This is the source of the endlessly reoccuring DMA interrupt.

There are a few possible solutions to this problem. Here’s one:

void DMA2_Stream1_IRQHandler (void){  DMA2->LIFCR = (uint32_t)0x00000F40;      // clear DMA IRQ flags   ... do a bunch of stuff ....   // setup the DMA for a new transfer - DMA currently on so turn it off first  DMA2->S1CR = (uint32_t)0x02025410;      // need to turn off DMA first to change settings  while (DMA2->S1CR & DMA_CR_EN);         // wait until DMA is actually off   NVIC_ClearPendingIRQ (DMA2_Stream1_IRQn);    // clear pending DMA IRQ from the NVIC   DMA2->S1M0AR = ...       DMA2->S1NDTR = ...       ...  DMA2->S1CR = (uint32_t)0x02065510;      // everything setup but not enabled       DMA2->LIFCR = (uint32_t)0x00000F40;     // clear any pending (old) DMA2 Stream 1 interrupts  DMA2->S1CR = (uint32_t)0x02065511;      // everything setup and enabled   }
8 u0 ^4 A# L# a9 R# R. g

Using the NVIC_ClearPendingIRQ() function works in this example because we’re in an interrupt service routine, so the new interrupt is pending but hasn’t had the opportunity to execute yet. Using NVIC_ClearPendingIRQ() in non-interrupt code would not work, because the interrupt will trigger the moment the DMA is turned off. In non-interrupt code (ie your regular main code) you need to prevent the interrupt from reaching the NVIC to begin with. Which would mean making sure bit 4 (TCIE: Transfer complete interrupt enable) in the DMA configuration register is clear (zero) before turning off the DMA. Or disabling the DMA interrupt in the NVIC before turning off the DMA. For example:

NVIC_DisableIRQ (DMA2_Stream1_IRQn);
1 _3 f. V0 y( ~) `1 z$ R

(I personally haven’t tried this one though, because NVIC_ClearPendingIRQ() worked in my case.)

STM32F DMA interrupts can be a source of great confusion – hopefully this post has clarified things a little.

2 RESPONSES
  • Bernard

    I’m not sure you got the correct explanation with the pipeline thing: the pipeline feeds data or instruction to the MCU, however the interrupts are signaled by the peripherals and don’t go in the pipeline, as I don’t think that write ops to peripherals are cached, this would make writing drivers impossible. IMHO the multiple interrupt problems happens because as soon as an interrupt is disabled, if a new interrupt condition occurs, then the pending interrupt bit is set. As soon as the interrupt can be processed again (ISR has been exited or interrupt is re-enabled), if the pending bit is set, then the ISR fires again. So yes the solution means clearing the pending interrupt bit, but when to do this is particularly important, I think it must be done before processing all the interrupt conditions in a driver and not later, otherwise you may miss an interrupt.

  • Eric

    The problem with re-entering interrupt reason 1 when the clear interrupt is at the end of interrupt is not cause by the pipeline. With the pipeline, the processor fetch then decode next instructions before executing one, but the execution of next instruction always take place after the previous instruction. If you look at assembly code, the end of interrupt is STR Rx,[Rx] to clear the interrupt and next BX LR to exit interrupt routine. The problem is cause by the write buffer of Cortex-M. For each STR instruction, the core put address and data in a buffer and then really do the store in parallel with the execution of next instruction. So the BX LR to exit is executed BEFORE the store to the DMA register is terminated. So the interrupt is not clear when the core re-enable interrupt priority. Reading DMA register stall the core because bus is busy by the store. There is another better solution. It is DSB instruction. This instruction wait for all the stores are finished before executing next instruction. It is more efficeint than reading DMA register. In compiler there is an intrinsic function __DSB() to generate a DSB instruction.


    7 l6 W6 B, h; a0 ^# o" t1 I
% |+ \9 e6 V8 s" P) ]& p
收藏 评论0 发布时间:2021-12-2 14:47

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32Cube扩展软件包
意法半导体边缘AI套件
ST - 理想汽车豪华SUV案例
ST意法半导体智能家居案例
STM32 ARM Cortex 32位微控制器
关注我们
st-img 微信公众号
st-img 手机版