Der ESP8266 ist ein vielseitiger Mikrocontroller, der nicht nur für WLAN-Anwendungen bekannt ist, sondern auch hervorragend digitale Signale verarbeiten kann. Eine häufige Aufgabe ist die Erkennung von Änderungen an einem Eingangssignal, wie z. B. einem TTL-Signal, und die Umwandlung dieser Änderungen in kurze Ausgangsimpulse. Dieser Leitfaden zeigt Ihnen, wie Sie Arduino-Code für den ESP8266 schreiben, der genau das tut: Er erkennt sowohl den Anstieg (LOW nach HIGH) als auch den Abfall (HIGH nach LOW) eines TTL-Signals und generiert bei jeder Flanke einen definierten Impuls auf einem anderen GPIO-Pin.
CHANGE-Modus bei Interrupts ermöglicht die Reaktion auf steigende (LOW zu HIGH) und fallende (HIGH zu LOW) Signalflanken mit derselben Routine.TTL steht für Transistor-Transistor-Logik und bezeichnet eine etablierte Familie digitaler Logikschaltungen. Im Kontext von Mikrocontrollern wie dem ESP8266 bezieht sich "TTL-Signal" oft allgemein auf ein digitales Signal mit zwei klar definierten Spannungspegeln:
Es ist entscheidend zu wissen, dass der ESP8266 nativ mit 3.3V Logikpegeln arbeitet. Das bedeutet, ein HIGH-Signal wird typischerweise als Spannung nahe 3.3V interpretiert. Wenn Ihr eingehendes Signal tatsächlich 5V TTL-Pegel verwendet, müssen Sie Maßnahmen ergreifen, um den ESP8266 nicht zu beschädigen.
Anstatt den Zustand des Eingangspins kontinuierlich in der Hauptprogrammschleife (loop()) abzufragen (Polling), sind Interrupts eine weitaus effizientere Methode. Ein Interrupt unterbricht kurzzeitig den normalen Programmablauf, um eine spezielle Funktion – die Interrupt Service Routine (ISR) – auszuführen, sobald eine Signaländerung am konfigurierten Pin erkannt wird.
Für die Erkennung von beiden Flanken (steigend und fallend) eignet sich der Modus CHANGE in der Arduino-Funktion attachInterrupt() ideal.
Der folgende Code verwendet einen Interrupt, um Änderungen am inputPin zu erkennen. Bei jeder Flanke (egal ob steigend oder fallend) wird nach einer kurzen Entprellzeit ein Flag (pulsePending) gesetzt. Die Hauptschleife (loop()) prüft dieses Flag und generiert bei Bedarf einen kurzen Impuls am outputPin.
// Arduino-Code für ESP8266: TTL-Flankenwechsel in Impulse umwandeln
// Erkennt LOW->HIGH und HIGH->LOW Übergänge und generiert einen Impuls
#include <Arduino.h>
// === Konfiguration ===
const int inputPin = D1; // GPIO-Pin für das eingehende TTL-Signal (z.B. GPIO5 auf NodeMCU)
const int outputPin = D2; // GPIO-Pin für den ausgehenden Impuls (z.B. GPIO4 auf NodeMCU)
const int pulseDuration = 10; // Dauer des Ausgangsimpulses in Millisekunden
const long debounceDelay = 20; // Entprellzeit in Millisekunden, um Signalrauschen zu filtern
// =====================
// Volatile Variablen für die Verwendung in ISR und Hauptschleife
volatile bool pulsePending = false; // Flag, das anzeigt, ob ein Impuls generiert werden soll
volatile unsigned long lastInterruptTime = 0; // Zeitstempel des letzten gültigen Interrupts (für Entprellung)
volatile unsigned long pulseStartTime = 0; // Zeitstempel, wann der Impuls gestartet wurde (für Dauer)
// Interrupt Service Routine (ISR) - Wird bei JEDER Flanke am inputPin aufgerufen
// IRAM_ATTR speichert die Funktion im schnellen IRAM des ESP8266
void IRAM_ATTR handleSignalChange() {
unsigned long interruptTime = millis(); // Aktuelle Zeit erfassen
// Entprellung: Nur fortfahren, wenn genug Zeit seit dem letzten Interrupt vergangen ist
if (interruptTime - lastInterruptTime > debounceDelay) {
pulsePending = true; // Impulsanforderung setzen
lastInterruptTime = interruptTime; // Zeitstempel für Entprellung aktualisieren
}
}
void setup() {
Serial.begin(115200); // Serielle Kommunikation für Debugging starten
Serial.println("\nESP8266 TTL Flankendetektor gestartet");
pinMode(inputPin, INPUT_PULLUP); // Eingangspin konfigurieren (INPUT_PULLUP kann helfen, wenn das Signal floatet)
// Verwenden Sie INPUT, wenn Sie einen externen Pull-up/Pull-down haben.
pinMode(outputPin, OUTPUT); // Ausgangspin konfigurieren
digitalWrite(outputPin, LOW); // Ausgang initial auf LOW setzen
// Interrupt konfigurieren:
// digitalPinToInterrupt(inputPin) ermittelt die korrekte Interrupt-Nummer für den Pin.
// handleSignalChange ist die Funktion, die bei einem Interrupt aufgerufen wird.
// CHANGE löst den Interrupt bei JEDER Pegeländerung aus (LOW->HIGH und HIGH->LOW).
attachInterrupt(digitalPinToInterrupt(inputPin), handleSignalChange, CHANGE);
Serial.print("Überwache Pin D");
Serial.print(digitalPinToInterrupt(inputPin)); // Zeigt die interne Pin-Nummer an
Serial.print(" (GPIO");
Serial.print(inputPin);
Serial.println(") auf Flankenwechsel...");
}
void loop() {
// Prüfen, ob ein Impuls von der ISR angefordert wurde
if (pulsePending) {
pulsePending = false; // Flag zurücksetzen, da wir den Impuls jetzt bearbeiten
Serial.print("Flankenwechsel erkannt um ");
Serial.print(lastInterruptTime);
Serial.println(" ms. Generiere Impuls...");
// Impuls generieren
digitalWrite(outputPin, HIGH); // Ausgang HIGH schalten (Impuls Start)
pulseStartTime = millis(); // Startzeit des Impulses merken
}
// Prüfen, ob der Impuls beendet werden muss
// Wir prüfen explizit, ob der Pin HIGH ist, um sicherzustellen, dass wir nur aktive Impulse beenden
if (digitalRead(outputPin) == HIGH) {
unsigned long currentTime = millis();
// Wenn die Impulsdauer abgelaufen ist
if (currentTime - pulseStartTime >= pulseDuration) {
digitalWrite(outputPin, LOW); // Ausgang LOW schalten (Impuls Ende)
Serial.println("Impuls beendet.");
}
}
// Hier kann anderer Code für das Hauptprogramm stehen.
// delay(1); // Ein kleines Delay kann manchmal die Stabilität verbessern, ist aber oft nicht nötig.
}
inputPin, outputPin), die Impulsdauer (pulseDuration) und die Entprellzeit (debounceDelay).volatile Variablen werden verwendet, da sie sowohl in der ISR als auch im Hauptprogramm (loop()) geändert oder gelesen werden. Dies stellt sicher, dass der Compiler die Variablen nicht wegoptimiert.handleSignalChange): Diese Funktion wird bei jeder Flanke aufgerufen. Sie prüft zuerst, ob seit dem letzten gültigen Interrupt genug Zeit vergangen ist (debounceDelay), um Rauschen zu filtern. Wenn ja, setzt sie das pulsePending-Flag und merkt sich die Zeit. Die ISR wird mit IRAM_ATTR markiert, damit sie im schnellen RAM des ESP8266 läuft, was für Interrupt-Routinen empfohlen wird.setup(): Initialisiert die serielle Kommunikation, konfiguriert die Pins (ggf. mit internem Pull-up-Widerstand für den Eingang) und richtet den Interrupt mit attachInterrupt() für den CHANGE-Modus ein.loop():
pulsePending-Flag.true ist, wird es zurückgesetzt, der Impuls gestartet (digitalWrite(outputPin, HIGH)) und die Startzeit pulseStartTime gespeichert.outputPin aktuell HIGH ist. Wenn ja, wird geprüft, ob die pulseDuration seit pulseStartTime abgelaufen ist. Wenn die Zeit erreicht ist, wird der Pin wieder auf LOW gesetzt, um den Impuls zu beenden. Dieser Ansatz mit millis() vermeidet blockierende delay()-Aufrufe im Hauptloop für die Impulsdauer.Das folgende Diagramm veranschaulicht den Weg des Signals von der Erkennung bis zur Impulsausgabe:
Nicht alle GPIO-Pins des ESP8266 sind gleich gut für alle Aufgaben geeignet. Für externe Interrupts können die meisten Pins (GPIO0-GPIO15) verwendet werden, aber einige haben spezielle Funktionen beim Booten (z.B. GPIO0, GPIO2, GPIO15) oder sind anderweitig belegt. Pins wie GPIO4 (D2) und GPIO5 (D1) auf NodeMCU-Boards sind oft eine gute Wahl für allgemeine Ein- und Ausgaben sowie Interrupts.
Diese Tabelle zeigt einige gängige Pins auf NodeMCU-Boards und ihre entsprechenden GPIO-Nummern sowie ihre Eignung für Interrupts.
| NodeMCU Label | GPIO Nummer | Interruptfähig? | Hinweise |
|---|---|---|---|
| D0 | GPIO16 | Nein (nur Wake-up) | Kein PWM, kein I2C/SPI |
| D1 | GPIO5 | Ja | Oft für I2C SCL verwendet |
| D2 | GPIO4 | Ja | Oft für I2C SDA verwendet |
| D3 | GPIO0 | Ja | Muss beim Booten HIGH sein (interner Pull-up), Flash-Button |
| D4 | GPIO2 | Ja | Muss beim Booten HIGH sein (externer Pull-up nötig), Onboard-LED |
| D5 | GPIO14 | Ja | HSPI CLK |
| D6 | GPIO12 | Ja | HSPI MISO |
| D7 | GPIO13 | Ja | HSPI MOSI |
| D8 | GPIO15 | Ja | Muss beim Booten LOW sein (externer Pull-down nötig), HSPI CS |
Wie bereits erwähnt, arbeitet der ESP8266 mit 3.3V. Das direkte Anlegen eines 5V-Signals an einen GPIO-Eingang kann den Chip beschädigen! Obwohl einige neuere ESP-Module möglicherweise 5V-tolerante Eingänge haben, ist es sicherer, einen Pegelwandler zu verwenden.
Ein bidirektionaler Logikpegelwandler ist eine kleine Platine, die Signale sicher zwischen 3.3V- und 5V-Systemen übersetzt. Alternativ kann für reine Eingangssignale auch ein einfacher Spannungsteiler aus zwei Widerständen verwendet werden, um die 5V auf ca. 3.3V zu reduzieren.
Ein NodeMCU ESP8266 Board, bereit für die Verbindung mit externen Signalen.
Digitale Eingänge sollten immer einen definierten Zustand (HIGH oder LOW) haben. Wenn das angeschlossene TTL-Signal in bestimmten Zuständen "schwebt" (hochohmig ist), kann der Eingang des ESP8266 undefinierte Zustände annehmen oder auf Störungen reagieren. Ein Pull-up-Widerstand (gegen 3.3V) oder ein Pull-down-Widerstand (gegen GND) sorgt für einen definierten Pegel, wenn das externe Signal nicht aktiv treibt. Der ESP8266 verfügt über interne Pull-up-Widerstände, die per Software aktiviert werden können (pinMode(pin, INPUT_PULLUP);), was oft ausreicht.
Die Wahl der Methode zur Flankenerkennung und Impulserzeugung beeinflusst verschiedene Aspekte wie Effizienz und Robustheit. Das folgende Diagramm vergleicht die hier vorgestellte Interrupt-basierte Methode mit einem einfachen Polling-Ansatz und bewertet Schlüsselfaktoren.
Wie das Diagramm zeigt, bietet der Ansatz mit Interrupts, Entprellung und Flag-basierter Impulsgenerierung im Loop die beste Balance aus Reaktionsgeschwindigkeit, Effizienz und Robustheit, auch wenn die ISR dadurch etwas komplexer wird als bei einem einfachen Polling.
Obwohl dieses Video einen Standard-Arduino verwendet, demonstriert es die grundlegenden Konzepte der Impulserzeugung, die auch für den ESP8266 relevant sind. Es zeigt, wie man mit digitalWrite und Zeitsteuerungen (hier delay) einen Impuls definierter Länge erzeugt.
Dieses Video erklärt die Grundlagen der Impulserzeugung mit Arduino-Befehlen.