# Tab5 本体統合版 v6 — 引き継ぎ資料

M5Stack Tab5 本体（生体信号取得システム）に、内蔵カメラ（MIPI-CSI / SC202CS）の QR 読み取りを組み込んだ版の最新成果物です。
**別チャットで作業を再開するときは、このファイルの URL をそのまま貼ってください。**

- このファイル: https://ailab-japan.com/TAB5CAM_QR/body/README.md
- フォルダ: https://ailab-japan.com/TAB5CAM_QR/body/
- 単体版サンプル（親フォルダ）: https://ailab-japan.com/TAB5CAM_QR/

---

## 1. ファイル一覧（md5 はアップロード後に取り戻して照合済み）

| ファイル | サイズ | md5 |
|---|---:|---|
| `tab5_cam_body_20261025.ino` | 471034 | `2cf17cd3b419778f4ae2e61ec32e830c` |
| `tab5_cam_body_20261025.zip`（quirc 同梱・フォルダ入り） | 169392 | `5f3b25d9a1b869b60e7140a4e54d5d1a` |
| `quirc.h` | 5,402 | `95260956b4e462795c479c87a2232050` |
| `quirc.c` | 4,598 | `0c0dc44ced30934372c2c01423957d84` |
| `quirc_internal.h` | 3,720 | `1437c864d870d4f82cc1d27288c96dc7` |
| `decode.c` | 21,512 | `fe7274780ba959367f61f19869e39773` |
| `identify.c` | 28,639 | `b537b9590c42f4c822d6e04ab86d72ee` |
| `version_db.c` | 11,779 | `cb832938ce640b66f1cf1af4e2b64770` |

ビルド結果: **Flash 2,810,730 bytes (42%) / Global 194,044 bytes (59%) / error 0・warning 0**

ファイル名は**改修日（YYYYMMDD）を末尾に付ける**規則。zip は Arduino の仕様（フォルダ名＝ino 名が必須）に合わせて `tab5_cam_body_20261025/` フォルダごとの形にしてある。

ビルド方法（Arduino IDE でも arduino-cli でも可）:

```
arduino-cli compile -b esp32:esp32:m5stack_tab5 <sketch_dir>
```

ino と同じフォルダに quirc の 6 ファイルを置くこと。ESP32 コアは 3.3.12。

---

## 2. これは何か（一言で）

本体ファーム（`tab5_port` 第 8 次安定版）に、**QR 画面を開いたときだけ内蔵カメラを初期化して QR を読み、閉じるとカメラを解放する**機能を足したもの。
読み取った文字列は `g_qrText` に保持し、従来の `# qr_code:` 行（live CSV 3 箇所＋BA サマリ）へ付加する。外部 QR ユニット（U173 / I2C 0x21）のコードは**完全に抹消済み**。

**安定版本体 `tab5_port.ino` は無傷**（md5 `7fcd292f15403b92cb4e948d0f36959d`）。この版はその上に載る差分版。

---

## 3. ここまでの経緯（時系列）

1. **内蔵カメラの解明** — 給電は IO エキスパンダ `0x43` の bit6。SCCB は SCL=G32 / SDA=G31、アドレス `0x36`、センサ ID `0xEB52`（SC202CS。M5 の表記が「SC2356」でも同ドライバで認識）。解像度は 1280x720 固定。
2. **QR が読めなかった真因を特定** — 実機の生画像を SD にダンプして実測した結果、**輝度のバイト入替が原因**。RGB565 標準（入替なし）で解析すれば読める。ZXing-C++ / ZBar / OpenCV / quirc の 4 種すべて標準で読取に成功した。「ISP のせい」「ノイズのせい」は**誤り**。
3. **クラッシュの原因** — quirc の `decode.c:406` が `uint8_t data[QUIRC_MAX_PAYLOAD]` = 8,896B をスタックに取り、`quirc_decode()` で約 9KB 消費する。Arduino の `loopTask` は既定 8KB なので保護違反で落ちる。**64KB の専用タスクに隔離するのが必須**。
4. **単体版 v24 で実機読み取り成功** — `QR,https://example.com` を連続取得。勝ち条件は **縮小 S=720（縮小なし）＋2 値化のみ**。
5. **v26 で「小さく遠い QR」対応** — 中心クロップ＋段階拡大（Z=1..4）。シミュレーションで QR150px は Z3/Z4、QR120px は Z4 で読めることを確認。
6. **サンプルプログラム化して公開** — 注意点 8 項目を README にまとめ、親フォルダで頒布中。
7. **本体への組み込み v1〜v6** — v1 で U173 を撤去して差し替え → v2 で画面真っ黒を修正 → v3 で SCCB ポート解放方式を確立 → v4 でちらつき修正 → v5 で解析を専用タスクへ移し、読取時にカメラを止めて I2C を返す方式に → **v6 で照明＋プレビュー分割と高速化**。

---

## 4. Tab5 で踏んだ罠（重要・再発防止）

- **XCLK(24MHz) を `M5.begin()` より先に取ると LEDC タイマが埋まる** → `ledc_set_timer_div(): timer clock conflict` → M5GFX のバックライト調光が失敗し **GPIO22 が LOW のまま＝画面真っ黒**。対策として `camBacklightForce()`（GPIO22 を素の出力で HIGH）を表示開始時と向き変更のたびに呼ぶ。**副作用＝調光不可・常時最大点灯**。XCLK の結果ログは `Serial.begin` の後に出す（前だと出ない）。
- **カメラ SCCB は G31/G32＝内蔵 I2C と同一ピン**。「空いているポート」を渡すのは誤り（`I2C bus id(0) has already been acquired`）。**内蔵 I2C が使っているポートを解放して渡す（貸し借り方式）**。3 段階：①`M5.In_I2C.release()` → そのポートをカメラへ ②`Wire1.end()` して再挑戦 ③`Wire.end()`（Grove G53/G54）して他ポート。終了時 `camRestoreI2c()` で Grove → `Wire1.begin(31,32,400000)` → `M5.In_I2C.begin()`。
- **カメラ使用中（`g_camQrActive`）は `M5.update()`・タッチ読み取り・`rtcPoll()` を止める**。
- **画面下の A/B/C/D はタッチ領域なのでカメラ使用中は届かない**。確実に効くのは **LED ボタン（G3＝物理・素の GPIO）だけ**。
- **表示転送は 16 ラインずつ内蔵 RAM 経由で `pushImage`**（PSRAM 直 DMA は黒画面リスク）。**表示はバイト入替で色化けが改善、解析は入替なしが正解**（別系統）。
- ESP_Video の初期化は `cam_config.begin(port,32,31,400000)` → **`csi.begin(cam_config, true)`（`dont_init_ldo=true` 必須）** → `video.begin(csi)` → `cap.begin(...)` → `cap.setFormat(RGB565)` → `cap.startCapture()` → `cap.captureBuffer()`。MIPI PHY 用 LDO は画面(DSI)とカメラ(CSI)で共用なので `dont_init_ldo=true` が要る。
- センサ初期化の確定手順：`0x0D=0x7F, 0x0B=0x7F, 0x05=0x76, 0x07=0x00, 0x03=0x7F, delay10, 0x03=0x6F, delay50`。**`0x01=0xFF` は絶対禁止**。
- `Failed to detect camera sensor with address=..` は正常ノイズ。`ISP: failed to set CCM` の洪水も読取に無関係。

---

## 5. v6 の設計（今回の変更点）

- 画面を左右に分割。片側を**真っ白＝照明**（GPIO22 直結の常時最大点灯なので白い面がそのまま最大光量）、反対側に**プレビュー**。既定は **照明が左／プレビューが右**。
  - 定数：`CAM_LIGHT_LEFT=1` / `CAM_HALF=640` / `CAM_W=640` / `CAM_H=(T5_BY-T5_WY)=504` / `CAM_X=640` / `CAM_Y=124` / `CAM_LIT_X=0`
  - **左右が逆なら `CAM_LIGHT_LEFT` を `0` にするだけ**（カメラの物理位置は製品画像が取得できず未確定）。
- プレビューは中央 640x504 を**等倍**表示（前版は 720x380）。候補枠は `dax=40, day=108` で写像し枠外クリップ。
- 解析用コピーを軽量化：全画面 1280x720 → **中央 720x720 だけを詰めた `g_camCopy`（1,036,800B）**。`(fc % 3) == 0` のフレームで転送。
- `camZoomRegion(const uint16_t* src, int stride, int ox, int oy, int Z)` に一般化。
- **解析タスクをコア 0 へ**：`xTaskCreatePinnedToCore(camTask,"camQr",64*1024,nullptr,1,nullptr,0)`。撮影・表示と解析を別コアに分離。
- 計測ログ：5 秒ごとに `[CAM] preview ◯ fps / 解析 ◯ ms (最大 ◯, ◯ 回) / コピー ◯ ms / Z=◯`
- **B ボタン（ext==2）＝照明 ON/OFF**。QR 画面を開くたびに照明 ON に戻る。A=ズーム、LED ボタン=戻る。
- 読み取り成功時：`qrBeep()`（2200Hz×2・90ms・音量下限 140）→ `t5QrStop()` でカメラ停止＋I2C 返却＝タッチ復活 → 緑ポップアップ → **タッチまたは LED ボタンで戻る**（自動復帰は廃止）。
- プレビューは `uiPresentTask` に描かせず `loop()` が専有（`if (!g_qrScreen) tab5ChromePresent(0);`）。固定部分は `camQrDrawStatic()` で 1 回だけ描く。
- 画面上のラベル語は **「照明」**（既存フォントに 照/明 があるため）。**□ になる場合は「ライト」に変更する**。

---

## 6. 実機で未確認の項目（次にやること）

v6 はコンパイルは通っているが、**実機での動作はまだ見ていない**。以下を確認する。

1. プレビューの滑らかさ — シリアルに 5 秒ごとに出る `[CAM] preview ◯ fps` の値（数十 fps 出れば合格）
2. 照明とプレビューの左右はこれで正しいか（逆なら `CAM_LIGHT_LEFT` を 0 に）
3. B ボタンで照明が ON/OFF できるか
4. 読み取ったら「ププッ」→ ポップアップ → **タッチで戻れるか**（カメラ停止で I2C を返しているのでタッチが復活しているはず）
5. **LED ボタンで必ず戻れるか**
6. 「照明」の文字が □ になっていないか

---

## 7. 既知の制約・未解決

- バックライトの PWM 調光は不可（XCLK と LEDC が競合、常時最大点灯）
- 解像度は 1280x720 固定（1600x900 / 1600x1200 は未実装）
- 露出・ゲインの自動制御がハンチングしている疑い（映像が白っぽく明るさが脈打つ、`blk=42%`）。**露出・ゲイン固定版は未作成**（`setSensorExposureTime()` / `setSensorGain()` / `setSensorAETargetLevel()` が候補）
- `ISP: failed to set CCM` の洪水は未抑制（読取に無関係なので深追いしていない）

---

## 8. 作業の進め方（このプロジェクトの約束）

- **日本語のみ。** 使い勝手に関わる判断は勝手に決めず先に確認する。
- **正しいのは実機の事実とログだけ。** 推測で断定しない。
- 失敗したら継ぎ足さず、**きれいな地点に戻してやり直す**。動く版ができたら即凍結。
- パッチは「行単位の完全一致でヒット数が必ず 1」の置換のみ。`re.subn` は使わない（置換文字列内の `\n` が実改行に化けて文字列リテラルを壊す）。置換行の行頭に `//` コメントを置くと行末の `}` まで飲み込まれて波括弧が崩れるので、コメントは独立行に。
- マクロを使用する行はマクロ定義より後ろに置く。
- コンパイルが通ったときだけ zip を作る（失敗時は前回の zip が残り、それが復元用の base になる）。

---

## 9. サーバーへの置き方

- サーバー: お名前.com レンタルサーバー（`www341.onamae.ne.jp`）。`ailab-japan.com` と同じ IP。
- 接続は **SFTP / SSH（ポート 8022、公開鍵認証）**。従来の FTP は廃止された。
- 設置先: `~/public_html/ailab-japan.com/TAB5CAM_QR/body/`
- **認証情報はこのファイルには書かない**（公開領域のため）。鍵は依頼者から都度受け取る。

---

## 10. 改修履歴

| 日付 | 内容 |
|---|---|
| 2026-10-25 | ヘッダに「エイアイ・ラボ・ジャパン」へのリンクを追加（https://ailab-japan.com/）。 |
| 2026-10-25 | ファイル名を改修日入りに変更（`tab5_cam_body_20261025.ino` / `.zip`）。zip をフォルダ入り形式に変更し、展開してそのままビルドが通ることを実測確認。 |
| 2026-10-25 | 公開ページを新規作成。本体統合版 v6 一式を `/TAB5CAM_QR/body/` に設置。FTP を廃止し SFTP（ポート 8022・公開鍵認証）へ移行。アップロード後に取り戻して md5 を全件照合。ページトップに改修日と履歴ポップを追加。 |
| 2026-10-15 | 本体統合版 v6 ビルド完了。照明（左半分・常時最大点灯）＋プレビュー（右半分・中央 640×504 等倍）の分割。解析コピーを中央 720×720 に絞り 3 フレームに 1 回転送。解析タスクをコア 0 へ移動。B ボタンで照明 ON/OFF。Flash 2,810,730 bytes (42%) / error 0・warning 0。 |

**新しい改修をしたら、この表の先頭に 1 行足す**（ページ側の履歴ポップも同じ内容に更新する）。
