| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
- 하드웨어 디버깅
- Azure RTOS
- bus-off
- error-passive
- fsl-sai
- xHCI
- can_filter
- CAN FD
- can-utils
- EASRC
- 임베디드 리눅스
- PTN5150A
- i.MX8MP
- TIM17
- 오디오 클럭
- SocketCAN
- 44.1kHz
- 임베디드
- CAN 필터
- CC 로직
- 임베디드 오디오
- 리눅스 커널
- vcan
- 타임베이스
- assigned-clocks
- EILSEQ
- 커스텀 보드
- 리눅스 CAN
- HAL_Delay
- restart-ms
- Today
- Total
임베디드 일기장
I2C SCL이 LOW에 박혀서 안 올라올 때 — i.MX8MP에서 버스 행(hang) 진단과 복구 전략 본문
I2C 버스를 오래 다루다 보면 언젠가 반드시 만나는 증상이 있다. SCL(또는 SDA)이 LOW로 떨어진 채 고착되고, 시스템을 리셋해야만 다시 HIGH로 올라오는 상황이다. 이번 글은 i.MX8MP 보드(커널 5.15.x)에서 이 증상을 추적하면서 정리한 내용이다. 결론부터 말하면 원인은 풀업 저항이 아니었고, PCA9548 멀티플렉서 하위 채널의 SDA 쇼트였다.
환경
- 보드: i.MX8MP (LPI2C 컨트롤러, 드라이버
i2c-imx.c) - 버스 구성: PCA9548 8채널 I2C mux 하위에 EEPROM(AT24), 온도센서(TMP102), 전력모니터(INA260) 등 다수 슬레이브
- 증상: 동작 중 SCL이 LOW로 떨어진 뒤 복구 불가, 시스템 리셋 시에만 회복
1. 첫 번째 용의자(오답): 풀업 저항 불일치
보드를 보니 SCL 풀업이 3.0kΩ, SDA 풀업이 2.4kΩ으로 서로 달랐다. 처음엔 이게 의심스러웠는데, 계산해 보면 무관하다는 결론이 나온다.
I2C의 SCL/SDA는 각자 자기 풀업으로 독립적으로 충방전한다. 버스 커패시턴스를 100pF로 잡으면:
SCL rise time ≈ 2.2 × 3.0kΩ × 100pF = 660 ns
SDA rise time ≈ 2.2 × 2.4kΩ × 100pF = 528 ns
Fast-mode(400kHz)의 rise time 한계 300ns는 초과하지만 Standard-mode(100kHz)의 1000ns 안에는 둘 다 들어온다. 각 라인이 독립적으로 스펙을 만족하면 값이 서로 달라도 기능상 문제가 없다. 무엇보다, 풀업 문제라면 파형이 뭉개지는 식으로 나타나지 LOW 고착으로 나타나지 않는다. 용의선상에서 제외.
2. SCL stuck LOW의 진짜 원인 후보 3가지
① Clock stretching 무한 대기 (가장 흔함) — 슬레이브가 SCL을 LOW로 잡고 놓지 않는 상태. 마스터가 SCL을 올리려 해도 슬레이브가 계속 끌어내리고 있으면, 타임아웃 처리가 없는 한 영원히 기다린다.
② SDA stuck LOW로 인한 2차 고착 — 트랜잭션 도중 슬레이브가 SDA를 LOW로 잡은 채 내부 상태가 꼬이면(예: 전송 중간에 슬레이브만 리셋), 슬레이브는 다음 클럭을 기다리는 상태로 굳고 마스터는 START/STOP 조건을 만들 수 없게 된다.
③ 마스터 컨트롤러 자체 hang — 드라이버가 busy 상태에서 복구하지 못하고 SCL 구동을 멈추는 경우.
구분하려면 고착 시점에 SDA가 어디 있는지가 핵심이다. 오실로스코프가 최선이고, 없으면 해당 핀을 GPIO로 읽어서라도 확인한다.
[파형 추가 예정]
내 경우는 ②였다. 특정 채널의 슬레이브 쪽에서 SDA가 그라운드로 쇼트되어 있었다.
3. PCA9548인데 왜 다른 채널까지 전멸하나
여기서 의문이 생긴다. PCA9548은 채널별로 개별 스위치가 있는 구조인데, 한 채널의 SDA가 죽었다고 왜 버스 전체가 먹통이 될까.
Master SDA ──┬── [SW0] ── CH0 SDA (stuck LOW)
├── [SW1] ── CH1 SDA
├── [SW2] ── CH2 SDA
└── ...
두 가지 이유가 겹친다. 첫째, 스위치는 개별이어도 마스터 쪽 SDA 라인은 공유된다. 문제 채널의 스위치가 닫혀(선택되어) 있는 동안 stuck LOW가 마스터 라인으로 그대로 올라온다. 둘째, PCA9548 내부 스위치는 FET 기반이라 완전한 갈바닉 절연이 아니다. OFF 상태에서도 누설이 존재해서, 하위에서 강하게 LOW로 끌면 비활성 채널을 통해서도 마스터 라인에 영향을 줄 수 있다.
그리고 결정적으로 — 일단 마스터 SDA가 LOW에 박히면 PCA9548 자체에 명령을 보낼 수가 없다. 채널 전환은 STOP condition 이후 mux의 제어 레지스터에 새 트랜잭션으로 write하는 방식인데, SDA가 LOW면 START condition부터 성립하지 않는다. i2c-mux-idle-disconnect DTS 프로퍼티를 걸어두면 매 트랜잭션 후 채널을 끊어주므로 예방에는 도움이 되지만, 이미 고착된 뒤에는 그 disconnect 명령조차 전달되지 않는다. 예방책이지 복구책이 아니라는 점을 구분해야 한다.
4. 커널의 복구 메커니즘 — 언제, 어떻게 동작하나
리눅스 커널에는 I2C 버스 복구 프레임워크가 있다. 자주 오해하는 부분부터 짚으면, 주기적으로 버스 상태를 감시하는 게 아니다. LPI2C 드라이버(i2c-imx-lpi2c.c)에는 감시 타이머나 워크큐가 없고, 복구는 트랜잭션이 실패한 시점에 트리거된다.
/* i2c-imx-lpi2c.c 흐름 */
i2c_imx_xfer_common()
→ i2c_imx_start()
→ 전송 시도 실패
→ i2c_recover_bus(adapter)
→ i2c_generic_scl_recovery()
i2c_generic_scl_recovery()가 하는 일은 고전적인 9-클럭 기법이다. SCL을 9번 토글해서, 데이터 비트를 물고 있던 슬레이브가 나머지 비트를 shift-out 하고 SDA를 놓게 만든 뒤 STOP condition을 만들어 버스를 idle로 되돌린다. 이 기능을 쓰려면 DTS에 복구용 GPIO 경로를 알려줘야 한다.
&i2c2 {
pinctrl-names = "default", "gpio";
pinctrl-0 = <&pinctrl_i2c2>;
pinctrl-1 = <&pinctrl_i2c2_gpio>;
scl-gpios = <&gpio1 X (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
sda-gpios = <&gpio1 Y (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
};
핀을 I2C 기능에서 GPIO로 잠시 바꿔 비트뱅잉으로 클럭을 만들기 때문에 pinctrl의 "gpio" 상태 정의가 함께 필요하다.
단, 9-클럭 복구에는 한계가 있다. 슬레이브가 프로토콜 상태만 꼬인 경우에는 잘 듣지만, 내 경우처럼 SDA가 물리적으로 그라운드에 쇼트된 수준이면 클럭을 아무리 줘도 풀리지 않는다. 소프트웨어 복구가 듣는 고장과 안 듣는 고장을 구분해야 한다.
5. 소프트웨어로 안 풀릴 때: 하드웨어 복구 경로
- PCA9548 RESET 핀 활용: active LOW로 최소 500ns 펄스를 주면 제어 레지스터가 0x00(전 채널 disable)으로 초기화된다. 문제 채널이 물리적으로 분리되므로 마스터 쪽 버스는 살아난다. RESET 핀을 SoC GPIO에 배선해 두는 것이 이런 상황의 보험이다.
- 슬레이브 전원 도메인 분리: 문제 슬레이브만 전원을 껐다 켤 수 있으면 시스템 리셋 없이 복구 가능하다.
- 채널별 버퍼 IC: 신뢰성이 중요한 설계라면 채널마다 버퍼를 넣어 물리적으로 격리한다.
시스템 리셋으로만 복구되던 증상의 정체는 결국 "리셋하면서 슬레이브 전원이 함께 재인가되어 쇼트 상태가 풀렸던 것"이었다. 즉 처음부터 소프트웨어 문제가 아니었다.
6. 진단 체크리스트
같은 증상을 만났을 때의 순서를 정리하면:
- 고착 시점의 SCL과 SDA 상태를 둘 다 확인한다 (스코프 또는 GPIO 읽기)
dmesg에서 timeout/arbitration lost 메시지와 마지막으로 접근한 슬레이브를 특정한다- SDA도 LOW라면: 어떤 채널/슬레이브를 분리했을 때 풀리는지 이분탐색한다
- 프로토콜 꼬임 수준이면
i2c_recover_bus()경로(DTS scl-gpios/sda-gpios)를 갖춘다 - 물리적 쇼트/래치업 수준이면 RESET 핀, 전원 도메인 등 하드웨어 복구 경로를 설계에 넣는다
- mux 구성이라면
i2c-mux-idle-disconnect는 예방책으로 항상 켜둔다
'Linux' 카테고리의 다른 글
| SocketCAN CAN FD 실전 설정 — ip link 구성, write() EILSEQ 에러, ID 마스크 필터링까지 (0) | 2026.07.11 |
|---|---|
| 리눅스 RS-485 DE 핀 제어, libmodbus에 맡기지 마세요 — TIOCSRS485로 커널에 위임하기 (0) | 2026.07.11 |
| 리눅스 ISO-TP에서 read()가 "Communication error on send"를 뱉는 이유 — sk_err와 커널 에러 전파 메커니즘 (0) | 2026.07.11 |
| 운영체제란 (0) | 2026.07.11 |
| i.MX8MP에서 TX700(Qualcomm MDM9207) LTE 모뎀 RNDIS 연결하기 — lsusb에 안 보이는 것부터 인터넷 연결까지 (0) | 2026.07.11 |