TouchOSC XY Controller

Ich route zuerst das USB-Differenzpaar und die kompakte Buck-Leistungsschleife, lege anschließend die Masse-/Versorgungsführung an und route danach alle übrigen Netze. Abschließend behebe ich Routing-Anomalien und prüfe die vollständigen DRC-Ergebnisse.
Der Haupt-Routinglauf ist abgeschlossen; übrig sind ausschließlich sechs USB-Verbindungen zwischen den doppelten USB-C-Pads, dem ESD-Schutz und dem ESP32. Ich schließe diese jetzt gezielt mit schmalen, impedanzgerechten Leiterbahnen und prüfe danach alle Kupfer- und DRC-Regeln.
Die Platine ist vollständig geroutet: USB, Buck-Leistungsschleife, Stromversorgung und Masseführung wurden priorisiert, danach alle übrigen Signale. Es bestehen keine Airwires, offenen Leiterbahnstummel oder DRC-Fehler.
Offener Fertigungspunkt
  • Für die generischen Widerstände, Kondensatoren und den Joystick-Header müssen vor der Bestellung noch konkrete Herstellerteilenummern festgelegt werden.
Empfohlene nächste Schritte
  1. BOM mit konkreten MPNs vervollständigen.
  2. Silkscreen und Beschriftungen bereinigen.
  3. Fertigungsdaten erzeugen und Board-Bring-up vorbereiten.
BOM und Silkscreen
Fertigungsdaten erzeugen
Bring-up Ablauf prüfen
BOM Kosten prüfen

Refine this doc
Ask about this doc
Firmware Getting Started
Pin Mapping

Table


FunctionESP32-S3 pinNotes
Joystick XGPIO1 / ADC1_CH01 kΩ + 100 nF RC filter
Joystick YGPIO2 / ADC1_CH11 kΩ + 100 nF RC filter
Joystick pressGPIO4Active-low, external 10 kΩ pull-up
Function button 1GPIO5Active-low, external 10 kΩ pull-up
Function button 2GPIO6Active-low, external 10 kΩ pull-up
Status/WLAN LEDGPIO7Active-high through 1 kΩ
Native USB D−GPIO19USB 2.0 differential pair
Native USB D+GPIO20USB 2.0 differential pair
BOOTGPIO0Dedicated 10 kΩ pull-up and button to GND
RESETEN10 kΩ pull-up, 1 µF to GND, button to GND
Toolchain
Create the following two files in a PlatformIO project. The target uses Arduino on esp32-s3-devkitc-1, 8 MB flash and USB CDC at boot.
platformio.ini

Ini


[env:esp32-s3-wroom-1-n8r2]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
monitor_speed = 115200
upload_speed = 921600
board_upload.flash_size = 8MB
board_build.partitions = default_8MB.csv
build_flags =
    -DARDUINO_USB_MODE=1
    -DARDUINO_USB_CDC_ON_BOOT=1
    -DBOARD_HAS_PSRAM
lib_deps =
    hideakitai/ArduinoOSC
src/main.cpp

Cpp


#include <Arduino.h>
#include <WiFi.h>
#include <ArduinoOSCWiFi.h>

// ---------- User configuration ----------
static const char* WIFI_SSID = "CHANGE_ME";
static const char* WIFI_PASSWORD = "CHANGE_ME";
static const char* OSC_TARGET_IP = "192.168.1.100";
static constexpr uint16_t OSC_TARGET_PORT = 9000;

// ADC calibration: measure and replace these values on the assembled controller.
static constexpr int X_MIN = 0;
static constexpr int X_CENTER = 2048;
static constexpr int X_MAX = 4095;
static constexpr int Y_MIN = 0;
static constexpr int Y_CENTER = 2048;
static constexpr int Y_MAX = 4095;
static constexpr float DEADZONE = 0.035f;

static constexpr uint8_t PIN_JOY_X = 1;
static constexpr uint8_t PIN_JOY_Y = 2;
static constexpr uint8_t PIN_JOY_SW = 4;
static constexpr uint8_t PIN_BUTTON_1 = 5;
static constexpr uint8_t PIN_BUTTON_2 = 6;
static constexpr uint8_t PIN_STATUS_LED = 7;

static constexpr uint32_t SAMPLE_INTERVAL_MS = 10;
static constexpr uint32_t OSC_REFRESH_MS = 50;
static constexpr uint32_t DEBOUNCE_MS = 25;
static constexpr uint32_t WIFI_RETRY_MS = 5000;

struct DebouncedButton {
    uint8_t pin;
    bool stablePressed = false;
    bool rawPressed = false;
    uint32_t changedAt = 0;

    void begin() {
        pinMode(pin, INPUT_PULLUP);
        rawPressed = digitalRead(pin) == LOW;
        stablePressed = rawPressed;
        changedAt = millis();
    }

    bool update() {
        const bool nowPressed = digitalRead(pin) == LOW;
        if (nowPressed != rawPressed) {
            rawPressed = nowPressed;
            changedAt = millis();
        }
        if (stablePressed != rawPressed && millis() - changedAt >= DEBOUNCE_MS) {
            stablePressed = rawPressed;
            return true;
        }
        return false;
    }
};

DebouncedButton joyButton{PIN_JOY_SW};
DebouncedButton button1{PIN_BUTTON_1};
DebouncedButton button2{PIN_BUTTON_2};

uint32_t lastSampleAt = 0;
uint32_t lastOscAt = 0;
uint32_t lastWifiAttemptAt = 0;
float lastX = -1.0f;
float lastY = -1.0f;

float calibrateAxis(int raw, int minimum, int center, int maximum) {
    float value;
    if (raw >= center) {
        const int span = max(1, maximum - center);
        value = static_cast<float>(raw - center) / static_cast<float>(span);
    } else {
        const int span = max(1, center - minimum);
        value = static_cast<float>(raw - center) / static_cast<float>(span);
    }
    value = constrain(value, -1.0f, 1.0f);

    const float magnitude = fabsf(value);
    if (magnitude <= DEADZONE) return 0.0f;
    const float rescaled = (magnitude - DEADZONE) / (1.0f - DEADZONE);
    return copysignf(constrain(rescaled, 0.0f, 1.0f), value);
}

void setStatusLed() {
    if (WiFi.status() == WL_CONNECTED) {
        digitalWrite(PIN_STATUS_LED, HIGH);
    } else {
        digitalWrite(PIN_STATUS_LED, (millis() / 250U) & 1U);
    }
}

void startWifi() {
    WiFi.mode(WIFI_STA);
    WiFi.setAutoReconnect(true);
    WiFi.persistent(false);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    lastWifiAttemptAt = millis();
}

void maintainWifi() {
    if (WiFi.status() == WL_CONNECTED) return;
    if (millis() - lastWifiAttemptAt >= WIFI_RETRY_MS) {
        WiFi.disconnect();
        WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
        lastWifiAttemptAt = millis();
    }
}

void sendButton(const char* address, bool pressed) {
    if (WiFi.status() == WL_CONNECTED) {
        OscWiFi.send(OSC_TARGET_IP, OSC_TARGET_PORT, address, pressed ? 1 : 0);
    }
}

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

    pinMode(PIN_STATUS_LED, OUTPUT);
    digitalWrite(PIN_STATUS_LED, LOW);

    joyButton.begin();
    button1.begin();
    button2.begin();

    analogReadResolution(12);
    analogSetPinAttenuation(PIN_JOY_X, ADC_11db);
    analogSetPinAttenuation(PIN_JOY_Y, ADC_11db);

    startWifi();
}

void loop() {
    maintainWifi();
    setStatusLed();

    if (joyButton.update()) sendButton("/joystick/press", joyButton.stablePressed);
    if (button1.update()) sendButton("/button/1", button1.stablePressed);
    if (button2.update()) sendButton("/button/2", button2.stablePressed);

    const uint32_t now = millis();
    if (now - lastSampleAt >= SAMPLE_INTERVAL_MS) {
        lastSampleAt = now;

        // Average four readings to reduce residual ADC noise after the hardware RC filter.
        uint32_t sumX = 0;
        uint32_t sumY = 0;
        for (int i = 0; i < 4; ++i) {
            sumX += analogRead(PIN_JOY_X);
            sumY += analogRead(PIN_JOY_Y);
        }
        const float x = calibrateAxis(sumX / 4, X_MIN, X_CENTER, X_MAX);
        const float y = calibrateAxis(sumY / 4, Y_MIN, Y_CENTER, Y_MAX);

        const bool changed = fabsf(x - lastX) >= 0.002f || fabsf(y - lastY) >= 0.002f;
        const bool refreshDue = now - lastOscAt >= OSC_REFRESH_MS;
        if (WiFi.status() == WL_CONNECTED && (changed || refreshDue)) {
            OscWiFi.send(OSC_TARGET_IP, OSC_TARGET_PORT, "/xy/x", x);
            OscWiFi.send(OSC_TARGET_IP, OSC_TARGET_PORT, "/xy/y", y);
            lastX = x;
            lastY = y;
            lastOscAt = now;
        }
    }

    delay(1);
}
Startup Sequence
  1. USB/3.3 V power stabilizes and EN RC releases reset.
  2. GPIO and ADC configuration.
  3. Wi-Fi station connection begins.
  4. Status LED blinks while disconnected and stays on when connected.
  5. ADC and buttons are sampled continuously; OSC updates are sent to the configured target.
TouchOSC Addresses
  • /xy/x float, range −1.0 to +1.0
  • /xy/y float, range −1.0 to +1.0
  • /button/1 integer 0/1
  • /button/2 integer 0/1
  • /joystick/press integer 0/1
Calibration
Read raw ADC values at full negative travel, center and full positive travel for both axes. Replace X_MIN, X_CENTER, X_MAX, Y_MIN, Y_CENTER, and Y_MAX; then tune DEADZONE if the center still moves.
  • Pin Mapping

  • Toolchain

  • `platformio.ini`

  • `src/main.cpp`

  • Startup Sequence

  • TouchOSC Addresses

  • Calibration

ESP32 WLAN OSC XY Controller 8f07

ESP32 WLAN OSC XY Controller 8f07 thumbnail
USB-C-versorgter WLAN-Controller mit ESP32-S3, analogem XY-Joystick und OSC/UDP-Ausgabe für TouchOSC, inklusive Tastern und Statusanzeige.

Properties

Properties describe core aspects of the project.

Pricing & Availability

Distributor

Qty 1

Arrow

$0.44–$1.39

Digi-Key

$0.32

HQonline

$5.71–$5.81

LCSC

$7.67–$7.81

Mouser

$7.73

TME

$1.82

Verical

$0.79–$1.42

Controls