Repository navigation
Bare metal C Ch17 DMA
한 줄 요약: DMA2 Stream0의 Memory-to-Memory mode로 uint16_t 배열을 CPU 복사 loop 없이 다른 배열로 전송하고, 완료 interrupt를 처리한다.
DMA는 CPU 대신 data movement를 담당하는 hardware block이다.
CPU
└─ source / destination / length 설정
↓
DMA
↓
data transfer 수행
↓
interrupt로 완료 통보
Peripheral → Memory
Memory → Peripheral
Memory → Memory
이번 실습은 외부 장치 없이 검증 가능한 Memory-to-Memory를 사용한다.
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배열을 순차 복사하므로 source와 destination 모두 address increment를 사용한다.
MINC = 1
PINC = 1
g_transfer_cmplt는 ISR이 변경하고 main loop가 확인하므로 실제 코드에서도 volatile로 선언되어 있다.
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로 결과 출력
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를 전부 기록한다.
#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
#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->CR = 0;
while ((DMA2_Stream0->CR & DMA_SCR_EN)) {}
DMA2_Stream0->CR |= (1U << 13);
DMA2_Stream0->CR &= ~(1U << 14);
DMA2_Stream0->CR |= (1U << 11);
DMA2_Stream0->CR &= ~(1U << 12);
DMA2_Stream0->CR |= DMA_SCR_MINC;
DMA2_Stream0->CR |= DMA_SCR_PINC;
DMA2_Stream0->CR &= ~(1U << 6);
DMA2_Stream0->CR |= (1U << 7);
DMA2_Stream0->CR |= DMA_SCR_TCIE;
DMA2_Stream0->CR |= DMA_SCR_TEIE;
DMA2_Stream0->FCR |= DMA_SFCR_DMDIS;
DMA2_Stream0->FCR |= (1U << 0);
DMA2_Stream0->FCR |= (1U << 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;
}
#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
#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->MODER &= ~(0x3UL << (8 * 2));
GPIOD->MODER |= (0x2UL << (8 * 2));
GPIOD->MODER &= ~(0x3UL << (9 * 2));
GPIOD->MODER |= (0x2UL << (9 * 2));
GPIOD->AFR[1] &= ~(0xFUL << 0);
GPIOD->AFR[1] |= (0x7UL << 0); /* PD8 -> AF7 (USART3_TX) */
GPIOD->AFR[1] &= ~(0xFUL << 4);
GPIOD->AFR[1] |= (0x7UL << 4); /* PD9 -> AF7 (USART3_RX) */
RCC->APB1ENR |= USART3EN;
uart_set_baudrate(APB1_CLK, DBG_UART_BAUDRATE);
USART3->CR1 = CR1_TE;
USART3->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);
}
#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 < 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->LISR) & LISR_TEIF0)
{
DMA2->LIFCR |= LIFCR_CTEIF0;
}
}
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는 개념만 정리하고 이 프로젝트에서는 구현하지 않았다고 명시했다.
DMA는 CPU 대신 data movement를 수행한다.
이번 실습은 DMA2 Stream0 Memory-to-Memory transfer다.
전송 완료/에러 flag는 ISR에서 각각 처리한다.