SPI — 클럭을 같이 보내는 대신 선이 넷
UART 는 클럭 없이 속도를 약속했다. SPI 는 반대로 클럭선을 함께 보낸다. 그래서 속도를 맞출 필요가 없고 수십 MHz 까지 올라간다. 대가는 선이 많다는 것.
선 넷
칩 셀렉트를 LOW 로 내리는 것이 "지금부터 너와 이야기한다" 는 선언이다. 슬레이브는 이 선이 활성일 때만 클럭을 자기 것으로 받아들인다.
마스터 슬레이브
SCLK ──────────────────────> SCLK 클럭 (마스터가 만든다)
MOSI ──────────────────────> MOSI Master Out Slave In
MISO <────────────────────── MISO Master In Slave Out
SS/CS ──────────────────────> SS 이 슬레이브를 고른다 (보통 LOW 활성)
전이중(full-duplex) 이라 보내면서 동시에 받는다. 이게 SPI 의 특징이다 — 한 바이트를 보내면 반드시 한 바이트가 들어온다.
uint8_t spi_xfer(uint8_t out) {
while (!(SPI1->SR & SPI_SR_TXE)) { }
SPI1->DR = out; // 보내면
while (!(SPI1->SR & SPI_SR_RXNE)) { }
return SPI1->DR; // 동시에 받은 것이 나온다
}
// 읽기만 하고 싶어도 무언가는 보내야 한다
uint8_t v = spi_xfer(0xFF); // 더미를 보내고 받는다
"읽기만" 이라는 개념이 없다. 클럭을 만들어야 데이터가 나오고, 클럭을 만들려면 보낼 바이트가 필요하다. 그래서 더미 바이트(0xFF 또는 0x00)를 보낸다.
CPOL·CPHA — 네 가지 모드
여기가 SPI 의 전부다. 두 비트가 클럭의 어느 엣지에서 데이터를 읽을지를 정한다.
- CPOL (Clock Polarity) — 유휴 상태의 클럭이 LOW(0)냐 HIGH(1)냐
- CPHA (Clock Phase) — 첫 번째 엣지에서 샘플링(0)하냐 두 번째 엣지에서(1)냐
| 모드 | CPOL | CPHA | 유휴 | 샘플링 |
|---|---|---|---|---|
| 0 | 0 | 0 | LOW | 상승 엣지 |
| 1 | 0 | 1 | LOW | 하강 엣지 |
| 2 | 1 | 0 | HIGH | 하강 엣지 |
| 3 | 1 | 1 | HIGH | 상승 엣지 |


Mode 0 (CPOL=0, CPHA=0) — 가장 많이 쓰인다
SCLK ___|‾|_|‾|_|‾|_|‾|___ 유휴 LOW
MOSI --< D7 >< D6 >< D5 >--
^ ^ ^
상승 엣지에서 읽는다
Mode 3 (CPOL=1, CPHA=1)
SCLK ‾‾‾|_|‾|_|‾|_|‾|‾‾‾ 유휴 HIGH
MOSI --< D7 >< D6 >< D5 >--
^ ^ ^
상승 엣지에서 읽는다 (두 번째 엣지)
모드가 틀리면 어떤 증상인가
모드를 틀리면 통신이 안 되는 게 아니라 "값이 이상하게" 나온다. 이게 진단을 어렵게 한다.
올바른 모드 0xA5 를 보내면 0xA5 로 읽힌다
CPHA 만 틀림 한 비트 밀려 0x4B 나 0x52 처럼 나온다
CPOL 만 틀림 첫 비트가 깨지고 나머지는 맞는 경우가 있다
"통신은 되는데 값이 절반씩 밀려 있다" 면 CPHA 를 뒤집어 본다. 데이터시트의 타이밍도를 보는 것이 정답이지만, 모드 4개를 순서대로 시도해 보는 것도 실무에서 흔한 방법이다.
// 모드 설정
SPI1->CR1 &= ~(SPI_CR1_CPOL | SPI_CR1_CPHA); // Mode 0
SPI1->CR1 |= SPI_CR1_CPOL | SPI_CR1_CPHA; // Mode 3
초기화
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
// PA5=SCK, PA6=MISO, PA7=MOSI → AF5
GPIOA->MODER |= (0b10<<(5*2)) | (0b10<<(6*2)) | (0b10<<(7*2));
GPIOA->AFR[0] |= (5<<(5*4)) | (5<<(6*4)) | (5<<(7*4));
// CS 는 소프트웨어로 직접 다루는 편이 편하다
GPIOA->MODER |= (0b01 << (4*2)); // PA4 출력
GPIOA->BSRR = (1 << 4); // HIGH = 비활성
SPI1->CR1 = SPI_CR1_MSTR // 마스터
| (0b011 << SPI_CR1_BR_Pos) // 분주 /16 → 84/16 = 5.25MHz
| SPI_CR1_SSM | SPI_CR1_SSI // 소프트웨어 CS
| SPI_CR1_SPE;
// 트랜잭션 — CS 를 내리고 주고받고 올린다
void spi_read_reg(uint8_t reg, uint8_t *buf, int n) {
GPIOA->BSRR = (1 << (4+16)); // CS LOW
spi_xfer(reg | 0x80); // 대개 최상위 비트가 읽기/쓰기 표시
for (int i = 0; i < n; i++) buf[i] = spi_xfer(0xFF);
GPIOA->BSRR = (1 << 4); // CS HIGH
}
CS 를 언제 올리느냐가 중요하다. 많은 슬레이브가 CS 상승 엣지를 "명령 끝" 으로 해석한다. 여러 바이트를 한 트랜잭션으로 보내야 하는데 바이트마다 CS 를 올리면 각각이 별개 명령이 된다.
// ✗ 바이트마다 CS 를 토글 — 슬레이브가 다중 바이트 읽기로 인식하지 않는다
for (int i = 0; i < n; i++) { cs_low(); buf[i] = spi_xfer(0xFF); cs_high(); }
슬레이브가 여러 개일 때
방법 ① CS 를 따로 — 가장 흔하다
마스터 ──SCLK/MOSI/MISO── (공유) ──┬── 슬레이브 A
CS1 ────────────────────────┘
CS2 ──────────────────────────── 슬레이브 B
CS3 ──────────────────────────── 슬레이브 C
슬레이브 n 개면 CS 핀 n 개가 필요하다
방법 ② 데이지 체인 — 핀을 아낀다
마스터 MOSI ──> A MOSI A MISO ──> B MOSI B MISO ──> 마스터 MISO
CS ─────┴──────────────────┴───────── (하나를 공유)
한 바이트를 밀어 넣으면 앞의 것이 다음 칸으로 밀린다 (시프트 레지스터)
데이지 체인은 CS 하나로 여러 개를 다룰 수 있지만 모든 슬레이브가 이 방식을 지원해야 하고, 개별 접근이 안 되며(전체를 다 밀어야 한다) 하나가 고장나면 뒤가 다 끊긴다. LED 드라이버나 시프트 레지스터에서 주로 쓴다.
속도 — 얼마까지 올릴 수 있나
SPI 클럭 = APB 클럭 / 분주비
84MHz / 2 = 42MHz (이론상 최대)
실제 한계는 슬레이브 데이터시트의 최대 SCLK 와 배선 품질이 정한다.
선이 길거나 정전용량이 크면 → 파형이 둥글어져 엣지가 흐려진다
→ 슬레이브가 잘못된 시점에 읽는다
증상은 "천천히 하면 되는데 빠르게 하면 깨진다" 다. 이때 순서는 이렇다.
- 분주를 키워 속도를 낮춘다 → 되면 배선·정전용량 문제 확정
- 오실로스코프로 SCLK 파형을 본다 → 링잉·둥글어짐 확인
- 선을 짧게, 접지선을 나란히, 직렬 저항(22~33Ω)으로 링잉 억제
상한을 감으로 잡지 말고 타이밍 예산으로 계산한다
"몇 MHz 까지 되나"는 데이터시트의 세 숫자를 더하면 나온다. 마스터가 데이터를 내보내고, 그것이 선을 타고 가서, 슬레이브가 읽기 전에 안정돼 있어야 하기 때문이다.
반주기(클럭 HIGH 또는 LOW 한 쪽) ≥ tV(마스터 출력 유효까지) + t전파(배선) + tSU(슬레이브 셋업)
슬레이브 tSU = 10ns, 마스터 tV = 15ns, 배선 지연 5ns 라면:
반주기 ≥ 15 + 5 + 10 = 30 ns
한 주기 ≥ 60 ns → f_max = 1 / 60ns ≈ 16.7 MHz
슬레이브 데이터시트에 "최대 SCLK 50MHz" 라고 적혀 있어도 실제 상한은 16.7MHz 다. 데이터시트의 최대 클럭은 이상적인 조건의 수이고, 우리 보드의 배선 지연과 마스터의 출력 지연이 예산을 먹기 때문이다.
여기에 홀드 시간까지 봐야 한다 — 슬레이브가 요구하는
tHD(읽은 뒤 데이터를 유지해야 하는 시간)를 마스터가 못 지키면, 클럭을 낮춰도 안 고쳐진다. 이때는 CPHA 를 바꿔 샘플 엣지를 옮기거나, 마스터의 출력 타이밍 옵션을 조정해야 한다.함정 — 속도를 낮췄더니 되길래 "배선 문제" 로 결론 내렸는데, 실은 홀드 위반이라 온도가 오르면 다시 깨지는 경우가 있다. 오실로스코프로 엣지와 데이터 변화 지점의 간격을 직접 재서 마진을 확인하는 것이 확실하다.
SD 카드 예 — 실무 시퀀스
// SD 카드는 초기화 시 반드시 낮은 속도로 시작해야 한다
void sd_init(void) {
spi_set_speed(SPI_BR_256); // 84MHz/256 = 328kHz (규격상 400kHz 이하)
cs_high();
for (int i = 0; i < 10; i++) spi_xfer(0xFF); // 80클럭 이상 — 카드 깨우기
sd_cmd(CMD0, 0); // GO_IDLE_STATE
...
spi_set_speed(SPI_BR_4); // 초기화 후 21MHz 로 올린다
}
초기화만 느리게, 이후 빠르게가 패턴이다. 규격이 그렇게 정해져 있고, 안 지키면 카드가 응답하지 않는다. 그리고 CS 를 HIGH 로 둔 채 클럭을 80개 이상 주는 것도 규격이다.
DMA
// 큰 블록 전송은 DMA 로 — 512바이트 SD 섹터 등
SPI1->CR2 |= SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN;
// 송신·수신 스트림을 둘 다 걸어야 한다 (전이중이므로)
전이중이라 DMA 도 두 개 필요하다. 송신만 걸면 수신 데이터가 오버런으로 버려진다.
SPI vs I²C vs UART
| SPI | I²C | UART | |
|---|---|---|---|
| 선 수 | 4 (+슬레이브당 CS) | 2 | 2 |
| 속도 | 수십 MHz | 100k~3.4MHz | ~수 Mbps |
| 클럭 | 있다 | 있다 | 없다 |
| 주소 | CS 핀으로 | 주소 바이트로 | 없다 |
| 전이중 | 가능 | 불가 | 가능 |
| 응답 확인 | 없다 | ACK 있다 | 없다 |
SPI 는 ACK 가 없다. 슬레이브가 없거나 죽어 있어도 마스터는 모른다 — 그냥 0xFF 나 0x00 이 읽힌다. 그래서 장치 ID 레지스터를 읽어 확인하는 것이 초기화의 정석이다.
uint8_t id = spi_read_reg(WHO_AM_I);
if (id != EXPECTED_ID) return ERR_NO_DEVICE; // 연결 확인
한눈에 정리
- 클럭을 같이 보내므로 속도 약속이 필요 없고 수십 MHz 까지 간다. 대가는 선이 넷
- 전이중이라 "읽기만" 이 없다 — 더미 바이트를 보내야 클럭이 생겨 데이터가 나온다
- CPOL·CPHA 네 조합이 SPI 의 핵심. Mode 0 과 3 이 가장 흔하다
- 모드가 틀리면 통신이 안 되는 게 아니라 값이 밀려 나온다 — 진단이 어려운 이유
- CS 상승 엣지를 "명령 끝" 으로 보는 슬레이브가 많다 — 바이트마다 토글하면 안 된다
- 데이지 체인은 CS 를 아끼지만 개별 접근이 안 되고 하나가 죽으면 뒤가 끊긴다
- "느리면 되고 빠르면 깨진다" → 배선·정전용량. 속도를 낮춰 확인하고 파형을 본다
- SD 카드는 초기화만 400kHz 이하, 이후 올린다. CS HIGH 로 80클럭 이상 먼저 준다
- 전이중이라 DMA 도 송·수신 둘 다 걸어야 한다
- ACK 가 없어 슬레이브 부재를 알 수 없다 — ID 레지스터를 읽어 확인한다
꼬리질문 대비
- "CPOL·CPHA 가 틀리면 어떤 증상인가?" → 값이 한 비트씩 밀리거나 전부 0/0xFF 로 읽힌다 — 통신은 되는데 값이 틀린 형태
- "데이터시트 최대 SCLK 가 50MHz 인데 실제 상한이 낮은 이유는?" → tV(마스터 출력)+전파+tSU(슬레이브)로 반주기 예산이 정해진다
- "클럭을 낮췄는데도 가끔 깨진다면?" → 홀드(tHD) 위반 가능성. CPHA 를 바꿔 샘플 엣지를 옮기거나 출력 타이밍을 조정
출처 — Motorola/NXP, SPI Block Guide V04.01 (CPOL·CPHA 정의) · STMicroelectronics, RM0090 STM32F4 Reference Manual §28 SPI(CR1·CR2·SR) · SD Association, Physical Layer Simplified Specification — SPI 모드 초기화 시퀀스