I2C/SCCB、register
Orin 上感測器經 I2C 控制(GMSL 時透過 deserializer 的 I2C 通道)。OV9281 常見 7-bit 位址 0x36。
sudo i2cdetect -y <bus> # 依 DTB 的 i2c 匯流排 sudo i2cget -y 0 0x36 0x300a # OV9281 ID 高位 = 0x92 sudo i2cget -y 0 0x36 0x300b # = 0x81
| Register | 功能 |
|---|---|
| 0x300A/0x300B | 感測器 ID |
| 0x3500–0x3503 | 曝光時間 |
| 0x350A–0x350B | 類比增益 |
OV 感測器用 16-bit register 位址(big-endian):
sudo i2cget -y 0 0x36 0x300a # 預期 OV9281 ID 高位I2C 一次 transaction:START → 7-bit 位址 + R/W → ACK → 資料位元組 → STOP。感測器位址左移 1 位當作控制位元:0x36 << 1 | 0 = 寫,0x36 << 1 | 1 = 讀。
i2cdetect 就是靠「送位址看有沒有 ACK」來列裝置——讀到 NACK 通常代表位址錯或感測器沒上電。GMSL 的感測器 I2C 不是直接接在 Orin 的匯流排上,而是經 deserializer 轉送。deserializer 可把感測器位址重映射(remap)到不同匯流排位址,避免多顆同型感測器衝突。
# 1. 先用 vendor 工具或直接下 deserializer 指令確認 link lock sudo i2cget -y 0 0x29 0x0012 # 範例:MAX96712 lock register(示意) # 2. 確認 remap 後的感測器位址 sudo i2cdetect -y 0 # 3. 才讀感測器 ID sudo i2cget -y 0 0x36 0x300a
# 讀 OV9281 ID:w2 = 寫 2 位元組位址,r1 = 讀 1 位元組 sudo i2ctransfer -y 0 w2@0x36 0x30 0x0a r1 # 預期回傳 0x92 # 寫入後回讀驗證(例:設曝光高位) sudo i2ctransfer -y 0 w3@0x36 0x35 0x00 0x50 sudo i2ctransfer -y 0 w2@0x36 0x35 0x00 r1 # 應回 0x50
「寫入 → 回讀 → 比對」是驗證 register 是否真的寫進感測器的唯一方法。寫了讀不回 = 電源/reset/時序問題(回顧單元 6)。
1. i2cdetect 看得到裝置? ├─ 看不到 ─┬─ 非 GMSL:位址/上拉/電源 │ └─ GMSL:先查 serdes lock 與 remap └─ 看得到 ┐ 2. 讀回全 0xFF? ├─ 是 ─→ 位址錯 / 上拉電壓不足 └─ 否 ┐ 3. 讀回全 0x00? ├─ 是 ─→ 上電 / reset 未釋放 └─ 否 ─→ 讀到預期 ID → ✅
i2cget -y bus addr reg 的 reg 只送 1 位元組,OV 系 16-bit 位址要用 i2ctransfer 的 w2。OmniVision 的 register 介面源自 SCCB(Serial Camera Control Bus)——它與 I2C 高度相容(位址 + 資料、START/STOP),差異多在細節(如是否支援重複起始、時脈規格)。實務上 Linux i2c 工具可直接操作,不必特判 SCCB。
| 症狀 | 最可能原因 | 先查 |
|---|---|---|
| i2cdetect 看不到 | 位址錯 / 沒上電 / GMSL 沒通 | deserializer lock → remap |
| 讀回全 0xFF | 上拉不足 / 位址錯 | 電壓、pull-up |
| 讀回全 0x00 | reset 未釋放 / 上電不完整 | GPIO 時序 |
| 寫了讀不回 | register 位址寬度錯 | 用 w2 送 16-bit 位址 |
場景:透過 GMSL deserializer(MAX96712)遠端控制 OV9281 的 register。GMSL 把 I2C 訊號「隧道化」到遠端 serializer + 感測器。
# 1. 確認 deserializer 本身可讀(local I2C) sudo i2cget -y 0 0x48 0x000d # MAX96712 device ID register # 2. 設定 GMSL link 的 I2C remap(把遠端感測器映射到不同位址) sudo i2cset -y 0 0x48 0x0000 0x36 # link0 remote sensor alias = 0x36 sudo i2cset -y 0 0x48 0x0001 0x10 # link0 remote sensor base = 0x10 # 3. 透過 deserializer 讀遠端 OV9281 的 chip ID sudo i2cget -y 0 0x36 0x300a # OV9281 chip ID register → 應回 0x9281 # 4. 若步驟 3 失敗:確認 link lock + 線材 i2cget -y 0 0x48 0x0013 # link0 lock status
設計決策:GMSL 的 I2C 透傳對驅動完全透明——tegracam 驅動以為自己在做 local I2C,實際上經過了 GMSL tunnel。這就是為什麼「除錯第一步是確認 lock」:lock 不通,I2C 就到不了遠端。
OmniVision 感測器使用 SCCB(Serial Camera Control Bus),外觀與 I2C 極像但有細微差異:
| 面向 | I2C | SCCB |
|---|---|---|
| 時脈線 | SCL(可被 slave 拉低 Stretch) | SCCB_CLK(不支持 stretch) |
| 資料線 | SDA(雙向) | SCCB_DATA(三態,含 acknowledge) |
| Acknowledge | 第 9 個 clock 由 slave 拉低 | SCCB 用 "don't care" 位(bit 8) |
| 相容性 | 標準 I2C 控制器可驅動 SCCB | SCCB 設備接 I2C 匯流排通常可行 |
| 症狀 | 可能原因 | 解決方案 |
|---|---|---|
| i2cget 回傳 -1(Read failed) | I2C 匯流排被其他裝置鎖住 或 位址衝突 | 用 i2cdetect 掃描整條 匯流排確認佔用裝置;暫時移除其他 I2C 裝置測試 |
| GMSL 遠端感測器 I2C 讀不到 | deserializer link 未 lock 或 I2C remap 未設定 | 先讀 deserializer lock register;再設定正確的 remote alias/base |
| I2C 讀得到 ID 但設定 register 後無效 | register 位址位元深(8-bit vs 16-bit)設定錯 | 確認感測器的 register addressing 模式(8-bit 位址用 i2cset -y 0 ADDR REG8 VAL;16-bit 用 i2cset -y 0 ADDR REG16 VAL) |
| I2C 通訊偶爾成功偶爾失敗 | 線材過長 或 pull-up 電阻值不匹配 | 縮短 I2C 線長;確認 pull-up 在 2.2kΩ~4.7kΩ 範圍;降低 I2C 時脈 |
| 多顆感測器共享 I2C 匯流排時互相干擾 | I2C 位址衝突(同型感測器預設位址相同) | 用 GMSL I2C remap 或 硬體 solder 切換位址腳 |
場景:團隊有多顆感測器要 bring-up,每次手動 i2cget/i2cset 容易出錯。這個專案建立一套可重用的 I2C 工具鏈:① register 讀寫 Python 庫 ② 全匯流排健康檢查 ③ 兩份 register dump 的自動 diff(呼應 6.17 的需求)。
# regctl.py — 包裝 i2ctransfer,支援 8/16-bit 位址 python3 - <<'EOF' import subprocess def read16(bus, addr, reg, n=1): hi, lo = reg >> 8, reg & 0xFF out = subprocess.check_output( ['i2ctransfer', '-y', str(bus), f'w2@{addr:x}', f'{hi:x}', f'{lo:x}'] + [f'r{n}']) return int(out.strip(), 16) def write16(bus, addr, reg, val): hi, lo = reg >> 8, reg & 0xFF vhi, vlo = val >> 8, val & 0xFF subprocess.run(['i2ctransfer', '-y', str(bus), f'w4@{addr:x}', f'{hi:x}', f'{lo:x}', f'{vhi:x}', f'{vlo:x}'], check=True) # 使用: print(hex(read16(0, 0x36, 0x300A))) # OV9281 ID → 0x92 write16(0, 0x36, 0x3500, 0x0050) # 寫曝光高位 EOF # health_check.sh — 掃全部 bus 並標記異常位址 for bus in 0 1 2 3 4 5 6 7 8; do echo "== bus $bus ==" sudo i2cdetect -y $bus | grep -v "^ " | grep -v "^\s*$" done
專案輸出:regctl.py(讀寫庫)+ health_check.sh(健康檢查)+ regdiff.py(兩份 dump 的 diff)。之後所有感測器 bring-up 都改用這套工具,避免手動打錯位址。
i2cdetect -y <bus> → 看到預期位址。w2)。| 面向 | Orin Nano | RPi5 | Orange Pi | Thor |
|---|---|---|---|---|
| 主要 bus | i2c0 / i2c1 / i2c8… | i2c22(CSI) | 依型號 i2c3… | 依配置 |
| GMSL I2C remap | ✅ 常用 | ❌ | ❌ | ✅ 常用 |
| SCCB 相容 | ✅(標準 I2C) | ✅ | ✅ | ✅ |
| 工具 | i2c-tools 全平台相同(i2cdetect / i2cget / i2cset / i2ctransfer) | |||
| 時脈預設 | 400kHz | 100kHz | 100kHz | 400kHz |
| 步驟 | 操作 | I2C Transaction | 預期回傳 | 判斷 |
|---|---|---|---|---|
| 1. 掃描匯流排 | i2cdetect | S→0x6C→ACK | 0x36 出現 | 感測器活著 |
| 2. 讀 ID 高位 | i2ctransfer | w2@0x36 0x30 0x0a r1 | 0x92 | OV9281 確認 |
| 3. 讀 ID 低位 | i2ctransfer | w2@0x36 0x30 0x0b r1 | 0x81 | 完整 ID |
| 4. 讀曝光高位 | i2ctransfer | w2@0x36 0x35 0x00 r1 | 依設定 | 確認目前值 |
| 5. 寫曝光高位 | i2ctransfer | w3@0x36 0x35 0x00 0x05 | — | 寫入成功 |
| 6. 回讀驗證 | i2ctransfer | w2@0x36 0x35 0x00 r1 | 0x05 | Verify OK |
#!/bin/bash # i2c_rw_test.sh — OV9281 完整 I2C 驗證 ADDR=0x36 BUS=0 echo "=== 1. Scan ===" i2cdetect -y $BUS | grep -c "$ADDR" && echo "Sensor found" || echo "MISSING" echo "=== 2. Read ID ===" id_hi=$(i2ctransfer -y $BUS w2@0x$(printf '%x' $ADDR) 0x30 0x0a r1) id_lo=$(i2ctransfer -y $BUS w2@0x$(printf '%x' $ADDR) 0x30 0x0b r1) echo "ID = 0x$id_hi$id_lo (expect 0x9281)" echo "=== 3. Write + Verify ===" i2ctransfer -y $BUS w3@0x$(printf '%x' $ADDR) 0x35 0x00 0x05 readback=$(i2ctransfer -y $BUS w2@0x$(printf '%x' $ADDR) 0x35 0x00 r1) [ "0x$readback" = "0x05" ] && echo "VERIFY OK" || echo "VERIFY FAIL: got 0x$readback" echo "=== 4. Stability Test (100 reads) ===" fail=0 for i in $(seq 1 100); do v=$(i2ctransfer -y $BUS w2@0x$(printf '%x' $ADDR) 0x30 0x0a r1) [ "0x$v" != "0x92" ] && fail=$((fail+1)) done echo "Failures: $fail/100"
1. i2cdetect 看得到 0x36? ├─ 看不到 ─┬─ 非 GMSL:量 SDA/SCL 電位(正常應 ~3.3V) │ │ 若 0V → 短路或某裝置 hang │ └─ GMSL:deserializer lock? │ ├─ 無 → 線材 / 同軸端接 / 電源 │ └─ 有 → I2C remap 設定? │ ├─ 否 → 設定 remote alias/base │ └─ 是 → 位址衝突(同型多顆) └─ 看得到 ┐ 2. 讀回全 0xFF? ├─ 是 → I2C pull-up 不足(量電壓)或位址錯 └─ 否 ┐ 3. 讀回全 0x00? ├─ 是 → reset GPIO 未釋放 / MCLK 未到 └─ 否 ┐ 4. 讀回其他值? ├─ 不一致 → 線材干擾 / pull-up 不匹配 └─ 一致 → ✅ I2C 正常
1. deserializer 本身讀得到?(i2cget 0x48) ├─ 無 → deserializer 沒電 / 位址錯 └─ 有 ┐ 2. link lock status?(寄存器 0x0013) ├─ 全 0 → 線材 / serializer / GMSL 頻率 └─ 有 1 ┐ 3. I2C remap 已設定? ├─ 否 → 設定 alias + source 位址 └─ 是 ┐ 4. 遠端感測器讀得到 ID? ├─ 否 → 降低 I2C 時脈(400kHz→100kHz)重試 └─ 是 → ✅
i2cdetect -y <bus> → 看到預期位址(0x36)。w2(如 w2@0x36 0x30 0x0a r1)。| 面向 | Orin Nano | RPi5 | Orange Pi | Thor | 推薦 |
|---|---|---|---|---|---|
| I2C 主要 bus | i2c0/i2c1/i2c8 | i2c22(CSI) | 依型號 i2c3… | 依配置 | 查 DTB 確認 |
| GMSL I2C remap | ✅ 常用 | ❌ | ❌ | ✅ 常用 | GMSL 必備 |
| SCCB 相容 | ✅ | ✅ | ✅ | ✅ | 全平台 OK |
| I2C 工具 | i2c-tools 全平台相同 | 統一工具鏈 | |||
| 時脈預設 | 400kHz | 100kHz | 100kHz | 400kHz | 不穩時降速 |
| Register 位址寬度 | OV 系 16-bit,IMX 系 8-bit | 依感測器 | |||
| 回讀驗證 | write→read→compare 全平台相同 | 統一流程 | |||