Skip to content

Bare metal C Ch17 DMA

Hongjayeong-02 edited this page Sep 21, 2026 · 2 revisions

Ch17. Direct Memory Access (DMA)

한 줄 요약: DMA2 Stream0의 Memory-to-Memory mode로 uint16_t 배열을 CPU 복사 loop 없이 다른 배열로 전송하고, 완료 interrupt를 처리한다.

1. 핵심 이론

DMA는 CPU 대신 data movement를 담당하는 hardware block이다.

CPU
 └─ source / destination / length 설정
        ↓
       DMA
        ↓
 data transfer 수행
        ↓
 interrupt로 완료 통보

전송 방향

  • Peripheral → Memory

  • Memory → Peripheral

  • Memory → Memory

이번 실습은 외부 장치 없이 검증 가능한 Memory-to-Memory를 사용한다.

핵심 register

Register | 역할 -- | -- DMA2_Stream0->CR | enable, direction, increment, size, interrupt NDTR | 전송 개수 PAR | source address로 사용 M0AR | destination address FCR | FIFO DMA2->LISR | status flags DMA2->LIFCR | flag clear

Increment

배열을 순차 복사하므로 source와 destination 모두 address increment를 사용한다.

MINC = 1
PINC = 1

ISR 공유 변수

g_transfer_cmplt는 ISR이 변경하고 main loop가 확인하므로 실제 코드에서도 volatile로 선언되어 있다.

2. 동작 흐름

sensor_data_arr[]
       │
       ▼
 DMA2 Stream0
       │
       ├─ 16-bit width
       ├─ source increment
       ├─ destination increment
       └─ length = 5
       │
       ▼
temp_data_arr[]
       │
       ▼
Transfer Complete
       │
       ▼
DMA2_Stream0_IRQHandler
       │
       ▼
g_transfer_cmplt = 1
       │
       ▼
UART로 결과 출력

3. 실제 프로젝트 구조


Ch17_dma/

├── Inc/dma.h

├── Src/dma.c

├── Inc/uart.h

├── Src/uart.c

├── Src/main.c

syscalls.c, sysmem.c, startup file, linker script, CubeIDE metadata/build output은 Ch04에서 다룬 공통 기반이므로 이 페이지에서는 반복하지 않는다. 아래에는 이 chapter 기능 구현에 직접 사용한 source/header를 전부 기록한다.

4. 전체 코드

Inc/dma.h

#ifndef DMA_H__
#define DMA_H__

#include <stdint.h> #include "stm32f4xx.h"

#define LISR_TCIF0 (1U << 5) #define LIFCR_CTCIF0 (1U << 5) #define LISR_TEIF0 (1U << 3) #define LIFCR_CTEIF0 (1U << 3)

void dma2_mem2mem_config(void); void dma_transfer_start(uint32_t src_buff, uint32_t dest_buff, uint32_t len);

#endif

Src/dma.c

#include "dma.h"

#define DMA2EN (1U << 22) #define DMA_SCR_EN (1U << 0) #define DMA_SCR_MINC (1U << 10) #define DMA_SCR_PINC (1U << 9) #define DMA_SCR_TCIE (1U << 4) #define DMA_SCR_TEIE (1U << 2) #define DMA_SFCR_DMDIS (1U << 2)

void dma2_mem2mem_config(void) { RCC->AHB1ENR |= DMA2EN;

DMA2_Stream0-&gt;CR = 0;
while ((DMA2_Stream0-&gt;CR &amp; DMA_SCR_EN)) {}

DMA2_Stream0-&gt;CR |= (1U &lt;&lt; 13);
DMA2_Stream0-&gt;CR &amp;= ~(1U &lt;&lt; 14);

DMA2_Stream0-&gt;CR |= (1U &lt;&lt; 11);
DMA2_Stream0-&gt;CR &amp;= ~(1U &lt;&lt; 12);

DMA2_Stream0-&gt;CR |= DMA_SCR_MINC;
DMA2_Stream0-&gt;CR |= DMA_SCR_PINC;

DMA2_Stream0-&gt;CR &amp;= ~(1U &lt;&lt; 6);
DMA2_Stream0-&gt;CR |= (1U &lt;&lt; 7);

DMA2_Stream0-&gt;CR |= DMA_SCR_TCIE;
DMA2_Stream0-&gt;CR |= DMA_SCR_TEIE;

DMA2_Stream0-&gt;FCR |= DMA_SFCR_DMDIS;
DMA2_Stream0-&gt;FCR |= (1U &lt;&lt; 0);
DMA2_Stream0-&gt;FCR |= (1U &lt;&lt; 1);

NVIC_EnableIRQ(DMA2_Stream0_IRQn);

}

void dma_transfer_start(uint32_t src_buff, uint32_t dest_buff, uint32_t len) { DMA2_Stream0->PAR = src_buff; DMA2_Stream0->M0AR = dest_buff; DMA2_Stream0->NDTR = len; DMA2_Stream0->CR |= DMA_SCR_EN; }

Inc/uart.h

#ifndef UART_H
#define UART_H

#include "stm32f4xx.h"

void uart_init(void); void uart_write_string(const char *s); int __io_putchar(int ch);

#endif

Src/uart.c

#include <stdint.h>
#include "uart.h"

#define GPIODEN (1U << 3) #define USART3EN (1U << 18)

#define DBG_UART_BAUDRATE 115200U #define SYS_FREQ 16000000U #define APB1_CLK SYS_FREQ

#define CR1_TE (1U << 3) #define CR1_UE (1U << 13) #define SR_TXE (1U << 7)

static void uart_write(int ch); static uint16_t compute_uart_bd(uint32_t periph_clk, uint32_t baudrate); static void uart_set_baudrate(uint32_t periph_clk, uint32_t baudrate);

int __io_putchar(int ch) { uart_write(ch); return ch; }

void uart_init(void) { RCC->AHB1ENR |= GPIODEN;

GPIOD-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (8 * 2));
GPIOD-&gt;MODER |=  (0x2UL &lt;&lt; (8 * 2));
GPIOD-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (9 * 2));
GPIOD-&gt;MODER |=  (0x2UL &lt;&lt; (9 * 2));

GPIOD-&gt;AFR[1] &amp;= ~(0xFUL &lt;&lt; 0);
GPIOD-&gt;AFR[1] |=  (0x7UL &lt;&lt; 0);   /* PD8 -&gt; AF7 (USART3_TX) */
GPIOD-&gt;AFR[1] &amp;= ~(0xFUL &lt;&lt; 4);
GPIOD-&gt;AFR[1] |=  (0x7UL &lt;&lt; 4);   /* PD9 -&gt; AF7 (USART3_RX) */

RCC-&gt;APB1ENR |= USART3EN;

uart_set_baudrate(APB1_CLK, DBG_UART_BAUDRATE);

USART3-&gt;CR1 = CR1_TE;
USART3-&gt;CR1 |= CR1_UE;

}

static void uart_write(int ch) { while (!(USART3->SR & SR_TXE)) { } USART3->DR = (ch & 0xFF); }

void uart_write_string(const char *s) { while (*s) uart_write((int)*s++); }

static uint16_t compute_uart_bd(uint32_t periph_clk, uint32_t baudrate) { return (uint16_t)((periph_clk + (baudrate / 2U)) / baudrate); }

static void uart_set_baudrate(uint32_t periph_clk, uint32_t baudrate) { USART3->BRR = compute_uart_bd(periph_clk, baudrate); }

Src/main.c

#include <stdio.h>
#include "dma.h"

extern void uart_init(void);

#define BUFFER_SIZE 5

uint16_t sensor_data_arr[BUFFER_SIZE] = { 892, 731, 1234, 90, 23 }; uint16_t temp_data_arr[BUFFER_SIZE]; volatile uint8_t g_transfer_cmplt;

int main(void) { g_transfer_cmplt = 0;

uart_init();
dma2_mem2mem_config();

dma_transfer_start((uint32_t)sensor_data_arr, (uint32_t)temp_data_arr, BUFFER_SIZE);

while (!g_transfer_cmplt) {}

for (int i = 0; i &lt; BUFFER_SIZE; i++)
{
    printf("Temp buffer[%d]: %d\r\n", i, temp_data_arr[i]);
}

g_transfer_cmplt = 0;

while (1) {}

}

void DMA2_Stream0_IRQHandler(void) { if ((DMA2->LISR) & LISR_TCIF0) { g_transfer_cmplt = 1; DMA2->LIFCR |= LIFCR_CTCIF0; }

if ((DMA2-&gt;LISR) &amp; LISR_TEIF0)
{
    DMA2-&gt;LIFCR |= LIFCR_CTEIF0;
}

}

5. 실습 결과

Notion 기록 기준 실제 보드 검증 완료.

Source:

892, 731, 1234, 90, 23

UART 출력에서 destination buffer가 동일하게 복사된 것을 확인했다.

Temp buffer[0]: 892
...
Temp buffer[4]: 23
text = 1640 bytes
data = 12 bytes
bss  = 2572 bytes
warning 없음

Notion에서는 ADC DMA와 UART DMA는 개념만 정리하고 이 프로젝트에서는 구현하지 않았다고 명시했다.

6. 핵심 정리

  • DMA는 CPU 대신 data movement를 수행한다.

  • 이번 실습은 DMA2 Stream0 Memory-to-Memory transfer다.

  • 전송 완료/에러 flag는 ISR에서 각각 처리한다.

Clone this wiki locally