XIAO ESP32-S3を使った自動操舵ONOFFスイッチのスケッチ

このスケッチは、Seeed Studio XIAO ESP32S3 を使用し、
✅ USB HIDマウス
✅ Wi-Fiアクセスポイント経由のWeb設定画面
✅ GPIO入力によるマウス操作
を組み合わせたシステムです。

🎯 主な目的
USB HIDマウスとしてCHCNAVに接続され、GPIO入力でマウス操作を実行する

設定用スイッチを押しながら起動することで Wi-Fiアクセスポイントを立ち上げ、ブラウザからマウス操作の座標設定ができる

設定内容は不揮発メモリ(Preferences/NVS)に保存され、電源OFFしても保持される

設定モード中はLEDが点灯し、状態がわかる

🔍 動作の流れ
XIAO ESP32S3をUSBでCHCNAVに接続すると、マウスとして認識される。

GPIO2に接続した「設定用スイッチ」を押したまま電源ONまたはリセットすると:

Wi-Fiアクセスポイントモードが起動

スマホやPCを ESP32S3-AP というWi-Fiに接続

ブラウザから 192.168.4.1 にアクセス

マウス座標設定ページが表示

LED(GPIO3接続)が点灯し、設定モード中であることを示す

設定ページから座標を入力・保存すると内部メモリに保存され、即時有効。

保存後は「テストクリック」ボタンで設定値に基づくマウス動作のテストが可能

GPIO2の設定用スイッチを押さずに起動した場合:

Wi-Fiは起動せず

LEDは消灯

USBマウスとGPIO入力のみ動作

GPIO0, GPIO1 に接続したタクトスイッチを押すと:

GPIO0 → 自動操舵ONOFF

GPIO1 → MOVE_FILL座標へ移動 → 塗りつぶしボタン等ONOFF → MOVE_RETURN座標へ戻る

✅ 特徴・利点
設定操作と通常動作をスイッチ1つで分離

設定値はPreferencesに保存されるため、電源OFFしても再設定不要

Wi-Fi設定中はLEDが点灯し、状態が一目でわかる

マウス座標の変更がPC不要・スマホだけで可能(Webブラウザ経由)

小型のXIAO ESP32S3で完結、外付け部品はタクトスイッチとLEDのみ
#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include "Adafruit_TinyUSB.h"

// GPIO定義
const int tactA = 0;        // 操作用タクトスイッチA
const int tactB = 1;        // 操作用タクトスイッチB
const int setupSwitch = 2;   // 設定用スイッチ
const int LED_PIN = 3;       // 設定モード表示用LED

// Wi-Fi設定
const char* ssid = "ESP32S3-AP";
const char* password = "12345678";

// Webサーバ
WebServer server(80);

// USB HIDマウス
Adafruit_USBD_HID usb_hid;
uint8_t mouse_report[4] = {0};

// 座標変数
int CURSOR_X = 238;
int CURSOR_Y = 150;
int MOVE_FILL_X = 0;
int MOVE_FILL_Y = -90;
int MOVE_RETURN_X = 0;
int MOVE_RETURN_Y = 90;

// Preferences
Preferences prefs;

// フラグ
bool setupMode = false;

// HTMLページ
String htmlPage = R"rawliteral(
<!DOCTYPE html>
<html>
<head><title>ESP32S3設定</title></head>
<body>
  <h2>座標設定</h2>
  <form action="/set" method="POST">
    CURSOR_X: <input name="CURSOR_X" value="%CURSOR_X%"><br>
    CURSOR_Y: <input name="CURSOR_Y" value="%CURSOR_Y%"><br>
    MOVE_FILL_X: <input name="MOVE_FILL_X" value="%MOVE_FILL_X%"><br>
    MOVE_FILL_Y: <input name="MOVE_FILL_Y" value="%MOVE_FILL_Y%"><br>
    MOVE_RETURN_X: <input name="MOVE_RETURN_X" value="%MOVE_RETURN_X%"><br>
    MOVE_RETURN_Y: <input name="MOVE_RETURN_Y" value="%MOVE_RETURN_Y%"><br>
    <input type="submit" value="保存">
  </form>
  <form action="/test" method="GET">
    <input type="submit" value="テストクリック">
  </form>
</body>
</html>
)rawliteral";

String processor(const String& var) {
  if (var == "CURSOR_X") return String(CURSOR_X);
  if (var == "CURSOR_Y") return String(CURSOR_Y);
  if (var == "MOVE_FILL_X") return String(MOVE_FILL_X);
  if (var == "MOVE_FILL_Y") return String(MOVE_FILL_Y);
  if (var == "MOVE_RETURN_X") return String(MOVE_RETURN_X);
  if (var == "MOVE_RETURN_Y") return String(MOVE_RETURN_Y);
  return "";
}

void handleRoot() {
  String page = htmlPage;
  page.replace("%CURSOR_X%", String(CURSOR_X));
  page.replace("%CURSOR_Y%", String(CURSOR_Y));
  page.replace("%MOVE_FILL_X%", String(MOVE_FILL_X));
  page.replace("%MOVE_FILL_Y%", String(MOVE_FILL_Y));
  page.replace("%MOVE_RETURN_X%", String(MOVE_RETURN_X));
  page.replace("%MOVE_RETURN_Y%", String(MOVE_RETURN_Y));
  server.send(200, "text/html", page);
}

void handleSet() {
  if (server.hasArg("CURSOR_X")) CURSOR_X = server.arg("CURSOR_X").toInt();
  if (server.hasArg("CURSOR_Y")) CURSOR_Y = server.arg("CURSOR_Y").toInt();
  if (server.hasArg("MOVE_FILL_X")) MOVE_FILL_X = server.arg("MOVE_FILL_X").toInt();
  if (server.hasArg("MOVE_FILL_Y")) MOVE_FILL_Y = server.arg("MOVE_FILL_Y").toInt();
  if (server.hasArg("MOVE_RETURN_X")) MOVE_RETURN_X = server.arg("MOVE_RETURN_X").toInt();
  if (server.hasArg("MOVE_RETURN_Y")) MOVE_RETURN_Y = server.arg("MOVE_RETURN_Y").toInt();

  prefs.putInt("CURSOR_X", CURSOR_X);
  prefs.putInt("CURSOR_Y", CURSOR_Y);
  prefs.putInt("MOVE_FILL_X", MOVE_FILL_X);
  prefs.putInt("MOVE_FILL_Y", MOVE_FILL_Y);
  prefs.putInt("MOVE_RETURN_X", MOVE_RETURN_X);
  prefs.putInt("MOVE_RETURN_Y", MOVE_RETURN_Y);

  server.sendHeader("Location", "/");
  server.send(303);
}

void handleTest() {
  mouse_report[0] = 0;
  mouse_report[1] = CURSOR_X;
  mouse_report[2] = CURSOR_Y;
  mouse_report[3] = 0;
  usb_hid.sendReport(1, mouse_report, 4);
  delay(100);

  mouse_report[0] = 1;
  mouse_report[1] = 0;
  mouse_report[2] = 0;
  mouse_report[3] = 0;
  usb_hid.sendReport(1, mouse_report, 4);
  delay(100);

  mouse_report[0] = 0;
  usb_hid.sendReport(1, mouse_report, 4);

  server.sendHeader("Location", "/");
  server.send(303);
}

void setup() {
  pinMode(tactA, INPUT_PULLDOWN);
  pinMode(tactB, INPUT_PULLDOWN);
  pinMode(setupSwitch, INPUT_PULLUP);  // 設定用スイッチ(プルアップ)
  pinMode(LED_PIN, OUTPUT);            // LED出力
  digitalWrite(LED_PIN, LOW);          // 初期は消灯

  usb_hid.begin();
  prefs.begin("config", false);

  // 設定値読み込み
  CURSOR_X = prefs.getInt("CURSOR_X", CURSOR_X);
  CURSOR_Y = prefs.getInt("CURSOR_Y", CURSOR_Y);
  MOVE_FILL_X = prefs.getInt("MOVE_FILL_X", MOVE_FILL_X);
  MOVE_FILL_Y = prefs.getInt("MOVE_FILL_Y", MOVE_FILL_Y);
  MOVE_RETURN_X = prefs.getInt("MOVE_RETURN_X", MOVE_RETURN_X);
  MOVE_RETURN_Y = prefs.getInt("MOVE_RETURN_Y", MOVE_RETURN_Y);

  // === 設定スイッチ判定 ===
  unsigned long pressTime = 0;
  const unsigned long threshold = 2000; // 2秒

  if (digitalRead(setupSwitch) == LOW) {
    pressTime = millis();
    while (digitalRead(setupSwitch) == LOW) {
      if (millis() - pressTime >= threshold) {
        setupMode = true;
        break;
      }
    }
  }

  if (setupMode) {
    digitalWrite(LED_PIN, HIGH);  // LED点灯
    WiFi.softAP(ssid, password);
    Serial.print("APモード IP: ");
    Serial.println(WiFi.softAPIP());

    server.on("/", handleRoot);
    server.on("/set", HTTP_POST, handleSet);
    server.on("/test", HTTP_GET, handleTest);
    server.begin();
  } else {
    digitalWrite(LED_PIN, LOW);   // 設定モードじゃなければLED消灯
  }

  while (!TinyUSBDevice.mounted()) delay(10);
}

void loop() {
  if (setupMode) {
    server.handleClient();
  }

  bool nowA = digitalRead(tactA);
  bool nowB = digitalRead(tactB);

  if (nowA) {
    mouse_report[0] = 1;
    mouse_report[1] = 0;
    mouse_report[2] = 0;
    mouse_report[3] = 0;
    usb_hid.sendReport(1, mouse_report, 4);
    delay(100);
    mouse_report[0] = 0;
    usb_hid.sendReport(1, mouse_report, 4);
  }

  if (nowB) {
    mouse_report[0] = 0;
    mouse_report[1] = MOVE_FILL_X;
    mouse_report[2] = MOVE_FILL_Y;
    mouse_report[3] = 0;
    usb_hid.sendReport(1, mouse_report, 4);
    delay(100);

    mouse_report[0] = 1;
    mouse_report[1] = 0;
    mouse_report[2] = 0;
    mouse_report[3] = 0;
    usb_hid.sendReport(1, mouse_report, 4);
    delay(100);

    mouse_report[0] = 0;
    mouse_report[1] = MOVE_RETURN_X;
    mouse_report[2] = MOVE_RETURN_Y;
    mouse_report[3] = 0;
    usb_hid.sendReport(1, mouse_report, 4);
  }

  delay(10);
}
  • このエントリーをはてなブックマークに追加
  • follow us in feedly

この記事の著者

momo

1966年訓子府町生まれの訓子府育ち。玉葱や米、メロンを栽培する農家です。一眼レフを本格的に始めたのは2005年。仕事の時でもいつでもカメラを持ち歩く自称農場カメラマン。普段の生活を撮るのが主で、その他ストロボを使っての商品撮影、スタジオ撮影も。愛好家グループで年1回写真展を行っている。農機具の改造や作製、電子工作など、モノづくりが大好きです。

この著者の最新の記事

関連記事

コメント

  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

2025年5月
 1234
567891011
12131415161718
19202122232425
262728293031  

カテゴリー

ページ上部へ戻る