메모리 맵: MCU의 모든 것은 주소가 있다
코드는 Flash에, 변수는 SRAM에, 주변장치 레지스터도 정해진 주소에 있습니다. 데이터시트의 메모리 맵 읽는 법.
32비트 Cortex-M은 0x0000_0000부터 0xFFFF_FFFF까지 4GB의 주소 공간을 가집니다. 실제 메모리는 일부지만 영역마다 용도가 정해져 있습니다.
| 주소 | 영역 | STM32 예 |
|---|---|---|
| 0x0800_0000 | 코드 (Flash) | 프로그램과 const 데이터 |
| 0x2000_0000 | SRAM | 전역 변수, 스택, 힙 |
| 0x4000_0000 | 주변장치 | GPIO, 타이머, UART 레지스터 |
| 0xE000_0000 | 코어 주변장치 | NVIC, SysTick, 디버그 |
데이터시트 읽기
레퍼런스 매뉴얼의 메모리 맵 표에서 "GPIOA 0x4002_0000 ~ 0x4002_03FF"를 찾고, 해당 장의 레지스터 표에서 "ODR 오프셋 0x14"를 찾으면 GPIOA_ODR의 주소는 0x4002_0014입니다. CMSIS 헤더의 GPIOA->ODR은 이 계산을 대신해 주는 것입니다.
디버깅에 쓰기
하드폴트가 났을 때 문제 주소가 0x2000_xxxx면 RAM, 0x4xxx_xxxx면 주변장치 접근입니다. 클럭을 켜지 않은 주변장치의 레지스터에 접근하면 버스 오류가 나는데, 주소만 보고도 원인을 짐작할 수 있습니다.
그림으로 보기: 주소 공간 한 줄
이 큰 구획은 모든 Cortex-M에서 같습니다. 그 안에서 어느 주소에 무엇을 놓을지는 칩 제조사가 정하고, 데이터시트의 "Memory map" 표에 적혀 있습니다.
레지스터 주소는 이렇게 만들어진다
주변장치 기본 주소 + 레지스터 오프셋 = 레지스터 주소
GPIOA 0x4800_0000 + ODR 0x14 = 0x4800_0014
제조사 헤더 파일은 이 계산을 구조체로 표현합니다.
typedef struct {
volatile uint32_t MODER; // +0x00
volatile uint32_t OTYPER; // +0x04
volatile uint32_t OSPEEDR; // +0x08
volatile uint32_t PUPDR; // +0x0C
volatile uint32_t IDR; // +0x10
volatile uint32_t ODR; // +0x14
} GPIO_TypeDef;
#define GPIOA ((GPIO_TypeDef *)0x48000000u)
GPIOA->ODR이라고 쓰면 컴파일러가 기본 주소에 0x14를 더해 접근합니다. 구조체 멤버의 순서와 크기가 레퍼런스 매뉴얼의 오프셋과 정확히 맞아야 합니다.
프로그램은 어디에 놓이는가
| 구역 | 내용 | 놓이는 곳 |
|---|---|---|
.text |
코드 | Flash |
.rodata |
const 데이터, 문자열 |
Flash |
.data |
초깃값이 있는 전역 변수 | RAM (초깃값은 Flash에 보관) |
.bss |
초깃값이 0인 전역 변수 | RAM |
| 스택, 힙 | 실행 중에 쓰는 공간 | RAM |
.data는 Flash와 RAM을 모두 차지합니다. 전원이 켜지면 스타트업 코드가 Flash에 보관된 초깃값을 RAM으로 복사하고, .bss를 0으로 채운 다음 main을 부릅니다. 전역 변수가 처음부터 값을 갖고 있는 것은 이 과정 덕분입니다.
맵 파일 읽기
빌드하면 나오는 .map 파일에는 모든 함수와 변수의 주소, 크기가 적혀 있습니다.
- Flash나 RAM이 부족할 때: 크기가 큰 순서로 정렬해 무엇이 자리를 차지하는지 찾습니다.
- 하드폴트가 났을 때: 폴트가 난 주소가 어느 함수 안인지 찾습니다.
- 변수가 깨질 때: 그 변수 바로 앞에 놓인 배열을 의심합니다.
비트 밴딩과 별칭
일부 Cortex-M3/M4에는 RAM과 주변장치의 비트 하나하나를 별도 주소로 접근하는 영역이 있습니다. 한 번의 쓰기로 비트 하나만 바꿀 수 있어 읽고-바꾸고-쓰기 사이에 인터럽트가 끼어드는 문제가 없습니다. 모든 칩에 있는 것은 아니므로 데이터시트를 확인합니다.
현업에서 자주 터지는 문제
- 예약 영역 접근: 맵에 없는 주소를 읽으면 버스 폴트가 납니다.
- 클럭이 꺼진 주변장치: 주소는 맞아도 읽으면 0, 써도 반영되지 않습니다.
- 칩을 바꿨더니 동작 안 함: 같은 계열이라도 주변장치 기본 주소가 다를 수 있습니다. 헤더 파일을 칩에 맞는 것으로 바꿉니다.
- 부트로더와 응용의 주소 겹침: 링커 스크립트의 Flash 시작 주소와 벡터 테이블 위치를 함께 옮겨야 합니다.
영상으로 이어 보기회로와 하드웨어 시리즈 8편전자제품 속 회로 소자는 어떻게 동작할까? · 저항 · 커패시터 · 인덕터 완벽 정리FREE PDF무료 전자책 3권 받기이 주제를 한 권으로 이어서 읽고 싶다면. BLDC 모터 제어, 직렬통신, 센서를 책 분량으로 묶었습니다. 이메일만 남기면 바로 받습니다.무료로 받기 →