The Sensor Is Fine. The Board Cannot Understand It
I Do Not Even Know Which Bus This File Is
한국어 원문으로 표시합니다.
목표
I2C 포착 네 편과 SPI 포착 세 편을 직접 해독하고, 마지막에 이름이 붙어 있지 않은 포착 셋을 받아 어떤 버스인지 가려내고 고장에 숫자 증거를 붙여 보고합니다.
왜 중요한가
드라이버가 내놓는 "장치가 응답하지 않습니다" 한 줄에는 서로 다른 원인 넷이 뭉쳐 있습니다. 주소가 틀린 것, 슬레이브가 시간을 버는 것, 풀업이 약해 상승이 비트 시간을 다 쓰는 것, 버스가 통째로 잠긴 것. 이 넷은 파형에서 전혀 다르게 생겼고, 고칠 사람도 각각 다릅니다 — 펌웨어, 드라이버의 복구 절차, 하드웨어. 파형에서 가려내면 보고서 한 줄이 담당자 한 명을 지목합니다. SPI 쪽은 반대로 클럭이 함께 오므로 속도 문제가 없고, 대신 어느 모서리에서 읽느냐와 선택선이 언제 내려가느냐가 조용한 고장을 만듭니다.
단계
- 두 선짜리 포착을 읽는다 — 표본 속도, 상승 시간, SCL 저 구간의 분포를 잽니다.
- START 와 STOP 을 찾는다 — SCL 이 높은 동안의 SDA 변화만 조건으로 셉니다.
- 주소와 ACK 를 읽어 낸다 — 9비트 묶음으로 바이트와 ACK 를 복원합니다.
- 아무도 응답하지 않은 주소 — NACK 가 실제로는 무엇의 부재인지 확인합니다.
- 마스터가 만들지 않은 구간 — 클럭 늘이기를 길이 분포로 찾아냅니다.
- 상승이 비트 시간을 다 쓴다 — 약한 풀업을 상승 시간과 임계값 두 개로 증명합니다.
- 네 조합 중 어느 것인가 — 유휴 준위와 데이터가 바뀌는 모서리로 모드를 정합니다.
- 선택선이 늦으면 개수가 먼저 말한다 — 8의 배수가 깨지는 것을 봅니다.
- 이름 없는 포착 셋을 가려낸다 — 버스와 고장을 증거와 함께 보고합니다.
참고
- 재료는
/opt/fixtures/serialbus/아래에 있습니다.i2c/read-ok.csv,i2c/nack.csv,i2c/stretch.csv,i2c/weak-pullup.csv,spi/mode0.csv,spi/mode3.csv,spi/cs-late.csv, 그리고unknown/capture-1.csv부터capture-3.csv까지입니다. 형식 설명은/opt/fixtures/serialbus/README.md입니다. - 채널 이름은 파일마다 다릅니다. I2C 는
scl·sda, SPI 는cs·sclk·mosi·miso, 정체를 모르는 포착은ch0부터입니다. 이름에 의미를 기대하지 말고 머리말에서 읽으세요. - 흔한 실수 하나: SCL 이 높은 동안의 SDA 변화를 찾을 때 모서리와 겹치는 표본까지 세는 것.
scl[i]와scl[i-1]이 둘 다 높을 때만 조건입니다. - 흔한 실수 둘: 마지막 바이트의 NACK 를 고장으로 보고하는 것. 읽기 전송에서는 정상 종료 절차입니다.
- 공급 전압은 3.30 V 이고 기본 판정 임계값은 그 절반인 1.65 V 입니다. VIH 를 쓰라고 한 단계에서만 0.7 배를 씁니다.
- 추가 설치도 인터넷도 필요 없습니다. python3 만 씁니다.
- 예상 85분이므로 기본 60분이 끝나기 전에 +시간으로 연장하세요(최대 180분). 세션이 끝나면
/root가 사라지므로 남기고 싶은 것은 따로 보관하세요.
두 선짜리 포착을 읽는다
mkdir -p /root/bus-triage 로 작업 폴더를 만들고 /opt/fixtures/serialbus/i2c/read-ok.csv 를 읽어 /root/bus-triage/levels.json 에 sample_rate_hz, sample_count, duration_us, channels(머리말의 채널 이름 목록), scl_rise_time_samples, sda_rise_time_samples, scl_low_runs, scl_low_median_us 를 저장하세요. 상승 시간은 포착 파일에서 마지막으로 일어나는 완전한 상승(0.33 V 이하에서 2.97 V 이상까지)의 표본 수이고, SCL 저 구간은 내려간 표본부터 다시 올라간 표본까지입니다.
0.33 V 와 2.97 V 는 3.30 V 의 10 % 와 90 % 입니다. 저 구간은 하강 모서리와 그다음 상승 모서리 사이이고, 마지막까지 낮은 채로 끝나는 구간은 길이를 알 수 없으니 세지 않습니다.
START 와 STOP 을 찾는다
/root/bus-triage/i2c.py 에 conditions(scl_v, sda_v, threshold_v=1.65) 를 구현하세요. SCL 이 두 표본 연속 높은 동안 SDA 가 내려가면 ("start", i), 올라가면 ("stop", i) 를 시간 순서대로 담아 돌려줍니다. SCL 이 낮을 때의 SDA 변화는 조건이 아닙니다. read-ok.csv 에 적용해 /root/bus-triage/i2c-frames.json 에 start_count, stop_count, starts, stops(표본 번호 목록), scl_high_windows(올라갔다가 다시 내려간 것까지 확인된 SCL 고 구간의 개수 — 끝까지 높은 채로 남은 구간은 길이를 알 수 없으니 세지 않습니다)를 저장하세요.
조건 판정은 scl[i] 와 scl[i-1] 이 둘 다 높을 때만 합니다. 모서리와 겹치는 순간을 빼야 반복 START 를 데이터로 착각하지 않습니다. 채점기는 짧은 시험표로 먼저 확인합니다.
주소와 ACK 를 읽어 낸다
read-ok.csv 를 끝까지 해독하세요. SCL 고 구간의 가운데에서 SDA 를 읽어 비트를 모으고, 9비트마다 앞 8비트를 높은 자리부터 묶어 바이트로, 아홉 번째 비트가 0이면 ACK 로 봅니다. /root/bus-triage/i2c-read.json 에 bytes(각 항목은 value 와 ack), address(첫 바이트를 1비트 오른쪽으로), first_rw, register, second_rw, data(세 번째 바이트 뒤의 값들), repeated_starts(표본 번호 목록), last_byte_acked 를 저장하세요. first_rw 와 second_rw 는 "read" 또는 "write" 입니다.
반복 START 는 전송을 끊지 않으므로 바이트를 세는 흐름을 초기화하되 전송은 이어 갑니다. 마지막 바이트의 NACK 는 고장이 아니라 마스터가 그만 보내라고 알리는 정상 절차입니다.
아무도 응답하지 않은 주소
/opt/fixtures/serialbus/i2c/nack.csv 를 해독해 /root/bus-triage/i2c-nack.json 에 start_n, stop_n, byte_count, address, rw, acked(아홉 번째 비트가 0이었는가), data_bytes_after_address(전체 바이트 수 빼기 1)를 저장하세요.
ACK 자리에서 마스터는 SDA 를 놓습니다. 높게 남았다는 것은 아무도 끌어내리지 않았다는 뜻이고, 주소 오류·전원 없음·장치 고장이 전부 같은 모양입니다. 마스터가 곧바로 STOP 을 내는 것도 확인하세요.
마스터가 만들지 않은 구간
/opt/fixtures/serialbus/i2c/stretch.csv 의 SCL 저 구간 길이를 전부 재서 /root/bus-triage/i2c-stretch.json 에 scl_low_runs, median_low_us, max_low_us, stretch_us(최댓값 빼기 중앙값), stretch_start_n(가장 긴 구간이 시작하는 표본 번호), bytes(해독된 바이트 목록), stop_n 을 저장하세요.
중앙값을 쓰는 이유는 평균이 긴 구간 하나에 끌려가기 때문입니다. 늘어난 구간이 몇 번째 바이트 뒤에 있는지 표본 번호로 짚어 보면 슬레이브가 언제 시간을 벌었는지 보입니다.
상승이 비트 시간을 다 쓴다
/opt/fixtures/serialbus/i2c/weak-pullup.csv 를 재세요. /root/bus-triage/i2c-pullup.json 에 sda_rise_time_us, scl_rise_time_us(둘 다 10 % 에서 90 % 까지), scl_high_windows(2단계와 같은 정의), sda_peak_v(각 SCL 고 구간 안 SDA 최댓값들의 최댓값), ones_at_1v65 와 ones_at_vih(그 구간 최댓값이 각각 1.65 V 와 VIH 이상인 구간의 수), vih_v(0.7 곱하기 3.30 을 소수 둘째 자리로 반올림), stops_at_1v65 와 stops_at_vih(SDA 판정 임계값만 바꿨을 때 검출되는 STOP 조건의 수)를 저장하세요.
SDA 와 SCL 의 상승 시간을 견주면 어느 선의 풀업이 약한지 바로 보입니다. STOP 개수가 임계값에 따라 달라진다면, 느린 상승이 SCL 고 구간 안으로 넘어와 조건처럼 보인다는 뜻입니다.
네 조합 중 어느 것인가
/root/bus-triage/spi.py 에 decode(cs_v, sclk_v, mosi_v, miso_v, cpol, cpha, threshold_v=1.65) 를 구현하세요. CS 가 낮은 동안, cpha 가 0이면 (1 빼기 cpol) 로 가는 모서리에서, 1이면 cpol 로 가는 모서리에서 표본합니다. {"edges": 표본한 모서리 수, "mosi": 바이트 목록, "miso": 바이트 목록, "leftover_bits": 모서리 수를 8로 나눈 나머지} 를 돌려주고 비트는 높은 자리부터 묶습니다. /opt/fixtures/serialbus/spi/mode0.csv 와 mode3.csv 를 각각 판정해 /root/bus-triage/spi-mode.json 에 mode0 과 mode3 두 블록으로 sclk_idle, cpol, cpha, mode, sampling_edges, mosi_changes_on, mosi, miso 를 저장하세요.
cpol 은 첫 표본의 클럭 준위입니다. cpha 는 MOSI 가 바뀌는 시각 바로 앞(8 표본 이내)의 클럭 모서리 방향으로 정합니다 — 그 모서리는 읽는 쪽이 아닙니다. 그 방향이 유휴 준위와 같으면 cpha 는 0, 다르면 1 입니다. mode 는 cpol 곱하기 2 더하기 cpha 입니다.
선택선이 늦으면 개수가 먼저 말한다
/opt/fixtures/serialbus/spi/cs-late.csv 를 모드 0 으로 해독해 /root/bus-triage/spi-cs.json 에 cs_low_start, cs_low_end(CS 가 내려간 표본과 다시 올라간 표본), sclk_edges_total(포착 전체의 클럭 모서리 수), sampling_edges_total(그 절반), sampling_edges_in_cs(CS 가 낮은 동안 표본한 모서리 수), leftover_bits, bytes(MOSI 바이트 목록)를 저장하세요.
값보다 개수가 먼저 이상해집니다. CS 밖으로 빠진 모서리가 있으면 8의 배수가 깨지고 남는 비트가 생깁니다. 그 상태로 묶은 바이트는 전부 한 칸씩 밀려 있습니다.
이름 없는 포착 셋을 가려낸다
/opt/fixtures/serialbus/unknown/ 의 capture-1.csv, capture-2.csv, capture-3.csv 를 판정해 /root/bus-triage/triage.json 에 capture_1, capture_2, capture_3 세 블록을 저장하세요. 세 블록 모두 channels(채널 수), bus("uart"·"i2c"·"spi" 중 하나), fault("polarity-inverted"·"sda-stuck-low"·"miso-idle"·"baud-mismatch"·"address-nack"·"clock-stretch"·"weak-pullup"·"cs-timing"·"mode-mismatch" 중 하나)를 담습니다. 여기에 더해 capture_1 은 idle_v·baud·text 를, capture_2 는 start_count·stop_count·longest_sda_low_us 를, capture_3 은 miso_edges_in_cs·miso_bytes·mosi_bytes 를 담습니다.
채널 수와 유휴 준위만으로 버스가 거의 정해집니다. 한 줄인데 낮게 쉰다면 모든 비트를 뒤집어 읽어 보세요. 두 줄인데 STOP 이 없다면 무엇이 STOP 을 막고 있는지 보세요. 네 줄에서 전송 내내 한 번도 안 바뀌는 선은 그 자체가 증거입니다.