ESP32 als Bluetooth-Tastatur (BLE HID): Makro-Pad mit Touch-Sensoren & Rotary Encoder bauen

Ein Makro-Pad für 120 Euro kaufen? Oder eins bauen, das genau die Shortcuts kann, die du brauchst – und sich dabei per Bluetooth ohne einen einzigen Treiber mit Windows, macOS, Android und iOS verbindet. Der ESP32 kann sich als BLE-HID-Gerät ausgeben, also als ganz normale Bluetooth-Tastatur. In diesem Tutorial baust du daraus Schritt für Schritt ein Makro-Pad mit Touch-Feldern, Drehregler, OLED-Anzeige und Akkubetrieb.

Was BLE HID eigentlich bedeutet

HID steht für Human Interface Device – die Geräteklasse, unter der Betriebssysteme Tastaturen, Mäuse und Gamepads einsortieren. Das Entscheidende daran: Diese Klasse ist standardisiert und in jedem Betriebssystem seit Jahrzehnten fest eingebaut. Wenn dein ESP32 sich als HID-Tastatur meldet, muss auf der Gegenseite nichts installiert werden. Kein Treiber, keine App, keine Freigabe.

Über Bluetooth Low Energy läuft das über ein GATT-Profil namens HID over GATT (HOGP). Der ESP32 kündigt beim Pairing an: „Ich bin eine Tastatur, hier ist mein Report-Descriptor.“ Von da an schickt er nur noch kleine Datenpakete – im Kern eine Liste gedrückter Tasten plus ein Byte für die Modifier (Strg, Shift, Alt, GUI).

ℹ️ Welche ESP32-Varianten können das? Klassisches BLE-HID läuft auf dem ESP32 WROOM32 und auf dem ESP32-C3. Der ESP32-S3 kann zusätzlich USB-HID über den nativen USB-Port. Der ESP8266 (etwa der Wemos D1 Mini) hat gar kein Bluetooth und fällt für dieses Projekt raus.

Das brauchst du

ESP32 Entwicklungsboard (WROOM32, USB-C) – das Hauptboard, BLE ist bereits an Bord
TTP223 Touch-Sensoren (10er-Set) – drei Stück als Makro-Tasten, verschleißfrei und flach
Rotary Encoder KY-040 – Lautstärkeregler mit Mute-Taste im Drehknopf
0,96 Zoll OLED SSD1306 – zeigt aktiven Layer, Verbindungsstatus und Akkustand
TP4056 Lademodul (3er-Set) – für den Akkubetrieb mit 18650-Zelle
Widerstands-Sortiment – zwei 100-kΩ-Widerstände für den Akku-Spannungsteiler
ESP32 Acrylgehäuse – wenn das Pad am Ende dauerhaft auf dem Schreibtisch stehen soll

💡 Klein anfangen: Für den ersten Test brauchst du nur den ESP32 und ein USB-Kabel. Der erste Sketch tippt Text, ohne dass irgendein Bauteil angeschlossen ist. Touch, Encoder und OLED kommen Schritt für Schritt dazu.

1Bibliothek installieren

Die Standardbibliothek für dieses Projekt heißt ESP32-BLE-Keyboard von T-vK. Sie ist nicht im Bibliotheksverwalter der Arduino IDE gelistet, die Installation läuft daher über ZIP:

  1. Lade das Repository T-vK/ESP32-BLE-Keyboard als ZIP herunter.
  2. Arduino IDE → Sketch → Bibliothek einbinden → .ZIP-Bibliothek hinzufügen.
  3. Board auf ESP32 Dev Module stellen, Partition Scheme auf Huge APP – der BLE-Stack ist groß.

⚠️ Häufigster Stolperstein direkt zu Beginn. Ab ESP32-Arduino-Core 3.x wurde der Bluetooth-Stack von Bluedroid auf NimBLE umgestellt. Ältere Versionen der Bibliothek lassen sich damit nicht kompilieren. Verwende entweder Core 2.0.17 mit der klassischen Version oder den aktuellen Stand der Bibliothek, der NimBLE unterstützt. Fehlermeldungen mit BLEDevice.h: No such file oder esp_hidd deuten fast immer auf genau diese Kombination hin.

2Erster Test: Der ESP32 tippt von selbst

Dieser Sketch braucht keinerlei Peripherie. Nach dem Hochladen taucht auf deinem Handy oder Rechner ein Bluetooth-Gerät namens „MAKEROO Makropad“ auf. Koppeln, dann ein beliebiges Textfeld öffnen:

#include <BleKeyboard.h>

// Name, Hersteller, Startwert Akkustand in Prozent
BleKeyboard bleKeyboard("MAKEROO Makropad", "MAKEROO", 100);

void setup() {
  Serial.begin(115200);
  Serial.println("Starte BLE-Tastatur ...");
  bleKeyboard.begin();
}

void loop() {
  if (bleKeyboard.isConnected()) {
    Serial.println("Verbunden - tippe Testtext");

    bleKeyboard.print("Hallo von MAKEROO");
    delay(500);

    // Enter druecken und wieder loslassen
    bleKeyboard.write(KEY_RETURN);
    delay(500);

    // Kombination: Strg + A (alles markieren)
    bleKeyboard.press(KEY_LEFT_CTRL);
    bleKeyboard.press('a');
    delay(60);
    bleKeyboard.releaseAll();

    delay(5000);
  } else {
    Serial.println("Warte auf Kopplung ...");
    delay(1000);
  }
}

Drei Methoden reichen für fast alles: print() tippt Text, write() drückt und löst eine Taste sofort wieder, press() hält sie gedrückt bis zum releaseAll(). Genau diese Trennung brauchst du für Tastenkombinationen.

3Das Umlaut-Problem – und wie du es löst

Jetzt kommt der Punkt, an dem die meisten Projekte hängenbleiben. Eine HID-Tastatur sendet keine Zeichen, sondern Positionen. Der Report enthält „Taste an Position 0x1D wurde gedrückt“ – welcher Buchstabe daraus wird, entscheidet allein das Tastaturlayout des Betriebssystems.

Die Bibliothek geht von einem US-Layout aus. Steht dein Rechner auf Deutsch, ergibt das vorhersagbare Verschiebungen:

Du sendest US-Layout zeigt DE-Layout zeigt
'y' y z
'z' z y
';' ; ö
'\'' ' ä
'[' [ ü
'-' - ß
'/' / -

Die Tabelle lässt sich in beide Richtungen lesen – und genau das nutzt die folgende Hilfsfunktion aus. Sie übersetzt deutschen Text in die US-Tasten, die auf einem deutschen Layout die gewünschten Zeichen erzeugen:

// Tippt deutschen Text korrekt auf einem System mit DE-Tastaturlayout
void tippeDeutsch(const char* text) {
  for (const char* p = text; *p; p++) {
    char c = *p;

    switch (c) {
      // vertauschte Buchstaben
      case 'y': bleKeyboard.write('z'); break;
      case 'z': bleKeyboard.write('y'); break;
      case 'Y': bleKeyboard.write('Z'); break;
      case 'Z': bleKeyboard.write('Y'); break;

      // Umlaute liegen auf den US-Satzzeichen
      case 'a': bleKeyboard.write(';');  break;   // ae
      case 'o': bleKeyboard.write('\''); break;   // oe
      case 'u': bleKeyboard.write('[');  break;   // ue

      // Sonderzeichen
      case '-': bleKeyboard.write('/');  break;
      case '/': bleKeyboard.write('&');  break;
      case '_': bleKeyboard.write('?');  break;

      default:  bleKeyboard.write(c);   break;
    }
    delay(8);   // gibt langsamen Hosts Zeit
  }
}

ℹ️ Achtung, die Umlaut-Zeilen oben sind bewusst vereinfacht. Echte Umlaute stehen in einer UTF-8-Quelldatei als zwei Bytes, ein char-Vergleich greift also nicht. Willst du „ä“, „ö“, „ü“ direkt im String schreiben, arbeite mit String und prüfe auf die Bytefolge – oder du gehst den pragmatischen Weg und schreibst deine Makros mit ae, oe, ue. Für Shortcuts, um die es hier hauptsächlich geht, spielt das ohnehin keine Rolle.

4Verkabelung

Die TTP223-Module laufen mit 3,3 V und liefern am SIG-Pin ein sauberes HIGH-Signal. Der KY-040 kommt ebenfalls mit 3,3 V zurecht:

Bauteil Pin am Modul ESP32 (WROOM32) Hinweis
TTP223 #1 SIG / VCC / GND GPIO4 / 3V3 / GND Makro 1
TTP223 #2 SIG / VCC / GND GPIO5 / 3V3 / GND Makro 2
TTP223 #3 SIG / VCC / GND GPIO18 / 3V3 / GND Layer-Umschaltung
KY-040 CLK / DT GPIO32 / GPIO33 Drehrichtung
KY-040 SW GPIO25 Druckschalter = Mute
KY-040 + / GND 3V3 / GND
OLED SSD1306 SDA / SCL GPIO21 / GPIO22 I2C-Adresse 0x3C
Akku-Messung Teilerabgriff GPIO34 nur Eingang, ADC1

⚠️ Finger weg von GPIO 6–11 und den Strapping-Pins. GPIO6 bis GPIO11 sind fest mit dem SPI-Flash verdrahtet – wer sie benutzt, bekommt einen Boot-Loop. GPIO0, GPIO2, GPIO12 und GPIO15 entscheiden beim Start über den Boot-Modus: Liegt beim Einschalten ein falscher Pegel an, startet das Board nicht. Für Touch-Sensoren, die im Ruhezustand LOW liefern, ist GPIO2 zwar meist unkritisch, aber die oben gewählten Pins sind schlicht sorgenfrei.

5Das Makro-Pad: Touch-Tasten mit Entprellung

Kapazitive Sensoren prellen nicht mechanisch, aber sie reagieren so schnell, dass ein einziger Fingertipp ohne Flankenerkennung dutzende Makros auslöst. Die Lösung ist immer dieselbe: nur auf den Wechsel von LOW nach HIGH reagieren und danach kurz sperren.

#include <BleKeyboard.h>

BleKeyboard bleKeyboard("MAKEROO Makropad", "MAKEROO", 100);

const uint8_t TOUCH_PINS[3] = { 4, 5, 18 };
const uint8_t ANZAHL_TASTEN = 3;
const unsigned long SPERRZEIT = 220;   // ms

bool          letzterZustand[ANZAHL_TASTEN] = { false, false, false };
unsigned long letzterWechsel[ANZAHL_TASTEN] = { 0, 0, 0 };

void makroKopieren() {
  bleKeyboard.press(KEY_LEFT_CTRL);
  bleKeyboard.press('c');
  delay(40);
  bleKeyboard.releaseAll();
}

void makroEinfuegenAlsText() {
  // Strg + Shift + V - haeufigstes Makro ueberhaupt
  bleKeyboard.press(KEY_LEFT_CTRL);
  bleKeyboard.press(KEY_LEFT_SHIFT);
  bleKeyboard.press('v');
  delay(40);
  bleKeyboard.releaseAll();
}

void makroFensterWechseln() {
  bleKeyboard.press(KEY_LEFT_ALT);
  bleKeyboard.press(KEY_TAB);
  delay(40);
  bleKeyboard.releaseAll();
}

void setup() {
  Serial.begin(115200);
  for (uint8_t i = 0; i < ANZAHL_TASTEN; i++) {
    pinMode(TOUCH_PINS[i], INPUT);
  }
  bleKeyboard.begin();
  Serial.println("Makropad bereit");
}

void loop() {
  if (!bleKeyboard.isConnected()) {
    delay(50);
    return;
  }

  unsigned long jetzt = millis();

  for (uint8_t i = 0; i < ANZAHL_TASTEN; i++) {
    bool aktuell = digitalRead(TOUCH_PINS[i]) == HIGH;

    // nur steigende Flanke, und nur nach Ablauf der Sperrzeit
    if (aktuell && !letzterZustand[i] &&
        jetzt - letzterWechsel[i] > SPERRZEIT) {

      letzterWechsel[i] = jetzt;
      Serial.printf("Taste %u ausgeloest\n", i + 1);

      switch (i) {
        case 0: makroKopieren();          break;
        case 1: makroEinfuegenAlsText();  break;
        case 2: makroFensterWechseln();   break;
      }
    }
    letzterZustand[i] = aktuell;
  }

  delay(5);
}

💡 Kein delay() in den Makros? Die 40 Millisekunden zwischen press() und releaseAll() sind wichtig. Ohne sie kommen Druck und Loslassen im selben BLE-Paket beim Host an und manche Betriebssysteme verschlucken das Makro. Länger als 60 ms bringt dagegen nichts. Wie du solche Wartezeiten sauber ohne Blockieren löst, zeigt der Artikel millis() statt delay().

6Rotary Encoder als Lautstärkeregler

Media-Tasten sind ein eigener Teil des HID-Standards – der Consumer Control-Report. Die Bibliothek bringt sie als Konstanten mit, sodass Lautstärke, Play/Pause und Titelsprung ohne Umwege funktionieren. Der Encoder wird per Interrupt ausgewertet, damit keine Rastung verlorengeht:

#define ENC_CLK 32
#define ENC_DT  33
#define ENC_SW  25

volatile int8_t  encoderDelta = 0;
volatile uint8_t letzterAB    = 0;

// Zustandstabelle fuer die Quadraturauswertung
const int8_t QUAD_TABELLE[16] = {
   0, -1,  1,  0,
   1,  0,  0, -1,
  -1,  0,  0,  1,
   0,  1, -1,  0
};

void IRAM_ATTR encoderISR() {
  uint8_t ab = (digitalRead(ENC_CLK) << 1) | digitalRead(ENC_DT);
  letzterAB = ((letzterAB << 2) | ab) & 0x0F;
  encoderDelta += QUAD_TABELLE[letzterAB];
}

void encoderInit() {
  pinMode(ENC_CLK, INPUT_PULLUP);
  pinMode(ENC_DT,  INPUT_PULLUP);
  pinMode(ENC_SW,  INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(ENC_CLK), encoderISR, CHANGE);
  attachInterrupt(digitalPinToInterrupt(ENC_DT),  encoderISR, CHANGE);
}

void encoderAuswerten() {
  noInterrupts();
  int8_t delta = encoderDelta;
  encoderDelta = 0;
  interrupts();

  // vier Flanken ergeben eine Rastung
  while (delta >= 4) {
    bleKeyboard.write(KEY_MEDIA_VOLUME_UP);
    delta -= 4;
  }
  while (delta <= -4) {
    bleKeyboard.write(KEY_MEDIA_VOLUME_DOWN);
    delta += 4;
  }
}

unsigned long letzterKlick = 0;

void encoderTasteAuswerten() {
  if (digitalRead(ENC_SW) == LOW && millis() - letzterKlick > 300) {
    letzterKlick = millis();
    bleKeyboard.write(KEY_MEDIA_MUTE);
    Serial.println("Mute");
  }
}

Weitere nützliche Media-Konstanten: KEY_MEDIA_PLAY_PAUSE, KEY_MEDIA_NEXT_TRACK, KEY_MEDIA_PREVIOUS_TRACK und KEY_MEDIA_WWW_SEARCH. Wer die Encoder-Grundlagen vertiefen will, findet sie im Artikel zum Rotary Encoder KY-040.

7Layer und OLED-Anzeige

Drei Tasten sind wenig. Mit Layern werden daraus beliebig viele: Die dritte Touchfläche schaltet nicht ein Makro, sondern die Bedeutung der ersten beiden um. Das OLED zeigt jederzeit, wo du gerade bist:

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Adafruit_SSD1306 oled(128, 64, &Wire, -1);

const char* LAYER_NAMEN[3] = { "TEXT", "MEDIA", "MEETING" };
const char* LAYER_TASTE1[3] = { "Kopieren", "Play/Pause", "Mikro stumm" };
const char* LAYER_TASTE2[3] = { "Einfuegen", "Naechster", "Kamera an/aus" };

uint8_t aktiverLayer = 0;

void zeichneOled(bool verbunden, uint8_t akkuProzent) {
  oled.clearDisplay();
  oled.setTextColor(SSD1306_WHITE);

  // Kopfzeile
  oled.setTextSize(1);
  oled.setCursor(0, 0);
  oled.print(verbunden ? F("BLE verbunden") : F("warte auf Host"));
  oled.setCursor(96, 0);
  oled.printf("%3u%%", akkuProzent);
  oled.drawFastHLine(0, 10, 128, SSD1306_WHITE);

  // Layername gross
  oled.setTextSize(2);
  oled.setCursor(0, 16);
  oled.println(LAYER_NAMEN[aktiverLayer]);

  // Belegung
  oled.setTextSize(1);
  oled.setCursor(0, 40);
  oled.print(F("1: "));
  oled.println(LAYER_TASTE1[aktiverLayer]);
  oled.setCursor(0, 52);
  oled.print(F("2: "));
  oled.println(LAYER_TASTE2[aktiverLayer]);

  oled.display();
}

void layerWeiter() {
  aktiverLayer = (aktiverLayer + 1) % 3;
  Serial.printf("Layer: %s\n", LAYER_NAMEN[aktiverLayer]);
}

void fuehreMakroAus(uint8_t taste) {
  if (aktiverLayer == 0) {                 // TEXT
    bleKeyboard.press(KEY_LEFT_CTRL);
    bleKeyboard.press(taste == 0 ? 'c' : 'v');
    delay(40);
    bleKeyboard.releaseAll();
  }
  else if (aktiverLayer == 1) {            // MEDIA
    bleKeyboard.write(taste == 0 ? KEY_MEDIA_PLAY_PAUSE
                                 : KEY_MEDIA_NEXT_TRACK);
  }
  else {                                   // MEETING
    // Zoom / Teams unter Windows
    bleKeyboard.press(KEY_LEFT_CTRL);
    bleKeyboard.press(KEY_LEFT_SHIFT);
    bleKeyboard.press(taste == 0 ? 'm' : 'o');
    delay(40);
    bleKeyboard.releaseAll();
  }
}

ℹ️ Layer sind der eigentliche Trick. Mit drei Layern und zwei Makro-Tasten hast du sechs Funktionen auf zwei Feldern. Ein größeres Display wie das 1,3 Zoll OLED macht die Belegung auch aus zwei Metern Entfernung lesbar. Wer richtig viele Tasten braucht, kann statt Touchflächen auch ein 4x4 Matrix-Keypad anschließen – 16 Tasten an acht Pins.

8Akkubetrieb, Batteriestand und Deep Sleep

Ein Makro-Pad am USB-Kabel ist halb so schön. Mit einer 18650-Zelle und einem TP4056-Modul wird es mobil. BLE-HID meldet den Ladestand sogar an das Betriebssystem – unter Windows und macOS siehst du ihn direkt in den Bluetooth-Einstellungen.

Der Akku liefert bis 4,2 V, der ADC des ESP32 verträgt maximal 3,3 V. Ein Spannungsteiler aus zwei gleichen Widerständen (zum Beispiel 100 kΩ) halbiert die Spannung:

#define AKKU_PIN 34            // ADC1, funktioniert auch mit aktivem WLAN/BLE
#define AKKU_LEER  3300        // mV, entspricht 0 %
#define AKKU_VOLL  4200        // mV, entspricht 100 %

uint32_t leseAkkuMillivolt() {
  uint32_t summe = 0;
  for (uint8_t i = 0; i < 16; i++) {
    summe += analogReadMilliVolts(AKKU_PIN);   // kalibrierte Messung
    delay(2);
  }
  return (summe / 16) * 2;     // mal 2 wegen Spannungsteiler
}

uint8_t akkuProzent() {
  int32_t mv = leseAkkuMillivolt();
  int32_t p  = (mv - AKKU_LEER) * 100 / (AKKU_VOLL - AKKU_LEER);
  return constrain(p, 0, 100);
}

// alle 60 Sekunden an den Host melden
unsigned long letzteAkkuMeldung = 0;

void akkuMelden() {
  if (millis() - letzteAkkuMeldung < 60000UL) return;
  letzteAkkuMeldung = millis();

  uint8_t p = akkuProzent();
  bleKeyboard.setBatteryLevel(p);
  Serial.printf("Akku: %u %% (%lu mV)\n", p, leseAkkuMillivolt());
}

Damit die Zelle Wochen statt Tage hält, schickst du den ESP32 nach einigen Minuten Untätigkeit schlafen. Aufwecken lässt er sich über jeden Touch-Sensor:

#define INAKTIV_TIMEOUT 300000UL    // 5 Minuten

unsigned long letzteAktion = 0;

void pruefeSchlaf() {
  if (millis() - letzteAktion < INAKTIV_TIMEOUT) return;

  Serial.println(F("Gehe schlafen - Touch weckt wieder auf"));
  oled.clearDisplay();
  oled.display();
  oled.ssd1306_command(SSD1306_DISPLAYOFF);

  // GPIO4, GPIO5 und GPIO18 als Weckquelle (HIGH = beruehrt)
  esp_sleep_enable_ext1_wakeup(
      (1ULL << 4) | (1ULL << 5) | (1ULL << 18),
      ESP_EXT1_WAKEUP_ANY_HIGH);

  delay(100);
  esp_deep_sleep_start();
}

// In jeder erkannten Tastenaktion aufrufen:
//   letzteAktion = millis();

⚠️ Nach Deep Sleep muss neu verbunden werden. Der ESP32 startet beim Aufwachen komplett neu, die BLE-Verbindung ist also weg. Der Host verbindet sich in der Regel innerhalb von zwei bis fünf Sekunden automatisch wieder – der erste Tastendruck nach dem Aufwachen geht aber verloren. Plane das ein: Erste Berührung weckt, ab der zweiten wird getippt. Details zum Stromsparen findest du im Artikel ESP32 Deep Sleep.

Praxistipps aus dem Alltag

  • Gerätename eindeutig wählen. Hast du mehrere ESP32-Projekte gekoppelt, wird „ESP32“ schnell unübersichtlich. Der Name lässt sich nur beim begin() setzen, nicht später ändern.
  • Alte Kopplung löschen. Änderst du den Namen, hat der Host noch die alte Bindung gespeichert und verbindet sich nicht. In den Bluetooth-Einstellungen das Gerät entfernen, dann neu koppeln.
  • Nicht mit WLAN kombinieren, wenn es vermeidbar ist. BLE und WLAN teilen sich auf dem WROOM32 eine Antenne. Beides gleichzeitig funktioniert, kostet aber Reaktionszeit und Strom.
  • Touchflächen hinter Acryl. Der TTP223 arbeitet durch dünnes Material hindurch. Klebst du ihn von innen an ein Acrylgehäuse, bekommst du ein völlig geschlossenes Pad ohne sichtbare Bauteile.
  • Lötpads auf dem TTP223 beachten. Über die Brücken A und B stellst du zwischen Momentkontakt, Umschalter und invertiertem Ausgang um. Für ein Makro-Pad willst du den Werkszustand: HIGH, solange berührt.

Troubleshooting

⚠️ Der ESP32 taucht in der Bluetooth-Liste gar nicht auf: Fast immer ein Speicherproblem beim Kompilieren. Stelle das Partition Scheme auf Huge APP (3MB No OTA). Prüfe außerdem im seriellen Monitor, ob bleKeyboard.begin() überhaupt erreicht wird – ein Absturz davor sieht von außen genauso aus.

⚠️ Verbindung wird nach wenigen Sekunden wieder getrennt: Meist Stromversorgung. Der BLE-Sendevorgang zieht Stromspitzen; ein schwaches USB-Kabel oder ein fast leerer Akku führt zu Brownouts. Ein Elko mit 470 µF zwischen 3V3 und GND hilft zuverlässig.

⚠️ Makros lösen mehrfach aus: Die Flankenerkennung fehlt oder die Sperrzeit ist zu kurz. Erhöhe SPERRZEIT schrittweise auf 250 bis 300 ms.

⚠️ Der Encoder springt oder zählt rückwärts: CLK und DT vertauscht – einfach die beiden Kabel tauschen. Zählt er doppelt, wertest du zu viele Flanken pro Rastung; die Division durch vier im Beispiel oben behebt das.

⚠️ Falsche Zeichen auf dem Bildschirm: Layout-Problem, siehe Schritt 3. Zum Prüfen: Stelle den Host testweise auf US-Layout um. Kommt dann das Richtige an, liegt es sicher am Layout und nicht am Code.

⚠️ Unter iOS funktioniert es, unter Windows nicht (oder umgekehrt): Windows ist beim HID-Report-Descriptor strenger. Nutze die aktuelle Version der Bibliothek und koppel über Einstellungen → Bluetooth → Gerät hinzufügen → Bluetooth, nicht über den Schnellzugriff in der Taskleiste.

⚠️ Nach dem Sketch-Upload reagiert nichts mehr: Ist ein Touch-Pin dauerhaft HIGH (etwa weil ein Finger auf dem Sensor liegt), kann der ESP32 direkt wieder in eine Weckschleife geraten. Trenne im Zweifel kurz alle Sensoren und lade den Sketch erneut.

🚀 Bauteile für dein BLE-Makro-Pad

Alles aus diesem Tutorial:

Fazit

BLE HID ist eine der dankbarsten Funktionen des ESP32: Der Aufwand ist überschaubar, das Ergebnis funktioniert sofort auf jedem Gerät mit Bluetooth – und niemand muss dafür etwas installieren. Die drei Bausteine, die du jetzt kennst, tragen praktisch jedes Eingabegerät: Flankenerkennung mit Sperrzeit für saubere Tastendrücke, Interrupt-Auswertung für den Encoder und Layer, um aus wenigen Tasten viele Funktionen zu machen.

Der einzige echte Stolperstein ist das Tastaturlayout – und der ist mit der Übersetzungsfunktion aus Schritt 3 erledigt. Von hier aus ist der Weg kurz: eine Maus-Emulation über BleMouse, ein Gamepad, ein Pedal für die Videobearbeitung oder ein Vorlese-Fernauslöser. Der HID-Report interessiert sich nicht dafür, was den Tastendruck ausgelöst hat.

Zurück zum Blog