Frank's Random Wanderings

STM32F2xx DMA Controllers

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)

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.

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

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.

5 thoughts on “STM32F2xx DMA Controllers

  1. frank Post author

    Although I’m not 100% certain of your question, perhaps this will help. From what I understand, the registers we can read & write are NOT actually the DMA registers. When a DMA transfer is started, the programmer-accessible DMA registers are copied to background “shadow” DMA registers, and it’s those shadow registers which are actually used by the DMA controller. Which means you can’t really change the operation of the DMA while it’s running. If you want to change things, you need to stop the DMA, re-program the various registers to your new configuration, and then start it.

    This was a huge frustration to me when I was dealing with the problem of the DMA stopping in mid-operation (the silicon bug). I couldn’t simply re-start the DMA because although the shadow registers knew the current values of the address pointers (where the DMA was reading and writing data from when it abruptly stopped), the programmer-accessible registers did not – they still contained the values I had initially programmed them with. Consequently it was quite difficult to work out exactly where the DMA had stopped, and hence difficult to know exactly what needed to be done to restart it so it could finish.

    That aside, you certainly can do variable-length DMA transfers. I had been. As you suggest, you just need to set a new NDTR value before you start the next transfer.

  2. Angel G.

    I’d like to use DMA for variable-length transfers. For example, writing messages to the usart.
    This post makes me wonder what if in the “transfer complete” IRQ handler (circular mode), I try to change the M0AR to point to the “current” position of my DMAbuffer and set NDTR = Min(buffer_end-M0AR, bytes_to_transfer).
    Will it transfer new_NDTR_value bytes, starting from the new M0AR – as I want, or it will do someting I can’t imagine ?

  3. Kenny Koller

    Thanks for the post.

    I’m using DMA with I2C. I2C only makes DMA requests during the data transfer portion.

    I have a situation where I try to write a single byte to a slave but the slave doesn’t ACK the address. The slave is intentionally absent as this is a test so I don’t expect it to ACK.

    The error handler is called and I software reset the I2C bus and reconfigure it. I think try to write a single byte to another slave that does exist. I get an interrupt for START and one for ADDRESS. The slave ACKs but data is never transferred (nothing on the scope. SCL high SDA low).

    I’m not sure what it doesn’t start but when I get the START event in the interrupt the DMA controller has become disabled.

    So I’m stumped. But also I’m wondering how to flush that first byte out of the FIFO when I2C is the one making the requests.

Leave a Reply

Your email address will not be published. Required fields are marked *