DMA Support — Details


#include <cyg/hal/var_dma.h>

pin = CYGHWR_HAL_STM32_DMA( ctlr , stream , chan , mode );

pin = CYGHWR_HAL_STM32_DMA( dmareq_id , dmamux_channel , mode );

hal_stm32_dma_init ( hal_stm32_dma_stream *stream , int priority );

hal_stm32_dma_delete ( hal_stm32_dma_stream *stream );

hal_stm32_dma_disable ( hal_stm32_dma_stream *stream );

hal_stm32_dma_configure ( hal_stm32_dma_stream *stream , int tfr_size , cyg_bool no_minc , cyg_bool polled );

hal_stm32_dma_configure_circular ( hal_stm32_dma_stream *stream , cyg_bool enable );

hal_stm32_dma_configure_doublebuffer ( hal_stm32_dma_stream *stream , cyg_bool enable , void *memory1 );

hal_stm32_dma_configure_flow ( hal_stm32_dma_stream *stream , cyg_bool enable );

hal_stm32_dma_start ( hal_stm32_dma_stream *stream , void *memory , CYG_ADDRESS peripheral , cyg_uint32 size );

hal_stm32_dma_stop ( hal_stm32_dma_stream *stream );

hal_stm32_dma_poll ( hal_stm32_dma_stream *stream );


The HAL provides support for access to the DMA controllers. This support is not intended to expose the full functionality of these devices and is mainly limited to supporting peripheral DMA, currently ADC, SPI, I²C and MMC/SD.

The user is referred to the ST documentation for a full description of the DMA devices, and to the sources of the ADC, I²C, SPI and MMC/SD drivers for examples of the use of this API. This documentation only gives a brief description of the functions available.

A DMA stream is defined by a controller number (0 or 1), a stream number (0 to 8), a channel number (0 to 7) and a mode defining transfer direction. The macro CYGHWR_HAL_STM32_DMA() combines these into a 32-bit descriptor that may be stored with a device driver and used to initialize the stream.

NOTE: the DMA terminology has changed between F1 and F2/F4 versions of the STM32 family. In F1 devices each DMA controller has a number of channels, each of which can be driven by a subset of the on-chip devices; there is no way to select which device drives the channel, and care must be taken to allocate channels so that devices don't trigger the wrong channel. In F2/F4 devices each DMA controller has a number of streams; each stream can explicitly select one of a number of driving devices by means of a channel selection field in a control register. F1 channels and F2/F4 streams are essentially the same thing, and the problems of false triggering in F1 devices is solved in F2/F4 by adding the explicit channel selection. To make things more complicated, F1 channels are numbered from one while F2/F4 streams are numbered from zero. HAL support for DMA largely follows the F2/F4 terminology, but the original channel numbering for the F1 is preserved when defining channels.

The following examples show how definitions should be made:

    // F1 definition: controller 1, channel 6, memory-to-peripheral
    #define CYGHWR_HAL_STM32_I2C1_DMA_TX            CYGHWR_HAL_STM32_DMA( 1, 6, 0, M2P )

    // F2/F4 definition: controller 2, stream 0, channel 3, peripheral-to-memory
    #define CYGHWR_HAL_STM32_SPI1_DMA_RX            CYGHWR_HAL_STM32_DMA( 2, 0, 3, P2M )

The special manifest CYGHWR_HAL_STM32_DMA_NONE can be used when the code does not require DMA support for a specific stream. For example:

    // I2C2 RX should NOT use DMA
    #define CYGHWR_HAL_STM32_I2C2_DMA_RX            CYGHWR_HAL_STM32_DMA_NONE

Not all device drivers support the ability of using CYGHWR_HAL_STM32_DMA_NONE to disable DMA use for specific stream mappings. For example, the STM32 I²C driver does allow for individual streams to be configured for interrupt-driven or DMA transfers as required.

Some later variants such as the L4+ or H7 implement a DMA multipexor which routes peripheral DMA requests to DMA controller channels. This means that the way in which DMA channels are associated with peripherals is somewhat different and DMA descriptors have a different format. Instead of selecting a DMA controller and channel, a DMAMUX channel is selected which automatically selects the DMA controller and channel. The following examples show how these descriptors are initialized:

// L4+
// I2C1 RX uses DMAMUX channel 0, which maps to DMAC 1, channel 0, peripheral-to-memory

// H7
// SPI5 TX uses DMAMUX channel 10, which maps to DMAC 2, channel 3, memory-to-peripheral

Before use a DMA stream must be initialized. This is done by calling hal_stm32_dma_init(). The first argument to this is a hal_stm32_dma_stream structure in which the desc field should have been initialized to a DMA descriptor; the callback field set to a callback function; and the data field set to any user defined data. The priority argument defines both the interrupt level assigned to the stream interrupt, and the DMA channel arbitration priority level (defined by the top two bits). This function initializes the hardware and the stream structure and needs only to be called once during driver initialization.

By default a stream is initialized to perform 8 bit transfers under interrupt control and to advance the memory address pointer. If a different configuration is required, then the driver should call hal_stm32_dma_configure() which will allow these options to be varied. The tfr_size argument defines the transfer size and may be 8, 16 or 32 bits. The no_minc argument disables memory increments if true. The polled argument configures the stream for polled mode if true, otherwise it will be interrupt driven. This function may either be called once to set up the stream permanently, or on a transfer-by-transfer basis, or not at all if the defaults are what is required.

If the driver needs circular mode DMA processing then it can call the function hal_stm32_dma_configure_circular() with enable set to true. This allows circular buffers and continuous data flows (e.g. ADC scan mode as used by the STM32F ADC driver). Calling the function with enable set to false will disable circular mode.

If a F2/F4 driver wants to use the continuous double-buffer support then it can call the hal_stm32_dma_configure_doublebuffer() with enable set to true. This configures the DMA to automatically switch between buffers at the end of a transfer. The memory1 is the second buffer to be used in conjunction with the buffer passed as the memory parameter to the hal_stm32_dma_start() function.


The second buffer must be at least the same size as the (first) buffer subsequently passed to the hal_stm32_dma_start() function.

Calling the function with enable set to false will disable the double buffer mode, with the passed memory1 parameter being ignored.


The DMA controller hardware when using double buffer mode will automatically switch buffers on a buffer fill event.

When using double buffer mode the developer should ensure that the buffer size used is large enough to cope with the processing code associated with a completed transaction being able to complete to avoid overrun. The size of the buffers will depend on the DMA transfer rate for the peripheral being used and the application DSR latency, plus the actual callback buffer processing code time.

If a F2/F4 driver needs to use peripheral controlled DMA flow then it can call the function hal_stm32_dma_configure_flow() with enable set to true. This configures the DMA to allow the peripheral to control the flow of DMA transfers, instead of the DMA controller (e.g. the STM32 SDIO device signals the end of data transfers). Calling the function with enable set to false will disable the peripheral flow control mode.

A transfer is defined and started by calling hal_stm32_dma_start(). The memory argument defines the memory address to/from which the transfer will be made. The peripheral argument is the address of the data register of the peripheral involved. The size argument defines the number of data items to be transferred, or in the case of peripheral flow control configurations the number of items expected, as defined by tfr_size. Once this call completes, the channel is operational and will transfer data once the peripheral starts triggering transfers.

If the stream is configured for interrupt control then when a transfer completes an interrupt is raised. This will disable the stream and cause the callback in the stream structure to be called from DSR mode. The prototype of the callback is as follows:

    typedef void hal_stm32_dma_callback( hal_stm32_dma_stream *stream, cyg_uint32 count, CYG_ADDRWORD data );

The stream argument is the stream structure initialized by the user. The count argument is a count of the number of data items that remain to be transferred, and will be zero for a successful transfer. The data argument is a copy of the data field from the stream structure.

The configuration option CYGIMP_HAL_STM32_DMA_CALLBACK_ISR can be used to enable the optional support for an ISR level callback when the DMA TransferComplete (TC) interrupt is triggered. This support is not normally required, but some STM32 family variants may require some collusion between the DMA system and the peripheral H/W controller to ensure correct operation. As such it is not expected that the developer should ever need to manually enable CYGIMP_HAL_STM32_DMA_CALLBACK_ISR since it will be automatically enabled if any of the configured packages require the functionality. The callback is executed within the ISR of the DMA TC processing and so should not block.

    typedef void hal_stm32_dma_callback_isr( hal_stm32_dma_stream *stream, CYG_ADDRWORD data );

As with the normal DSR callback the stream argument is the stream structure initialized by the user and the data argument is a copy of the data field from the stream structure.

If the stream is configured for polled mode, then the driver must call hal_stm32_dma_poll() frequently. When the transfer has completed the callback function will be called from within the poll routine. The driver needs to detect this and terminate the polling loop.

Most drivers will initialize a DMA stream and keep it enabled throughout the system lifetime. However, if it is necessary to share a stream, or otherwise disable use of a stream, the driver may call hal_stm32_dma_delete() to return a stream to unused state. It will be necessary to call hal_stm32_dma_init() before it can be used again.

Alternatively for circular mode configured streams the hal_stm32_dma_disable() can be used to disable the stream DMA without clearing the state. The function hal_stm32_dma_start() can then be used to re-enable the DMA stream with the previous configured state.

The hal_stm32_dma_stop() function allows a stream to be disabled without clearing the transfer state. The normal callback handler is subsequently called with a non-zero count indicating a partial transfer.


The hal_stm32_dma_stop() function will return immediately, however the stream may remain active until any active “item” transfer has completed (not the full size amount). When appropriate the stream will be disabled by the DMA controller and the relevant callback handler function called asynchronously.