CPU가 보는 하나의 주소 공간에 플래시·RAM·주변장치가 각각 구역을 차지하도록 배치한 것(memory-mapped I/O).
핵심 발상 — 주변장치도 메모리처럼 보인다
*(volatile uint32_t *)0x40020014 = 0x20; // GPIOA의 5번 핀을 켠다
이 코드는 메모리에 값을 쓰는 것처럼 생겼지만 실제로는 GPIO 하드웨어를 조작한다. 그 주소에는 RAM이 없고 GPIO 컨트롤러가 연결되어 있다.
왜 이렇게 하나 — 별도의 I/O 명령(x86의 in/out)이 필요 없어진다.
포인터 하나로 하드웨어를 다룰 수 있으니 C 언어만으로 드라이버를 쓸 수 있다.
ARM을 비롯한 대부분의 임베디드 아키텍처가 이 방식이다.
Cortex-M의 표준 배치
0xFFFFFFFF ┌───────────────────┐
│ Vendor specific │
0xE0000000 ├───────────────────┤
│ Private Peripheral│ ← NVIC, SysTick, SCB, 디버그
0xA0000000 ├───────────────────┤
│ External Device │
0x60000000 ├───────────────────┤
│ External RAM │ ← 외부 SDRAM (FSMC/FMC)
0x40000000 ├───────────────────┤
│ Peripheral │ ← GPIO, UART, TIM, ADC …
0x20000000 ├───────────────────┤
│ SRAM │ ← 변수, 스택, 힙
0x00000000 └───────────────────┘ ← 플래시 (코드, .rodata)
Cortex-M은 이 배치가 아키텍처 차원에서 고정되어 있다. 그래서 벤더가 달라도 큰 틀이 같고, 포팅이 수월하다.
주소만 봐도 알 수 있다
0x0800_0000 → 플래시. 코드가 여기서 실행된다
0x2000_0000 → SRAM. 스택 포인터가 이 범위여야 정상
0x4002_0000 → GPIO 계열 레지스터
0xE000_E010 → SysTick (Cortex-M 공통)
HardFault를 분석할 때 폴트 주소가 어느 구역인지 보면 원인이 좁혀진다.
0x00000000 근처면 NULL 포인터, 플래시 영역에 쓰기를 시도했으면 const 위반,
아무것도 없는 구역이면 잘못된 포인터 연산이다.
비트 밴딩 — Cortex-M3/M4의 특별한 기능
SRAM 0x20000000~ 의 각 비트가
- 0x22000000~ 영역에 워드 하나씩으로 매핑된다
bit_word_addr = 0x22000000 + (byte_offset × 32) + (bit_number × 4)
// 일반 방식 — read-modify-write (원자적이지 않다)
GPIOA->ODR |= (1 << 5);
// 비트 밴딩 — 단일 쓰기 (원자적)
*(volatile uint32_t *)BITBAND(&GPIOA->ODR, 5) = 1;
비트 하나를 원자적으로 조작할 수 있어 인터럽트와의 경쟁을 피한다. 다만 Cortex-M7 이후에는 빠졌고, 대신 대부분의 주변장치가 BSRR 같은 set/clear 전용 레지스터를 제공한다.
주소 별칭(alias) — 부팅과 관련
많은 MCU가 0x00000000에 플래시 또는 SRAM을 매핑할 수 있다(BOOT 핀).
BOOT0 = 0 → 0x00000000 에 플래시가 보인다 (일반 부팅) BOOT0 = 1 → 시스템 메모리(내장 부트로더) 또는 SRAM
CPU는 리셋 시 무조건 0x00000000에서 벡터 테이블을 읽으므로,
무엇을 거기에 보이게 하느냐로 부팅 방식이 결정된다.
부트로더가 있는 경우 SCB->VTOR로 벡터 테이블 위치를 옮긴다.
접근 속성 — 그냥 메모리가 아니다
메모리 맵의 각 구역에는 속성이 붙는다.
| 속성 | 뜻 |
|---|---|
| Device / Strongly-ordered | 주변장치 영역. 캐시 금지, 순서 보장 |
| Normal | 일반 메모리. 캐시·재배치 허용 |
| Execute Never (XN) | 코드 실행 금지 |
주변장치 영역이 캐시되면 안 되는 이유 — 레지스터 값은 하드웨어가
바꾸는데 캐시에 옛 값이 남아 있으면 잘못 읽는다.
Cortex-M은 0x40000000 영역을 기본적으로 Device로 취급한다.
캐시가 있는 Cortex-M7에서 외부 SDRAM을 DMA 버퍼로 쓸 때 MPU로 그 구역을 non-cacheable로 설정하는 것이 흔한 작업이다.