九州・福岡・東京ときどきIoT

21年間のはてなダイアリー&アメブロからの避難所

超音波センサとesp32による機械学習推論について

 では **超音波センサ2セット(各セットは送受信)で起きるクロストーク前提** に基づき、「\*\*信号の取得方法(ハード/タイミング)」「信号処理による判定ロジック(自信度付き)」「制御ループ(スケジューリング+追尾/除外)」を具体的に設計・検討します。
以下は実用的で実装に移せるレベルの手順・パラメータ・疑似コードを含みます。目的は「他ユニットの送波と自ユニットの送波を区別して正しく距離/方位を得ること」と「得られた情報で安定に制御(追従・回避・測位融合)すること」です。

 

# 要点まとめ(先に短く)

1. **取得は時分割(TDM)を基本**に、必要なら送波に識別符号を付与して受信で相互相関による識別を併用する。
2. **判定は複合ルール**(期待TOFウィンドウ+相互相関ピーク+振幅閾値+複数回一致判定)で行い、各測定に **信頼度スコア** を付ける。
3. **制御は階層化**(タイミングスケジューラ → データ処理 → トラッカ/Kalman → アクチュエータPID)で行う。
4. 実パラメータ目安:R\_max=5 m → 各発射に40 ms受信ウィンドウ、2ユニット輪番で周期≈80 ms(12.5 Hz)。相関符号長16–32サンプル。

以下、詳細。

---

# 1) 信号の「取得」:ハードとタイミング設計

## A. モード選択(2通り)

* **モード1(簡易)**:市販モジュール(HC-SR04等)風 → 発射トリガ→エコー幅(GPIO)を測定。利点は単純でESP32のRMT/GPIOで実装容易。欠点:識別能力が低くクロストークはTDMで回避。
* **モード2(拡張)**:自前トランスデューサ+ADCで受信波形をサンプリング → 符号化(Barker/PN)を送信して受信で相互相関(マッチドフィルタ)。利点はクロストーク識別や同時発射耐性。欠点:ADCサンプリング・計算負荷が増える。

以下は両方に使える共通タイミング設計。

## B. マスター時分割スケジュール(中央管理)

ESP32(中央)で全センサの発射を管理。2セット(A,B)輪番の例:

* 設定例:R\_max = 5 m → tRT ≈29.2 ms。余裕を見て t\_wait = 40 ms。
* スケジュール(繰り返し):

  * t=0 ms: A トリガ送出(短いパルス/符号)
  * t=send\_pulse\_ms (例 2 ms): A 受信ウィンドウ開始
  * t=send\_pulse\_ms + t\_wait (例 42 ms): A受信ウィンドウ終了 → データ処理
  * t≈42 ms: B トリガ → 同様に B 受信ウィンドウを開く
  * 次のAは t≈84 ms → 周期 ≈ 84 ms(約 12 Hz)

**注意**:send\_pulse\_ms は符号送信長(符号化する場合は長くなる)。受信ウィンドウは送波長+2×R\_max/c+マージン。

## C. 物理的対策(並行)

* トランスデューサ周りに小バッフルで指向性向上。
* 発射レベルを必要最小に。
* センサ筐体で互いの直接波を遮る(可能なら)。

---

# 2) 信号処理・判定ロジック(「どう判断するか」)

目的:**受信信号が「自分の送波由来の反射」か「他センサの送波」か「ノイズ/反射」か**を高確率で判別し、距離/到来時間を信頼度付きで出す。

## A. 基本ルール(ルールセット)

1. **期待TOFウィンドウ判定**

   * 受信時間 t\_recv が自ユニット送波からの期待範囲 $t_{\min} \le t_{\text{recv}} \le t_{\max}$ にあるか(t\_min は送波の終端+受信硬直時間)。外れたら低信頼。
   * 例:t\_min = 0.2 ms(機器復帰)、t\_max = 2×R\_max/c + margin(R\_max=5m→≈31 ms、marginを+8msで40ms)。

2. **相互相関(符号化がある場合)**

   * 受信波形に対し、各センサの符号で相互相関を計算。最大相関が自符号であれば「自分の送波由来」と判定しやすい。
   * 相関ピーク高さ/ノイズ床比(SNR)でスコア化(例:相関比 > 6 → 高信頼)。

3. **振幅(エネルギ)閾値**

   * 受信振幅が一定以上であるか。非常に小さいピークはノイズ扱い。複数回平均で確認。

4. **一貫性(複数発射での再現性)**

   * 直近 K 回(例 K=3)で同様の距離が得られるか(差 < δ\_d, 例 δ\_d=5 cm)。一貫するほど信頼度UP。

5. **他センサとの整合(クロス検査)**

   * もしBが同時刻に強い受信(自発信か)を示している場合、Aの受信がBの送波に合致するか(時間的に矛盾しないか)を検査。矛盾あれば低信頼。

## B. 信頼度スコア化(数値化)

各測定に対して 0..1 の信頼度 w を計算。例(線形重み付け):

$$
w = w_{\text{TOF}} \cdot w_{\text{corr}} \cdot w_{\text{amp}} \cdot w_{\text{consistency}}
$$

* $w_{\text{TOF}} = 1$ if TOF within window else 0.2
* $w_{\text{corr}} = \min(1, \frac{\text{corr\_peak}}{\text{corr\_threshold}})$(符号ありの場合)
* $w_{\text{amp}} = \text{sigmoid}*1;
}

void setup() {
  Serial.begin(115200);
  pinMode(TRIG1, OUTPUT);
  pinMode(ECHO1, INPUT);
  pinMode(TRIG2, OUTPUT);
  pinMode(ECHO2, INPUT);
  servo.attach(SERVO_PIN);
  servo.write(90);
}

void loop() {
  // センサ1
  long dist1 = readUltrasonic(TRIG1, ECHO1);
  delay(50);
  // センサ2
  long dist2 = readUltrasonic(TRIG2, ECHO2);

  // 差分
  long dDist1 = dist1 - prevDist1;
  long dDist2 = dist2 - prevDist2;

  // AIモデル計算
  float x = W1 * (dist1 - dist2) + W2 * (dDist1 - dDist2) + b;
  float prob = sigmoid(x);

  Serial.print("dist1="); Serial.print(dist1);
  Serial.print(" dist2="); Serial.print(dist2);
  Serial.print(" prob(right)="); Serial.println(prob);

  // 判断と制御
  int servoPos = 90;
  if (prob > 0.7) {
    servoPos = 120; // 右へ
  } else if (prob < 0.3) {
    servoPos = 60;  // 左へ
  } else {
    servoPos = 90;  // 中央
  }
  servo.write(servoPos);

  // 状態更新
  prevDist1 = dist1;
  prevDist2 = dist2;

  delay(200);
}
```

---

## まとめ

* PCで学習した「簡易AIモデル」(線形分類器など)をESP32に**重みとして埋め込み**、`if文` と `sigmoid` で計算可能。
* ESP32でも十分リアルタイムで「方向推定」できる。
* より複雑なモデル(決定木やTinyMLモデル)も **TensorFlow Lite for Microcontrollers** を使えば導入可能。

---

👉 次のステップは

* **本当にPCでデータを集めて学習→重み抽出** のフローを作る
* それをESP32に組み込む

ところになります。

了解です!
では **「線形モデルをESP32に埋め込む」方式** に絞って整理してみます。

---

## 🔹線形モデル埋め込み方式の流れ

1. **データ収集**

   * ESP32でセンサデータ(`dist1, dist2, Δdist1, Δdist2` など)をPCにシリアル送信
   * PC側でログ保存(CSV

2. **PCで学習(オフライン)**

   * ロジスティック回帰や線形回帰をPython (scikit-learn等) で学習
   * 学習結果の **重み W, バイアス b** を抽出

3. **ESP32に埋め込み**

   * 学習済みの数値(`W1, W2, b` など)をコードに直接書く
   * ESP32はシンプルな式で分類・推定を実行

---

## 🔹ESP32コード例(最小構成)

```cpp
#include <Arduino.h>
#include <Servo.h>

// ==== 超音波センサ ====
#define TRIG1 5
#define ECHO1 18
#define TRIG2 19
#define ECHO2 21

// ==== サーボ ====
#define SERVO_PIN 23
Servo servo;

// ==== 学習済みモデルの重み(例) ====
// PCで学習した結果をここに埋め込む
float W_dist = 0.05;    // 距離差の重み
float W_dDist = 0.1;    // 距離変化差の重み
float b = -0.2;         // バイアス

// ==== 前回の距離保存 ====
long prevDist1 = 0, prevDist2 = 0;

long readUltrasonic(int trigPin, int echoPin) {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  long duration = pulseIn(echoPin, HIGH, 30000);
  long distance = duration * 0.034 / 2;
  return distance == 0 ? 999 : distance; // 999をエラー扱い
}

float sigmoid(float x) {
  return 1.0 / (1.0 + exp(-x));
}

void setup() {
  Serial.begin(115200);
  pinMode(TRIG1, OUTPUT);
  pinMode(ECHO1, INPUT);
  pinMode(TRIG2, OUTPUT);
  pinMode(ECHO2, INPUT);

  servo.attach(SERVO_PIN);
  servo.write(90); // 中央
}

void loop() {
  // ==== センサ読み取り ====
  long dist1 = readUltrasonic(TRIG1, ECHO1);
  delay(50); // 干渉防止
  long dist2 = readUltrasonic(TRIG2, ECHO2);

  long dDist1 = dist1 - prevDist1;
  long dDist2 = dist2 - prevDist2;

  // ==== 線形モデル計算 ====
  float x = W_dist * (dist1 - dist2) + W_dDist * (dDist1 - dDist2) + b;
  float prob = sigmoid(x);

  Serial.print("dist1="); Serial.print(dist1);
  Serial.print(" dist2="); Serial.print(dist2);
  Serial.print(" prob(right)="); Serial.println(prob);

  // ==== 制御 ====
  int servoPos = 90;
  if (prob > 0.7) {
    servoPos = 120; // 右
  } else if (prob < 0.3) {
    servoPos = 60;  // 左
  } else {
    servoPos = 90;  // 中央
  }
  servo.write(servoPos);

  // ==== 状態更新 ====
  prevDist1 = dist1;
  prevDist2 = dist2;

  delay(200);
}
```

---

## 🔹ポイント

* **PC学習 → ESP32に転写**

  * 実際にはPCで大量データを集めて `scikit-learn` で学習 → `coef_, intercept_` をそのまま W と b に代入。

* **AI感を保ちつつ軽量化**

  * ESP32上では単なる加算・乗算・exp() のみ → メモリ負荷少ない。

* **拡張**

  * 入力特徴を3つ以上にしてもOK(例: `dist1`, `dist2`, `dist差`, `速度`)
  * Wを配列にしてループ計算すれば、入力数を増やせる。

---

👉 この方式なら、ESP32に**重みだけ埋め込めばOK**なので「TinyMLほど重くない」し、「AI学習はPCで一度やるだけ」なのでシンプルです。

ご質問ですが、ユーザーさんは **Python側の学習コード(scikit-learnで重みを出すところ)** も欲しいですか?

 

なるほど 👍
では「ESP32に埋め込むための **線形モデルの重みをPCのPythonで求める方法**」を指南します。
ここでは **scikit-learn** を使います。

---

# 🔹 手順

## 1. データ収集(ESP32 → PC)

ESP32からシリアルでログを送ります(例)

```
dist1,dist2,label
30,50,left
25,40,left
80,20,right
60,55,center
```

* `dist1, dist2` … センサの距離(cm)
* `label` … 人間がつける正解ラベル(例: "left", "right", "center")

ESP32からCSV形式で吐き出し、PCに保存 (`data.csv`) します。

---

## 2. Python環境準備

必要ライブラリをインストール:

```bash
pip install scikit-learn pandas numpy
```

---

## 3. 学習コード例 (logistic regression)

```python
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import LabelEncoder

# ==== データ読み込み ====
data = pd.read_csv("data.csv")

# 特徴量: 距離差と距離変化差を仮定
data["diff"] = data["dist1"] - data["dist2"]

# 今回はシンプルに差だけを使う
X = data"diff".values  

# ラベルを数値化 (left=0, center=1, right=2)
le = LabelEncoder()
y = le.fit_transform(data["label"])

# ==== モデル学習 ====
clf = LogisticRegression(multi_class="multinomial", solver="lbfgs")
clf.fit(X, y)

# ==== 学習結果 ====
print("Classes:", le.classes_)
print("Coefficients:", clf.coef_)
print("Intercepts:", clf.intercept_)
```

---

## 4. 出力例

```
Classes: ['center' 'left' 'right']
Coefficients: [[-0.1], [0.3], [-0.2]]
Intercepts: [0.0, -0.5, 0.5]
```

* `coef_` → W の値
* `intercept_` → b の値
* `classes_` → ラベルの順番(ESP32で判断するとき必要)

---

## 5. ESP32へ埋め込み

上記の出力をそのまま **コードに転記** します。

例えば `coef_ = [0.3]`, `intercept_ = -0.5` なら:

```cpp
float W = 0.3;
float b = -0.5;
float x = W * (dist1 - dist2) + b;
float prob = 1.0 / (1.0 + exp(-x));
```

`prob` が大きければ「右」、小さければ「左」、真ん中なら「中央」と判定できます。

---

## 6. 拡張

* 特徴量を増やす(例: `dist1`, `dist2`, `dist差`, `速度`)
* モデルを `clf.coef_` と `clf.intercept_` のベクトルにして、ESP32では `Σ(W[i]*X[i]) + b` を計算
* これで多次元の判断も可能

---

👉 この流れを作れば、ESP32に **軽量な線形AIモデル** を埋め込めます。
つまり「学習はPC」「推論はESP32」という構成です。

---

物体追従について

なるほど、「物体追従」を狙う場合は、分類よりも **回帰的アプローチ** が適しています。
ESP32上でリアルタイムに動かす場合、線形モデルで十分です。

---

## 🔹 物体追従の基本方針

1. **入力データ**

   * 超音波センサ2台の距離 `dist1, dist2`
   * 差分 `dist1 - dist2`(左右どちらが近いか)
   * 前回との差分 `Δdist1, Δdist2`(速度のような情報)

2. **出力(回帰値)**

   * サーボの角度 `0~180` を直接予測
   * もしくは「左右どちらに振るか」の連続値 `-1 ~ +1`(左〜右)

3. **線形モデル(回帰)**

   * 回帰式例(線形):

   ```
   servo_angle = W1*(dist1 - dist2) + W2*(Δdist1 - Δdist2) + b
   ```

   * `W1, W2, b` はPCで学習してESP32に埋め込み

4. **学習の方法**

   * ESP32から距離データをPCに送信
   * 人間がサーボを操作して角度ログを記録(ラベルとして使用)
   * PCで線形回帰して `W1, W2, b` を算出

---

## 🔹 Pythonでの回帰学習例

```python
import pandas as pd
from sklearn.linear_model import LinearRegression

# CSV: dist1, dist2, delta1, delta2, servo_angle
data = pd.read_csv("tracking_data.csv")

# 特徴量
X = data"dist1", "dist2", "delta1", "delta2".values
y = data["servo_angle"].values

# 線形回帰
reg = LinearRegression()
reg.fit(X, y)

print("Coefficients:", reg.coef_)  # W1, W2, W3, W4
print("Intercept:", reg.intercept_) # b
```

* `reg.coef_` が各特徴量の重み
* `reg.intercept_` がバイアス
* この4つの値をESP32の式に埋め込むだけで、リアルタイム物体追従が可能

---

## 🔹 ESP32での実装例(線形モデル埋め込み)

```cpp
// 学習済みの重み
float W_dist1 = 0.4;
float W_dist2 = -0.3;
float W_dDist1 = 0.2;
float W_dDist2 = -0.1;
float b = 90; // 初期中央角度

// 特徴量
float x = W_dist1*dist1 + W_dist2*dist2 + W_dDist1*dDist1 + W_dDist2*dDist2 + b;
int servoPos = constrain((int)x, 0, 180);
servo.write(servoPos);
```

* `constrain` で角度範囲を0~180に制限
* この方式なら、サーボは物体の左右移動に応じてリアルタイムで追従

---

### 🔹 ポイント

* **PCで学習 → ESP32に重み埋め込み** が基本
* 超音波センサは **交互発信で干渉回避**
* 回帰モデルなら「左/右/中央」の分類より滑らかで自然な追従動作
* 将来的にTinyMLを導入すれば非線形モデルも可能

---

💡 ここまでの流れで、ESP32単体でも **リアルタイム物体追従** が実現できます。

了解です!
では **ESP32 + 超音波センサ2台 + SG90サーボ + 物体追従用線形モデル埋め込み** の完全サンプルコードを提示します。

---

### 🔹 サンプルコード

```cpp
#include <Arduino.h>
#include <Servo.h>

// ==== 超音波センサピン ====
#define TRIG1 5
#define ECHO1 18
#define TRIG2 19
#define ECHO2 21

// ==== サーボ ====
#define SERVO_PIN 23
Servo servo;

// ==== 学習済み線形モデルの重み(PCで回帰学習済み) ====
float W_dist1 = 0.4;
float W_dist2 = -0.3;
float W_dDist1 = 0.2;
float W_dDist2 = -0.1;
float b = 90; // 中央角度

// 前回の距離保持
long prevDist1 = 0;
long prevDist2 = 0;

// 超音波測定関数
long readUltrasonic(int trigPin, int echoPin) {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  long duration = pulseIn(echoPin, HIGH, 30000); // タイムアウト30ms
  long distance = duration * 0.034 / 2; // cm換算
  return distance == 0 ? 999 : distance; // 0は異常値
}

void setup() {
  Serial.begin(115200);

  pinMode(TRIG1, OUTPUT);
  pinMode(ECHO1, INPUT);
  pinMode(TRIG2, OUTPUT);
  pinMode(ECHO2, INPUT);

  servo.attach(SERVO_PIN);
  servo.write(90); // 初期中央
}

void loop() {
  // ==== センサ1計測 ====
  long dist1 = readUltrasonic(TRIG1, ECHO1);
  delay(50); // 干渉防止
  // ==== センサ2計測 ====
  long dist2 = readUltrasonic(TRIG2, ECHO2);

  // 距離変化(速度のような情報)
  long dDist1 = dist1 - prevDist1;
  long dDist2 = dist2 - prevDist2;

  // ==== 線形回帰モデル計算(サーボ角度予測) ====
  float servoAngle = W_dist1*dist1 + W_dist2*dist2 + W_dDist1*dDist1 + W_dDist2*dDist2 + b;

  // サーボ角度制限
  int servoPos = constrain((int)servoAngle, 0, 180);
  servo.write(servoPos);

  // デバッグ表示
  Serial.print("dist1="); Serial.print(dist1);
  Serial.print(" dist2="); Serial.print(dist2);
  Serial.print(" dDist1="); Serial.print(dDist1);
  Serial.print(" dDist2="); Serial.print(dDist2);
  Serial.print(" servo="); Serial.println(servoPos);

  // 前回値更新
  prevDist1 = dist1;
  prevDist2 = dist2;

  delay(100); // 制御周期(10Hz程度)
}
```

---

### 🔹 ポイント

1. **干渉回避**

   * `delay(50)` でセンサ1→センサ2の順に発信してクロストークを防止

2. **線形モデル埋め込み**

   * `W_dist1, W_dist2, W_dDist1, W_dDist2, b` は PCで学習した重みをそのまま使用

3. **滑らかな追従**

   * サーボ角度を連続値として計算
   * 左右どちらかに大きく振れるだけでなく、微調整も可能

4. **拡張**

   * 特徴量を増やす(例えば `dist1², dist2²` など)
   * 制御周期やサーボ応答速度に応じて `delay()` を調整

---

💡 このコードで、ESP32単体で **リアルタイム物体追従** が可能です。

希望であれば、次のステップとして **ESP32上でサーボ動作の応答性をさらに滑らかにする補間・フィルタリング付きバージョン** も提示できます。
作りますか?

 

*1:A-A_{th})/A_{scale})$
* $w_{\text{consistency}} = 1$ if K回一致 else 0.5

閾値例:corr\_threshold = 0.6 (正規化相関), A\_th = 0.05V rms など(ハード依存)。

## C. 判定フロー(受信側)

1. 受信開始 → 受信波形 or エコーパルス幅取得。
2. TOFチェック → 外れたら low-confidence。
3. (符号有り)相互相関を計算 → 最強が自符号か?
4. 振幅チェック → ノイズ除外。
5. 再現性チェック(バッファに最近値を保持)→ consistencyスコア。
6. 最終信頼度 w を算出。w ≥ 0.7 → 有効測定、0.4–0.7 → 保留(追跡に弱く参照のみ)、<0.4 → 無効(破棄)。

---

# 3) 制御設計(「どう制御するか」)

制御は2階層で考えます:

* 低レベル:**スケジューラ/デバイス制御**(誰がいつ送るか)
* 高レベル:**トラッカ/アクチュエータ制御**(方位追従や位置推定→サーボや通知)

## A. スケジューラ(必須)

* マスターで TDM を管理。各発射で受信ウィンドウを専有。
* 追加:発射間にランダムごく短時間 jitter(数百 µs〜数 ms)を入れて同時衝突確率を更に下げる。
* クロストークフラグ:各受信で「他ユニット送波可能性フラグ」を付け、複合的にスケジューラが次周期の遅延/順番を動的調整できる。

## B. データ融合 & トラッカ(Kalmanフィルタ等)

* 各ユニットから得られる(位置/角度/距離、信頼度 w)を取り込み、時間フィルタで追跡。推奨:**拡張カルマン(EKF)または単純な加重移動平均→カルマン**。
* 状態ベクトル例(2D位置追跡): $x = [x, y, v_x, v_y]^T$。観測はセンサごとの位置推定(交差法)または単独距離+方位。
* 観測モデルに信頼度 w を組み込み(観測共分散を $R/w$ のようにスケール)して、信頼の高い観測の影響を強くする。

## C. アクチュエータ制御(サーボ追尾)

* 方位追従の場合:観測角度 − 現在角度 = エラー → PID制御でサーボ角度を更新。
* 目標は「滑らかでオーバーシュート少ない追跡」なので、**角速度制限**と\*\*dead-zone(小エラー無視)\*\*を導入。
* 音源が消えた場合(観測が無効または w<0.4 継続)→「保持モード」→ 一定時間経過後に「探索モード(スキャン)」へ遷移。

## D. 例:追跡ステートマシン

States: SEARCH → ACQUIRE → TRACK → LOST

* SEARCH:輪番発射で広域探索。検出したら ACQUIRE へ(w一定を超えた観測)。
* ACQUIRE:集中的に周辺のみ短周期取得で信頼度を上げる。一定回数で TRACK へ。
* TRACK:カルマンで予測→PIDで追従。継続観測で位置更新。
* LOST:観測消失時は最後の予測を元に短時間追跡、その後SEARCHへ戻る。

---

# 4) 数学:二点三角測量(複数ソースの融合・重み付け)

2ユニットがそれぞれ角度 $\theta_1,\theta_2$(信頼度 $w_1,w_2$)を出す場合、まず交点 x,y を算出(前回示した式)。不安定時(|θ1−θ2| small)では重みを使って妥当性を評価。

あるいは各観測を「直線の形での制約」として最小二乗で解く(観測の信頼度を重み行列 W に入れる):

$$
\min_{x} \sum_i w_i \| \mathbf{n}_i^T x - b_i \|^2
$$

ここで直線は $\mathbf{n}_i^T x = b_i$(法線表現)。実装は簡単な重み付き最小二乗(線形)で安定化できる。

---

# 5) 実装疑似コード(ESP32向け、モード混合)

### a) 時分割 with echo-width(簡易モード)

```cpp
// パラメータ
const int N = 2;
const int t_wait_ms = 40; // ウィンドウ
const float R_max = 5.0;  // m

loop() {
  for (int i=0; i<N; ++i) {
    triggerSensor(i);              // RMTでトリガ
    delay(send_pulse_ms);          // 送波長
    enableEchoCapture(i);          // GPIO割り込み開始
    delay(t_wait_ms);              // 受信ウィンドウ
    disableEchoCapture(i);
    t = readEchoTime(i);           // µs
    if (t==0) { markInvalid(i); continue; }
    float d = (343.0e-6 * t) / 2.0; // m
    float w = tofWindowScore(t) * amplitudeScore(i) * consistencyScore(i);
    pushObservation(i, d, w);
  }
  fuseAndTrack();
  controlActuators();
}
```

### b) 符号化+相互相関(拡張モード:受信はADCバッファ)

```cpp
// 送信: 符号 s_i (長さ Ls samples) をオン/オフで出力
triggerCodedPulse(sensor_id);

// 受信: ADCでサンプリング into buffer R[n]
// 相互相関を計算: corr_k = sum_n R[n] * s_k[n-shift]
// 正規化相関を使い、最大のcorrを自符号と比較
if (corr_self > corr_other + margin && corr_self > corr_thresh) {
  // 自信度高
  computeTOFfromShift(...);
  pushObservation(...);
}
```

相互相関は短い符号長(16–64 サンプル)を推奨。サンプリング周波数は符号設計に合わせる(例えば fs=100 kHz で符号長16→160 µs)。

---

# 6) 故障モードと対策

* **同時送受信で誤検知**:TDMで解決(まず採用)。
* **反射豊富な室内で反射ピークを直撃**:相互相関でタイムスレッショルド、複数回の中央値採用。
* **センサ故障/外乱**:各観測に信頼度付け、融合時に低信頼観測を除外。
* **動的目標で遅延が致命的**:TDM周波数を上げる、または複数センサを同時受信可能なアーキテクチャ(符号化)に移行。

---

# 7) パラメータ例(実用目安)

* R\_max = 5 m → t\_wait ≈ 40 ms(安全);周期 ≈ N × 40 ms。
* 符号長(符号化)= 16–32 サンプル。サンプリング fs=100 kHz → 符号長 160–320 µs。
* 相関閾値 corr\_thresh = 0.5..0.7(ノイズ環境で実測調整)。
* 再現性 K = 3(3回中2回一致でOK)。
* 信頼度切断 w\_valid = 0.7、w\_hold = 0.4。

---

# 8) 開発ステップ(実践順)

1. **TDMのみ**の簡易実装で動作確認(各センサ輪番、受信ウィンドウ確保)。ログを取る。
2. 実環境でクロストークが残る場合は **ランダム遅延 + 物理遮蔽** を追加。
3. 必要なら **符号化+相互相関** に進み、受信波形を取り込み相関比を調べる(ESP32のメモリ・演算負荷注意)。
4. データ融合(重み付き最小二乗、簡易カルマン)を導入してトラッカを完成。
5. パラメータを現場実測でチューニング(t\_wait, corr\_thresh, window サイズ等)。

---

# 9) 最後に:出力可能なもの(提示)

今の内容をベースに、どれか作りたいものがあれば一気に出します(いずれも即出力可能):

* ESP32(RMT+GPIO割り込み)向けの**完全なサンプルコード(時分割+echo幅測定+信頼度算出)**。
* 符号化+相互相関版の\*\*受信バッファ処理+相関アルゴリズムC++)\*\*の実装例(ADCサンプリング前提)。
* 輪番スケジュールと観測融合(加重最小二乗/EKF)を組み合わせた\*\*追跡実装(Kalman)\*\*のサンプル。

ーーーーーーーー

なるほど。では「**2つの超音波センサをESP32で使い、相互干渉を避けながら距離情報を取得・判断・制御する流れ**」を想定したサンプルコードを提示します。
例として **HC-SR04系の超音波距離センサ×2** を想定し、以下の工夫を入れます:

1. **片方ずつ順番に発信**

   * センサ1で距離測定 → 終了後にセンサ2を動作
   * 発信タイミングをずらすことでクロストーク(干渉)を防ぐ。

2. **判断ロジック**

   * それぞれの距離を比較し、近い方の値を優先
   * あるいは「左右差」を見てサーボ制御などに利用

3. **制御**

   * 例として SG90 サーボを回して「近い方向を向く」ようにする

---

### サンプルコード (Arduino for ESP32)

```cpp
#include <Arduino.h>
#include <Servo.h>

// ==== 超音波センサ1 ====
#define TRIG1 5
#define ECHO1 18

// ==== 超音波センサ2 ====
#define TRIG2 19
#define ECHO2 21

// ==== サーボ ====
#define SERVO_PIN 23
Servo servo;

long readUltrasonic(int trigPin, int echoPin) {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  long duration = pulseIn(echoPin, HIGH, 30000); // タイムアウト30ms
  long distance = duration * 0.034 / 2; // cm換算
  return distance == 0 ? 999 : distance; // 0をエラー扱い
}

void setup() {
  Serial.begin(115200);

  pinMode(TRIG1, OUTPUT);
  pinMode(ECHO1, INPUT);
  pinMode(TRIG2, OUTPUT);
  pinMode(ECHO2, INPUT);

  servo.attach(SERVO_PIN);
  servo.write(90); // 初期中央
}

void loop() {
  // センサ1計測
  long dist1 = readUltrasonic(TRIG1, ECHO1);
  delay(50); // 干渉防止
  // センサ2計測
  long dist2 = readUltrasonic(TRIG2, ECHO2);

  Serial.print("Sensor1: "); Serial.print(dist1); Serial.print(" cm, ");
  Serial.print("Sensor2: "); Serial.print(dist2); Serial.println(" cm");

  // ==== 判断ロジック ====
  int servoPos = 90; // 基本は中央
  if (dist1 < 50 || dist2 < 50) { // 50cm以内の物体を検出したら向きを変える
    if (dist1 < dist2) {
      servoPos = 60; // センサ1側を向く
    } else {
      servoPos = 120; // センサ2側を向く
    }
  }
  servo.write(servoPos);

  delay(200); // 計測間隔
}
```

---

#include <Arduino.h>
#include <Servo.h>

// ==== 超音波センサ1 ====
#define TRIG1 5
#define ECHO1 18

// ==== 超音波センサ2 ====
#define TRIG2 19
#define ECHO2 21

// ==== サーボ ====
#define SERVO_PIN 23
Servo servo;

long readUltrasonic(int trigPin, int echoPin) {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  long duration = pulseIn(echoPin, HIGH, 30000); // タイムアウト30ms
  long distance = duration * 0.034 / 2; // cm換算
  return distance == 0 ? 999 : distance; // 0をエラー扱い
}

void setup() {
  Serial.begin(115200);

  pinMode(TRIG1, OUTPUT);
  pinMode(ECHO1, INPUT);
  pinMode(TRIG2, OUTPUT);
  pinMode(ECHO2, INPUT);

  servo.attach(SERVO_PIN);
  servo.write(90); // 初期中央
}

void loop() {
  // センサ1計測
  long dist1 = readUltrasonic(TRIG1, ECHO1);
  delay(50); // 干渉防止
  // センサ2計測
  long dist2 = readUltrasonic(TRIG2, ECHO2);

  Serial.print("Sensor1: "); Serial.print(dist1); Serial.print(" cm, ");
  Serial.print("Sensor2: "); Serial.print(dist2); Serial.println(" cm");

  // ==== 判断ロジック ====
  int servoPos = 90; // 基本は中央
  if (dist1 < 50 || dist2 < 50) { // 50cm以内の物体を検出したら向きを変える
    if (dist1 < dist2) {
      servoPos = 60; // センサ1側を向く
    } else {
      servoPos = 120; // センサ2側を向く
    }
  }
  servo.write(servoPos);

  delay(200); // 計測間隔
}

 

 

### ポイント

* **干渉回避**

  * `delay(50);` を挟んでセンサ1とセンサ2を時間的に分離。
  * 本格的には「時間スロット制御」や「センサごとに異なる変調周波数」を利用するとより堅牢。

* **判断の方法**

  * 2つの距離の差分で方向判断
  * AIを導入するなら、過去の系列データ(距離変化の履歴)を入力として「物体が右から来ているか左から来ているか」を分類するモデルを構築可能。

* **制御の例**

  * 今回はサーボで「物体の近い方へ首を振る」例
  * 実際にはモータ駆動、アラート音、他システム連携にも拡張可

---

👉 ご希望に応じて、

* 「AI(機械学習やESP32上の軽量モデル)」での分類例
* 「相互干渉防止のための変調方式」

ーーーーーーーー

了解しました 👍
ここでは **ESP32 + 超音波センサ×2 + SG90サーボ** に **AIを組み込んだ判断ロジック** のサンプルを考察します。

ESP32上でフルサイズのAIモデルを動かすのは難しいので、ここでは以下の流れを取ります:

---

## AI組込みの基本方針

1. **入力データ**

   * 超音波センサ1・2の距離データを連続して取得(時系列データ)
   * 例: `dist1[t], dist2[t]` を10回分くらいバッファに格納

2. **特徴量作成**

   * 距離そのもの
   * 差分(左右差 `dist1 - dist2`)
   * 移動方向(距離変化の符号)

3. **AIによる分類 or 回帰**

   * 「物体は左から来ている / 右から来ている / 静止」などを分類
   * 簡易的にはロジスティック回帰や決定木をPCで学習して、**重みだけESP32に持ち込む**
   * ESP32では **if文で計算可能な軽量モデル** に変換する

4. **制御**

   * 「右から来た」と判断すればサーボを右へ振る
   * 「左から来た」と判断すれば左へ
   * 「中央にいる」なら中央維持

---

## サンプル: 超簡易AIモデル (線形分類器)

### 学習済み重みを仮定

例えばPCで学習済みの重みがこうだったとします:

```
class = sigmoid( W1 * (dist1 - dist2) + W2 * (Δdist1 - Δdist2) + b )
```

ここで:

* dist1, dist2 : 現在の距離
* Δdist1, Δdist2 : 前回との差分
* 出力 `class` : 0なら左寄り、1なら右寄り

### ESP32実装例

```cpp
#include <Arduino.h>
#include <Servo.h>

// ==== 超音波センサ ====
#define TRIG1 5
#define ECHO1 18
#define TRIG2 19
#define ECHO2 21

#define SERVO_PIN 23
Servo servo;

// ==== AIモデルの重み (仮定値) ====
float W1 = 0.05;  
float W2 = 0.1;   
float b  = 0.0;   

long readUltrasonic(int trigPin, int echoPin) {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  long duration = pulseIn(echoPin, HIGH, 30000);
  long distance = duration * 0.034 / 2;
  return distance == 0 ? 999 : distance;
}

// 前回の距離
long prevDist1 = 0, prevDist2 = 0;

float sigmoid(float x) {
  return 1.0 / (1.0 + exp(-x