# Tab5 内蔵カメラ サンプル（カメラ単体版）

M5Stack Tab5 の内蔵カメラ（MIPI-CSI / SC202CS）を **Arduino IDE で動かして、画面に出す** ところだけに絞ったサンプルです。
本体（生体信号取得システム）に組み込む前の「切り分け」に使うことを目的にしています。

- このファイル: https://ailab-japan.com/TAB5CAM_QR/README.md
- 配布ページ: https://ailab-japan.com/TAB5CAM_QR/
- 本体統合版 v6: https://ailab-japan.com/TAB5CAM_QR/body/

> **お知らせ**
> 現状ではI2Cと画面描画の性能に影響するため、本ライブラリとしては、二次元コードの読み取りサンプル部分を抹消しました。

---

## 1. ファイル

| ファイル | 内容 |
|---|---|
| `Tab5_Camera_Basic_20261106.ino` | スケッチ本体（映像表示＋モード切替＋ライト＋露出/ゲイン） |
| `Tab5_Camera_Basic_20261106.zip` | 上記をフォルダ入りにした一式（この README 同梱） |
| `ESP_Video_for_3.3.12.zip` | ESP_Video 単体（コアに見当たらない環境用） |

`Tab5_Camera_Basic_20261106.ino` は **外部ライブラリを必要としません**。ino 単体と M5Unified だけでビルドできます。

---

## 2. ビルド確認結果

```
Arduino-ESP32 コア : 3.3.12
ボード             : M5Stack Tab5 (esp32:esp32:m5stack_tab5)
Flash              : 1,077,064 bytes (16%)
Global variables   :   182,692 bytes (55%)
warning 0 / error 0
```

### 実機での確認結果（2026-11-06）

Tab5 実機（USB-Serial/JTAG、COM12）へ書き込み、起動まで確認しました。

| 項目 | 結果 |
|---|---|
| 書き込み | merged 16MB を 0x0 へ → `Hash of data verified` |
| XCLK | 23,981,265 Hz（24MHz 取得成功） |
| カメラ給電 | `0x03=6F / 0x05=76 / 0x07=00` |
| カメラ初期化 | 成功（`撮影開始 RGB565 1280x720`） |
| 表示 | 左半分が白（照明）／右半分に 640×360 のプレビュー |
| **実測 fps** | **26.0 fps**（半分プレビュー化＋タッチ廃止後。全面表示は 6〜7 fps） |

`Failed to detect camera sensor with address=..` が大量に出ますが、これは候補アドレスを順に試す正常なログで、最後に正しいセンサ（SC202CS）で成功します。

**fps の律速は表示転送です。** 全面 1280×720 を毎フレーム `pushImage` すると 6〜7 fps、右半分だけ 640×360 に間引くと 26 fps。撮影そのものは十分速いことが分かりました。

`arduino-cli compile -b esp32:esp32:m5stack_tab5 Tab5_Camera_Basic_20261106`

Arduino IDE で開くときは、**フォルダ名と .ino 名を一致**させてください（`Tab5_Camera_Basic_20261106/` の中に `Tab5_Camera_Basic_20261106.ino`）。

---

## 3. これは何をするものか

1. 内蔵カメラの映像を 5 インチ画面（1280×720）に表示する
2. 表示モードを切り替える（生 / バイト入替 / 明暗伸長）
3. 画面を真っ白にして「ライト」として使う
4. 露出時間とゲインを手で動かす（AE のハンチング対策の足がかり）

映像は RGB565 / 1280×720 固定。**PSRAM は必須ではありません**（M5Unified は使います）。
本スケッチ自身は内蔵 RAM の 40KB のライン バッファだけで動きます。

---

## 4. 初期化の順序（入れ替えると動きません）

| # | 手順 | 理由 |
|---|---|---|
| 【1】 | XCLK 24MHz を `M5.begin()` **より先**に設定 | ESP32-P4 の LEDC は全タイマがクロック源を共有。後から取ると M5GFX のバックライト用と衝突し 24MHz が出ない |
| 【2】 | `M5.begin()` → `setRotation(1)` → `backlightFix()` | M5GFX の調光が失敗して GPIO22 が LOW のまま＝黒画面になるのを防ぐ |
| 【3】 | IO エキスパンダ 0x43 で CAM_RST を解放 | これをやらないとカメラが SCCB に応答しない（`0x36` が出ない） |
| 【4】 | `csi.begin(cam_config, true)` → `video.begin()` | `true` = `dont_init_ldo`。MIPI PHY の LDO は画面（DSI）と共用で、M5GFX が取得済み |
| 【5】 | `cap.begin(name, 3)` → `setFormat(RGB565)` → `startCapture()` | バッファ 3 枚。表示中に 1 枚取られても余裕を持たせる |
| 【6】 | `loop()` で `captureBuffer()` → 表示 | バッファは変数が消えると自動でドライバに返る（RAII） |
| 【7】 | 60 ラインずつ内蔵 RAM 経由で `pushImage` | PSRAM から直接 DMA すると黒画面になることがある |

ログの見どころ:

```
[1] XCLK 24MHz AUTO t3 : 24000000 Hz     ← ここが 20MHz 未満なら失敗
[3] 完了 (0x03=6F / 0x05=76 / 0x07=00)
[4] 初期化成功 / 撮影開始（RGB565 1280x720）
```

`Failed to detect camera sensor with address=..` はドライバが候補アドレスを順に試すときのログで、**正常**です。
`ISP: failed to set CCM` の洪水も表示・読取には影響しません（本スケッチでは抑止しています）。


---

## 5. 半分照明 — 左半分が白、右半分が映像（既定）

暗い場所で「白い面を照明（証明写真の背景・手元の明かり）にしながら、残りで被写体を確認する」ための表示です。起動直後からこの状態になります。

| 位置 | 内容 |
|---|---|
| 左半分（0〜640px） | 真っ白（照明） |
| 右半分（640〜1280px） | カメラ映像（1280×720 を 1/2 縮小＝640×360、上下中央に配置） |

- 白い側は `g_lightLeft` で決まります（`1`=左 / `0`=右）。シリアル `r` でも反転できます。
- シリアル `t` で「半分照明 ⇔ 全面映像」を切り替えます。`l` は全面ライト（従来の白画面）。
- プレビューは縦横 1/2 に間引いているので、**全面表示より軽く、fps が大きく上がります**。

### ★タッチを読まない理由（実測）

最初はタッチで ON/OFF する版を作りましたが、実機で問題が出ました。`M5.update()`（内蔵I2C＝タッチ）を毎フレーム呼ぶと、カメラの SCCB と同じ G31/G32 を取り合い、次のエラーが毎秒十数回出ます。

```
E esp_video_cam: failed to set ctrl id=980913
E ISP: failed to set pixel gain
E i2c.master: clear bus failed.
```

その間 **6 fps → 1.7 fps** まで低下しました。タッチを外した現在の版は次のとおりです。

| 版 | 表示 | fps | I2Cエラー | AEエラー |
|---|---|---:|---:|---:|
| 全面表示・タッチあり（毎フレーム） | 1280×720 | 1.7〜6 | 多数 | 多数 |
| **左半分照明・タッチなし（現在）** | 左=白 / 右=640×360 | **26.0** | **0** | **0** |

タッチが必要になったら、`M5.update()` を 250ms 間隔に間引く（4回/秒）ことで干渉を減らせます。ただし AE への影響はゼロにはなりません。

---

## 6. 外すと動かないポイント（実機で確認した罠）

| 罠 | 外すとどうなるか | 対処 |
|---|---|---|
| XCLK を `M5.begin()` より後に設定 | `ledc_set_timer_div(): timer clock conflict`。24MHz が出ずカメラが応答しない | `xclkSetup()` を setup の先頭に置く |
| `dont_init_ldo` を `false` にする | LDO の取り合いで初期化失敗 | `csi.begin(cam_config, true)` |
| IO エキスパンダ `0x01` に `0xFF` を書く | LCD_RST / TP_RST まで戻り **画面が真っ黒** | §4【3】の値（`0x0D/0x0B/0x05/0x07/0x03`）を変えない |
| GPIO22 を直 High にしない | M5GFX の調光失敗で黒画面のまま | `backlightFix()` を呼ぶ。`setBrightness()` だけでは直らない |
| 表示を PSRAM 直 DMA で行う | 黒画面・ちらつき | 16 ラインずつ内蔵 RAM にコピーして `pushImage` |
| フレームバッファを長く握る | 次のフレームが来ず、fps が落ちる／止まる | 重い処理の前に必要な分だけコピーして手放す |
| 画面表示でバイト入替をしない | 赤と青が入れ替わって見える（色化け） | `s` モード（既定）。**表示は入替、解析は入替なし** が原則 |
| カメラ使用中にタッチ／`M5.update()` を回す | 内蔵 I2C（G31/G32）とカメラ SCCB が同じピンで `clear bus failed` が多発 | 本スケッチは `M5.update()` を呼ばず、操作はシリアル 1 文字 |

### 補足: SCCB のポート貸し借り

M5Unified の版によっては、内蔵 I2C が使っているポートをそのまま渡すと
`I2C bus id(0) has already been acquired` で失敗します。本スケッチは
「M5 が使っていないほうのポート（`I2C_NUM_0` / `I2C_NUM_1`）を選ぶ」方式です。
それでも失敗する環境では、本体統合版（`body/`）で確立した **貸し借り方式** を使ってください。

1. `M5.In_I2C.release()` → そのポートをカメラへ渡す
2. だめなら `Wire1.end()` して再挑戦
3. さらにだめなら `Wire.end()`（Grove G53/G54）して別ポート
4. 終了時は `camRestoreI2c()`（Grove → `Wire1.begin(31,32,400000)` → `M5.In_I2C.begin()`）で返す

---

## 7. キー操作（シリアルモニタ 115200bps で 1 文字）

| キー | 動作 | 使いどころ |
|---|---|---|
| `c` | COLOR（生） | そのままの映像 |
| `s` | COLOR バイト入替 | 色化け（赤青反転）が直る。**既定** |
| `g` | GRAY（明暗を伸ばす） | 暗い・コントラストが弱いとき |
| `t` | 半分照明 ON/OFF | 既定はON（左半分が白／右半分が映像）。OFFで全面映像 |
| `l` | 全面ライト ON/OFF | 画面全体を白くする（従来のライト） |
| `r` | 白い側を左右反転 | カメラの位置に合わせて、照らしたい側へ |
| `e` / `E` | 露出時間を 1 段下げる / 上げる | AE がハンチングするとき |
| `-` / `+` | ゲインを 1 段下げる / 上げる | 暗いとき（ノイズと引き換え） |
| `0` | 露出・ゲインを初期値へ | 元に戻す |
| `h` | ヘルプ | — |

画面下のバーに状態が 1 行で出ます。

```
半分照明: SWAP | 26.1 fps | exp 1000  ／  全面表示: COLOR swap | 26.1 fps | 16 ms | exp 1000 | gain 0
表示モード        実測fps   表示時間  露出時間    ゲイン
```

`*` が付いているときは「手で動かした（手動）」の意味です。

---

## 8. 露出・ゲインについて（実験的）

`ESP_Video.h` の capture デバイスには V4L2 拡張コントロールが用意されています。

```cpp
cap.setSensorExposureTime(int32_t);   // V4L2_CID_EXPOSURE_ABSOLUTE
cap.setSensorGain(int32_t);           // V4L2_CID_GAIN
cap.setSensorExposure(int32_t);       // V4L2_CID_EXPOSURE
cap.setSensorAETargetLevel(int32_t);  // V4L2_CID_CAMERA_AE_LEVEL
```

本スケッチはこのうち露出時間とゲインを使います。**正直な注意点:**

- 返り値は「ioctl が通ったか」であり、**映像が変わったかではありません**。`OK` でも暗さが変わらないことがあります。
- その場合、ドライバ側の AE（自動露出）が優先されています。AE を止める手段は ESP_Video の公開 API には見当たりません。
- 値の単位（マイクロ秒相当か、行数相当か）はドライバ実装依存です。表の数値は**出発点**でしかありません。
- したがって「固定露出版」は、この版では**未完成**です。実機で `e`/`E` を押しながら、`g` モードの見え方と fps を見て決めてください。

本体統合版で見えた「映像が白っぽく、明るさが脈打つ」現象は、この AE のハンチングが疑わしいままです。

---

## 9. 既知の制限

- 解像度は **1280×720 固定**。SC202CS は 1600×900 / 1600×1200 も持っていますが、本スケッチでは未対応です。
- **バックライトの PWM 調光は不可**（XCLK と LEDC が競合）。常時最大点灯です。
- **タッチ操作は使いません**（内蔵 I2C がカメラ SCCB とピンを共有するため）。操作はシリアル 1 文字。
- **露出・ゲインの固定は未完成**（§7）。
- ライトモード中もカメラは動き続けます（止めると再開に時間がかかるため）。

---

## 10. 書き込みが `Write timeout` で失敗するとき

クラッシュ再起動ループで USB が掴めなくなっています。

1. シリアルモニタを閉じる
2. **リセットを約 2 秒長押し**（内部の緑 LED が高速点滅＝ダウンロードモード）
3. 書き込む
4. もう一度リセット

USB ケーブルは**データ通信対応**のものを使ってください。

---

## 11. ライセンス

- このスケッチ: サンプルとして自由に改修・再配布できます（無保証）。
- M5Unified / M5GFX / ESP_Video: それぞれのライセンスに従ってください。

## 12. 改修履歴

| 日付 | 内容 |
|---|---|
| 2026-11-06 | 半分照明（左半分が白・右半分が映像）を既定に。タッチ読み取りを廃止し、fps 6→26、I2C/AEエラー0を実機で確認。 |
| 2026-11-06 | 実機（Tab5）で書き込み・起動を確認。実測 6.0〜7.0 fps。転送ブロックを 60 ラインに拡大し `startWrite()/endWrite()` で囲む変更を追加（効果は小幅）。 |
| 2026-11-06 | 現状ではI2Cと画面描画の性能に影響するため、本ライブラリとしては、二次元コードの読み取りサンプル部分を抹消。露出・ゲイン操作と fps 表示を追加。arduino-cli（コア 3.3.12）で warning 0 / error 0 を確認。 |
| 2026-10-25 | スケッチのファイル名に改修日を追加。本体統合版 v6 を `body/` に公開。 |
