# Tab5 内蔵カメラ サンプル（映像表示 + QR読み取り + 画面ライト）

M5Stack **Tab5** の内蔵カメラ（MIPI-CSI / SC202CS）を **Arduino IDE** で動かすためのサンプルです。
試行錯誤で分かった「Tab5 カメラの落とし穴」を、コードとこの文書に全部残しました。
同じ所で転ばないための資料として、どうぞ自由にお使いください。

---

## 1. できること

| 機能 | 内容 |
|---|---|
| カメラ映像の表示 | 1280x720 の映像を5インチ画面に表示（COLOR / バイト入替 / 2値 / 明暗伸長） |
| QRコードの読み取り | [quirc](https://github.com/dlbeer/quirc) で解析。読めた内容をシリアルに `QR,内容` で出力 |
| 画面ライトモード | 画面を真っ白にして「ライト代わり」に使う（暗い場所対策） |
| 解析の可視化 | quirc が見ている2値画像・QR候補の枠・黒の割合を画面表示 |

> 本サンプルは**本体（SEIDEN2finger 等）に組み込む前の切り分け**を目的にしています。
> カメラだけを取り出して「どこまで動くか」を確かめられます。

---

## 2. 必要なもの / 環境

### ハードウェア
- M5Stack Tab5 本体
- USB Type-C ケーブル（**データ通信対応**のもの。充電専用は書き込み不可）
- QRコード（紙でも画面でも可）

### ソフトウェア

| 項目 | 内容 |
|---|---|
| Arduino-ESP32 コア | **3.3.11 以降**（確認は 3.3.12） |
| ボード設定 | **M5Stack Tab5** |
| PSRAM | 有効（OPI PSRAM） |
| ライブラリ | M5Unified / ESP_Video（後述） |
| 同梱物 | quirc の6ファイル（`quirc.h` `quirc.c` `quirc_internal.h` `decode.c` `identify.c` `version_db.c`） |

### 2.1 ESP_Video ライブラリの入れ方（ここが最大のつまずき）

Arduino-ESP32 コア 3.3.11 以降には ESP_Video が同梱されていますが、環境によっては
コア内の `libraries\ESP_Video` が見当たりません。その場合は **配布している
`ESP_Video_for_3.3.12.zip` を `Arduino\libraries\ESP_Video` に展開**してください。

```
（Windows の例）
C:\Users\＜ユーザー名＞\Documents\Arduino\libraries\ESP_Video\src\ESP_Video.h   ← この形になるように
```

- **1階層深くならないように注意**してください（`ESP_Video\ESP_Video\src\...` はNG）。
- 展開後は **Arduino IDE を再起動**します。
- IDE再起動後もスケッチ冒頭のマクロ定義（`CONFIG_ESP_VIDEO_ENABLE_MIPI_CSI_VIDEO_DEVICE` など）は**消さないでください**。

### 2.2 スケッチの置き方

```
Arduino/
└─ Tab5_CameraQR_Sample_20261015/          ← フォルダ名と .ino 名は必ず一致させる
   ├─ Tab5_CameraQR_Sample_20261015.ino
   ├─ README.md
   ├─ quirc.h
   ├─ quirc.c
   ├─ quirc_internal.h
   ├─ decode.c
   ├─ identify.c
   └─ version_db.c
```

`Tab5_CameraQR_Sample_20261015.ino` を開いて、**M5Stack Tab5** を選び、書き込みます。

---

## 3. コンパイル・実行に必要な注意点（重要）

**これだけは外すと動きません。** 実機で1つずつ確かめた項目です。

| # | 注意点 | 外すとどうなるか |
|---|---|---|
| 1 | **XCLK は `M5.begin()` より先に設定する** | LEDC のタイマ衝突で 24MHz が出ず、カメラが応答しない |
| 2 | **`csi.begin(cam_config, true)` の第2引数＝`dont_init_ldo=true`** | LDO の取り合いで初期化失敗（画面が先に取っている） |
| 3 | **IOエキスパンダの `0x01` に `0xFF` を書かない** | LCD_RST/TP_RST まで戻り、**画面が真っ黒**になる |
| 4 | **バックライト GPIO22 を直Highにする** | M5GFX の LEDC 調光が失敗し、画面が真っ黒のまま |
| 5 | **QR解析は専用タスク（スタック64KB）で行う** | `decode.c` が約9KBのスタックを使い、`loopTask`（8KB）では保護違反でリセット |
| 6 | **解析は「バイト入替なし」の輝度で行う** | 画像が壊れて、どんなQRデコーダでも読めなくなる |
| 7 | **表示は16ラインずつ内蔵RAMを経由する** | PSRAM から直接 DMA しようとすると黒画面になることがある |
| 8 | **このサンプルでは `M5.update()` とタッチを使わない** | 内蔵I2C(31/32) とカメラSCCB が同じピン。`clear bus failed` が大量発生 |

### 3.1 XCLK（カメラのマスタークロック）

ESP32-P4 の LEDC は **Low Speed モードのみで、全タイマがクロック源を共有**します。
M5GFX はバックライト用に LEDC を使うため、**`M5.begin()` の後に XCLK を設定すると**

```
E ledc: ledc_set_timer_div(693): timer clock conflict ...
```

が出て、24MHz を作れません。よって **XCLK を先に**設定します。

なお LEDC の出力周波数は「源クロック ÷（分周比 × 2）」で、分周比は 1 以上です。
XTAL 40MHz を源にすると最大 20MHz で **24MHz は出せません**。`LEDC_AUTO_CLK` に任せると
PLL_F80M（80MHz）が選ばれ、24.0MHz が得られます。

```
[1] XCLK 24MHz AUTO t3 : 24000000 Hz     ← こう出ればOK
```

### 3.2 LDO（`dont_init_ldo = true` が必須）

ESP32-P4 の MIPI PHY 用 LDO（**チャネル3 / 2500mV**）は、**画面（DSI）とカメラ（CSI）で共用**です。
`M5.begin()`（M5GFX）がすでに取得しているので、ESP_Video 側で取り直すと失敗します。
`csi.begin(cam_config, true)` の `true` がこれを回避するフラグです。

### 3.3 カメラ給電（IOエキスパンダ `0x43`）

Tab5 のカメラは IO エキスパンダ **PI4IOE5V6408-1（I2C `0x43`）** で制御されています。
`E1.P6 = CAM_RST` を解放しないと、カメラは SCCB に応答しません（`0x36` が出てきません）。

本サンプルが書いている値（読み戻し確認済み）:

| レジスタ | 値 | 意味 |
|---|---|---|
| `0x0D` | `0x7F` | PULL_SEL |
| `0x0B` | `0x7F` | PULL_EN |
| `0x05` | `0x76` | OUT_SET（bit1=SPK_EN / bit2=EXT5V_EN / bit4=LCD_RST / bit5=TP_RST / bit6=CAM_RST） |
| `0x07` | `0x00` | OUT_HIM |
| `0x03` | `0x7F`→`0x6F` | IO_DIR（bit4 だけ入力に戻す） |

> **`0x01`（CHIP_RESET）に `0xFF` は絶対に書かないでください。**
> M5GFX が設定した LCD_RST / TP_RST を巻き戻し、画面が真っ黒になります（実機で確定）。

### 3.4 バックライト（GPIO22 / LEDA）

M5GFX はバックライトを LEDC で調光しようとしますが、**3.1 の理由で失敗し、GPIO22 を LOW のまま終了**します。
その結果、画面が真っ黒になります。本サンプルは `backlightFix()` で **GPIO22 を素の出力として HIGH** に固定します。

- 副作用として **調光（`setBrightness()`）は効かず、常時最大点灯**になります。
- 「画面をライト代わりにする」用途では、むしろ好都合です。

### 3.5 スタック（QR解析タスク）

`quirc_decode()` は `decode.c` の中で **`uint8_t data[QUIRC_MAX_PAYLOAD]`（=8,896バイト）** を使います。
Arduino の `loopTask` のスタックは既定 **8KB** なので、`loop()` から直接呼ぶと

```
Guru Meditation Error: Core 1 panic'ed (Stack protection fault).
Detected in task "loopTask"
```

でリセットします。本サンプルは `xTaskCreatePinnedToCore(qrTask, "qrTask", 64*1024, ...)` で
**64KB の専用タスク**に隔離しています。

### 3.6 輝度のバイト順（いちばん大事な教訓）

カメラが返す RGB565 は **標準の並び（赤が上位ビット）** です。
ここを「バイト入替してから輝度を取る」と画像が壊れ、**どんなデコーダでも読めません**。
実機の生データで4種類のデコーダ（ZXing-C++ / ZBar / OpenCV / quirc）を比較して確定しました。

- **解析（デコード）＝ バイト入替なしが正解**
- **画面表示 ＝ バイト入替すると色化け（赤青反転）が直る**（`s` キー）

「解析用」と「表示用」は別系統、と覚えてください。

---

## 4. ピン（Tab5 公式 PinMap より抜粋）

内蔵なので配線は不要ですが、切り分けの参考に。出典: [M5Stack Tab5 公式ドキュメント](https://docs.m5stack.com/en/core/Tab5)

| 機能 | ピン |
|---|---|
| CAM_SCL | G32 |
| CAM_SDA | G31 |
| CAM_MCLK (XCLK) | G36 |
| CSI データ線 | 専用（D0/D1P/D1N/CKP/CKN） |
| LCD バックライト LEDA | G22 |
| IOエキスパンダ `0x43` | SCL=G32 / SDA=G31 |

`0x43` のビット割り当て（公式ドキュメントより）:

| ビット | 信号 |
|---|---|
| E1.P1 | SPK_EN（NS4150B） |
| E1.P2 | EXT5V_EN（`EXT_5V_BUS`） |
| E1.P4 | LCD_RST |
| E1.P5 | TP_RST |
| E1.P6 | **CAM_RST** |

> TAB5 で使用禁止のピン: G39〜G44（microSD）, G11〜G15（ESP32-C6）, G26〜G30（音声）, G20/G21/G34（RS485）, G36（本用途でカメラMCLK）ほか。

---

## 5. 使い方

1. 書き込み後、**シリアルモニタを 115200bps** で開きます。
2. カラーバー（白→赤→緑→青）が出れば画面は正常です。
3. QRコードをカメラに映すと、読めた内容が画面とシリアルに出ます。

```
QR,https://example.com
  (QR発見 index=0 len=19 ECC=1 ver=2)
  ★読めた条件: ズームZ=3 (中心 240px) / 2値化のみ
```

4. 5秒ごとに状態が出ます。

```
[300] 1280x720 / mode=COLOR byte-swap / ズームZ=4 / QR候補 0 / 黒47%
```

### キー操作（シリアルモニタに1文字）

| キー | 動作 |
|---|---|
| `c` | COLOR（生） |
| `s` | COLOR バイト入替（色化け対策・既定） |
| `b` | BINARY（quirc が見ている2値画像） |
| `g` | GRAY（明暗を伸ばす） |
| `k` | 先鋭化 ON/OFF（既定OFF） |
| `z` | ズーム：自動（Z=1..4 巡回）⇔ 固定 Z=1..4 |
| `a` | 8秒ごとの表示自動切替 |
| `l` | **ライトモード**（画面を白くする）ON/OFF |
| `h` | ヘルプ |

---

## 6. 画面の見方

```
┌──────────────────────────────────┐
│ QR: https://example.com          │ ← 読めた内容（黄色）
│                                  │
│   ┌──────────────────┐           │
│   │  緑枠＝解析範囲     │           │ 赤枠＝QR候補（quircの検出）
│   │   （720x720）      │           │
│   └──────────────────┘           │
│                                  │
│ COLOR byte-swap | Z3 | q720 | cand 1 | blk 47%   ← 下のバー＝状態
└──────────────────────────────────┘
```

- **緑枠**: 解析している範囲（映像中央の 720x720）
- **赤枠**: quirc が見つけた QR 候補（読めていなくても出ます）
- **下のバー**: 表示モード / 使用ズームZ / 入力サイズ / 候補数 / 2値画像の黒の割合

`blk`（黒の割合）は明るさの当たり外れが一目で分かります。
**5%未満＝明るすぎ／白飛び**、**70%超＝暗すぎ**が目安です。

---

## 7. QR解析の設計と、読めないときの勘所

### 7.1 なぜ quirc か
- 軽量（PSRAM に数百KB）でマイコン向き、ライセンスも ISC で扱いやすい。
- ただし **内部は VGA（640x480）以下の画像を想定**しており、
  `QUIRC_MAX_REGIONS = 254` の制約があります（連結領域が254個を超えると探索を打ち切る）。
  大きなノイズ画像をそのまま入れると、QRの角を探す前に打ち切られて**候補0**になります。

### 7.2 本サンプルの前処理（ここが効きます）
1. **中心 720x720 を切り出す**（探す範囲を絞る＝ノイズ削減）
2. **バイト入替なしでグレー化**（3.6 の教訓）
3. **局所適応しきい値で 0/255 に2値化**（quirc の大域 Otsu を安定させる）
4. **3x3 多数決で孤立点除去**（連結領域の数を減らす）
5. **ズーム Z=1..4 を巡回**（中心を切り出して 720 に拡大）

### 7.3 ズーム（小さく遠いQR対策）

実機の生画像でシミュレーションした実測結果です（zx=ZXing-C++ / zb=ZBar / cv=OpenCV）。

| QRサイズ | モジュール | 従来(Z=1) | Z=2 | Z=3 | Z=4 |
|---|---|---|---|---|---|
| 200px | 5.1px | zx zb – | zx zb cv | zx zb cv | – zb – |
| 150px | 3.8px | **– zb –** | – zb – | **zx zb cv** | **zx zb cv** |
| 120px | 3.1px | **– zb –** | – zb – | – zb cv | **zx zb cv** |
| 100px | 2.6px | – zb – | – zb – | – zb – | – – cv |

→ **「これまでは読めなかった遠いQR」が Z=3・Z=4 で読める**ようになります。
Zが大きいほど中心だけを見るので、**遠いQRは画面中央に置いて**ください。

### 7.4 読めないときのチェックリスト

1. `b` キーで2値画像を見る → QRの形が見えているか（見えていなければ前処理・ピントの問題）
2. `blk`（黒の割合）が5〜70%に入っているか
3. `l` キーで**ライトモード**（暗い場所なら特に）
4. `z` キーで Z=3 / Z=4 に固定（遠い場合）
5. カメラとQRの**距離・角度・ピント**を変えてみる（近すぎてもぼけます）
6. `s` / `c` で表示の色は正しいか（解析自体は色に依存しません）

---

## 8. ライトモード（画面をライト代わりに）

`l` キーで画面が**真っ白**になり、5インチ・バックライト最大点灯の光を照明として使えます。

- ライトモード中もカメラとQR解析は動き続けます。
- **QRが読めたら自動でライトモードを解除**して結果を表示します。
- バックライトは GPIO22 直Highで常時最大点灯なので、白画面＝最大光量です。

> 画面とカメラの向き・対象との距離で効果は変わります。暗い場所での「保険」としてお使いください。
> なお**PWM調光は入れていません**。LEDC が XCLK と競合するためで、これは今後の課題です。

---

## 9. トラブルシューティング

| 症状 | 原因 | 対処 |
|---|---|---|
| 画面が真っ黒（カラーバーも出ない） | バックライトが LOW のまま | `backlightFix()` が呼ばれているか確認。`setBrightness()` だけでは直りません |
| 画面が真っ黒（急に、再起動後） | IOエキスパンダ `0x01` に `0xFF` を書いた | 書かない。`camPowerOn()` の値を変えない |
| `ledc: timer clock conflict` | XCLK を `M5.begin()` の後に設定した | 順序を守る（`xclkSetup()` を先に） |
| `Failed to detect camera sensor with address=...` | ドライバが候補アドレスを順に試しているログ | **正常です。無害**。無視してください |
| `ISP: failed to set CCM` が大量に出る | ISP の補正設定が無いためのログ | **読取には影響しません**（実測）。`esp_log_level_set("ISP", ESP_LOG_NONE)` で抑制 |
| 映像の色が赤と青で反転 | 表示側のバイト順 | `s` キー（バイト入替）。**解析には影響しません** |
| QR候補が0のまま | ノイズ過多 / 白飛び / 暗すぎ / ピント | 7.4 のチェックリストへ |
| 起動直後にリセットを繰り返す | `loopTask` のスタック保護違反 | 解析を専用タスク（64KB）で行う |
| 書き込みが `Write timeout` で失敗 | クラッシュ再起動ループでUSBが掴めない | シリアルモニタを閉じる → リセット約2秒長押し（緑LED高速点滅＝ダウンロードモード）→ 書き込み → リセット |
| `ESP_Video.h` が見つからない | ライブラリの配置 | 2.1 の手順（`Arduino\libraries\ESP_Video\src\ESP_Video.h`） |
| `cap.begin` が失敗する | カメラ給電/初期化順 | 3.2 / 3.3 を確認。`dont_init_ldo=true` を忘れずに |

---

## 10. ファイル構成

```
Tab5_CameraQR_Sample_20261015/
├─ Tab5_CameraQR_Sample_20261015.ino   本体（このサンプル）
├─ README.md                  この文書
├─ quirc.h                    ┐
├─ quirc.c                    │
├─ quirc_internal.h           ├ quirc (QRコード認識ライブラリ) 同梱分
├─ decode.c                   │
├─ identify.c                 │
└─ version_db.c               ┘
```

> `quirc.c` は ESP32 用に1行だけ手を入れています。
> `malloc` を PSRAM に割り当てるためのものです（下記）。

```c
#include <esp_heap_caps.h>
#define malloc(sz) heap_caps_malloc((sz), MALLOC_CAP_SPIRAM)
```

---

## 11. ライセンス

- **このスケッチ**: サンプルとして自由に改変・再配布できます（無保証）。
- **quirc**: ISC ライセンス（下記）／Copyright (C) 2010-2012 Daniel Beer &lt;dlbeer@gmail.com&gt;

```
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
```

- **M5Unified / ESP_Video**: それぞれのライセンスに従ってください。

---

## 12. 既知の制限（これから）

- 解像度は **1280x720 固定**。SC202CS は 1600x900 / 1600x1200 も持っていますが本サンプルは未対応です
  （遠いQRには高解像度が効く可能性があります）。
- **露出・ゲインはカメラ任せ**（`setSensorExposureTime()` 等は未使用）。
- バックライトの **PWM調光は未実装**（LEDC が XCLK と競合するため）。
- タッチ操作は使いません（内蔵I2C がカメラSCCB とピンを共有するため）。
- QR読み取りは「はっきり写ったもの」が対象です。極端に小さい・遠い・ぼけたQRは限界があります。

---

## 13. 動作確認の範囲（正直な記載）

- 内蔵カメラの映像表示、QR読み取り、IOエクスパンダ経由の給電、ESP_Video の初期化手順は、
  **実機（Tab5）で動作を確認済み**です。
- 本サンプルは、その実機で確認できた処理を1本に整理したものです。
  整理後の**全機能の通し確認**はこれからです。差異があればお知らせください。

---

## 14. 改訂履歴

| 版 | 内容 |
|---|---|
| 1.0 | 初版。映像表示 + QR読み取り + ライトモード。実機で確立した設定（給電・XCLK・LDO・バックライト・スタック分離）をすべて反映 |

---

## 本体への組み込み版

このサンプルを本体ファームに組み込んだ版を `../body/` に置いています（2026.10.25 公開）。
照明とプレビューの分割、解析タスクのコア分離、Bボタンでの照明 ON/OFF が入っています。

- 案内ページ: https://ailab-japan.com/TAB5CAM_QR/body/
- 引き継ぎ資料: https://ailab-japan.com/TAB5CAM_QR/body/README.md

改修履歴は案内ページ上部の日付から見られます。

---

## 改修履歴

| 日付 | 内容 |
|---|---|
| 2026-10-25 | ヘッダに「エイアイ・ラボ・ジャパン」へのリンクを追加。 |
| 2026-10-25 | ファイル名に改修日を追加（`Tab5_CameraQR_Sample_20261015.ino` / `.zip`）。zip は Arduino の仕様（フォルダ名＝ino 名）に合わせてフォルダ入り形式に変更。 |
| 2026-10-15 | サンプル v1.0 公開。 |

新しい改修をしたら、この表の先頭に 1 行足してください。
