임베디드 학습 노트 목차

I²C — 선 두 개로 여럿을 다룬다

SPI 는 슬레이브마다 CS 핀이 필요했다. I²C 는 선 두 개로 최대 100여 개를 다룬다. 비결은 주소를 데이터로 보내는 것과 **오픈드레인 + 풀업**이다.

그리고 I²C 가 안 될 때 원인은 거의 정해져 있다 — 풀업 저항, 주소, 그리고 버스 잠김.


선 둘, 모두 오픈드레인

        VDD
         |
       [Rp]  [Rp]      풀업 저항 (둘 다 필요)
         |     |
SDA ─────┴─────┼───────┬───────────┬──────────
SCL ───────────┴───────┼───────┬───┼──────┬───
                       |       |   |      |
                    마스터   슬레이브A  슬레이브B

모든 장치가 당기기만 하고(open-drain) 밀지 않는다. 그래서 여럿이 같은 선에 붙어도 단락이 나지 않는다.

아무도 안 당기면  → 풀업이 HIGH 로 올린다
하나라도 당기면    → LOW

이 성질을 와이어드 AND 라 하고, I²C 의 중재·클럭 스트레칭·ACK 가 전부 이것 위에 서 있다.

풀업이 없으면 아무것도 안 된다. 오픈드레인은 HIGH 를 만들 수 없으므로 선이 계속 떠 있고, 통신은 시작조차 못 한다. "I²C 가 전혀 응답이 없다" 의 1순위 원인이다.

풀업 저항을 어떻게 고르나

너무 크면 → 상승이 느려 고속에서 파형이 삼각형이 된다
너무 작면 → 전류가 늘고 LOW 전압이 충분히 안 내려간다
상승 시간 ≈ 0.85 × Rp × Cb

Cb = 버스 전체 정전용량 (선 + 장치 핀, 규격 최대 400pF)
속도 모드최대 속도최대 상승 시간흔한 Rp
Standard100 kHz1000 ns4.7k~10k
Fast400 kHz300 ns2.2k~4.7k
Fast Plus1 MHz120 ns1k 내외
Fast 모드(300ns) 에 Cb=200pF 이면
  Rp ≤ 300ns / (0.85 × 200pF) ≈ 1.76kΩ

규격상 버스 정전용량 상한이 400pF 다. 선을 길게 뽑거나 장치를 많이 붙이면 이 한계에 닿아, 저항을 아무리 낮춰도 파형이 안 선다. 그때는 I²C 버스 버퍼나 리피터를 쓴다.

증상 진단
  파형이 삼각형 · 상승이 느리다   → Rp 가 너무 크거나 Cb 가 너무 크다
  LOW 가 0.5V 까지밖에 안 내려간다 → Rp 가 너무 작다 (전류 과다)

아래쪽 한계도 계산으로 나온다 — 저항은 "범위"다

위는 최대값(상승 시간)만 본 것이다. 최소값은 반대편, 즉 LOW 를 만들 수 있는가에서 나온다. 오픈드레인이라 LOW 는 슬레이브의 트랜지스터가 전류를 빨아들여(sink) 만드는데, 규격이 그 능력을 IOL 로 정해 둔다.

Rp(min) = (VDD − VOL(max)) / IOL

I²C 규격:  IOL = 3 mA,  VOL(max) = 0.4 V
VDD = 3.3V →  Rp(min) = (3.3 − 0.4) / 3mA ≈ 967 Ω
VDD = 5.0V →  Rp(min) = (5.0 − 0.4) / 3mA ≈ 1.53 kΩ

이보다 낮추면 트랜지스터가 다 못 빨아들여 LOW 가 0.4V 아래로 안 내려간다. 그러면 상대가 그 신호를 0 으로 못 읽어 통신이 깨진다 — 위 "증상 진단"의 두 번째 줄이 이것이다.

두 한계를 합치면 창(window)이 나온다 (VDD 3.3V, Cb 100pF 기준).

모드Rp(min) — LOW 보장Rp(max) — 상승 시간실무 선택
Standard 100kHz967 Ω11.8 kΩ4.7 kΩ (여유 큼)
Fast 400kHz967 Ω3.5 kΩ2.2 kΩ
Fast Plus 1MHz967 Ω1.4 kΩ1 kΩ (창이 거의 없다)

왜 4.7kΩ 이 관행이 됐나 — 100kHz·낮은 용량에서 창의 한가운데라 웬만해선 안 틀린다. 그런데 400kHz 로 올리거나 선을 길게 뽑아 Cb 가 커지면 창의 위쪽이 내려와 4.7kΩ 이 창 밖으로 밀려난다. "속도만 올렸는데 통신이 깨졌다"의 정체가 이것이다 — 저항은 그대로인데 허용 범위가 좁아진 것이다.

함정 — Cb 는 선 길이뿐 아니라 붙은 장치 수에도 비례한다(핀당 약 10pF). 센서를 하나 더 달았더니 깨지는 일이 그래서 생긴다.

꼬리질문 대비 — "풀업이 왜 필요한가?"(오픈드레인은 LOW 만 만들 수 있어 HIGH 를 저항이 만든다 — 그래서 와이어드 AND 가 되고 다중 마스터 중재가 가능하다). "Rp 를 최소값으로 하면 항상 좋은가?"(상승은 빨라지지만 LOW 소비 전류가 늘고 배터리 기기에서 문제 — 3.3V/1kΩ 이면 LOW 마다 3.3mA).

프레임 — 시작·주소·ACK·정지

I²C 데이터 전송 — START 조건, 주소, ACK, 데이터, STOP 순서가 한 그림에 있다.
I2C data transfer — Marcin Floryan, Public domain (Wikimedia Commons)

START      SCL 이 HIGH 인 동안 SDA 가 HIGH→LOW   (유일하게 이때만 SDA 가 변한다)
데이터      SCL 이 LOW 인 동안에만 SDA 를 바꾼다
ACK        받는 쪽이 9번째 클럭에 SDA 를 LOW 로 당긴다
STOP       SCL 이 HIGH 인 동안 SDA 가 LOW→HIGH

"SCL 이 HIGH 일 때 SDA 가 변하면 특수 신호" 라는 규칙 하나가 START·STOP 을 데이터와 구분한다. 별도의 제어선 없이 프레임 경계를 만드는 영리한 설계다.

쓰기:  [S] [주소+W] [A] [레지스터] [A] [데이터] [A] [P]
읽기:  [S] [주소+W] [A] [레지스터] [A] [Sr] [주소+R] [A] [데이터] [N] [P]
                                     ^ 반복 시작 — STOP 없이 방향을 바꾼다

반복 시작(repeated start) 이 중요하다. STOP 을 내면 버스가 풀려 다른 마스터가 끼어들 수 있다. 레지스터 주소를 쓴 뒤 값을 읽는 사이에 끼어들면 엉뚱한 레지스터를 읽는다. 그래서 STOP 없이 Sr 로 이어 간다.

주소 — 7비트인데 8비트로 쓴다

7비트 주소 0x68 을 보낼 때 실제 바이트는

  1101000 0  = 0xD0   쓰기 (R/W = 0)
  1101000 1  = 0xD1   읽기 (R/W = 1)
  └─0x68─┘ └ 방향

데이터시트가 어느 형식으로 적었는지 확인해야 한다. 어떤 문서는 0x68(7비트)로, 어떤 문서는 0xD0(8비트, 쓰기)로 적는다. 이걸 혼동해 주소를 두 배로 넣거나 절반으로 넣는 것이 초보의 대표적 실수다.

// STM32 HAL 은 8비트 형식(왼쪽 시프트된 값)을 받는다
HAL_I2C_Master_Transmit(&hi2c1, 0x68 << 1, buf, n, 100);
//                              ^^^^^^^^^ 7비트를 시프트
// 붙어 있는 장치를 전부 찾아보는 스캐너 — 가장 먼저 돌려 볼 코드
for (uint8_t a = 1; a < 128; a++) {
    if (HAL_I2C_IsDeviceReady(&hi2c1, a << 1, 1, 10) == HAL_OK)
        printf("found 0x%02X\r\n", a);
}

주소가 안 맞는지 확인하는 가장 빠른 방법이다. 아무것도 안 나오면 배선·풀업 문제, 예상과 다른 주소가 나오면 데이터시트 해석 문제다.

클럭 스트레칭 — 슬레이브가 시간을 번다

마스터가 SCL 을 놓아 HIGH 가 되려는 순간
슬레이브가 SCL 을 계속 LOW 로 당기고 있으면 → 마스터는 기다린다

오픈드레인이라 슬레이브도 SCL 을 당길 수 있다. 준비가 안 됐을 때 클럭을 붙잡아 마스터를 기다리게 하는 것이 클럭 스트레칭이다.

문제는 이것을 지원하지 않는 마스터가 있다는 것이다. 특히 소프트웨어로 I²C 를 구현한 경우 SCL 을 놓고 바로 다음으로 넘어가 버린다. 그러면 슬레이브가 준비되기 전에 데이터를 읽어 값이 깨진다.

// 소프트웨어 I²C 라면 반드시 확인해야 한다
static void scl_release(void) {
    SCL_HIGH();
    uint32_t t = 10000;
    while (!SCL_READ() && --t) { }        // ✓ 슬레이브가 놓을 때까지 기다린다
}

버스가 잠기면 — 실무에서 가장 골치 아픈 것

증상: SDA 가 계속 LOW 에 붙어 있고 아무 통신도 안 된다
원인: 슬레이브가 바이트를 절반만 보낸 상태에서 마스터가 리셋됐다
      슬레이브는 클럭을 더 받아야 다음 상태로 가는데 아무도 안 준다

전원을 껐다 켜야 풀리는 상황이라 제품에서 치명적이다. 해법이 정해져 있다.

// 버스 복구 — SCL 을 9번 이상 흔들어 슬레이브를 빼낸다
void i2c_bus_recover(void) {
    i2c_pins_to_gpio();                  // I2C 주변장치를 떼고 GPIO 로

    for (int i = 0; i < 9; i++) {        // 최대 9클럭이면 어떤 상태에서든 빠져나온다
        SCL_LOW();  delay_us(5);
        SCL_HIGH(); delay_us(5);
        if (SDA_READ()) break;           // SDA 가 풀렸으면 됐다
    }
    // STOP 조건을 만들어 확실히 마무리
    SDA_LOW();  delay_us(5);
    SCL_HIGH(); delay_us(5);
    SDA_HIGH();

    i2c_pins_to_af();                    // 다시 I2C 로
    I2C1->CR1 |= I2C_CR1_SWRST;          // 주변장치도 리셋
    I2C1->CR1 &= ~I2C_CR1_SWRST;
    i2c_init();
}

9클럭인 이유는 한 바이트가 8비트 + ACK 이므로, 어떤 지점에 멈춰 있든 9클럭 안에 프레임이 끝나기 때문이다.

이 복구 루틴을 초기화 시 한 번 무조건 돌리는 것이 실무 관행이다. 이전 실행에서 잠긴 채 리셋됐을 수 있기 때문이다.

다중 마스터와 중재

두 마스터가 동시에 시작하면
  각자 주소를 보내다가, 자기가 HIGH 를 보냈는데 선이 LOW 면
  → "다른 놈이 당겼다" 를 알아채고 즉시 물러난다

와이어드 AND 덕분에 충돌이 자동으로 해결된다. 0 을 보내는 쪽이 이기므로, 주소가 작은 쪽이 우선이다. 진 쪽은 데이터가 깨지지 않고 그냥 물러나 다시 시도한다.

CAN 의 중재와 같은 원리인데, I²C 는 이것을 선 두 개로 한다.

실무에서 데는 곳

// ✗ 주소를 시프트 안 함
HAL_I2C_Master_Transmit(&hi2c1, 0x68, ...);      // 실제로는 0x34 로 간다

// ✗ 풀업이 없거나 내부 풀업만 믿음
GPIOB->PUPDR |= (0b01 << (6*2));                  // 내부 풀업은 수십 kΩ — 너무 약하다

내부 풀업으로 I²C 를 돌리려는 시도가 자주 보인다. 100kHz 에 짧은 선이면 우연히 될 수도 있지만, 400kHz 나 선이 길면 파형이 안 선다. 외부에 4.7k 를 다는 것이 기본이다.

✗ 여러 장치가 같은 주소를 쓴다
  → ADDR 핀으로 주소를 바꿀 수 있는지 데이터시트 확인
  → 안 되면 I2C 멀티플렉서(TCA9548A 등)나 별도 버스
// NACK 를 무시하지 않는다
if (HAL_I2C_Master_Transmit(...) != HAL_OK) {
    // 슬레이브가 응답하지 않았다 — 재시도하거나 오류 처리
}

I²C 는 ACK 가 있어 응답 확인이 가능하다 — SPI 와 다른 큰 장점이다. 그걸 무시하고 반환값을 안 보면 그 이점을 버리는 것이다.


한눈에 정리

  • 모두 오픈드레인 + 풀업 — 와이어드 AND 가 중재·스트레칭·ACK 의 근거다
  • 풀업이 없으면 통신이 시작조차 안 된다 — 1순위 원인
  • 상승 시간 ≈ 0.85 × Rp × Cb, 버스 정전용량 규격 상한은 400pF
  • 파형이 삼각형이면 Rp·Cb 가 크고, LOW 가 안 내려가면 Rp 가 작다
  • "SCL 이 HIGH 일 때 SDA 가 변하면 특수 신호" 라는 규칙이 START·STOP 을 만든다
  • 반복 시작(Sr) 은 STOP 없이 방향을 바꿔 다른 마스터가 끼어드는 것을 막는다
  • **7비트 주소를 8비트로 시프트**해야 한다. 데이터시트가 어느 형식인지 확인
  • 주소 스캐너를 먼저 돌린다 — 아무것도 없으면 배선, 다르면 주소 해석
  • **클럭 스트레칭**은 슬레이브가 SCL 을 붙잡는 것. 소프트웨어 마스터는 반드시 기다려야 한다
  • 버스 잠김은 SCL 9클럭 + STOP 으로 복구한다. 초기화 시 무조건 한 번 돌린다
  • ACK 가 있으니 반환값을 확인한다 — SPI 에 없는 이점을 버리지 않는다

출처 — NXP, UM10204 I²C-bus specification and user manual Rev.7 (§3 규격·§7.1 전기 특성·400pF·상승 시간) · STMicroelectronics, RM0090 STM32F4 Reference Manual §27 I²C · STMicroelectronics, AN4235 STM32 I²C 최적화

SPI — 클럭을 같이 보내는 대신 선이 넷CAN — 차량이 쓰는 버스