February 8, 2026

Interrupts nutzen: So reagiert dein Mikrocontroller in Echtzeit

Interrupts nutzen ist eine der wichtigsten Techniken, wenn Ihr Mikrocontroller wirklich „in Echtzeit“ reagieren soll. Viele Einsteiger starten mit einer einfachen Schleife: Im loop() wird ständig geprüft, ob ein Taster gedrückt ist, ob ein Sensorwert sich geändert hat oder ob eine serielle Nachricht angekommen ist. Das funktioniert, solange die Schleife schnell genug ist und keine langen Wartezeiten enthält. In realen Projekten sieht es jedoch anders aus: WLAN-Verbindungen, Displays, Sensor-Abfragen, Logging oder komplexe Berechnungen blockieren den Programmfluss. Genau hier kommen Interrupts ins Spiel. Ein Interrupt ist ein Hardware- oder Systemereignis, das den Mikrocontroller veranlasst, die aktuelle Arbeit kurz zu unterbrechen und sofort eine definierte Routine auszuführen. So können Sie z. B. präzise Impulse zählen, Taster entprellen, Encoder auswerten, Zeitmessungen durchführen oder sicherstellen, dass ein Sicherheitsereignis (Endschalter, Überstromsignal, Not-Aus) nicht „übersehen“ wird. Allerdings sind Interrupts kein Allheilmittel: Falsch eingesetzt verursachen sie schwer zu findende Bugs, Timing-Probleme und instabile Systeme. Dieser Artikel erklärt Ihnen verständlich und praxisnah, wie Interrupts funktionieren, welche Arten es gibt, wann sie sinnvoll sind, welche Regeln in Interrupt-Service-Routinen (ISR) gelten, wie Sie typische Fallstricke vermeiden und wie Sie mit einem sauberen Design stabile Echtzeitreaktionen erreichen.

Was ist ein Interrupt? Das Prinzip einfach erklärt

Ein Interrupt ist ein Mechanismus, der dem Mikrocontroller erlaubt, auf bestimmte Ereignisse sofort zu reagieren. Statt in einer Schleife ständig zu „pollen“ (also abzufragen), meldet die Hardware: „Jetzt ist etwas passiert.“ Der Mikrocontroller speichert den aktuellen Zustand (z. B. Program Counter, Register), springt in eine spezielle Funktion – die Interrupt-Service-Routine (ISR) – und kehrt danach wieder an die ursprüngliche Stelle zurück. Das Ziel ist eine schnelle, deterministischere Reaktion, unabhängig davon, was der Hauptcode gerade macht.

  • Polling: Hauptschleife fragt regelmäßig ab („Ist der Taster gedrückt?“)
  • Interrupt: Ereignis löst sofort eine Reaktion aus („Taster wurde gedrückt!“)
  • ISR: kurze Routine, die das Ereignis minimal verarbeitet

Für eine allgemeine Einordnung des Konzepts bietet Wikipedia zu Interrupts einen guten Überblick.

Warum Interrupts echte Projekte stabiler machen können

In vielen Mikrocontroller-Projekten gibt es kritische Zeitfenster: Ein Impuls ist nur wenige Mikrosekunden lang, ein Encoder liefert schnelle Flanken, ein Sensor signalisiert „Daten bereit“, oder ein Endschalter muss zuverlässig erkannt werden. Wenn Sie hier nur pollen, kann Ihr Code das Ereignis verpassen – besonders wenn gerade andere Aufgaben laufen. Interrupts lösen dieses Problem, weil die Hardware den Zeitpunkt „festhält“ und die CPU unmittelbar anstoßen kann. Dadurch erreichen Sie Reaktionszeiten, die mit einer normalen Schleife schwer realisierbar sind.

  • Impulszählung: Drehgeber, Durchflussmesser, Hall-Sensoren
  • Timing-Messung: Pulsbreite, Frequenz, Signalabstände
  • Sicherheitsreaktionen: Endschalter, Not-Aus, Überlastsignale
  • Effizienz: weniger CPU-Zeit für ständiges Abfragen

Interrupts sind besonders wertvoll, wenn Ihre loop()-Schleife „lang“ ist

Jede Verzögerung in der Hauptschleife – etwa durch delay(), Netzwerkverbindungen, Dateizugriffe oder Display-Updates – erhöht die Wahrscheinlichkeit, dass Polling Ereignisse übersieht. Interrupts umgehen genau dieses Problem, indem sie unabhängig vom Schleifentempo reagieren.

Arten von Interrupts: Extern, Timer, Pin-Change und System-Interrupts

Nicht jeder Interrupt ist gleich. Je nach Mikrocontroller und Plattform gibt es unterschiedliche Quellen. Für Maker sind vor allem externe Interrupts (GPIO-Ereignisse) und Timer-Interrupts relevant. Auf manchen Plattformen gibt es zusätzlich Pin-Change-Interrupts (mehr Pins, aber weniger Komfort) oder interne Interrupts von Peripherie (UART, I2C, ADC, Wi-Fi-Subsystem).

  • Externe Interrupts: Flanke am GPIO (RISING/FALLING/CHANGE)
  • Timer-Interrupts: periodische Ereignisse, präzise Zeitbasis
  • Pin-Change: Gruppen-Interrupts für viele Pins (Board-abhängig)
  • Peripherie-Interrupts: z. B. „Byte empfangen“, „ADC fertig“, „DMA beendet“

Flanken und Pegel: RISING, FALLING, CHANGE, LEVEL

Für Taster, Sensoren und Encoder nutzen Sie meist Flanken-Interrupts: Eine steigende Flanke (RISING) oder fallende Flanke (FALLING). CHANGE reagiert auf beide. Pegel-Interrupts (LEVEL) reagieren, solange ein Pin einen bestimmten Zustand hält – das ist weniger verbreitet im Arduino-Alltag, kann aber für bestimmte Signale sinnvoll sein.

Interrupt-Service-Routine (ISR): Die wichtigste Regel lautet „kurz und simpel“

Die ISR ist der Ort, an dem viele Projekte scheitern – nicht weil Interrupts „kompliziert“ sind, sondern weil man zu viel in der ISR erledigen will. Eine ISR sollte minimal sein: Sie markiert, dass etwas passiert ist, speichert einen Zeitstempel oder erhöht einen Zähler. Alles Weitere gehört in den Hauptcode. Der Grund: Während eine ISR läuft, sind je nach Plattform andere Interrupts blockiert oder verzögert. Lange ISR-Zeiten führen zu Jitter, verpassten Ereignissen und instabilem Verhalten.

  • In der ISR tun: Flag setzen, Zähler erhöhen, Zeitstempel speichern
  • Nicht in der ISR tun: Serial.print, Netzwerk, Speicherallokation, lange Berechnungen
  • ISR-Ziel: Ereignis „sichern“, dann schnell zurück

Warum Serial.print in der ISR oft ein Fehler ist

Serielle Ausgaben sind langsam und nutzen häufig selbst Interrupts oder Puffermechanismen. In einer ISR kann das zu Deadlocks oder extremen Verzögerungen führen. Wenn Sie debuggen müssen, loggen Sie in der ISR nur minimal (z. B. Zähler) und geben die Werte später im Hauptcode aus.

Shared Data: volatile, atomare Zugriffe und Race Conditions

Interrupts erzeugen Parallelität: Hauptcode und ISR greifen potenziell auf dieselben Variablen zu. Ohne Vorsicht entstehen Race Conditions – seltene, schwer reproduzierbare Fehler. Deshalb markiert man gemeinsam genutzte Variablen häufig als volatile, damit der Compiler sie nicht „wegoptimiert“. Zusätzlich müssen Zugriffe auf mehrbyteige Variablen (z. B. 16/32 Bit auf 8-Bit-AVR) geschützt werden, damit sie nicht mitten im Lesen/Schreiben unterbrochen werden.

  • volatile: signalisiert, dass sich Werte außerhalb des normalen Codeflusses ändern
  • Atomarität: mehrbyteige Variablen ggf. geschützt lesen/schreiben
  • Critical Sections: kurz Interrupts deaktivieren, um konsistent zu kopieren

Best Practice: In der ISR nur schreiben, im Hauptcode nur kopieren

Ein robustes Muster ist: Die ISR aktualisiert einen Zähler oder setzt ein Flag. Der Hauptcode kopiert den Wert in eine lokale Variable innerhalb einer sehr kurzen kritischen Sektion und arbeitet dann mit der lokalen Kopie weiter. So minimieren Sie Konflikte.

Interrupts vs. Debouncing: Warum Buttons „spinnen“

Taster prellen mechanisch. Das bedeutet: Ein einziger Druck erzeugt mehrere schnelle Flanken. Wenn Sie einen Interrupt auf RISING setzen, kann ein Tasterdruck mehrere Interrupts auslösen – und Ihr Code interpretiert das als mehrfaches Drücken. Die Lösung ist Entprellung: entweder hardwareseitig (RC-Glied, Schmitt-Trigger) oder softwareseitig (Zeitfenster). Wichtig: Entprellen gehört meist nicht komplett in die ISR, sondern wird über Zeitstempel und eine einfache Sperrlogik umgesetzt.

  • Hardware-Debounce: Widerstand/Kondensator, sauberes Signal
  • Software-Debounce: Ereignisse innerhalb von z. B. 20–50 ms ignorieren
  • Hybrid: leichtes Hardware-Glätten plus Softwarefilter

ISR-Debounce-Muster mit Zeitstempel

In der ISR speichern Sie nur die Zeit des letzten gültigen Interrupts und setzen ein Flag, wenn das Zeitfenster überschritten ist. Die eigentliche Aktion (z. B. „Licht toggeln“) passiert im Hauptcode, nachdem das Flag gelesen wurde.

Timer-Interrupts: Präzise Zeitbasis für regelmäßige Aufgaben

Timer-Interrupts sind ideal, wenn Sie in festen Intervallen etwas tun müssen: Sensor abtasten, PID-Regler berechnen, PWM aktualisieren oder eine Software-Uhr betreiben. Im Unterschied zum delay() blockieren Timer-Interrupts nicht Ihren Hauptcode. Sie erzeugen eine stabile Zeitbasis. Gleichzeitig gilt auch hier: Timer-ISR kurz halten, sonst wird das Timing schlechter statt besser.

  • Sampling: gleichmäßige Messintervalle
  • Regelung: konstantes Zeitraster für Controller
  • Timing-Genauigkeit: weniger abhängig vom loop()-Durchlauf
  • Grenzen: zu hohe Frequenz oder zu lange ISR erzeugt Jitter

Prioritäten und Interrupt-Latenz: Was passiert, wenn viel los ist?

Auf einfachen Mikrocontrollern (z. B. AVR) sind Prioritäten oft fix und simpel. Auf komplexeren Systemen wie dem ESP32 können mehrere Interrupt-Quellen konkurrieren, und es gibt Mechanismen zur Priorisierung. In der Praxis bedeutet das: Nicht jeder Interrupt kommt „sofort“, sondern innerhalb einer Latenz, die vom Systemzustand abhängt. Wenn WLAN-Subsysteme, RTOS-Tasks oder andere Interrupts aktiv sind, kann die Latenz steigen. Daher ist „Echtzeit“ immer relativ: Für viele Maker-Anwendungen reicht es vollkommen, für harte Echtzeitaufgaben ist ein bewusstes, hardwarenahes Design nötig.

  • Interrupt-Latenz: Zeit zwischen Ereignis und ISR-Start
  • Jitter: Schwankung dieser Latenz
  • Konflikte: lange ISR blockiert andere Ereignisse
  • Systemlast: WLAN/RTOS kann Einfluss auf Timing haben

Praxisregel: Erst messen, dann optimieren

Wenn Timing kritisch ist, messen Sie. Nutzen Sie z. B. einen GPIO, den Sie am ISR-Start toggeln, und messen Sie mit Logikanalysator oder Oszilloskop Latenz und Jitter. Das ist wesentlich zuverlässiger als Vermutungen.

Interrupts richtig designen: Events, Queues und Zustandsmaschinen

Ein professionelles Muster ist, Interrupts als „Event-Generatoren“ zu betrachten: Die ISR erzeugt ein Ereignis (z. B. „Impuls empfangen“, „Taster gedrückt“) und legt es in einer sehr leichten Struktur ab (Flag, Ringbuffer, Queue). Die Verarbeitung geschieht anschließend im Hauptcode oder in einer Task. So bleiben ISR kurz, und Ihr Programm bleibt testbar und wartbar.

  • Flag/Event: für seltene Ereignisse
  • Ringbuffer: für häufige Ereignisse (z. B. Encoder)
  • Queue/RTOS: auf Plattformen wie ESP32 möglich, wenn sauber umgesetzt
  • Zustandsmaschine: klare Logik im Hauptcode, keine „ISR-Spaghetti“

Wann ein Ringbuffer sinnvoll ist

Bei sehr schnellen Ereignissen (z. B. hochauflösende Encoder) reicht ein einzelnes Flag nicht, weil mehrere Ereignisse zwischen zwei loop()-Durchläufen auftreten können. Ein Ringbuffer speichert mehrere Zeitstempel oder Zustände, die später abgearbeitet werden.

Typische Anwendungsbeispiele: Wo Interrupts wirklich glänzen

Interrupts sind besonders stark in Situationen, in denen Ereignisse kurz sind oder präzise Zeitmessung erforderlich ist. Im Folgenden einige typische Maker-Szenarien, bei denen Interrupts gegenüber Polling klare Vorteile bringen.

  • Drehgeber (Encoder): saubere Erfassung schneller Flanken
  • Durchflussmesser: Impulse zählen und daraus Liter pro Minute berechnen
  • Frequenzmessung: Periodendauer oder Impulsabstände messen
  • Endschalter: sofortiges Stoppen eines Motors
  • IR-Empfänger: Timing-basierte Protokolle benötigen schnelle Reaktion

Häufige Fehler und wie Sie sie vermeiden

Die meisten Interrupt-Probleme lassen sich auf wenige Muster zurückführen: zu viel Arbeit in der ISR, unsaubere gemeinsame Variablen, Prellen, falsche Pull-ups oder die Annahme, dass „Echtzeit“ automatisch garantiert ist. Wenn Sie diese Punkte im Blick behalten, werden Interrupts zu einem sehr zuverlässigen Werkzeug.

  • Zu lange ISR: führt zu verpassten Ereignissen und Timing-Jitter
  • Serial/Delay in ISR: blockiert und destabilisiert
  • volatile vergessen: Werte wirken „stuck“ oder inkonsistent
  • Mehrbytezugriff ungeschützt: seltene, zufällige Zahlenfehler
  • Taster ohne Debounce: Mehrfachauslösung
  • Floating Input: ungewollte Interrupts durch Störungen

Quick-Check: Ist Ihr Interrupt-Design gesund?

  • ISR dauert kurz: idealerweise nur wenige Mikrosekunden bis sehr wenige Millisekunden
  • Keine blockierenden Calls: kein Print, kein WLAN, keine langen Berechnungen
  • Klare Datenübergabe: Flag/Zähler + sichere Kopie im Hauptcode
  • Entprellung vorhanden: besonders bei mechanischen Schaltern
  • Signal sauber: Pull-ups/Pull-downs und saubere Verdrahtung

Weiterführende Ressourcen

IoT-PCB-Design, Mikrocontroller-Programmierung & Firmware-Entwicklung

PCB Design • Arduino • Embedded Systems • Firmware

Ich biete professionelle Entwicklung von IoT-Hardware, einschließlich PCB-Design, Arduino- und Mikrocontroller-Programmierung sowie Firmware-Entwicklung. Die Lösungen werden zuverlässig, effizient und anwendungsorientiert umgesetzt – von der Konzeptphase bis zum funktionsfähigen Prototyp.

Diese Dienstleistung richtet sich an Unternehmen, Start-ups, Entwickler und Produktteams, die maßgeschneiderte Embedded- und IoT-Lösungen benötigen. Finden Sie mich auf Fiverr.

Leistungsumfang:

  • IoT-PCB-Design & Schaltplanerstellung

  • Leiterplattenlayout (mehrlagig, produktionstauglich)

  • Arduino- & Mikrocontroller-Programmierung (z. B. ESP32, STM32, ATmega)

  • Firmware-Entwicklung für Embedded Systems

  • Sensor- & Aktor-Integration

  • Kommunikation: Wi-Fi, Bluetooth, MQTT, I²C, SPI, UART

  • Optimierung für Leistung, Stabilität & Energieeffizienz

Lieferumfang:

  • Schaltpläne & PCB-Layouts

  • Gerber- & Produktionsdaten

  • Quellcode & Firmware

  • Dokumentation & Support zur Integration

Arbeitsweise:Strukturiert • Zuverlässig • Hardware-nah • Produktorientiert

CTA:
Planen Sie ein IoT- oder Embedded-System-Projekt?
Kontaktieren Sie mich gerne für eine technische Abstimmung oder ein unverbindliches Angebot. Finden Sie mich auf Fiverr.

 

Related Articles