

임베디드 시스템의 이해 편을 전부 작성하지 않은 상태인데,
아직 정리가 덜 된 관계로 ARM 프로세서의 이해부터 쓰게 되었다.
다음 주 내로 밀린 것들 싹 정리해서 업데이트하는 게 목표이다.
이번 주에는 STM32 보드를 써서 ARM 프로세서에 관해 알아볼 수 있었다.
버전과 패밀리네임
| 버전 | 패밀리/코어 | 주요 특징 및 용도 |
|---|---|---|
| v1, v2, v3 | 초기 ARM 코어 | 제한적 내장형/교육·연구용 |
| v4 | ARM7 (low-end) | 저가형, 32비트 RISC, 광범위 임베디드 |
| v5 | ARM9 (mid) | 기능/성능 개선, 디지털가전·네트워킹·산업용 |
| v6 | ARM11 (high-end) | 고성능, 스마트폰·고성능 임베디드 타깃 |
| v7 | Cortex-A (high-end) | 모바일·고성능 애플리케이션(스마트폰 등) |
| Cortex-R | 실시간·안전성(자동차·산업 제어) | |
| Cortex-M (low-end) | 저가형 MCU, 센서·제어, 하위호환성 낮음 | |
| v8 | aarch32, aarch64 | 32/64비트, 최신 스마트폰, 고성능·가상화·보안 |
범용 레지스터(GPR)
r0~r12는 연산에 사용되는 기본적인 저장 공간
산술 연산(+, -, & 등), 임시 데이터, 함수 인수 전달 등에 활용함
ALU와 직접 연결되어 빠른 임시 저장소 역할
특수 목적 레지스터(SPR)
r13 (Stack Pointer, SP)
함수·인터럽트 호출 시 지역 변수를 쌓거나 복귀 주소를 저장하는 공간의 최상단 주소
스택 포인터가 어디서 데이터를 읽고 쓸지 가리킴 (스택의 꼭대기)
r14 (Link Register, LR)
다른 함수를 호출(BL 명령 등)할 때, 복귀할 PC(프로그램 카운터) 값을 잠깐 저장
여러 번 함수 호출 시, 이전 LR을 스택에 쌓은 뒤 새 LR값을 사용
r15 (Program Counter, PC)
프로그램이 읽고서 실행할 명령어의 주소를 가리키며 Fetch Unit 소속
CPU가 다음에 어떤 명령을 가져올지, PC가 항상 현재 읽으려는 위치(주소)를 기억하고 있음
// 파이프라인 순서
IF ID IE
BL i5 // 함수 호출 명령어(BL) 실행 시, i5 instruction
i2
i3 <- PC가 가리키는 주소
i4
i5
IF (Instruction Fetch): PC가 가리키는 주소(i3)에 있는 명령어를 가져옴
ID (Instruction Decode): 가져온 명령어 해독
IE (Instruction Execute): 해독된 명령어 실행
이처럼 PC(r15)는 늘 다음에 읽을 명령어의 위치를 기억해, 순차적으로 프로그램이 실행됨
SFR(Special Function Register)
CPU 바깥, 각종 장치(주변기기)와 직접 연결된 레지스터
하드웨어와 소프트웨어의 접점: GPIO(핀 입력/출력 제어), UART(직렬 통신), TIMER(카운터 등)
C에서 메모리 맵(Address Mapping)을 통해 SFR의 주소에 직접 접근
register의 값(비트)을 바꾸면 정해진 주소로 접근하여 하드웨어의 모드, 제어, 상태 등이 즉시 바뀜
configuration
디바이스가 어떻게 동작할지 옵션(모드/기능) 등을 설정
예: GPIO의 input/output 핀 설정, UART의 속도·동작 방식 등
control
디바이스의 구동/정지, 동작 방식 전환 등 ON/OFF 결정
예: 타이머를 켜고 끄기, 통신 모듈 활성화 등
data
실제 데이터의 입출력
MCU가 디바이스에서 값을 읽어 오거나, 데이터를 보냄
예: 센서값 읽기, 액추에이터에 신호 보내기
status
디바이스의 현재 동작 상태를 확인
예: 데이터가 준비됐는지(Ready), 에러가 났는지(Flag), 인터럽트 발생 여부 등 확인
#define GPIO_MODER (*(volatile unsigned int *)0x40020000) // 모드 설정
#define GPIO_ODR (*(volatile unsigned int *)0x40020014) // 출력 데이터
#define GPIO_IDR (*(volatile unsigned int *)0x40020010) // 입력 데이터
#define GPIO_BSRR (*(volatile unsigned int *)0x40020018) // 비트별 제어
// GPIO를 Output으로 설정
GPIO_MODER |= (1 << (5 * 2)); // 5번 핀을 Output 모드로
// GPIO 5번 핀에 High 출력
GPIO_ODR |= (1 << 5); // 5번 핀 ON
// GPIO 5번 핀의 입력값 확인
if (GPIO_IDR & (1 << 5)) { /* 입력 High인 경우 */ }
Instruction
CPU가 직접 실행할 하나의 최소 단위 명령
하드웨어(레지스터, 메모리, I/O 등)에 바로 영향을 주는 동작 지정
ARM에서는 하드웨어 플랫폼이 달라져도 명령어 대부분 그대로 사용 가능(Portable)
C 코드를 어셈블리로 분석해 보면 하드웨어에서 소스 코드 동작 방식을 익힐 수 있음
임베디드 디버깅 시 매우 유용
주요 명령어
| 분류 | 명령어 | 설명 | 사용 예시 |
|---|---|---|---|
| 데이터 이동 | MOV, MVN | 레지스터/메모리 간 값 복사, 반전 | MOV r1, r2 : r1에 r2 복사 |
| MVN r1, r2 : r2 NOT, r1에 저장 | |||
| 시스템 레지스터 | MRS, MSR | 시스템 상태 레지스터 읽기/쓰기 | MRS r0, CPSR : r0에 CPSR 값 읽기 |
| MSR CPSR, r1 : CPSR에 r1 값 쓰기 | |||
| 비교/비트 연산 | CMP, BIC | 두 값 비교(ZF 등 상태 비트 갱신) | |
| 비트 마스크 처리 | CMP r1, r2 : r1과 r2 비교, 조건 플래그 설정 | ||
| BIC r1, r2, r3 : r1 = r2 &~ r3 | |||
| 산술/논리 연산 | ADD/SUB/... | 더하기, 빼기, 곱하기, AND/OR/XOR | ADD r0, r1, r2 : r0 = r1 + r2 |
| SUB r1, r2, #1 : r1에서 1 빼기 | |||
| 메모리 연산 | LDR, STR | 메모리에서 값 읽고/쓰기 | LDR r1, [r2] : r2가 가리키는 주소에서 r1로 값 읽기 |
| STR r1, [r2] : r1값을 r2가 가리키는 주소로 기록 | |||
| 분기/함수 호출 | BL, BLX | 함수 실행 및 복귀주소 저장 | BL func : func 호출, 복귀주소 LR에 저장 |
| 조건 분기 | BEQ, BNE | 조건에 따라 브랜치 | BEQ label : ZF=1일 때 label로 이동 |
| BNE label : ZF=0일 때 label 이동 | |||
| 시스템콜/익셉션 | SVC | 운영체제 서비스 호출, 인터럽트 발생 | SVC #0 : 소프트웨어 인터럽트 호출 |
| 상태 레지스터 | CPSR, SPSR | 현재 상태 및 모드 유지/갱신 | CPSR: 연산 결과 따라 NZCV 플래그, SPSR: 예외 진입 시 이전 CPSR 저장 |
명령어 사용 예시
ADD r0, r1, r2 ; r1, r2의 값을 더해서 r0에 저장 (r0 = r1 + r2)
; r1, r2의 값을 더하고 연산 결과를 플래그에 남긴다 (ADD에 s를 붙임)
ADDS r0, r1, r2
; r1에 저장된 주소(옵션 더함)에서 값을 읽어서 r0에 복사 (메모리 → 레지스터)
LDR r0, [r1, #option]
; r0에 저장된 값을 [r1 + 옵션] 메모리 주소에 저장 (레지스터 → 메모리)
STR r0, [r1, #option]
CMP r1, r2 ; r1과 r2를 비교 (동등하면 Z 플래그=1)
BEQ yes ; Z 플래그가 1(같으면) yes 위치로 점프
BNE no ; Z 플래그가 0(다르면) no 위치로 점프
; 다양한 조건 접미사: EQ(같음), NE(다름), GT(크다), LT(작다) 등
BGT bigger ; r1 > r2이면 bigger로 점프
BLT lower ; r1 < r2이면 lower로 점프
조건부 실행과 효율
명령어에 조건부 접미사를 붙이면 명령어 실행 시점을 컨트롤 가능
조건부 실행을 사용하면 파이프라인 구조를 깨지않고 효율적인 분기 가능
2바이트, 4바이트 명령어 사용 이유
ARM 명령어는 기본 4바이트이지만, Thumb 등에서는 2바이트 명령어도 지원
4바이트 자리에 2바이트 명령어 2개를 저장하면 공간 효율성 높음(코드 압축 효과)
디코딩이 다소 복잡해질 수 있지만, 메모리 절약·효율 증대 가능
auth (권한 체계)
privileged(슈퍼유저/kernel mode): 모든 명령과 장치 제어 가능, 운영체제 핵심(커널 등)이 주로 사용
non-privileged(user mode): 사용자 프로그램/앱에서 주로 사용, 시스템 핵심 보호
mode (실행 모드)
thread mode: 프로그램 실행시의 일반 모드, 전원을 켜면 전체가 privileged 모드, OS 부팅 후에는 multi-task 기반으로 non-privileged(앱)도 사용됨
handler mode: 인터럽트 등 시스템 차원에서 동작하는 처리 모드, 항상 privileged
stack 종류
main stack: handler mode와 privileged thread mode에서 사용 (인터럽트, 시스템 함수)
process stack: non-privileged thread(운영체제 task)에서 사용
RISC vs CISC
RISC: 적은 종류의 간단한 명령어로 빠르고 효율적. ARM은 RISC 구조 사용
CISC: 복잡한 명령어 집합으로 다양한 기능. x86 등에서 사용
ARM 프로세서 종류
Cortex-A: 고성능 애플리케이션 프로세서(스마트폰 등)
Cortex-R: 실시간 처리용(자동차, 산업)
Cortex-M: 저전력·소형 MCU(센서, IoT, 임베디드 제어)
Cortex-M4 특징
32비트 RISC, FPU(부동소수점 연산), DSP 명령어, 저전력, 3단계 파이프라인, Thumb/Thumb-2 하이브리드, 하드웨어 디버깅, 실시간 인터럽트, MPU로 메모리 보호 등
Execution state
ARM: 4바이트 명령 실행 (기본)
Thumb: 2바이트 명령 실행 (공간 절약)
Thumb-2: ARM+Thumb 혼합
워드(Word)
한 번에 처리 가능한 데이터의 크기
Cortex-M4에서는 4바이트
CPSR(Current Program Status Register), SPSR(Stored Program Status Register)
코드 실행 및 인터럽트 상태, 모드 등 시스템 동작을 실시간으로 저장/관리
SVC(Supervisor Call) 모드 등 제한된 상황에서만 직접 접근
상태 플래그
N: 연산 결과 음수
Z: 연산 결과가 0
C: 캐리/버로우, 빼기나 더하기시 자리올림/내림 발생
IF: IRQ/FIQ 인터럽트 발생 여부
디바이스 접근 방법
메모리 매핑된 SFR(주소에 직접 접근), C에서 volatile 키워드 사용하여 하드웨어 제어
어셈블리 언어의 LDR/STR 명령어로 레지스터(메모리) 읽고 쓰기
비트 연산
하드웨어 레지스터를 비트 단위로 조작할 때 사용
bit set : 특정 비트를 1로 만듦 (|)
bit clr : 특정 비트를 0으로 만듦 (&, ~)
bit filter : 원하는 비트만 남김 (&)
bit toggle : 0/1 전환 (^)
비트 마스킹 연산
레지스터 내부의 특정 비트만 읽거나, 바꾸거나, 필터링할 때 사용
주로 하드웨어 제어, 플래그 처리, GPIO 핀 제어, 주변장치 설정 등에 자주 활용
rWDTCON = xxxx:zzxx ; // 레지스터 기존 값
msk = 0000:1100 ; // 관심 있는 비트 영역 (마스킹)
~msk = 1111:0011 ; // 마스크 반전 (NOT)
rWDTCON & ~msk ; // 관심 영역만 0으로 클리어 (나머지 비트 유지)
val = 0000:0100 ; // 원하는 값 (설정)
rWDTCON | val ; // 관심 영역에 값 설정(비트 set)
=> xxxx:01xx ; // 최종적으로, 관심 비트만 원하는 값으로 변경됨
비트 마스킹 연산 예시
// 관심 비트만 마스킹해서 클리어/셋/필터/토글
// 클리어하고(특정 비트를 모두 0으로), 원하는 값만 set
rWDTCON = rWDTCON & ~0x0c; // 0x0c = 00001100, 관심 영역(비트 2,3)을 모두 0으로 클리어
rWDTCON = rWDTCON | 0x04; // 0x04 = 00000100, 비트 2에만 1 set (비트 3은 여전히 0)
rWDTCON &= ~(3<<2); // 비트 2,3 클리어(3: 11, <<2: 비트 2,3 위치)
rWDTCON |= (1<<2); // 비트 2에만 set
GPIO (General Purpose Input Output)
임베디드 MCU가 외부 핀을 입력(스위치, 센서 등) 또는 출력(LED, 모터 등)으로 자유롭게 제어할 수 있게 해주는 회로/핀
Read
입력모드로 설정 시, 외부에서 신호가 들어오는지 읽음
(IDR: input data register)
Write
출력모드로 설정 시, 레지스터에 값을 기록해서 전기적 신호를 외부로 전송
(ODR: output data register 또는 BSRR: bit set/reset register)
내부 논리회로(P-MOS, N-MOS)를 통해 I/O핀을 직접 제어해 외부 장치(LED/KEY 등)를 구동
Write → registers → 1 → output control → P-MOS, N-MOS → I/O pin → LED/KEY
MCU의 코드에서 GPIO에 Write 시도
→ 레지스터(ODR, BSRR 등)에 값 기록
→ 하드웨어 회로(P-MOS 또는 N-MOS)가 해당 신호를 I/O 핀에 내보냄
→ I/O 핀이 LED, 스위치 등 외부 소자를 직접 제어
Push-pull
약한 신호(0/1)를 강하게 증폭해서 출력하는 방식
LED 등 일반 출력에 널리 사용
Open-drain
외부에서 풀업/풀다운 저항 연결
신호를 0 으로만 직접 만들고, 1 은 외부 회로에서 보조
풀업/풀다운 저항
눌리지 않을 때/no input일 때 불안정 신호를 방지하고 입력을 안정화
Protection diode
입력 전압이 0~3.3V 같은 허용 범위 내에서만 동작하도록 보호
Schmitt trigger
작은 변동에 흔들리지 않고, 명확한 high(3.3V)/low(0V) 판단
IH ~ IL 사이의 값으로 측정되는 경우에는 기존의 값을 유지함
Alternate function
GPIO는 필수 외 주변장치(UART, SPI 등) 기능도 담당할 수 있고 핀마다 다름
MODER
각 핀의 사용 용도를 설정
(00: 입력, 01: 출력, 10: 대체기능, 11: 아날로그)
OTYPER
push-pull/open-drain 모드 선택
IDR/ODR
입력/출력 데이터 레지스터
BSRR
각 핀 별로 set/reset 명령 빠르게 실행 (LED 켜기/끄기에 자주 활용)
PUPDR
풀업/풀다운 저항 설정
int main(void)
{
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
RCC->AHB1ENR |= (1 << 0); // clock for PA5
GPIOA->MODER &= ~(3 << 2*5); // clear 10, 11th bit
GPIOA->MODER |= (1 << 2*5); // mode for PA5
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// ODR, BSRR
GPIOA->BSRR = (1 << 5); // ODR5 == 1
HAL_Delay(500);
GPIOA->BSRR = (1 << (5 + 16)); // ODR5 == 0
HAL_Delay(500);
/* USER CODE END WHILE */
}
}int main(void)
{
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
GPIOA->MODER &= ~(3 << 2*9); // clear bits
GPIOA->MODER |= (1 << 2*9);
GPIOC->PUPDR &= ~(3 << 2*7); // no pull-up, no-pull-down
GPIOC->PUPDR |= (1 << 2*7);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_7) == 0)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_RESET); // LED OFF
}
else
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_SET); // LED ON
}
HAL_Delay(100);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}임베디드 시스템의 I/O 및 데이터 흐름
각 입출력 장치는 자체적으로 작은 내부 버퍼를 갖고 있음
CPU는 이런 버퍼에서 데이터를 읽어와 더 큰 RAM에 저장해 효율·속도를 맞춤
폴링, 인터럽트, DMA 등 다양한 데이터 처리(이벤트 응답) 방식이 존재함
인터럽트란 디바이스가 입력/출력/에러의 상황에 대한 프로세서의 처리를 요구하는 것
폴링(Polling)
CPU가 주기적으로 장치의 데이터 버퍼를 확인해서 직접 데이터를 읽어오는 방식
CPU가 다른 작업을 수행 중일 때 데이터를 놓칠 수 있으며, 비효율적
온도계 등 실시간성이 덜 요구되는 경우 사용
인터럽트(Interrupt)
장치에 새로운 데이터가 준비되면 CPU에 인터럽트 요청(irqreq)을 보냄
CPU는 요청을 받으면 지정된 인터럽트 핸들러에서 즉시 데이터를 처리
효율적이고, 실시간 반응성 보장 (CPU는 평소 자신의 일 하다가 신호 받았을 때만 처리)
EXTI(외부 인터럽트)·NVIC(인터럽트 관리 컨트롤러) 등으로 관리
DMA(Direct Memory Access)
장치에 새로운 데이터가 준비되면 DMA Request(dmareq)를 보냄
CPU 대신 DMA라는 전용 장치가 데이터를 직접 옮김
CPU는 계산 등 본연의 일에 집중, 데이터 복사는 DMA가 전용채널로 수행
인터럽트 핸들링 과정
각 디바이스, 센서 등이 인터럽트 요청(IRQ 핀/번호) 발생
인터럽트 컨트롤러(NVIC)가 우선순위·처리 여부 결정
GPIO/타이머/UART 등 다양한 인터럽트(WWDG, DMA1_Channel 등) 지원
EXTI(External Interrupt) 회로는 신호의 변화(에지/레벨 등)를 인식해 소프트웨어/하드웨어적으로 이벤트 발생
Pending Register가 여러 요청에서 처리 우선순위 정함 (OR/AND 연산)
NVIC가 적합한 핸들러로 분배 후 처리
NVIC (Nested Vectored Interrupt Controller)
ARM Cortex-M 계열 MCU는 NVIC라는 인터럽트 컨트롤러를 내장
수십~수백 개의 인터럽트 라인(입력 채널)을 관리하며, 최대 16단계 우선순위 설정이 가능함
WWDG(윈도우 와치독), EXTI0~4, DMA1_Channel1~7, EXTI15_10, 타이머 등 다양한 라인 관리
주요 기능
각 인터럽트의 활성화/비활성화
우선순위 레벨 설정 (중첩 인터럽트가 오면 높은 것부터 처리)
인터럽트별 핸들러(함수)로 자동 분배
빠른(저지연) 예외 및 인터럽트 처리
EXTI (External Interrupt/Event Controller)
GPIO에서 들어오는 외부 신호에 대한 에지(Edge, 상승/하강, 변화)를 감지
감지 방식
edge detect circuit : 입력 신호의 변화(0→1 or 1→0)를 감지
software interrupt event register : 실제 외부 신호 대신 소프트웨어가 임의로 이벤트 생성 가능
인터럽트 요청 처리 메커니즘
Input line: GPIO 핀 등에서 인터럽트 신호 입력
Edge detect: 변화가 발생(예: 버튼 눌림)하면 감지
OR 연산: 여러 입력이 동시에 올 수 있으니 OR 연산으로 다중 검출
interrupt mask register(IMR): 특정 라인(switch, 센서 등)을 마스킹(허용/차단) 가능, AND 연산으로 선택적 허용
pending request register(PR): 어떤 인터럽트 요청이 대기 중인지 기록, 여러 요청이 쌓이면 우선순위로 정렬
NVIC: 우선순위대로 핸들러로 분배, 실제 인터럽트 핸들링 수행
Input line → edge detect circuit/software interrupt
→ OR 연산 → interrupt mask(AND 연산) → pending register → NVIC(우선순위 처리)
Time-base unit
MCU 내부의 카운터가 일정 시간마다 숫자를 셈
타임 베이스 유닛 값을 모니터링하다 필요에 따라 특정 이벤트를 발생시키는 장치
카운터(Counter)
카운트 업(+) 또는 다운(-) 또는 둘 다(업/다운) 지원
1초, 1ms 등 원하는 시간 단위 기준으로 인터럽트 등의 이벤트를 생성
핵심 레지스터
counter register : 현재 카운터 값(몇 번 셌는지 기록)
prescaler register : 카운트 속도 조절(클럭을 나눠서 처리, 빠르게 또는 느리게)
auto-reload register : 카운터가 끝까지 세면 자동으로 값 복구하고, 반복 동작
클럭 및 프리스케일러(Prescaler) 설정
MCU의 클럭(예: 84MHz)에서 프리스케일(default 0~65535 등)을 나눠 속도 조절
프리스케일 값(PSC)에 83(=84-1)을 넣으면 카운터는 1MHz로 동작
예: 84MHz / 84 → 1MHz (1초에 100만 번 카운트)
counter period(주기) 설정
카운터 세는 값 한계를 정하고 원하는 시간 단위로 타이머를 반복시킴
예: 1ms, 50ms, 500ms 등(자동 반복)
NVIC Settings 및 인터럽트 핸들러
NVIC에서 TIM3 global interrupt 활성화
타이머가 주기마다 자동으로 TIM3_IRQHandler 호출
handler 내부에서 전역 변수 등으로 플래그 관리(예: 카운트 증가)
반복 동작, 실시간 이벤트 트리거에 활용
타이머로 LED 조작 실습(delay 구현)
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private variables ---------------------------------------------------------*/
TIM_HandleTypeDef htim3;
UART_HandleTypeDef huart2;
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM3_Init(void);
static void MX_USART2_UART_Init(void);
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void delay_usec( int us, int loop ) // total delay time = us * loop (usec)
{
while (loop--)
{
__HAL_TIM_SET_COUNTER(&htim3, 0); // up counter
HAL_TIM_Base_Start(&htim3);
while (__HAL_TIM_GET_COUNTER(&htim3) < us) ; // delay by while
HAL_TIM_Base_Stop(&htim3);
}
}
/* USER CODE END 0 */
int main(void)
{
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM3_Init();
MX_USART2_UART_Init();
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, 1);
delay_usec(1000, 1000);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, 0);
delay_usec(1000, 1000);
/* USER CODE END WHILE */
}
}
DMA(Direct Memory Access)
메모리와 외부 장치(Peripheral) 사이의 데이터 전송을 CPU의 개입 없이 전용 DMA 컨트롤러가 직접 처리하는 방식
CPU의 부담을 덜고, 데이터 이동이 훨씬 빠르고 효율적으로 진행됨
실무에서는 센서 데이터, 통신, 대용량 데이터 전송 등에 필수 사용
DMA 트랜잭션 종류(AHB 버스 기준)
Peripheral to memory (00) : 센서 등에서 RAM으로 데이터 전달
Memory to peripheral (01) : RAM에 저장된 데이터를 장치로 전달
Memory to memory (10) : RAM에서 RAM으로 데이터 복사
DMA 요청 흐름
peripheral → DMAREQ(DMA 요청) → DMA → BUSREQ(버스 사용할 요청) → CPU
→ BUSACK(버스 사용 허용) → DMA → DMAACK(작동 신호) → peripheral
Arbiter(중재기): 여러 채널이 동시에 데이터 전송을 요구할 때, 우선순위를 정해서 버스 충돌 방지
DMA 전송 모드
Peripheral-to-memory, Memory-to-peripheral, Memory-to-memory
필수 설정 항목
Source(데이터 읽는 쪽), Destination(데이터 쓰는 쪽) 선택
Flow controller: DMA가 직접 이동 관리하거나, peripheral(장치) 측에서 제어
Circular mode: 데이터 송수신을 자동 반복할지 여부
Transfer type: single(데이터 1건씩), burst(여러 건 연속)
Direct mode, Double buffer mode: 빠른 연속 처리/두 개 버퍼를 번갈아 사용해 끊김 없이 데이터 가능
Tranfer type
버스트(burst): 대용량, 연속 데이터 처리에 적합
싱글(single): 보통의 단일, 순서 처리
자동 반복(circular), 더블 버퍼: 실시간 신호처리, 스트리밍 등 정밀 자동화 처리
DMA 포인터 증분
대부분의 DMA 전송은 복사할 때 버퍼 포인터(주소)를 이동시키면서 옮김
peripheral의 일부 레지스터는 직접 포인터 이동 없이 고정된 주소를 반복해서 읽고, 메모리 쪽 포인터만 증가시켜 복사하기도 함
DMA 완료와 인터럽트
DMA가 작업을 마치면 CPU에 작업 완료 인터럽트로 알림
에러 상황(데이터 에러, FIFO 에러 등)도 별도 인터럽트로 신호 가능
실무에서는 DMA가 작업 완료 시 Interrupt Handler에서 후속 작업(버퍼 처리, 상태 업데이트 등)을 관리
DMA configurations 요약
출처 : STM32F401 Reference Manual

보드를 처음 다뤄보는 거라 많이 낯설고 어려웠다.
이번 주에 배운 내용을 제대로 이해하지 못한 것 같아서 많이 아쉬웠다.
정리는 끝났지만 프로젝트 만들고 이런저런 기능을 더 써 봐야 감이 제대로 잡힐 것 같다.
이론 - 실습 - 이론 다시보기 구조로 배우면 괜찮을까 싶기도 하다.
들어와서 거의 처음으로 나를 의심할 수 있었던 시간이었다...,,!!