I2C 통신 안될 때: 풀업 저항·슬레이브 주소·클록 점검 가이드
임베디드 보드를 제작하고 새로운 센서나 전력 관리 IC(PMIC)를 부착한 직후, 펌웨어 초기화 단계에서 버스가 응답하지 않고 무한 루프에 빠지는 현상은 하드웨어 엔지니어를 끊임없이 괴롭히는 대표적인 통신 병목입니다. 소프트웨어 디버거 상에서는 단순한 타임아웃 에러로 표시되지만, I2C 통신 안될 때 발생하는 버그의 근본 원인을 추적해보면 오픈 드레인 버스의 풀업 저항값 미스매칭, 7비트 주소 체계 비트 시프트 혼선, 그리고 슬레이브 장치의 클록 스트레칭에 따른 버스 잠김(Lock-up) 등 복합적인 하드웨어 및 프로토콜 레벨의 트레이드오프와 마주하게 됩니다.
I2C는 단 두 개의 라인(SDA, SCL)만으로 다중 마스터와 슬레이브를 제어할 수 있는 뛰어난 확장성을 제공하지만, 신호선이 능동적으로 전압을 밀어 올리는 푸시풀(Push-Pull) 방식이 아니기 때문에 물리 계층(PHY)의 기생 커패시턴스와 슬루율(Slew-rate) 왜곡에 극도로 취약합니다. 스코프를 연결하지 않고 코드 수준에서만 재시도를 반복하면 간헐적인 버스 정지 현상을 해결할 수 없으므로, 하드웨어 풀업 조건부터 슬레이브 주소 바이트 정합성까지 계층적인 단계별 디버깅이 선행되어야 합니다.

페이지내용
I2C 통신 안될 때: 오픈 드레인 풀업 저항과 RC 시정수 한계 분석
I2C 버스의 물리 계층은 모든 마스터와 슬레이브 디바이스의 내부 트랜지스터가 오픈 드레인(또는 오픈 컬렉터)으로 연결되어 있습니다. 라인을 High 레벨로 견인하는 유일한 수단은 외부 풀업 저항($R_p$)이며, 라인의 총 기생 커패시턴스($C_b$)와 결합하여 $RC$ 지연 네트워크를 형성합니다. 풀업 저항값이 과도하게 크면 신호의 상승 시간($t_r$)이 길어져 구형파가 톱니파로 왜곡되고, 반대로 너무 작으면 디바이스가 라인을 Low로 충분히 당기지 못해 $V_{OL}$ 임계치를 초과하게 됩니다.
스위칭 노이즈가 유입되거나 긴 연장선(FPCB 등)을 통해 센서가 연결된 환경에서는 배선 자체의 부유 커패시턴스가 수백 피코패럿(pF)까지 치솟아 신호 상승 에지가 둔화됩니다. 이로 인해 마스터와 슬레이브의 내부 슈미트 트리거 입력단이 논리 High를 안정적으로 인식하지 못하면서 NACK가 연속으로 발생하는 파경에 이릅니다.
"I2C 버스 사양에서 허용되는 최대 버스 커패시턴스는 표준 모드(100kHz) 및 패스트 모드(400kHz) 기준 400pF로 제한됩니다. 패스트 모드에서 최대 허용 상승 시간(tr)은 300ns이며, 슬레이브 장치의 출력 Low 전압(VOL)이 3mA 흡수 전류 조건에서 최대 0.4V를 넘지 않도록 풀업 저항의 최소치(Rp,min)와 최대치(Rp,max)를 엄격히 산정해야 합니다."
— NXP Semiconductors: I2C-bus specification and user manual (UM10204, Rev. 7.0)
NXP 표준 스펙에서 정의하는 바와 같이 400kHz Fast-mode 기준 버스 기생 용량이 150pF일 때, 풀업 저항의 상한선은 $R_{p(max)} = \frac{300\text{ns}}{0.8473 \times 150\text{pF}} \approx 2.36\text{k}\Omega$로 엄격히 제한됩니다. 만약 관습적으로 $10\text{k}\Omega$ 풀업 저항을 방치했다면 고속 통신 시 에지가 무너져 통신이 두절되는 것은 필연적인 결과입니다.

I2C 7비트 주소 체계와 8비트 R/W 비트 시프트 혼선
하드웨어 결선이 완벽함에도 불구하고 슬레이브 장치가 ACK를 전혀 반환하지 않는 두 번째 주요 원인은 7비트 슬레이브 주소(Slave Address)의 표현 방식 차이에 있습니다. I2C 통신에서 마스터가 START 컨디션 직후 전송하는 첫 번째 바이트는 상위 7비트의 슬레이브 주소와 최하위 1비트의 읽기/쓰기(R/W) 플래그(쓰기: 0, 읽기: 1)가 결합된 8비트 구조입니다.
반도체 제조사마다 데이터시트에 표기하는 주소 포맷이 상이합니다. 어떤 제조사는 7비트 순수 주소(예: 0x68)를 명시하는 반면, 다른 제조사는 이미 1비트 좌측 시프트 후 쓰기 비트(0)를 합산한 8비트 주소(예: 0xD0)를 기본 주소로 기재합니다. 펌웨어 드라이버 라이브러리(STM32 HAL, Linux I2C-dev, Arduino Wire 등)가 요구하는 주소 포맷을 일치시키지 않으면 마스터는 전혀 엉뚱한 주소로 패킷을 날리게 됩니다.
| 구분 항목 | 7비트 주소 방식 (Standard) | 8비트 쓰기 주소 (Write Addr) | 8비트 읽기 주소 (Read Addr) |
|---|---|---|---|
| 비트 배열 구조 | [A6 : A5 : A4 : A3 : A2 : A1 : A0] | [A6 : A5 : A4 : A3 : A2 : A1 : A0 : 0] | [A6 : A5 : A4 : A3 : A2 : A1 : A0 : 1] |
| 예시 1 (MPU6050 IMU) | 0x68 (Arduino Wire 라이브러리) | 0xD0 (STM32 HAL / C 임베디드) | 0xD1 |
| 예시 2 (24C02 EEPROM) | 0x50 | 0xA0 | 0xA1 |
| 소프트웨어 처리 방식 | 라이브러리가 내부에서 (Addr << 1) 처리 | 레지스터에 주소 바이트 직접 기입 | 최하위 비트에 1을 세트하여 전송 |
위 비교표에서 볼 수 있듯이, 개발 환경의 API 규격을 혼동하면 MCU는 엉뚱한 슬레이브 ID를 호출하게 되며, 당연히 아무런 응답 장치도 없으므로 9번째 클록에서 SDA 라인이 High로 유지되어 NACK 에러가 발생합니다. 이를 검증하는 가장 빠르고 확실한 방법은 버스 상의 모든 주소를 순차 폴링하는 스캐너 코드를 실행하는 것입니다.

실전 I2C 전체 주소 스캐너 펌웨어 및 응답 검증 스니펫
실제 타깃 하드웨어에 연결된 슬레이브의 물리적 주소를 판별하기 위해, 유효한 7비트 주소 범위(0x08 ~ 0x77) 전체로 쓰기 요청을 보내고 ACK 응답 여부를 직렬 터미널(UART)로 출력하는 STM32 C 펌웨어 드라이버 스니펫입니다.
#include <stdio.h>
#include "stm32f4xx_hal.h"
extern I2C_HandleTypeDef hi2c1;
extern UART_HandleTypeDef huart2;
// I2C 버스 상의 모든 7비트 주소(0x08 ~ 0x77) 폴링 스캔 함수
void I2C_Scan_Bus(void) {
char uart_buf[64];
uint8_t devices_found = 0;
printf("\r\n--- I2C Bus Scanning Started ---\r\n");
for (uint8_t addr7 = 0x08; addr7 <= 0x77; addr7++) {
// STM32 HAL은 8비트 주소 포맷(Addr << 1)을 인자로 요구함
uint16_t dev_addr = (uint16_t)(addr7 << 1);
// 1회 시도, 타임아웃 10ms로 장치 준비 상태 폴링
HAL_StatusTypeDef result = HAL_I2C_IsDeviceReady(&hi2c1, dev_addr, 1, 10);
if (result == HAL_OK) {
snprintf(uart_buf, sizeof(uart_buf), "Device Found! 7-bit: 0x%02X | 8-bit(W): 0x%02X\r\n", addr7, dev_addr);
HAL_UART_Transmit(&huart2, (uint8_t*)uart_buf, strlen(uart_buf), 100);
devices_found++;
}
}
if (devices_found == 0) {
printf("No I2C devices responded! Check pull-up resistors and power rail.\r\n");
} else {
printf("--- Scanning Finished: %d device(s) found ---\r\n", devices_found);
}
}위 코드에서 핵심은 `dev_addr`를 구성할 때 `addr7 << 1` 처리를 통해 최하위 R/W 비트를 위한 공간을 만들어 HAL 함수에 전달하는 점입니다. 만약 스캐너를 실행했음에도 단 하나의 장치도 감지되지 않는다면 주소의 문제가 아니라 버스 라인 자체가 물리적으로 단락되었거나 전원이 차단된 상태입니다.
→
2단계: 7비트 주소 비트시프트 검증
→
3단계: 클록 스트레칭 및 SDA 접지 고착 확인
→
4단계: 수동 9클록 펄스 버스 언록 복구

슬레이브 버스 잠김(Lock-up) 탈출: 9-클록 강제 토글 복구 기법
마스터가 데이터를 수신하던 도중 시스템 인터럽트나 하드웨어 워치독에 의해 MCU만 단독 리셋되는 경우, 슬레이브 장치는 여전히 데이터를 전송 중인 상태로 남아 SDA 라인을 접지(0V)로 붙잡고 있게 됩니다. 마스터는 리셋 후 I2C 버스를 다시 시작하려 하지만 SDA 라인이 이미 Low 상태로 고착되어 있어 START 컨디션(SCL High 상태에서 SDA 하강)을 생성하지 못하고 영구적인 Bus Busy 락에 걸립니다.
이 치명적인 교착 상태를 탈출하기 위해 펌웨어 부팅 초기화 루틴에 GPIO 수동 9-클록 펄스 토글(9-Clock Recovery) 시퀀스를 강제 주입해야 합니다. SCL 라인을 최대 9번 수동으로 토글해주면 슬레이브는 자신이 전송 중이던 1바이트 데이터 시프트 레지스터를 비우고 마스터로부터 NACK를 기다리는 상태로 빠져나와 SDA 라인을 High로 해제합니다.
// SCL 및 SDA 핀을 GPIO 오픈드레인 모드로 전환하여 버스 락을 해제하는 복구 루틴
void I2C_Bus_Clear_Recovery(void) {
// 1. 하드웨어 I2C 모듈 비활성화
I2C1->CR1 &= ~I2C_CR1_PE;
// 2. PB6(SCL) 및 PB7(SDA)를 범용 GPIO 오픈드레인 출력 모드로 재구성
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// 3. SDA 라인이 Low인지 검사 (슬레이브에 의한 고착 여부 확인)
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_RESET) {
// 최대 9번 SCL 클록을 수동으로 클러킹하여 슬레이브 시프트 레지스터 방출 유도
for (uint8_t i = 0; i < 9; i++) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET); // SCL Low
for (volatile int d = 0; d < 100; d++);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); // SCL High
for (volatile int d = 0; d < 100; d++);
// 슬레이브가 SDA를 릴리즈했는지 실시간 확인
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET) {
break;
}
}
}
// 4. STOP 컨디션 강제 발생 (SDA Low 상태에서 SCL High 후 SDA High 전이)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET); // SDA Low
for (volatile int d = 0; d < 100; d++);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); // SCL High
for (volatile int d = 0; d < 100; d++);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); // SDA High
for (volatile int d = 0; d < 100; d++);
// 5. 핀을 다시 하드웨어 I2C 얼터너티브 기능(AF)으로 복구하고 I2C 모듈 재기동
HAL_I2C_DeInit(&hi2c1);
HAL_I2C_Init(&hi2c1);
}위 복구 루틴을 부팅 시퀀스의 최우선 단계에 배치하면, 현장에서 비정상적인 전원 리셋이 발생하더라도 슬레이브 고착으로 인해 기기 전체가 정지하는 소프트웨어적 장애를 원천 방어할 수 있습니다.
I2C 통신 신뢰성 확보를 위한 실무 점검 체크리스트
- 저항값 실측: 전원을 차단한 상태에서 SDA-VCC, SCL-VCC 사이의 합성 저항을 측정하여 $2.2\text{k}\Omega \sim 4.7\text{k}\Omega$ 범위에 위치하는지 검증한다.
- 공통 접지(GND) 루프: 분리된 전원을 사용하는 모듈 간 연결 시 기준 접지선이 최단 거리로 직결되었는지 확인한다.
- 클록 스트레칭 지원 여부: 슬레이브 장치가 연산 지연을 위해 SCL을 잡고 있을 때 마스터 MCU의 하드웨어 I2C 컨트롤러가 이를 지원하는지(NOSTRETCH 비트 해제) 확인한다.
- 이종 전압 간섭 배제: 5V 슬레이브와 3.3V 마스터를 혼용할 경우 MOSFET 기반 양방향 레벨 시프터를 반드시 삽입하여 내부 ESD 다이오드 도통을 차단한다.

실무 엔지니어링 FAQ
Q1. MCU 내부 풀업 저항(Internal Pull-up)만으로 I2C 통신을 구동할 수 있습니까?
원칙적으로 권장되지 않습니다. MCU 내부 풀업 저항은 통상 $30\text{k}\Omega \sim 50\text{k}\Omega$ 수준으로 매우 높은 고임피던스입니다. 이는 10kHz 수준의 극단적인 저속 통신에서만 겨우 동작할 뿐, 100kHz 표준 모드에서도 $RC$ 시정수 지연으로 인해 파형 상승 시간이 수 마이크로초에 달해 통신 불능 상태가 됩니다. 반드시 외장 저항을 물리적으로 배치해야 합니다.
Q2. 버스 상에 여러 개의 센서 모듈을 연결할 때 풀업 저항은 어디에 달아야 합니까?
각 센서 보드마다 자체 풀업 저항이 탑재되어 있는 경우가 많으므로 주의해야 합니다. 4개의 센서 모듈에 각각 $4.7\text{k}\Omega$ 풀업 저항이 장착되어 있다면 병렬 합성 저항값은 약 $1.17\text{k}\Omega$으로 급락합니다. 이는 드라이버의 싱크 전류 한계를 초과하여 Low 레벨 전압($V_{OL}$)이 상승하는 부작용을 일으키므로, 버스 라인 전체에서 단 한 쌍(SDA 1개, SCL 1개)의 최적 합성 저항만 남기고 나머지 모듈의 풀업 저항은 제거해야 합니다.
Q3. I2C 주소 충돌이 발생하는 동일 센서 2개를 단일 버스에서 제어하는 방법은 무엇입니까?
하드웨어 핀(예: AD0, ADDR)을 VCC 또는 GND로 변경하여 7비트 주소 최하위 비트를 변경할 수 있는지 확인합니다. 주소 변경 핀이 없는 동일 디바이스라면 TCA9548A와 같은 I2C 멀티플렉서 IC를 전단에 배치하여 채널을 소프트웨어로 스위칭하거나, MCU의 별도 독립 I2C 하드웨어 채널(I2C2 등)로 분리 라우팅해야 합니다.
공식 기술 가이드 및 표준 규격 레퍼런스
이 글은 정보 제공을 목적으로 작성되었으며, 실제 기기·환경·정책에 따라 결과가 다를 수 있습니다. 보다 정확하고 최신인 정보는 아래 공신력 있는 기관의 자료를 함께 참고하시기 바랍니다.




