GMSL 多相機
Orin 可透過 GMSL deserializer(MAX96712,多 link)接多顆遠距離感測器,每顆有獨立 I2C 通道與 lock 狀態。
□ 硬體:CSI lane / I2C / reset / clock □ DT overlay:compatible / reg / clocks / reset-gpios □ 驅動:現成 or 改 init table □ dmesg:probe 成功?讀到 ID? □ 出圖:能取到幀? □ RAW:Bayer order / 黑位 / 缺陷像素 □ 調校:黑位 → LSC → AWB/CCM → NR/Sharpen
MAX96712 一顆 deserializer 有多個 link,每條 link 獨立成一路影像。除錯模型:每一路都是「獨立的 CSI-2 通道 + 獨立的 I2C 通道 + 獨立的 lock 狀態」——一條 lock 不代表全部 lock。
| 面向 | 單 link | 多 link |
|---|---|---|
| I2C | 一顆感測器 | 每路 remap 到不同位址 |
| lock | 一組 serdes | 逐路確認 |
| CSI | 一組 lane | 多 virtual channel / 多 CSI 介面 |
| DTB | 一個 camera 節點 | 每個 camera 節點各宣告 |
# 1. 兩路 serdes 都 lock? # (讀 MAX96712 各 link 的 lock register,vendor 工具最佳) # 2. 兩顆 ID 都讀得到(remap 後位址不同) sudo i2cget -y 0 0x36 0x300a # cam0 ID sudo i2cget -y 0 0x56 0x300a # cam1 ID(remap 範例) # 3. 兩路 media 管線 media-ctl -p -d /dev/media0 media-ctl -p -d /dev/media1 # 4. 逐路出圖 v4l2-ctl -d /dev/video0 --stream-mmap=1 --stream-count=1 --stream-to=cam0.raw v4l2-ctl -d /dev/video1 --stream-mmap=1 --stream-count=1 --stream-to=cam1.raw
兩路都出圖後,才談同步(frame sync)與共同調校。同步是進階題——先確保「每一路都獨立正確」。
1. 壞的那路 serdes 有 lock? ├─ 無 → 線材/接頭/電源(該路專屬) └─ 有 ┐ 2. 該路感測器 ID 讀得到? ├─ 否 → I2C remap 衝突 / 通道未切 └─ 是 ┐ 3. 該路 DTB 節點 probe? ├─ 否 → DTB mode / 電源 / MCLK(該路專屬) └─ 是 ┐ 4. 該路 video device 出圖? ├─ 否 → media 管線 / CSI 配置 └─ 是 → ✅ 繼續下一路
多感測器應用常需要「同時曝光」:GMSL 有硬體 frame sync 機制(多顆 serializer 共用觸發),或用軟體對齊幀時間戳。決定要不要同步的判準:你的演算法是否假設「同一瞬間」的影像。
| 場景 | 需要硬體同步? | 理由 |
|---|---|---|
| 3D 立體視覺 | 是 | 左右眼需同一瞬間 |
| 多視角監控 | 不必 | 各自獨立即可 |
| 環視系統 | 視設計 | 拼接需要對齊 |
1. 硬體:每路 lane / I2C / 電源 獨立確認 2. serdes:逐路 lock 3. I2C:remap 避開位址衝突,逐顆讀 ID 4. DTB:每顆 camera 節點正確 5. 逐顆出圖驗證 6. 逐顆 RAW 分析(黑位/Bayer/缺陷) 7. 逐顆獨立調校(LSC/AWB…) 8. 最後才處理同步與共同場景 9. 回歸:壞一顆能立即歸因
# 兩路各取 10 幀,記錄時間戳 v4l2-ctl -d /dev/video0 --stream-mmap=10 --stream-count=10 --stream-to=c0.raw v4l2-ctl -d /dev/video1 --stream-mmap=10 --stream-count=10 --stream-to=c1.raw # 比較時間戳差: # - 固定小偏移 → 軟體對齊即可 # - 差異隨機/漂移 → 需硬體 frame sync
場景:自駕小車需要四顆 OV9281 同時工作,每顆走獨立 ISP pipeline,最終四路 NV12 同時送到 AI 推論引擎。
# 四路各走獨立 Argus pipeline # Pipeline 0: OV9281 front → ISP → NV12 stream 0 → /dev/video0 # Pipeline 1: OV9281 rear → ISP → NV12 stream 1 → /dev/video1 # Pipeline 2: OV9281 left → ISP → NV12 stream 2 → /dev/video2 # Pipeline 3: OV9281 right → ISP → NV12 stream 3 → /dev/video3 # 關鍵:每路 ISP 的參數獨立(各感測器的 LSC/AWB 各自校正) # 用 Argus session 管理四路:一個 session 四個 stream # 驗證同步性 python3 - <<'EOF' import time, subprocess results = {} for cam in range(4): start = time.time() subprocess.run(['v4l2-ctl', f'-d /dev/video{cam}', '--stream-mmap=1', '--stream-count=1', f'--stream-to=cam{cam}.raw']) results[cam] = time.time() - start print('各路擷取時間:', results) diff = max(results.values()) - min(results.values()) print(f'最大時間差:{diff*1000:.1f} ms') # < 1ms → 同步良好;> 5ms → 需要硬體 frame sync EOF
設計決策:四路獨立 ISP 的好處是「互不干擾」——某路掉幀不影響其他路。壞處是 ISP bandwidth 需要四倍,Orin Nano 可能需要降單路解析度以確保四路同時穩定。
MAX96712 支援 Frame Sync:透過 GPIO 觸發多顆 serializer 同時開始曝光。
| 同步方式 | 精度 | 實作複雜度 | 適用場景 |
|---|---|---|---|
| 硬體 frame sync | μs 級 | 高(需要額外 GPIO 接線) | 3D 立體視覺、高速拼接 |
| 軟體 timestamp 對齊 | ms 級 | 低(只需比對 V4L2 時間戳) | 多視角監控、非即時拼接 |
| 外部 trigger(PWM) | μs 級 | 中(需要 PWM 控制器) | 工業線掃描、頻閃照明 |
| 症狀 | 可能原因 | 解決方案 |
|---|---|---|
| 四路同時串流時某路偶爾掉幀 | ISP bandwidth 超限(四路同時處理) | 降低單路解析度或幀率;確認 ISP 資源分配 |
| 硬體 frame sync 設了但兩路仍有 ms 級偏移 | serializer 的 readout delay 不同 | 用同一型號 serializer;或用外部 trigger 統一時序 |
| 多顆同型感測器的色彩偏差不一致 | 每顆感測器的個體差異(Bayer response 不同) | 每顆獨立做 AWB/CCM 校正;不可共用參數 |
| GMSL link 偶爾掉 link(lock 閃爍) | 線材接頭鬆動 或 EMI 干擾 | 重新固定接頭;用遮蔽更好的同軸線;降低 GMSL link 速率 |
| 多路同時開時 CPU 佔用飆高 | V4L2 的 mmap buffer 管理消耗 CPU | 用 dmabuf 而非 mmap;或用 NVIDIA 的 hardware buffer(NVMM) |
場景:自駕小車需要前後左右四顆 OV9281(回顧 16.16)。專案目標:端到端完成「四路 GMSL bring-up → 逐路調校 → 同步驗證 → 健康監控」,全部用專案腳本管理。
# Phase 1 · 逐路 bring-up(一次一顆,回顧 16.7) # 每路:lock → ID → probe → 出圖 # 位址 remap 表:cam0=0x36, cam1=0x56, cam2=0x76, cam3=0x96 # Phase 2 · 四路同時出圖 + ISP 資源驗證 for cam in 0 1 2 3; do v4l2-ctl -d /dev/video$cam --stream-mmap=1 --stream-count=1 --stream-to=c$cam.raw & done; wait # 檢查是否掉幀 → 決定單路解析度上限 # Phase 3 · 逐路獨立調校(LSC / AWB 各自校正) # Phase 4 · 同步驗證(回顧 16.14 時間戳法) python3 - <<'EOF' import time, subprocess r = {} for cam in range(4): t0 = time.time() subprocess.run(['v4l2-ctl', '-d', f'/dev/video{cam}', '--stream-mmap=1', '--stream-count=1', f'--stream-to=c{cam}.raw']) r[cam] = time.time()-t0 d = max(r.values())-min(r.values()) print(f'最大時間差 {d*1000:.1f} ms', '< 1ms → 同步好' if d < 0.001 else '→ 需 frame sync') EOF # Phase 5 · 健康監控 dashboard(回顧 16.19-1) # lock 狀態 / 幀率 / 掉幀數 即時顯示
專案輸出:四路 bring-up 腳本 + 每路獨立調校檔 + 同步報告 + 監控 dashboard。
| 面向 | Orin Nano | RPi5 | Orange Pi | Thor |
|---|---|---|---|---|
| GMSL | ✅(MAX96712 類) | ❌ | ❌ | ✅(HSB) |
| 同時擷取路數 | 3(VI channel) | 1-2 | 1-2 | 多路 |
| 硬體 frame sync | ✅(GMSL GPIO) | ❌ | ❌ | ✅ |
| 每路獨立調校 | ✅ | ✅(有限路) | ⚠️ | ✅ |
| 健康監控 | V4L2 / 自寫 | libcamera | V4L2 | Holoscan |
| 步驟 | Command | 驗證目標 | 預期結果 |
|---|---|---|---|
| 1. deserializer ID | i2cget 0x48 | MAX96712 活著 | 讀到 ID |
| 2. lock 狀態 | i2cget 0x48 0x0013 | 4 路全 lock | bit 0-3 全 1 |
| 3. link0 alias | i2cset 0x48 0x0010 0x36 | link0 remap | 設定成功 |
| 4. link1 alias | i2cset 0x48 0x0011 0x37 | link1 remap | 設定成功 |
| 5. link2 alias | i2cset 0x48 0x0012 0x38 | link2 remap | 設定成功 |
| 6. link3 alias | i2cset 0x48 0x0013 0x39 | link3 remap | 設定成功 |
| 7. 逐路讀 ID | i2ctransfer 各 alias | 感測器可達 | 各路 0x92 |
#!/bin/bash # multi_sensor_bringup.sh — 四路 OV9281 逐路驗證 DESER=0x48; BUS=0 ALIASES=(0x36 0x37 0x38 0x39) echo "=== Deserializer Check ===" i2cget -y $BUS $DESER && echo "MAX96712: OK" || echo "MAX96712: FAIL" echo "=== Lock Status ===" lock=$(i2cget -y $BUS $DESER 0x0013) echo "Lock register: 0x$(printf '%02x' $lock)" for i in 0 1 2 3; do bit=$(( (lock >> i) & 1 )) echo " link$i: $([ $bit -eq 1 ] && echo 'LOCKED' || echo 'UNLOCKED')" done echo "=== I2C Remap ===" for i in 0 1 2 3; do i2cset -y $BUS $DESER $((0x0010 + i)) ${ALIASES[$i]} echo "link$i: remote 0x36 → local 0x$(printf '%x' ${ALIASES[$i]})" done echo "===逐路 Sensor ID ===" FAIL=0 for alias in "${ALIASES[@]}"; do id=$(i2ctransfer -y $BUS w2@0x$alias 0x30 0x0a r1 2>/dev/null) if [ "0x$id" = "0x92" ]; then echo "0x$alias: OK (ID=0x$id)" else echo "0x$alias: FAIL (ID=0x$id)" FAIL=$((FAIL+1)) fi done echo "" [ $FAIL -eq 0 ] && echo "ALL SENSORS: PASS" || echo "FAILURES: $FAIL/4"
1. N 路同時開,部分路失敗?
├─ 是 ┐
│ 2. 哪些路失敗?(逐路 bring-up 定位)
│ ├─ 特定路 → 該路硬體問題(線材/端接/電源)
│ └─ 全部路 → deserializer 問題(電源/韌體)
└─ 否 ┐
2. VI channel 不足?
├─ 是 → Orin Nano 最多 3 路
│ → 減路 / 降解析度 / 用外部 ISP
└─ 否 → ISP bandwidth 超限
→ 降單路解析度/幀率1. 前後攝時間差 > 50ms? ├─ 是 → GMSL link 延遲不同 │ → 調整各路 trigger delay └─ 否 ┐ 2. 偶爾一幀延遲? ├─ 是 → OS scheduling jitter │ → 用 RT thread + isolated core └─ 否 → 同步正常
| 面向 | Orin Nano | RPi5 | Orange Pi | Thor | 推薦 |
|---|---|---|---|---|---|
| 最多感測器數 | 4(GMSL) | 2(CSI) | 1 | 8+(GMSL) | 多路 → Thor |
| GMSL 多路 | ✅ MAX96712 | ❌ | ❌ | ✅ 內建 | GMSL → Orin/Thor |
| I2C remap | ✅ 必要 | ❌ | ❌ | ✅ 必要 | GMSL 專用 |
| 同步精度 | ~1 frame | ~2 frames | N/A | sub-frame | 精準同步 → Thor |
| 案例適用 | 車載/ADAS | 雙目視覺 | 單路應用 | 自動駕駛 | 各有定位 |
| 頻寬 | ~1.5 Gbps | ~800 Mbps | ~400 Mbps | >3 Gbps | 高頻寬 → Thor |
| 韌體升級 | MAX96712 flash | ❌ | ❌ | OTA | 各有方式 |
| 量產成本 | 中 | 低 | 最低 | 高 | 成本敏感 → RPi5 |
| 學習曲線 | 高 | 中 | 低 | 極高 | 入門 → RPi5 |