플립플롭(래치) 회로로 비트를 저장하는 메모리(Static RAM). 전원이 있는 한 값이 유지되어 리프레시가 필요 없다.
구조 — 왜 "정적"인가
1비트 = 트랜지스터 6개 (6T 셀)
┌─── 인버터 두 개가 서로를 물고 있는 구조 ───┐
상태가 스스로 유지된다 → 리프레시 불필요 → Static
DRAM은 커패시터에 전하를 담아 두는데 누설되어 사라지므로 주기적으로 다시 채워야 한다(refresh). 그래서 Dynamic이다.
SRAM vs DRAM
| SRAM | DRAM | |
|---|---|---|
| 셀 구성 | 트랜지스터 6개 | 트랜지스터 1개 + 커패시터 1개 |
| 밀도 | 낮다 | 높다 (같은 면적에 6배 이상) |
| 가격 | 비싸다 | 싸다 |
| 속도 | 빠르다 (1~수 사이클) | 느리다 (수십 사이클) |
| 리프레시 | 불필요 | 필요 |
| 대기 전력 | 낮다 | 리프레시 때문에 계속 소모 |
| 인터페이스 | 단순 (주소 → 데이터) | 복잡 (행/열 분리, 컨트롤러 필요) |
"작고 빠르고 비싼 SRAM, 크고 느리고 싼 DRAM" 이 요약이다.
어디에 쓰이나
CPU 캐시(L1/L2) → SRAM (속도가 전부)
MCU 내장 RAM → SRAM (수 KB ~ 수백 KB)
PC 메인 메모리 → DRAM (수 GB)
외장 메모리(SDRAM) → DRAM
MCU에 SRAM만 있는 이유 — 용량이 수십 KB면 충분하고, DRAM 컨트롤러를 넣을 여유(칩 면적·전력·핀 수)가 없다. 무엇보다 리프레시 로직 없이 바로 붙일 수 있는 단순함이 임베디드에 맞는다.
MCU에서 SRAM은 무엇을 담나
0x20000000 ┌──────────────┐
│ .data │ ← 초기값 있는 전역변수 (플래시에서 복사됨)
│ .bss │ ← 초기값 없는 전역변수 (0으로 채워짐)
│ 힙 ↓ │ ← malloc (임베디드에서는 잘 안 씀)
│ │
│ 스택 ↑ │ ← 지역변수, 함수 호출
0x2001FFFF └──────────────┘
힙과 스택이 서로를 향해 자란다. 만나면 충돌인데, 하드웨어가 알려 주지 않아 조용히 데이터가 깨진다. → MPU로 스택 경계에 가드를 두거나, 스택 워터마크로 감시한다.
임베디드에서 RAM이 부족할 때
MCU의 SRAM은 32KB, 64KB처럼 작다. 절약 요령:
- ① const 를 붙여 .rodata(플래시)로 보낸다 ← 효과가 가장 크다
- ② 큰 지역 배열을 static 으로 (스택 → .bss)
- ③ 구조체 멤버 순서를 조정해 패딩 제거
- ④ 동적 할당 대신 정적 풀 할당
- ⑤ 문자열 리터럴을 플래시에 유지 (AVR은 PROGMEM 필요)
arm-none-eabi-size firmware.elf
text data bss dec
45231 412 8192 53835
↑플래시 ↑RAM ↑RAM
RAM 사용량 = data + bss (+ 런타임 스택·힙)
빌드할 때마다 이 수치를 확인하는 습관이 중요하다.
여러 종류의 SRAM
고급 MCU는 SRAM을 여러 블록으로 나눈다.
| 종류 | 특징 |
|---|---|
| TCM (Tightly Coupled Memory) | CPU에 직접 붙어 지연이 일정. ISR·중요 코드 배치 |
| SRAM1/SRAM2 | 일반 영역. 버스가 달라 DMA와 병렬 접근 가능 |
| 백업 SRAM | VBAT로 유지. 전원이 꺼져도 소량 데이터 보존 |
| CCM (Core Coupled) | 코어 전용. DMA가 접근 못 하는 경우가 있다 ★ |
★ CCM에 DMA 버퍼를 두면 전송이 안 된다. DMA 컨트롤러가 그 버스에 연결되어 있지 않기 때문이다. "DMA가 동작하지 않는다"의 흔한 원인이며 링커 스크립트에서 배치 영역을 확인해야 한다.