I2C 位元級、register 讀寫、page 機制
I2C 兩線:SDA(資料)+ SCL(時脈),開集極 + 上拉電阻(RPi 通常 1.8k–4.7k 到 3.3V/1.8V)。所有傳輸以「位元」進行:
| 事件 | 電氣條件 | 備註 |
|---|---|---|
| START | SCL=H 時 SDA 由 H→L | — |
| STOP | SCL=H 時 SDA 由 L→H | — |
| ACK | 接收方每 byte 後把 SDA 拉低 1 bit | — |
| NACK | 接收方不拉低 | 傳送方應中止 |
| 資料有效 | SDA 在 SCL=H 期間必須穩定 | 在 SCL=L 期間改變 |
OV5647 寫入序列(16-bit register 位址,big-endian):
RPi5 的 Camera 0 對應 i2c-22(Camera 1 對應 i2c-10)。
$ sudo i2cdetect -y 22
0 1 2 3 4 5 6 7 8 9 a b c d e f
30: 36 --看到 0x36 → 感測器在 I2C 上回應。全空 → 電源 / clock / reset / 位址 / 匯流排問題(決策樹見單元 6)。
$ sudo i2cget -y 22 0x36 0x300a
0x56
$ sudo i2cget -y 22 0x36 0x300b
0x47
# 組合 0x5647 = OV5647 ✓| Register | 值 | 意義 |
|---|---|---|
| 0x300A | 0x56 | 型號高位(ASCII 'V') |
| 0x300B | 0x47 | 型號低位(5647) |
# w3 = 寫 3 byte:位址高、位址低、資料 $ sudo i2ctransfer -y 22 w3@0x36 0x30 0x34 0x1e # 回讀:寫 2 byte 位址後讀 1 byte $ sudo i2ctransfer -y 22 w2@0x36 0x30 0x34 r1 0x1e
| 參數 | 解釋 |
|---|---|
w3@0x36 0x30 0x34 0x1e | 對 0x36 寫 3 byte(0x30 0x34 0x1e) |
w2@0x36 0x30 0x34 r1 | 寫 2 byte 位址後讀 1 byte |
i2cget 對 8-bit 位址感測器方便;OV 用 16-bit 位址,習慣用 i2ctransfer。
OV5647 部分 register 需先「切 bank」才能存取:
| Register | 角色 |
|---|---|
| 0x3000 | sccb_bank 控制 |
| 0x3008[6] | sccb_slave_id_en |
| 0x3100 | sccb_slave_id(模式切換) |
| Register | 功能 | 對應單元 |
|---|---|---|
| 0x300A/B | 感測器 ID | 3 / 6 |
| 0x3034–0x3037 | PLL / clock 分頻 | 6 |
| 0x3100–0x3103 | 時序控制 | 6 |
| 0x3500–0x3503 | 曝光時間(AEC) | 10 |
| 0x350A–0x350B | 類比增益(AGC) | 10 |
| 0x3800–0x3814 | 裁切 / 解析度(timing) | 8 |
| 0x3A00–0x3A03 | banding filter(50/60Hz) | 10 |
| 0x4740–0x4742 | MIPI 介面 | 6 |
I2C 可靠度與上拉電阻、匯流排電容高度相關:
| 參數 | 典型 | 影響 |
|---|---|---|
| 上拉電阻 | 1.8k–4.7k | 太小→電流大;太大→上升緣慢 |
| 匯流排電容 | ≤ 400 pF | 線過長→訊號變形 |
| 速度 | 400 kHz(Fast) | OV 支援;不穩可降 100 kHz |
i2ctransfer -y 22 w2@0x36 0x30 0x0a r1 就對應「寫 2 byte 位址後讀 1 byte」這整段時序。看懂這個,你就懂所有 I2C 感測器。
i2ctransfer 讀回 0x300A/0x300B。場景:OV5647 正在串流,你想即時觀察 AE 演算法如何調整曝光暫存器。需要在不中斷串流的情況下,每秒讀取一次曝光值。
#!/bin/bash # 持續監控 OV5647 曝光 register(不中斷 streaming) echo "timestamp,exp_hi,exp_mid,exp_lo,total_lines" for i in $(seq 1 60); do TS=$(date +%s.%N) # 讀 0x3501(高)、0x3502(中)、0x3503(低) EH=$(sudo i2ctransfer -y 22 w2@0x36 0x35 0x01 r1 2>/dev/null | awk '{print $1}') EM=$(sudo i2ctransfer -y 22 w2@0x36 0x35 0x02 r1 2>/dev/null | awk '{print $1}') EL=$(sudo i2ctransfer -y 22 w2@0x36 0x35 0x03 r1 2>/dev/null | awk '{print $1}') # 組合成曝光行數(16-bit,高 byte 在前) TOTAL=$(( (16#${EH#0x} << 8) | (16#${EM#0x}) )) echo "$TS,$EH,$EM,$EL,$TOTAL" sleep 1 done > exposure_log.csv
RPi5 有兩條 Camera I2C 匯流排(i2c-22 對應 Camera 0、i2c-10 對應 Camera 1),各自獨立。但有幾個容易忽略的电气限制:
| 症狀 | 可能原因 | 解決方案 |
|---|---|---|
| i2cdetect 掃到 0x36 但 i2cget 讀回 0xFF | 感測器 RESET 未釋放,I2C 雖通但暫存器不可存取 | 確認 DT overlay 的 reset-gpios 正確;用 GPIO 工具手動 toggle reset 腳位 |
| i2cget 讀值偶爾跳動(±1) | 匯流排雜訊或上拉不足導致上升緣緩慢 | 降低 I2C 速度到 100kHz(i2cset -y 22 0x00 0x04 設控制暫存器);檢查上拉電阻 |
| 兩顆感測器都掃不到 | I2C 匯流排被其他 device 佔用或 SDA/SCL 接反 | 用 GPIO 18/19(i2c-22)和 GPIO 0/1(i2c-10)的 pin mux 確認;排除其他 I2C 設備 |
| i2ctransfer 寫入後回讀值不同 | 暫存器是 write-only 或需要 delay 才生效 | 查 datasheet 確認暫存器屬性;部分 OV暫存器需等待 VBlank 才更新 |
| probe 成功但讀回的 ID 不是 0x5647 | I2C 位址 0x36 上接的是另一顆感測器 | 讀完整的 0x300A/0x300B 確認型號;查板子原理圖確認感測器型號 |
場景:你將長期處理多顆 OV 感測器。與其每次打指令,不如建立一套可重用的 I2C 探針腳本:讀寫、dump、監控、回讀驗證一次到位。本專案整合單元 3(I2C 位元級)、6(bring-up)知識。
#!/bin/bash # 用法:. ./sensor_probe.sh 然後用函式 BUS=22; ADDR=0x36 sensor_read() { # $1 = 16-bit register sudo i2ctransfer -y $BUS w2@$ADDR 0x${1:0:2} 0x${1:2:2} r1 } sensor_write() { # $1 = register, $2 = value sudo i2ctransfer -y $BUS w3@$ADDR 0x${1:0:2} 0x${1:2:2} $2 } sensor_id() { echo "ID = $(sensor_read 300A)$(sensor_read 300B)" # 預期 0x5647 } sensor_dump() { # 關鍵 register 群 for r in 300A 300B 3034 3035 3036 3037 3501 3502 3503 350A 350B; do printf "%s: %s\n" "$r" "$(sensor_read $r)" done } sensor_monitor() { # 串流中監控曝光,每秒一筆 echo "t,exp_hi,exp_lo,gain" for i in $(seq 1 30); do EH=$(sensor_read 3501); EL=$(sensor_read 3502) GA=$(sensor_read 350A) echo "$(date +%s),$EH,$EL,$GA" sleep 1 done }
. ./sensor_probe.sh sensor_id # 預期:ID = 0x5647 sensor_read 3500 # 讀曝光高位 sensor_write 3034 0x18; sensor_read 3034 # 寫後回讀:預期 0x18 sensor_dump # 完整快照,記錄下來做 bring-up log
sudo i2cdetect -y 22 確認 0x36 回應。| 面向 | RPi5 | Orange Pi | Orin Nano | Thor |
|---|---|---|---|---|
| Camera I2C bus | i2c-22 / i2c-10 | i2c-0~3(依 BSP) | i2c-0 / i2c-30(依感測器槽) | HSB 乙太感測器(非本地 I2C) |
| 常用工具 | i2ctransfer/i2cdetect | i2ctransfer(需裝) | i2ctransfer / busybox | i2ctransfer(透過 carrier) |
| 位址轉換 | 7-bit(0x36) | 同(0x36/0x3c) | 同(0x36) | 同(register 相同) |
| 坑 | 上拉 1.8V 別亂接 3.3V | bus 編號常因 BSP 而變 | 需先載入 NVIDIA driver | 感測器連在乙太上,工具鏈不同 |
| 步驟 | 暫存器 | 寫入值 | 位元級說明 |
|---|---|---|---|
| 1. 設定 AEC 高位 | 0x3500 | 0x00 | bit[3:0] = 曝光 bit[19:16] |
| 2. 設定 AEC 中位 | 0x3501 | 0x64 | bit[7:0] = 曝光 bit[15:8],行數 = 0x064 = 100 行 |
| 3. 設定 AEC 低位 | 0x3502 | 0x00 | bit[7:4] = 曝光 bit[11:8] 的 4-bit 小數 |
| 4. 設定 AEC 控制 | 0x3503 | 0x03 | bit[0]=1 manual AEC, bit[1]=1 manual AGC |
i2cset 可直接替代 SCCB。決策樹 A:I2C 寫入成功但感測器無反應
I2C 寫入成功但感測器無反應
├─ 檢查 A:確認是 SCCB 還是標準 I2C
│ ├─ SCCB 3-phase → i2cset 自動處理
│ └─ 非標準 → 需自訂 i2c driver
├─ 檢查 B:PLL 設定是否正確(0x3034-0x3037)
│ ├─ PLL 未鎖定 → pixel clock 為 0 → 無輸出
│ └─ PLL 鎖定 → 繼續
├─ 檢查 C:data lane 是否啟用(0x3000-0x3002)
│ ├─ 未啟用 → 寫入 0x00 到 0x3000(stream on)
│ └─ 已啟用 → 繼續
└─ 檢查 D:MCLK 是否送達感測器
├─ 無 MCLK → 外部時鐘源問題
└─ 有 MCLK → 感測器內部問題,嘗試 reset sequence
| 面向 | RPi5 | Orange Pi | Orin Nano | Thor | 推薦 |
|---|---|---|---|---|---|
| I2C 控制器 | Broadcom BCM2712, 400kHz | Allwinner, 400kHz | Tegra, 400kHz | Tegra, 1MHz+ | Thor 最快 |
| SCCB 支援 | i2cset 直接支援 | i2cset | NV 驅動內建 | NV 驅動內建 | RPi5 最直觀 |
| I2C 多主機 | 不原生支援 | 有限 | 支援 | 支援 | Orin+ 最佳 |
| debug 便利性 | i2c-tools 直接用 | i2c-tools | NV 自有工具 | NV + Holoscan | RPi5 最易 debug |