otr_logo_grey OntarioTechRacing.github.io

STM32 DMA

What is DMA?

DMA stands for Direct Memory Access. It allows for efficient data transfer between peripherals and memory without involving the CPU. It essentially offloads data transfer tasks from the CPU, freeing up resources.

Here's a simple explanation of how DMA works with STM32:

  1. Instead of the CPU managing data transfer between peripherals (like ADC, USART, or SPI) and memory, the DMA controller takes over this task.

  2. You configure the DMA controller to specify the source (peripheral) and destination (memory) addresses and the amount of data to transfer.

  3. When a trigger condition is met (e.g., data ready from a sensor), the DMA controller automatically moves the data from the peripheral to the memory or vice versa.

.ioc Configuration

  1. Peripheral Configuration:

    Configure your GPIO pins for the specific peripheral (e.g., ADC, USART, SPI) you want to use with DMA.

  2. Configure the DMA:

    Open the "DMA" panel under "System Core". Click "Add" and select the target peripheral. After adding the peripheral to DMA you will be able to configure multiple important parameters:

    1. Mode: Normal or Circular
      • Normal: DMA transfers the specified number of data items from the source to the destination. Once the transfer is complete, the DMA will interrupt (if interrupts are enabled) and the transfer will stop. You would typically use normal mode if you know how much data you want to transfer, and you only want to do it once, or if you want to be notified when a specific number of data items has been transferred.
      • Circular Mode: DMA automatically wraps around to the beginning when it reaches the end of the data. This mode effectively creates a continuous loop of data transfers / flow of data. Once the transfer of the specified number of data items is complete, instead of stopping, the DMA will start over from the beginning without any intervention from the CPU. Circular mode is useful for applications like streaming data where you want a continuous flow of data (e.g., audio streaming, PWM waveform generation).
    2. Direction: peripheral-to-memory or memory-to-peripheral
    3. Data width: Byte, Half Word or Word
  3. Configure Memory-to-Memory Transfer (Optional):

    If you want to perform memory-to-memory transfers, you can configure a memory-to-memory DMA channel. This is useful for tasks like memory copying.

  4. Assign DMA Channels and Streams (Optional):

    In the "Project Manager" tab, go to "Middleware & Hardware Abstraction Layer (HAL)" > "DMA Settings." Assign the available DMA channels and streams to specific peripherals and memory locations. This step specifies which DMA channel/stream is associated with a particular peripheral.

Code

  1. Define the source and destination buffers

    uint32_t sourceBuffer[10];
    uint32_t destinationBuffer[10];
    
  2. Function to configure and start a DMA transfer (Automatically generated by STM32CubeMX)

    // Initialize the HAL DMA handle
    DMA_HandleTypeDef dmaHandle;
    
    // Configure the DMA handle with the desired settings
    dmaHandle.Instance = DMA1_Stream0;  // Replace with your specific stream
    dmaHandle.Init.Channel = DMA_CHANNEL_0;  // Replace with the appropriate channel
    dmaHandle.Init.Direction = DMA_MEMORY_TO_MEMORY;  // Set to DMA_PERIPH_TO_MEMORY if needed
    dmaHandle.Init.PeriphInc = DMA_PINC_DISABLE;
    dmaHandle.Init.MemInc = DMA_MINC_ENABLE;
    dmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
    dmaHandle.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
    dmaHandle.Init.Mode = DMA_NORMAL;
    dmaHandle.Init.Priority = DMA_PRIORITY_HIGH;
    
    // Initialize the source and destination addresses
    dmaHandle.Instance->M0AR = (uint32_t)sourceBuffer;
    dmaHandle.Instance->PAR = (uint32_t)destinationBuffer;
    dmaHandle.Init.PeriphInc = DMA_PINC_ENABLE; // Enable if using peripheral-to-memory
    
    // Initialize and start the DMA transfer
    HAL_DMA_Init(&dmaHandle);
    HAL_DMA_Start(&dmaHandle, (uint32_t)sourceBuffer, (uint32_t)destinationBuffer, sizeof(sourceBuffer) / sizeof(sourceBuffer[0]));
    
    // Wait for the DMA transfer to complete (optional)
    HAL_DMA_PollForTransfer(&dmaHandle, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY);
    
  3. Main loop access

    int main(void) {
        // Initialize HAL and system clock
        
        // Configure and start the DMA transfer
        startDMA();
        
        // Your main application code here
        
        while (1) {
            // Your application code
        }
    }