CPU를 거치지 않고 주변장치와 메모리가 직접 데이터를 주고받는 기능 (Direct Memory Access).
무엇을 해결하나
CPU가 직접 옮기는 경우 (polling) for (i = 0; i < 1000; i++) {
-
while (!(UART->SR & TXE)) { } // 준비될 때까지 대기
-
UART->DR = buf[i]; }
-
CPU가 1000번 반복하며 대부분 기다리기만 한다
DMA DMA_Start(buf, &UART->DR, 1000);
- 설정만 하고 CPU는 다른 일을 한다. 끝나면 인터럽트로 알려 준다
CPU 사이클을 데이터 복사에 쓰지 않는 것이 핵심 이득이다.
구성 요소
- 소스 주소 — : 어디서 (메모리 또는 주변장치 레지스터)
- 목적지 주소 — : 어디로
- 전송 크기 — : 몇 개
- 데이터 폭 — : 바이트/하프워드/워드
- 주소 증가 — : 소스·목적지 각각 증가할지 고정할지
- 트리거 — : 무엇이 전송을 유발하나 (UART 수신, 타이머, 소프트웨어)
- 모드 — : 일반(1회) / 순환(circular)
주소 증가 설정이 중요하다. 메모리 → UART : 소스 증가 O, 목적지 증가 X (레지스터는 고정) 메모리 → 메모리 : 둘 다 증가
- ADC → 메모리 — : 소스 고정, 목적지 증가
순환 모드 — 임베디드에서 매우 유용
// ADC 값을 링 버퍼에 계속 채운다. 끝에 도달하면 처음으로
HAL_ADC_Start_DMA(&hadc, buf, 128); // circular 설정 시
CPU 개입 없이 무한히 데이터가 수집된다. 절반 완료(half-transfer)와 완료 인터럽트를 이용하면 한쪽을 처리하는 동안 다른 쪽을 채우는 더블 버퍼링이 된다.
[───── 앞 절반 ─────][───── 뒤 절반 ─────]
↑ DMA가 채우는 중 ↑ CPU가 처리 중
오디오·ADC 스트리밍의 표준 패턴이다.
반드시 알아야 할 함정
① 버스 경합 — CPU가 느려진다
DMA와 CPU가 같은 버스·같은 메모리를 쓰면 서로 기다린다. "DMA를 켰더니 오히려 전체가 느려졌다"는 경우가 있다.
→ 서로 다른 SRAM 블록이나 다른 버스 매트릭스 경로를 쓰도록 배치한다.
② 캐시 일관성 (Cortex-M7 등)
송신 : CPU가 캐시에만 쓴 데이터를 DMA가 못 본다 → Clean 필요 수신 : DMA가 메모리에 쓴 데이터를 CPU가 캐시에서 옛것을 읽는다 → Invalidate 필요
③ 접근 불가 영역
- CCM(Core Coupled Memory)에 버퍼를 두면 DMA가 접근하지 못한다
- 전송이 아예 안 되거나 버스 폴트 "DMA가 동작하지 않는다"의 가장 흔한 원인 중 하나다. 링커 스크립트에서 버퍼 배치를 확인해야 한다.
④ 버퍼 수명
void send(void) {
uint8_t buf[64]; // ✗ 지역 변수 — 함수를 나가면 스택이 재사용된다
HAL_UART_Transmit_DMA(&h, buf, 64);
} // DMA는 아직 전송 중인데 buf 영역이 다른 데이터로 덮인다
static 또는 전역으로 두어야 한다.
⑤ volatile
DMA가 채우는 버퍼는 CPU가 모르게 바뀌므로 volatile이 필요하다.
다만 큰 배열 전체를 volatile로 하면 최적화가 다 막히므로,
완료 플래그만 volatile로 두고 완료 후 일반 접근하는 편이 낫다.
인터럽트와의 관계 — 비교
| 인터럽트 방식 | DMA | |
|---|---|---|
| 바이트당 CPU 개입 | 매번 | 없음 |
| 오버헤드 | 진입·복귀 비용 × 개수 | 설정 1회 + 완료 1회 |
| 적합 | 소량, 불규칙 | 대량, 연속 |
| 지연 | 낮음 | 설정 비용이 있어 소량엔 손해 |
한두 바이트 전송에 DMA를 쓰면 설정 비용이 더 크다. UART로 1바이트씩 명령을 받는다면 인터럽트가 낫고, 1KB 로그를 보낸다면 DMA가 낫다.
디버깅
- 전송이 안 된다 — → 클럭 인가? 스트림/채널 매핑이 맞나? 버퍼가 접근 가능 영역인가?
- 값이 깨진다 — → 캐시? 정렬? 데이터 폭 설정?
- 중간에 멈춘다 — → 전송 에러 인터럽트(TE) 확인. 버스 폴트일 수 있다 DMA 상태 레지스터의 에러 플래그를 반드시 확인하는 습관이 중요하다.